Rozpoznawanie tekstu na obrazach za pomocą ML Kit na Androidzie

Możesz używać pakietu ML Kit do rozpoznawania tekstu na obrazach lub w filmach, np. na znakach drogowych. Główne cechy tej funkcji:

Funkcja Niegrupowane Łączenie w pakiety
Nazwa biblioteki com.google.android.gms:play-services-mlkit-text-recognition

com.google.android.gms:play-services-mlkit-text-recognition-chinese

com.google.android.gms:play-services-mlkit-text-recognition-devanagari

com.google.android.gms:play-services-mlkit-text-recognition-japanese

com.google.android.gms:play-services-mlkit-text-recognition-korean

com.google.mlkit:text-recognition

com.google.mlkit:text-recognition-chinese

com.google.mlkit:text-recognition-devanagari

com.google.mlkit:text-recognition-japanese

com.google.mlkit:text-recognition-korean

Implementacja Model jest pobierany dynamicznie przez Usługi Google Play. Model jest statycznie połączony z aplikacją w momencie kompilacji.
Rozmiar aplikacji Zwiększenie rozmiaru o około 260 KB na architekturę skryptu. Zwiększenie rozmiaru skryptu o około 4 MB na skrypt na architekturę.
Czas inicjowania Przed jego pierwszym użyciem konieczne może być poczekać na pobranie modelu. Model jest dostępny od razu.
Wydajność Biblioteka alfabetu łacińskiego w czasie rzeczywistym na większości urządzeń, u innych wolniej. Biblioteka alfabetu łacińskiego w czasie rzeczywistym na większości urządzeń, u innych wolniej.

Wypróbuj

Zanim zaczniesz

  1. W pliku build.gradle na poziomie projektu dodaj repozytorium Google Maven w sekcjach buildscript i allprojects.
  2. Dodaj zależności bibliotek ML Kit na Androida do pliku Gradle na poziomie aplikacji modułu, którym jest zwykle app/build.gradle:

    Aby połączyć model z aplikacją:

    dependencies {
      // To recognize Latin script
      implementation 'com.google.mlkit:text-recognition:16.0.0'
    
      // To recognize Chinese script
      implementation 'com.google.mlkit:text-recognition-chinese:16.0.0'
    
      // To recognize Devanagari script
      implementation 'com.google.mlkit:text-recognition-devanagari:16.0.0'
    
      // To recognize Japanese script
      implementation 'com.google.mlkit:text-recognition-japanese:16.0.0'
    
      // To recognize Korean script
      implementation 'com.google.mlkit:text-recognition-korean:16.0.0'
    }
    

    Aby używać modelu w Usługach Google Play:

    dependencies {
      // To recognize Latin script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.0'
    
      // To recognize Chinese script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.0'
    
      // To recognize Devanagari script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.0'
    
      // To recognize Japanese script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.0'
    
      // To recognize Korean script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.0'
    }
    
  3. Jeśli zdecydujesz się używać modelu w Usługach Google Play, możesz skonfigurować aplikację tak, aby po jej zainstalowaniu ze Sklepu Play automatycznie pobierała model na urządzenie. Aby to zrobić, dodaj do pliku AndroidManifest.xml aplikacji tę deklarację:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="ocr" >
          <!-- To use multiple models: android:value="ocr,ocr_chinese,ocr_devanagari,ocr_japanese,ocr_korean,..." -->
    </application>
    

    Możesz też bezpośrednio sprawdzić dostępność modelu i poprosić o pobranie za pomocą interfejsu ModuleInstallClient API Usług Google Play. Jeśli nie włączysz pobierania modelu w czasie instalacji lub nie poprosisz o jawne pobranie, model zostanie pobrany przy pierwszym uruchomieniu skanera. Żądania zgłoszone przed zakończeniem pobierania nie przyniosą skutku.

1. Tworzenie instancji maszyny wirtualnej TextRecognizer

Utwórz instancję TextRecognizer, przekazując opcje związane z biblioteką, od której zadeklarowano zależność:

Kotlin

// When using Latin script library
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

// When using Chinese script library
val recognizer = TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())

// When using Devanagari script library
val recognizer = TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())

// When using Japanese script library
val recognizer = TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())

// When using Korean script library
val recognizer = TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())

Java

// When using Latin script library
TextRecognizer recognizer =
  TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

// When using Chinese script library
TextRecognizer recognizer =
  TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());

// When using Devanagari script library
TextRecognizer recognizer =
  TextRecognition.getClient(new DevanagariTextRecognizerOptions.Builder().build());

// When using Japanese script library
TextRecognizer recognizer =
  TextRecognition.getClient(new JapaneseTextRecognizerOptions.Builder().build());

