在 Android 上使用 ML Kit 偵測及追蹤物件

你可以使用 ML Kit 偵測及追蹤影片畫面中的物件。

傳遞 ML Kit 圖片後,ML Kit 就會針對每個圖片傳回一份清單 偵測到的物體 (最多五個),以及物件在圖片中的位置。偵測時 每個物件都會有專屬的 ID,可用來追蹤 物件您也可以視需要啟用粗略物件 也就是將物件加上廣泛類別說明的標籤

事前準備

  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-object-detection-model:19.0.6'
    }
    

1. 設定物件偵測工具

如要開始偵測和追蹤物件,請先建立 FirebaseVisionObjectDetector,您可以視需要指定任何偵測工具設定 變更預設項目

  1. 使用 FirebaseVisionObjectDetectorOptions 物件。您可以變更下列項目 設定:

    物件偵測器設定
    偵測模式 STREAM_MODE (預設) |SINGLE_IMAGE_MODE

    STREAM_MODE (預設) 中,物件偵測工具會執行 低延遲,但可能產生不完整的結果 (例如 未指定定界框或類別標籤) 每個回呼函式的保留時間。此外,在「STREAM_MODE」中: 偵測工具會指派追蹤 ID 給物件,您可以用來 跨影格追蹤物件使用此模式追蹤 或低延遲的重要時機 觀看串流影片

    SINGLE_IMAGE_MODE 中,物件偵測工具會等待 直到偵測到物件的定界框,以及 類別標籤才會傳回 結果。因此,偵測延遲時間可能會增加。 此外,SINGLE_IMAGE_MODE 中的追蹤 ID 也不會 。如果延遲時間不重要,也沒有 可以處理部分結果

    偵測並追蹤多個物件 false (預設) |true

    偵測及追蹤最多五個物件 明顯的物件 (預設)。

    將物件分類 false (預設) |true

    是否將偵測到的物件歸類為粗略的類別。 啟用時,物件偵測工具會將物件 以下類別:時尚商品、食品、居家用品 例如景點、植物和不明地點

    物件偵測及追蹤 API 已針對這兩種核心用途進行最佳化 案件:

    • 即時偵測和追蹤相機中最顯眼的物體 觀景窗
    • 偵測靜態圖片中的多個物體

    如何針對這些用途設定 API:

    Java

    // Live detection and tracking
    FirebaseVisionObjectDetectorOptions options =
            new FirebaseVisionObjectDetectorOptions.Builder()
                    .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE)
                    .enableClassification()  // Optional
                    .build();
    
    // Multiple object detection in static images
    FirebaseVisionObjectDetectorOptions options =
            new FirebaseVisionObjectDetectorOptions.Builder()
                    .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE)
                    .enableMultipleObjects()
                    .enableClassification()  // Optional
                    .build();
    

    Kotlin+KTX

    // Live detection and tracking
    val options = FirebaseVisionObjectDetectorOptions.Builder()
            .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE)
            .enableClassification()  // Optional
            .build()
    
    // Multiple object detection in static images
    val options = FirebaseVisionObjectDetectorOptions.Builder()
            .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE)
            .enableMultipleObjects()
            .enableClassification()  // Optional
            .build()
    
  2. 取得 FirebaseVisionObjectDetector 的例項:

    Java

    FirebaseVisionObjectDetector objectDetector =
            FirebaseVision.getInstance().getOnDeviceObjectDetector();
    
    // Or, to change the default settings:
    FirebaseVisionObjectDetector objectDetector =
            FirebaseVision.getInstance().getOnDeviceObjectDetector(options);
    

    Kotlin+KTX

    val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector()
    
    // Or, to change the default settings:
    val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options)
    

2. 執行物件偵測工具

如要偵測及追蹤物件,請將圖片傳遞至 FirebaseVisionObjectDetector 執行個體的 processImage() 方法

