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

您可以使用 ML Kit 辨識圖片或影片中的文字,例如路標上的文字。這項功能的主要特性如下:

功能 未綁定 組合
程式庫名稱 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

實作 模型會透過 Google Play 服務動態下載。 模型會在建構期間與應用程式建立靜態連結。
應用程式大小 每個指令碼架構的大小會增加約 260 KB。 每個架構的每個指令碼大小約增加 4 MB。
初始化時間 首次使用前,可能需要等待模型下載。 模型可立即使用。
效能 在大多數裝置上,拉丁字母程式庫可即時運作,其他則較慢。 在大多數裝置上,拉丁字母程式庫可即時運作,其他則較慢。

立即試用

事前準備

  1. 在專案層級的 build.gradle 檔案中,請務必在 buildscriptallprojects 區段中納入 Google 的 Maven 存放區。
  2. 將 ML Kit Android 程式庫的依附元件新增至模組的應用程式層級 Gradle 檔案,通常為 app/build.gradle

    如要將模型與應用程式組合:

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

    如要在 Google Play 服務中使用模型:

    dependencies {
      // To recognize Latin script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.1'
    
      // To recognize Chinese script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1'
    
      // To recognize Devanagari script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.1'
    
      // To recognize Japanese script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1'
    
      // To recognize Korean script
      implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1'
    }
    
  3. 如果您選擇使用 Google Play 服務中的模型,可以設定應用程式,在從 Play 商店安裝應用程式後,自動將模型下載到裝置。如要這麼做,請在應用程式的 AndroidManifest.xml 檔案中新增下列宣告:

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

    您也可以透過 Google Play 服務的 ModuleInstallClient API,明確檢查模型的可用性,並要求下載。如果您未啟用安裝時間模型下載功能或要求明確下載,系統會在您首次執行檢查工具時下載模型。在下載完成前提出的要求不會產生任何結果。

1. 建立 TextRecognizer 的執行個體

建立 TextRecognizer 的例項,傳遞與您在上述步驟中宣告依附元件的程式庫相關的選項:

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. 準備輸入圖片

如要辨識圖片中的文字,請使用 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();
            }
        }
    }
}

輸入圖片規範

  • 為了讓 ML Kit 準確辨識文字,輸入圖片必須包含由足夠像素資料代表的文字。理想情況下,每個字元至少應為 16 x 16 像素。一般而言,字元大小超過 24x24 像素並不會帶來任何準確度優勢。

    舉例來說,如果要掃描占據整個圖片寬度的名片,使用 640x480 的圖片可能會比較適合。如要掃描列印在 A4 紙張上的文件,可能需要 720 x 1280 像素的圖片。

  • 圖片對焦不佳可能會影響文字辨識準確度。如果您無法取得可接受的結果,請嘗試要求使用者重新拍攝圖片。

  • 如果您要在即時應用程式中辨識文字,應考量輸入圖片的整體尺寸。較小的圖片可加快處理速度。為減少延遲時間,請確保文字盡可能占用圖片的大部分空間,並以較低解析度擷取圖片 (請注意上述精確度規定)。詳情請參閱「提升效能的小訣竅」。

提升效能的訣竅

  • 如果您使用 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 的圖片尺寸規定。