OCR in Android-Apps mit Google ML Kit implementieren
Mit optischer Zeichenerkennung (OCR) kann Ihre Android-App Text in Bildern erkennen und extrahieren. Google ML Kit bietet eine OCR-Bibliothek, die auf dem Gerät arbeitet. Das folgende Beispiel kann Text daher erkennen, ohne jedes Bild an eine entfernte API zu senden. Diese Anleitung ergänzt die Texterkennung für lateinische Schrift, ermöglicht das Aufnehmen oder Auswählen eines Bildes und zeigt den erkannten Text an.
Voraussetzungen
Stellen Sie vor Beginn sicher, dass Sie Folgendes haben:
- Eine aktuelle stabile Version von Android Studio und das dafür empfohlene Android-Gradle-Plugin
- Ein Android-Gerät oder einen Emulator mit Android-API-Level 23 oder höher
- Grundkenntnisse in der Android-Entwicklung und Kotlin
Android-Projekt einrichten
Erstellen Sie ein neues Android-Projekt in Android Studio:
- Öffnen Sie Android Studio und wählen Sie File, New und dann New Project.
- Wählen Sie Empty Views Activity und klicken Sie auf Next.
- Benennen Sie das Projekt, zum Beispiel
TextRecognitionApp. - Wählen Sie
Kotlinals Sprache undGroovy DSLals Sprache für die Build-Konfiguration. - Setzen Sie Minimum SDK auf
API 23oder neuer, wie von der Text Recognition v2 API gefordert. - Klicken Sie auf Finish, um das Projekt zu erstellen.
ML-Kit-Abhängigkeit hinzufügen
Aktivieren Sie View Binding und fügen Sie das mitgelieferte Modell für lateinische Schrift in der
Datei build.gradle auf App-Ebene hinzu:
android {
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.activity:activity-ktx:1.9.3'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.mlkit:text-recognition:16.0.1'
}
Diese Abhängigkeit integriert das Erkennungsmodell in Ihre App, sodass es sofort verfügbar ist.
Wenn die Downloadgröße wichtiger ist als die Verfügbarkeit beim ersten Start, bietet Google auch
die kleinere Abhängigkeit com.google.android.gms:play-services-mlkit-text-recognition:19.0.1 an. Diese Version lädt ihr Modell über die
Google Play-Dienste herunter. Lesen Sie vor Ihrer Entscheidung Googles
Vergleich mitgelieferter und separat heruntergeladener Modelle.
Berechtigungen konfigurieren
Die folgende Implementierung nutzt die System-Kamera-App von Android und die Fotoauswahl.
Sie benötigt weder READ_EXTERNAL_STORAGE noch umfassenden Zugriff auf die Fotobibliothek
oder direkten Kamerazugriff. Die Fotoauswahl gewährt nur Zugriff auf das ausgewählte Element und
verwendet auf älteren unterstützten Geräten ersatzweise ACTION_OPEN_DOCUMENT.
Wenn Sie den Kamera-Intent später für eine Vorschau innerhalb der App durch CameraX ersetzen,
fordern Sie die Berechtigung CAMERA zur Laufzeit an.
Layout erstellen
Erstellen Sie activity_main.xml mit Bedienelementen zum Aufnehmen und Auswählen von
Bildern, einer Bildvorschau und einem scrollbaren Textergebnis:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btnCapture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Capture Image" />
<Button
android:id="@+id/btnGallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select from Gallery" />
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="16dp"
android:contentDescription="Selected image"
android:scaleType="centerCrop" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_weight="1">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
</ScrollView>
</LinearLayout>
OCR-Funktionalität implementieren
Verwenden Sie TakePicture, um ein Bild in voller Auflösung unter einem
Content-URI zu speichern, und PickVisualMedia, um Nutzern die Fotoauswahl des
Systems bereitzustellen. InputImage.fromFilePath() erstellt die Eingabe für ML Kit direkt
aus diesem URI. Behalten Sie die von Android Studio erzeugte Paketdeklaration bei und importieren
Sie ActivityMainBinding aus dem generierten Paket databinding
Ihrer App. Ersetzen Sie den übrigen Inhalt von MainActivity.kt durch den folgenden
Code. Er speichert den Dateinamen der ausstehenden Kameraaufnahme getrennt von der Registrierung
für Activity-Ergebnisse, damit die Rückkehr aus der Kamera auch nach einer
Neuerstellung des Prozesses funktioniert.
import android.content.ActivityNotFoundException
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.TextRecognition
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.io.File
import java.io.IOException
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var pendingCameraName: String? = null
private val cameraDirectory: File
get() = File(cacheDir, "ocr-camera")
private val takePictureLauncher = registerForActivityResult(
ActivityResultContracts.TakePicture()
) { success ->
val name = pendingCameraName
pendingCameraName = null
if (name == null) {
setBusy(false)
showMessage("The camera result is no longer available")
} else {
val file = File(cameraDirectory, name)
if (success && file.isFile && file.length() > 0) {
processImage(cameraUri(file), file)
} else {
file.delete()
setBusy(false)
}
}
}
private val selectPictureLauncher = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri ->
if (uri != null) {
processImage(uri)
} else {
setBusy(false)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
pendingCameraName = savedInstanceState?.getString("pendingCameraName")
binding.textView.text = savedInstanceState?.getString("recognizedText")
setBusy(pendingCameraName != null)
// A process killed during recognition may leave a file without a result callback.
cameraDirectory.listFiles()?.filter {
it.name != pendingCameraName && it.lastModified() < System.currentTimeMillis() - 86_400_000
}?.forEach { it.delete() }
binding.btnCapture.setOnClickListener {
captureImage()
}
binding.btnGallery.setOnClickListener {
setBusy(true)
selectPictureLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
}
}
private fun captureImage() {
try {
if (!cameraDirectory.isDirectory && !cameraDirectory.mkdirs()) {
throw IOException("Could not create capture directory")
}
val imageFile = File.createTempFile("IMG_", ".jpg", cameraDirectory)
pendingCameraName = imageFile.name
setBusy(true)
takePictureLauncher.launch(cameraUri(imageFile))
} catch (error: IOException) {
cancelCapture()
} catch (error: ActivityNotFoundException) {
cancelCapture()
} catch (error: SecurityException) {
cancelCapture()
}
}
private fun cameraUri(file: File): Uri =
FileProvider.getUriForFile(this, "${packageName}.fileprovider", file)
private fun cancelCapture() {
pendingCameraName?.let { File(cameraDirectory, it).delete() }
pendingCameraName = null
setBusy(false)
showMessage("The camera could not be opened")
}
private fun processImage(uri: Uri, temporaryFile: File? = null) {
setBusy(true)
val image = try {
binding.imageView.setImageURI(uri)
InputImage.fromFilePath(this, uri)
} catch (error: IOException) {
temporaryFile?.delete()
setBusy(false)
showMessage("The selected image could not be opened")
return
} catch (error: SecurityException) {
temporaryFile?.delete()
setBusy(false)
showMessage("The selected image could not be opened")
return
}
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
recognizer.process(image)
.addOnSuccessListener { visionText ->
if (!isDestroyed) binding.textView.text = visionText.text
}
.addOnFailureListener {
if (!isDestroyed) showMessage("Text recognition failed. Try a clearer image")
}
.addOnCompleteListener {
// Do not use an Activity-scoped listener: cleanup must also run after onStop.
recognizer.close()
temporaryFile?.delete()
if (!isDestroyed) setBusy(false)
}
}
private fun setBusy(value: Boolean) {
binding.btnCapture.isEnabled = !value
binding.btnGallery.isEnabled = !value
}
private fun showMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("pendingCameraName", pendingCameraName)
outState.putString("recognizedText", binding.textView.text.toString())
super.onSaveInstanceState(outState)
}
}
Fügen Sie die FileProvider-Konfiguration in AndroidManifest.xml innerhalb des Tags
<application> hinzu:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Erstellen Sie res/xml/file_paths.xml:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="ocr_camera" path="ocr-camera/" />
</paths>
Nur das Aufnahmeverzeichnis wird freigegeben. Abgeschlossene und abgebrochene Aufnahmen werden
gelöscht; eine zurückgelassene Aufnahme wird bei einem späteren Start nach einem Tag entfernt.
Löschen Sie die ausstehende Datei nicht in onDestroy(): Die Kamera-App
schreibt möglicherweise noch in diese Datei, während Android Ihre Activity neu erstellt.
Erkennungsaufgaben schließen nach Abschluss ihre eigene Erkennungsinstanz, auch wenn die Activity
bereits gestoppt wurde. Fertig erkannter Text bleibt bei einer Neuerstellung erhalten; die Vorschau
ist temporär. Wenn eine Neuerstellung die Erkennung unterbricht, wählen Sie das Bild erneut aus
oder nehmen Sie es erneut auf. Der URI der Fotoauswahl wird nur für den unmittelbaren Vorgang
verwendet, ohne dauerhaften Zugriff auf das ausgewählte Foto anzufordern.
OCR-Genauigkeit und Leistung optimieren
Befolgen Sie Googles Richtlinien für Eingabebilder:
- Zeichengröße: Streben Sie mindestens 16x16 Pixel pro Zeichen an. Zeichen auf mehr als etwa 24x24 Pixel zu vergrößern, verbessert die Erkennungsgenauigkeit in der Regel nicht.
- Bildqualität: Achten Sie auf gute Beleuchtung, scharfen Fokus und minimale Bewegungsunschärfe.
- Bildabmessungen: Verwenden Sie die niedrigste Auflösung, bei der jedes Zeichen noch genügend Pixel erhält. Kleinere Bilder verringern die Latenz beim Scannen in Echtzeit.
- Echtzeitanalyse: Behalten Sie bei CameraX die standardmäßige Backpressure-Strategie
ImageAnalysis.STRATEGY_KEEP_ONLY_LATESTbei, damit sich keine Frames in der Warteschlange ansammeln, während die Erkennungsinstanz beschäftigt ist.
Anwendung testen
Testen Sie Ihre OCR-Implementierung mit:
- Gedrucktem Text in verschiedenen Schriftarten und Schriftgrößen
- Sprachen mit lateinischer Schrift, die von
TextRecognizerOptions.DEFAULT_OPTIONSunterstützt werden - Unterschiedlichen Beleuchtungsverhältnissen, Fokuseinstellungen und Textausrichtungen
- Bildern aus der Kamera und der Fotoauswahl
- Gerätedrehungen und der Neuerstellung des Prozesses
Chinesisch, Devanagari, Japanisch und Koreanisch benötigen jeweils eine eigene ML-Kit-Abhängigkeit und eigene Optionen für die Erkennungsinstanz. Fügen Sie die entsprechende Bibliothek hinzu, bevor Sie eine dieser Schriften in Ihre Testmatrix aufnehmen.
Fehlerbehebung
Wenn die Texterkennung fehlschlägt:
- Prüfen Sie, ob das Bild scharf und gut beleuchtet ist und jedes Zeichen genügend Pixel hat.
- Stellen Sie sicher, dass FileProvider konfiguriert und die temporäre Bilddatei zugänglich ist.
- Wenn Sie die Abhängigkeit für die Google Play-Dienste gewählt haben, stellen Sie sicher, dass ihr Modell vollständig heruntergeladen wurde. Die in diesem Beispiel verwendete Abhängigkeit mit mitgeliefertem Modell benötigt keinen Modelldownload.
- Untersuchen Sie während der Entwicklung den zugrunde liegenden Fehler mit Logcat.
Fazit
Sie haben nun einen OCR-Ablauf für Android, der Bilder aus der Kamera und der Fotoauswahl annimmt und Text in lateinischer Schrift auf dem Gerät erkennt. Informationen zu serverseitiger OCR für hochgeladene Dateien finden Sie beim Robot /document/ocr von Transloadit und in der OCR-Demo (English).