// When using Korean script library
TextRecognizer recognizer =
  TextRecognition.getClient(new KoreanTextRecognizerOptions.Builder().build());

2. Przygotowywanie obrazu wejściowego

Aby rozpoznać tekst na obrazie, utwórz obiekt InputImage z Bitmap, media.Image, ByteBuffer, tablicy bajtów albo z pliku na urządzeniu. Następnie przekaż obiekt InputImage do metody processImage instancji TextRecognizer.

Obiekt InputImage możesz utworzyć z różnych źródeł. Poniżej znajdziesz opis każdego z nich.

Korzystanie z: media.Image

Aby utworzyć obiekt InputImage na podstawie obiektu media.Image, na przykład podczas rejestrowania obrazu aparatem urządzenia, przekaż obiekt media.Image i obrót obrazu do InputImage.fromMediaImage().

Jeśli używasz biblioteki AparatuX, klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość obrotu za Ciebie.

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

Jeśli nie korzystasz z biblioteki aparatu, która określa stopień obrotu obrazu, możesz go obliczyć na podstawie stopnia obrotu urządzenia i orientacji czujnika aparatu w urządzeniu:

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Throws(CameraAccessException::class)
private fun getRotationCompensation(cameraId: String, activity: Activity, isFrontFacing: Boolean): Int {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    val deviceRotation = activity.windowManager.defaultDisplay.rotation
    var rotationCompensation = ORIENTATIONS.get(deviceRotation)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * Get the angle by which an image must be rotated given the device's current
 * orientation.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private int getRotationCompensation(String cameraId, Activity activity, boolean isFrontFacing)
        throws CameraAccessException {
    // Get the device's current rotation relative to its "native" orientation.
    // Then, from the ORIENTATIONS table, look up the angle the image must be
    // rotated to compensate for the device's rotation.
    int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int rotationCompensation = ORIENTATIONS.get(deviceRotation);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

Następnie przekaż obiekt media.Image i wartość stopnia obrotu do InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Za pomocą identyfikatora URI pliku

Aby utworzyć obiekt InputImage na podstawie identyfikatora URI pliku, przekaż do InputImage.fromFilePath() kontekst aplikacji i identyfikator URI pliku. Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT, aby zachęcić użytkownika do wybrania obrazu z aplikacji galerii.

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

InputImage image;
try {
    image = InputImage.fromFilePath(context, uri);
} catch (IOException e) {
    e.printStackTrace();
}

Przy użyciu: ByteBuffer lub ByteArray

Aby utworzyć obiekt InputImage na podstawie ByteBuffer lub ByteArray, najpierw oblicz stopień obrotu obrazu zgodnie z wcześniejszym opisem dla danych wejściowych media.Image. Następnie utwórz obiekt InputImage z buforem lub tablicą i podaj wysokość, szerokość, format kodowania kolorów i stopień obrotu:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Korzystanie z: Bitmap

Aby utworzyć obiekt InputImage z obiektu Bitmap, wypełnij tę deklarację:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

Obraz jest reprezentowany przez obiekt Bitmap wraz z informacją o obróceniu w stopniach.

3. Przetwarzanie obrazu

Przekaż obraz do metody process:

Kotlin

val result = recognizer.process(image)
        .addOnSuccessListener { visionText ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Java

Task<Text> result =
        recognizer.process(image)
                .addOnSuccessListener(new OnSuccessListener<Text>() {
                    @Override
                    public void onSuccess(Text visionText) {
                        // Task completed successfully
                        // ...
                    }
                })
                .addOnFailureListener(
                        new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                // Task failed with an exception
                                // ...
                            }
                        });

4. Wyodrębnianie tekstu z bloków rozpoznanego tekstu

Jeśli operacja rozpoznawania tekstu się powiedzie, do detektora powodzenia zostanie przekazany obiekt Text. Obiekt Text zawiera pełny tekst rozpoznany na obrazie i 0 lub więcej obiektów TextBlock.

Każdy obiekt TextBlock to prostokątny blok tekstu, który zawiera co najmniej 0 obiektów Line. Każdy obiekt Line reprezentuje wiersz tekstu, który zawiera co najmniej 0 obiektów Element. Każdy obiekt Element reprezentuje słowo lub element słowny, który zawiera 0 lub więcej obiektów Symbol. Każdy obiekt Symbol reprezentuje znak, cyfrę lub element słowny.

W przypadku każdego obiektu TextBlock, Line, Element i Symbol można wyświetlić tekst rozpoznany w regionie, współrzędne ograniczające regionu oraz wiele innych atrybutów, takich jak informacje o rotacji czy wskaźnik ufności.

Na przykład:

Kotlin

val resultText = result.text
for (block in result.textBlocks) {
    val blockText = block.text
    val blockCornerPoints = block.cornerPoints
    val blockFrame = block.boundingBox
    for (line in block.lines) {
        val lineText = line.text
        val lineCornerPoints = line.cornerPoints
        val lineFrame = line.boundingBox
        for (element in line.elements) {
            val elementText = element.text
            val elementCornerPoints = element.cornerPoints
            val elementFrame = element.boundingBox
        }
    }
}

Java

String resultText = result.getText();
for (Text.TextBlock block : result.getTextBlocks()) {
    String blockText = block.getText();
    Point[] blockCornerPoints = block.getCornerPoints();
    Rect blockFrame = block.getBoundingBox();
    for (Text.Line line : block.getLines()) {
        String lineText = line.getText();
        Point[] lineCornerPoints = line.getCornerPoints();
        Rect lineFrame = line.getBoundingBox();
        for (Text.Element element : line.getElements()) {
            String elementText = element.getText();
            Point[] elementCornerPoints = element.getCornerPoints();
            Rect elementFrame = element.getBoundingBox();
            for (Text.Symbol symbol : element.getSymbols()) {
                String symbolText = symbol.getText();
                Point[] symbolCornerPoints = symbol.getCornerPoints();
                Rect symbolFrame = symbol.getBoundingBox();
            }
        }
    }
}

Wytyczne dotyczące obrazu wejściowego

  • Aby ML Kit dokładnie rozpoznawał tekst, obrazy wejściowe muszą zawierać tekst reprezentowany przez wystarczającą ilość danych pikseli. Najlepiej, aby każdy znak miał rozmiar co najmniej 16 x 16 pikseli. Zazwyczaj nie zwiększa się dokładność w przypadku znaków większych niż 24 x 24 piksele.

    Na przykład obraz o wymiarach 640 x 480 może dobrze sprawdzić się do zeskanowania wizytówki, która zajmuje całą szerokość obrazu. Aby zeskanować dokument wydrukowany na papierze w formacie literowym, wymagany może być obraz o rozdzielczości 720 x 1280 pikseli.

  • Słaba ostrość obrazu może zmniejszyć dokładność rozpoznawania tekstu. Jeśli nie uzyskujesz akceptowalnych wyników, poproś użytkownika o ponowne przechwycenie obrazu.

  • Jeśli rozpoznajesz tekst w aplikacji czasu rzeczywistego, musisz wziąć pod uwagę ogólne wymiary obrazów wejściowych. Mniejsze obrazy mogą być przetwarzane szybciej. Aby skrócić czas oczekiwania, zadbaj o to, aby tekst zajmował jak największą część obrazu i przechwytuj obrazy w niższej rozdzielczości (pamiętając o powyższych wymaganiach dotyczących dokładności). Więcej informacji znajdziesz w artykule Wskazówki pozwalające zwiększyć wydajność.

Wskazówki dotyczące poprawy skuteczności

  • Jeśli używasz interfejsu API Camera lub camera2, ograniczaj wywołania detektora. Jeśli podczas działania detektora pojawi się nowa klatka wideo, upuść ją. Przykład znajdziesz w klasie VisionProcessorBase w przykładowej aplikacji z krótkim wprowadzeniem.
  • Jeśli używasz interfejsu API CameraX, upewnij się, że strategia obciążenia zwrotnego jest ustawiona na wartość domyślną ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Gwarantuje to, że do analizy zostanie dostarczony tylko 1 obraz naraz. Jeśli zostanie utworzonych więcej obrazów, gdy analizator jest zajęty, zostaną one automatycznie usunięte i nie trafią do kolejki dostarczania. Gdy analizowany obraz zostanie zamknięty przez wywołanie metody ImageProxy.close(), zostanie wyświetlony następny najnowszy obraz.
  • Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a potem wyrenderuj obraz i nakładkę w jednym kroku. Powoduje to renderowanie na powierzchni wyświetlania tylko raz na każdą klatkę wejściową. Przykład znajdziesz w klasach CameraSourcePreview i GraphicOverlay w przykładowej aplikacji z krótkim wprowadzeniem.
  • Jeśli używasz interfejsu Camera2 API, rób zdjęcia w formacie ImageFormat.YUV_420_888. Jeśli używasz starszej wersji interfejsu Camera API, rób zdjęcia w formacie ImageFormat.NV21.
  • Rozważ robienie zdjęć w niższej rozdzielczości. Pamiętaj jednak o wymaganiach tego interfejsu dotyczących wymiarów obrazów.