获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

在 Android 上使用 ML Kit 标记图像

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

您可以使用 ML Kit 标记图像中识别的对象,使用设备上的模型或云模型。请参阅概述以了解每种方法的好处。

在你开始之前

  1. 如果您还没有,请将 Firebase 添加到您的 Android 项目中。
  2. 将 ML Kit Android 库的依赖项添加到您的模块(应用级)Gradle 文件(通常是app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-image-label-model:20.0.1'
    }
    
  3. 可选但推荐:如果您使用设备上 API,请将您的应用配置为在从 Play 商店安装您的应用后自动将 ML 模型下载到设备。

    为此,请将以下声明添加到您应用的AndroidManifest.xml文件中:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="label" />
      <!-- To use multiple models: android:value="label,model2,model3" -->
    </application>
    
    如果您不启用安装时模型下载,则模型将在您第一次运行设备检测器时下载。您在下载完成之前提出的请求不会产生任何结果。
  4. 如果您想使用基于云的模型,并且尚未为您的项目启用基于云的 API,请立即执行此操作:

    1. 打开 Firebase 控制台的ML Kit API 页面
    2. 如果您尚未将项目升级到 Blaze 定价计划,请单击升级以执行此操作。 (仅当您的项目不在 Blaze 计划中时,系统才会提示您升级。)

      只有 Blaze 级项目可以使用基于云的 API。

    3. 如果尚未启用基于云的 API,请单击启用基于云的 API

    如果您只想使用设备端模型,则可以跳过此步骤。

现在您已准备好使用设备上模型或基于云的模型来标记图像。

1.准备输入图像

