使用机器学习套件识别图片中的文本 (Android)

您可以使用机器学习套件识别图片或视频中的文本,例如 路牌。此功能的主要特点包括:

功能 不分类显示 组合
库名称 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. 将 Android 版机器学习套件库的依赖项添加到模块的应用级 Gradle 文件(通常为 app/build.gradle):

    如需将模型与应用捆绑,请执行以下操作

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

    对于在 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. 如果您选择在 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. 准备输入图片

如需识别图片中的文本,请使用以下模板创建一个 InputImage 对象: Bitmapmedia.ImageByteBuffer、字节数组或 设备。然后,将 InputImage 对象传递给 TextRecognizerprocessImage 方法。

您可以创建 InputImage 对象,下文对每种方法进行了说明。

使用 media.Image

如需创建 InputImage,请执行以下操作: 对象(例如从 media.Image 对象中捕获图片时) 请传递 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

如需创建 InputImage,请执行以下操作: 对象时,请将应用上下文和文件 URI 传递给 InputImage.fromFilePath()。在需要满足特定条件时 使用 ACTION_GET_CONTENT intent 提示用户进行选择 从图库应用中获取图片

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

如需创建 InputImage,请执行以下操作: 对象ByteBufferByteArray时,首先计算图像 旋转角度。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

如需创建 InputImage,请执行以下操作: 对象时,请进行以下声明:Bitmap

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, 确保将 backpressure 策略设置为默认值 ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST。 这可保证一次只传送一张图片进行分析。如果有更多图片 在分析器繁忙时生成,它们会被自动丢弃,不会排队等待 。通过调用 ImageProxy.close(),将传递下一张图片。
  • 如果您使用检测器的输出在图像上叠加显示 输入图片,首先从机器学习套件获取结果, 和叠加层。这会渲染到 每个输入帧只执行一次。请参阅 CameraSourcePreview GraphicOverlay 类。
  • 如果您使用 Camera2 API,请以 ImageFormat.YUV_420_888 格式。如果您使用旧版 Camera API,请使用 ImageFormat.NV21 格式。
  • 建议以较低的分辨率捕获图片。但请注意 该 API 的图片尺寸要求