在 Android 上使用 ML Kit 辨識圖片中的文字

您可以使用機器學習套件辨識圖片或影片中的文字,例如路標的文字。這項功能的主要特徵如下:

文字辨識 API
說明辨識圖片或影片中的拉丁字母文字。
程式庫名稱com.google.android.gms:play-services-mlkit-text-recognition
實作Google Play 服務會以動態方式下載程式庫。
應用程式大小影響260KB
初始化時間可能必須先等待程式庫下載完成才能使用。
效能在多數裝置上即時執行。

立即體驗

事前準備

  1. 在專案層級的 build.gradle 檔案中,請務必在您的 buildscriptallprojects 區段中加入 Google 的 Maven 存放區。
  2. 將 ML Kit Android 程式庫的依附元件新增至模組的應用程式層級 Gradle 檔案 (通常為 app/build.gradle):
    dependencies {
      // ...
    
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.2'
    }
    
  3. 選用,但建議使用:您可以在從 Play 商店安裝應用程式後,將機器學習模型自動下載到裝置上。 為此,請在應用程式的 AndroidManifest.xml 檔案中新增以下宣告:

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

    您也可以明確檢查模型可用性,以及透過 Google Play 服務 ModuleInstallClient API 要求下載。

    如未啟用安裝時模型下載,系統會在您第一次執行裝置端偵測工具時下載模型。下載作業完成前提出的要求不會產生任何結果。

1. 建立 TextRecognizer 的執行個體

建立 TextRecognizer 的例項:

Kotlin

val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

Java

TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);

2. 準備輸入圖片

如要識別圖片中的文字,請透過 Bitmapmedia.ImageByteBuffer、位元組陣列或裝置上的檔案建立 InputImage 物件。接著,將 InputImage 物件傳遞至 TextRecognizerprocessImage 方法。

您可以從不同來源建立 InputImage 物件,以下將分別說明。

使用 media.Image

如要從 media.Image 物件建立 InputImage 物件 (例如從裝置相機拍攝圖片時),請將 media.Image 物件和圖片旋轉至 InputImage.fromMediaImage()

如果使用 CameraX 程式庫,OnImageCapturedListenerImageAnalysis.Analyzer 類別會為您計算旋轉值。

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
          // ...
        }
    }
}

如果您使用的相機程式庫會提供圖片旋轉角度,則可從裝置的旋轉度和裝置相機感應器的方向計算,如下所示:

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;
}

接著,將 media.Image 物件和旋轉度值傳送至 InputImage.fromMediaImage()

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

使用檔案 URI

如要透過檔案 URI 建立 InputImage 物件,請將應用程式結構定義和檔案 URI 傳遞至 InputImage.fromFilePath()。當您利用 ACTION_GET_CONTENT 意圖提示使用者從圖片庫應用程式中選取圖片時,這項功能就非常實用。

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();
}

使用 ByteBufferByteArray

如要透過 ByteBufferByteArray 建立 InputImage 物件,請先按照先前針對 media.Image 輸入內容所述的圖像旋轉度數計算。接著使用緩衝區或陣列來建立 InputImage 物件,並搭配圖片的高度、寬度、顏色編碼格式和旋轉度數:

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
);

使用 Bitmap

如要透過 Bitmap 物件建立 InputImage 物件,請進行以下宣告:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

此圖像以 Bitmap 物件表示,並以旋轉度數表示。

3. 處理圖片

將圖片傳遞至 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. 從已辨識的文字區塊擷取文字

如果文字辨識作業成功,Text 物件就會傳遞至成功的事件監聽器。Text 物件包含圖片中辨識的完整文字,以及零或多個 TextBlock 物件。

每個 TextBlock 都是矩形文字區塊,其中包含零個或多個 Line 物件。每個 Line 物件都代表一行文字,其中包含零或多個 Element 物件。每個 Element 物件都代表一個單字或類似字詞的實體,其中包含零個或多個 Symbol 物件。每個 Symbol 物件都代表一個字元、數字或類似的字詞實體。

針對各個 TextBlockLineElementSymbol 物件,您可以取得在地區內可辨識的文字、地區的邊界座標,以及旋轉資訊、可信度分數等許多其他屬性。

例如:

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();
            }
        }
    }
}

輸入圖片規範

  • 為了讓機器學習套件能正確識別文字,輸入圖片必須包含以足夠像素資料呈現的文字。在理想情況下,每個字元至少應為 16x16 像素。一般來說,如果字元大於 24x24 像素,通常就沒有準確率。

    例如,640x480 圖片可能有助於掃描佔滿圖片寬度的名片。如要掃描以字母大小列印的文件,您可能需要提供 720x1280 像素的圖片。

  • 圖片焦點不佳可能會影響文字辨識的準確度。如果無法收到可接受的結果,請嘗試請使用者重新擷取圖片。

  • 如果您要在即時應用程式中辨識文字,應考慮輸入圖片的整體尺寸。較小型的影像處理速度較快。為了縮短延遲時間,請確保文字會盡可能佔用最多圖片,並以較低解析度擷取圖片 (請注意上述的準確率規定)。如需詳細資訊,請參閱效能改善提示

改善成效的訣竅

  • 如果您使用的是 Cameracamera2 API,請呼叫偵測工具。如果有新的影片畫面在偵測工具執行時可供使用,請捨棄該影格。如需範例,請參閱快速入門導覽課程範例應用程式中的 VisionProcessorBase 類別。
  • 如果您使用 CameraX API,請確認背壓策略已設為預設值 ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST。這麼做可確保系統每次只會傳送一張圖片進行分析。如果在分析器處於忙碌狀態時產生更多圖片,系統會自動捨棄這些圖片,不會排入佇列。透過呼叫 ImageProxy.close() 將所分析的圖片關閉之後,即可提供下一張最新的圖片。
  • 如果您使用偵測工具的輸出內容,為輸入圖片上的圖像重疊,請先透過 ML Kit 取得結果,然後透過單一步驟算繪圖像和疊加層。每個輸入框只會向顯示途徑轉譯一次。如需範例,請參閱快速入門導覽課程範例應用程式中的 CameraSourcePreview GraphicOverlay 類別。
  • 如果您使用 Camera2 API,請以 ImageFormat.YUV_420_888 格式擷取圖片。如果您使用的是舊版 Camera API,請以 ImageFormat.NV21 格式擷取圖片。
  • 請考慮以較低的解析度拍照。同時也請注意,此 API 的圖片尺寸規定。