从您的图像创建一个FirebaseVisionImage对象。当您使用Bitmap或如果您使用 camera2 API 时,图像标注器运行速度最快,如果可能,建议使用 JPEG 格式的media.Image

  • 要从media.Image对象创建FirebaseVisionImage对象,例如从设备的摄像头捕获图像时,请将media.Image对象和图像的旋转传递给FirebaseVisionImage.fromMediaImage()

    如果您使用CameraX库, OnImageCapturedListenerImageAnalysis.Analyzer类会为您计算旋转值,因此您只需在调用FirebaseVisionImage.fromMediaImage()之前将旋转转换为 ML Kit 的ROTATION_常量之一:

    Java

    private class YourAnalyzer implements ImageAnalysis.Analyzer {
    
        private int degreesToFirebaseRotation(int degrees) {
            switch (degrees) {
                case 0:
                    return FirebaseVisionImageMetadata.ROTATION_0;
                case 90:
                    return FirebaseVisionImageMetadata.ROTATION_90;
                case 180:
                    return FirebaseVisionImageMetadata.ROTATION_180;
                case 270:
                    return FirebaseVisionImageMetadata.ROTATION_270;
                default:
                    throw new IllegalArgumentException(
                            "Rotation must be 0, 90, 180, or 270.");
            }
        }
    
        @Override
        public void analyze(ImageProxy imageProxy, int degrees) {
            if (imageProxy == null || imageProxy.getImage() == null) {
                return;
            }
            Image mediaImage = imageProxy.getImage();
            int rotation = degreesToFirebaseRotation(degrees);
            FirebaseVisionImage image =
                    FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
    

    Kotlin+KTX

    private class YourImageAnalyzer : ImageAnalysis.Analyzer {
        private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
            0 -> FirebaseVisionImageMetadata.ROTATION_0
            90 -> FirebaseVisionImageMetadata.ROTATION_90
            180 -> FirebaseVisionImageMetadata.ROTATION_180
            270 -> FirebaseVisionImageMetadata.ROTATION_270
            else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
        }
    
        override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
            val mediaImage = imageProxy?.image
            val imageRotation = degreesToFirebaseRotation(degrees)
            if (mediaImage != null) {
                val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                // Pass image to an ML Kit Vision API
                // ...
            }
        }
    }
    

    如果您不使用为您提供图像旋转的相机库,您可以根据设备的旋转和设备中相机传感器的方向来计算它:

    Java

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }
    
    /**
     * 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, Context context)
            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);
    
        // On most devices, the sensor orientation is 90 degrees, but for some
        // devices it is 270 degrees. For devices with a sensor orientation of
        // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
        CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
        int sensorOrientation = cameraManager
                .getCameraCharacteristics(cameraId)
                .get(CameraCharacteristics.SENSOR_ORIENTATION);
        rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
    
        // Return the corresponding FirebaseVisionImageMetadata rotation value.
        int result;
        switch (rotationCompensation) {
            case 0:
                result = FirebaseVisionImageMetadata.ROTATION_0;
                break;
            case 90:
                result = FirebaseVisionImageMetadata.ROTATION_90;
                break;
            case 180:
                result = FirebaseVisionImageMetadata.ROTATION_180;
                break;
            case 270:
                result = FirebaseVisionImageMetadata.ROTATION_270;
                break;
            default:
                result = FirebaseVisionImageMetadata.ROTATION_0;
                Log.e(TAG, "Bad rotation value: " + rotationCompensation);
        }
        return result;
    }

    Kotlin+KTX

    private val ORIENTATIONS = SparseIntArray()
    
    init {
        ORIENTATIONS.append(Surface.ROTATION_0, 90)
        ORIENTATIONS.append(Surface.ROTATION_90, 0)
        ORIENTATIONS.append(Surface.ROTATION_180, 270)
        ORIENTATIONS.append(Surface.ROTATION_270, 180)
    }
    /**
     * 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, context: Context): 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)
    
        // On most devices, the sensor orientation is 90 degrees, but for some
        // devices it is 270 degrees. For devices with a sensor orientation of
        // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
        val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
        val sensorOrientation = cameraManager
                .getCameraCharacteristics(cameraId)
                .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
        rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
    
        // Return the corresponding FirebaseVisionImageMetadata rotation value.
        val result: Int
        when (rotationCompensation) {
            0 -> result = FirebaseVisionImageMetadata.ROTATION_0
            90 -> result = FirebaseVisionImageMetadata.ROTATION_90
            180 -> result = FirebaseVisionImageMetadata.ROTATION_180
            270 -> result = FirebaseVisionImageMetadata.ROTATION_270
            else -> {
                result = FirebaseVisionImageMetadata.ROTATION_0
                Log.e(TAG, "Bad rotation value: $rotationCompensation")
            }
        }
        return result
    }

    然后,将media.Image对象和旋转值传递给FirebaseVisionImage.fromMediaImage()

    Java

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

    Kotlin+KTX

    val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
  • 要从文件 URI 创建FirebaseVisionImage对象,请将应用上下文和文件 URI 传递给FirebaseVisionImage.fromFilePath() 。当您使用ACTION_GET_CONTENT意图提示用户从他们的图库应用中选择图像时,这很有用。

    Java

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

    Kotlin+KTX

    val image: FirebaseVisionImage
    try {
        image = FirebaseVisionImage.fromFilePath(context, uri)
    } catch (e: IOException) {
        e.printStackTrace()
    }
  • 要从ByteBuffer或字节数组创建FirebaseVisionImage对象,请首先按照上面针对media.Image输入的描述计算图像旋转。

    然后,创建一个FirebaseVisionImageMetadata对象,其中包含图像的高度、宽度、颜色编码格式和旋转:

    Java

    FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
            .setWidth(480)   // 480x360 is typically sufficient for
            .setHeight(360)  // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build();

    Kotlin+KTX

    val metadata = FirebaseVisionImageMetadata.Builder()
            .setWidth(480) // 480x360 is typically sufficient for
            .setHeight(360) // image recognition
            .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
            .setRotation(rotation)
            .build()

    使用缓冲区或数组以及元数据对象来创建FirebaseVisionImage对象:

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
    // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

    Kotlin+KTX

    val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
    // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
  • 要从Bitmap对象创建FirebaseVisionImage对象:

    Java

    FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

    Kotlin+KTX

    val image = FirebaseVisionImage.fromBitmap(bitmap)
    Bitmap对象表示的图像必须是直立的,不需要额外的旋转。

2. 配置并运行图像标注器

要标记图像中的对象,请将FirebaseVisionImage对象传递给FirebaseVisionImageLabelerprocessImage方法。

  1. 首先,获取FirebaseVisionImageLabeler的实例。

    如果您想使用设备上的图像标注器:

    Java

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
        .getOnDeviceImageLabeler();
    
    // Or, to set the minimum confidence required:
    // FirebaseVisionOnDeviceImageLabelerOptions options =
    //     new FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getOnDeviceImageLabeler(options);
    

    Kotlin+KTX

    val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler()
    
    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionOnDeviceImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getOnDeviceImageLabeler(options)
    

    如果要使用云图像标注器:

    Java

    FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
        .getCloudImageLabeler();
    
    // Or, to set the minimum confidence required:
    // FirebaseVisionCloudImageLabelerOptions options =
    //     new FirebaseVisionCloudImageLabelerOptions.Builder()
    //         .setConfidenceThreshold(0.7f)
    //         .build();
    // FirebaseVisionImageLabeler labeler = FirebaseVision.getInstance()
    //     .getCloudImageLabeler(options);
    

    Kotlin+KTX

    val labeler = FirebaseVision.getInstance().getCloudImageLabeler()
    
    // Or, to set the minimum confidence required:
    // val options = FirebaseVisionCloudImageLabelerOptions.Builder()
    //     .setConfidenceThreshold(0.7f)
    //     .build()
    // val labeler = FirebaseVision.getInstance().getCloudImageLabeler(options)
    

  2. 然后,将图像传递给processImage()方法:

    Java

    labeler.processImage(image)
        .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionImageLabel>>() {
          @Override
          public void onSuccess(List<FirebaseVisionImageLabel> labels) {
            // Task completed successfully
            // ...
          }
        })
        .addOnFailureListener(new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Task failed with an exception
            // ...
          }
        });
    

    Kotlin+KTX

    labeler.processImage(image)
        .addOnSuccessListener { labels ->
          // Task completed successfully
          // ...
        }
        .addOnFailureListener { e ->
          // Task failed with an exception
          // ...
        }
    

3. 获取标记对象的信息

如果图像标记操作成功, FirebaseVisionImageLabel对象列表将传递给成功侦听器。每个FirebaseVisionImageLabel对象都代表图像中标记的内容。对于每个标签,您可以获得标签的文本描述、其知识图实体 ID (如果可用)和匹配的置信度分数。例如:

Java

for (FirebaseVisionImageLabel label: labels) {
  String text = label.getText();
  String entityId = label.getEntityId();
  float confidence = label.getConfidence();
}

Kotlin+KTX

for (label in labels) {
  val text = label.text
  val entityId = label.entityId
  val confidence = label.confidence
}

提高实时性能的技巧

如果您想在实时应用程序中标记图像,请遵循以下指南以获得最佳帧率:

  • 限制对图像标注器的调用。如果在图像标注器运行时有新的视频帧可用,则丢弃该帧。
  • 如果您使用图像标注器的输出在输入图像上叠加图形,首先从 ML Kit 获取结果,然后在一个步骤中渲染图像并叠加。通过这样做,您只为每个输入帧渲染到显示表面一次。
  • 如果您使用 Camera2 API,请以ImageFormat.YUV_420_888格式捕获图像。

    如果您使用较旧的 Camera API,请以ImageFormat.NV21格式捕获图像。

下一步