針對連續影片或圖片影格,執行下列操作:

  1. 從您的圖片建立 FirebaseVisionImage 物件。

    • 如何透過FirebaseVisionImage media.Image 物件,例如從 裝置的相機,請傳遞 media.Image 物件和圖片的 旋轉至 FirebaseVisionImage.fromMediaImage()

      如果您使用 CameraX 程式庫、OnImageCapturedListenerImageAnalysis.Analyzer 類別會計算旋轉值 因此只需將旋轉模型 轉換為 ML Kit 的 呼叫前 ROTATION_ 常數 FirebaseVisionImage.fromMediaImage()

      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()
      }
    • 如何透過FirebaseVisionImage ByteBuffer 或位元組陣列,請先計算圖片 旋轉 (方法如上所述) 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)
    • 如何透過FirebaseVisionImage Bitmap 物件:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Bitmap 物件代表的圖片必須 保持直立,不用另外旋轉。
  2. 將圖片傳遞至 processImage() 方法:

    Java

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

    Kotlin+KTX

    objectDetector.processImage(image)
            .addOnSuccessListener { detectedObjects ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }
    
  3. 如果呼叫 processImage() 成功,則為 FirebaseVisionObject 清單。 傳遞到成功事件監聽器

    每個 FirebaseVisionObject 都包含下列屬性:

    定界框 Rect:指出物件在 圖片。
    追蹤 ID 一個整數,可在圖片中識別物件。出現空值 SINGLE_IMAGE_MODE。
    類別 物件的概略類別。如果物件偵測工具並未 如果已啟用分類功能 FirebaseVisionObject.CATEGORY_UNKNOWN
    可信度 物件分類的可信度值。如果物件 偵測工具未啟用分類功能,或是 已歸類為不明 null

    Java

    // The list of detected objects contains one item if multiple object detection wasn't enabled.
    for (FirebaseVisionObject obj : detectedObjects) {
        Integer id = obj.getTrackingId();
        Rect bounds = obj.getBoundingBox();
    
        // If classification was enabled:
        int category = obj.getClassificationCategory();
        Float confidence = obj.getClassificationConfidence();
    }
    

    Kotlin+KTX

    // The list of detected objects contains one item if multiple object detection wasn't enabled.
    for (obj in detectedObjects) {
        val id = obj.trackingId       // A number that identifies the object across images
        val bounds = obj.boundingBox  // The object's position in the image
    
        // If classification was enabled:
        val category = obj.classificationCategory
        val confidence = obj.classificationConfidence
    }
    

提升可用性和效能

為獲得最佳使用者體驗,請在應用程式中遵循以下規範:

  • 是否成功偵測物件,取決於物件的視覺複雜度。物品 但如果有一小部分視覺特徵,您可能需要完成較大 指定要偵測到的映像檔您應為使用者提供應用程式擷取的相關指引 輸入適用您欲偵測物件的種類的輸入查詢
  • 使用分類功能時,可偵測未達預期狀態的物件 完整地新增至支援的類別,並針對不明狀況導入特殊處理 如需儲存大量結構化物件 建議使用 Cloud Bigtable

此外,也請參閱 [ML Kit Material Design 展示應用程式][showcase-link]{: .external } 和 質感設計 機器學習輔助功能的模式集合。

在即時應用程式中使用串流模式時,請遵循下列準則 最佳的影格速率:

  • 大多數裝置不會在串流模式下使用多項物件偵測功能, 產生足夠的影格速率

  • 如果不需要分類功能,請停用分類功能。

  • 限制對偵測工具的呼叫。如果新的影片影格 因此請在偵測器執行時捨棄影格。
  • 使用偵測工具的輸出內容將圖像重疊 先從 ML Kit 取得結果,然後算繪圖片 並疊加單一步驟這麼一來,您的應用程式就會算繪到顯示途徑 每個輸入影格只能建立一次
  • 如果你使用 Camera2 API, ImageFormat.YUV_420_888 格式。

    如果使用舊版 Camera API,請以 ImageFormat.NV21 格式。