在 Android 上使用自訂模型為圖片加上標籤

您可以使用 ML Kit 辨識圖片中的實體,並加上標籤。 這個 API 支援多種自訂圖片分類模型。請 相關指南請參閱「使用 ML Kit 自訂模型」一文 模型相容性需求,哪裡可以找到預先訓練模型 以及如何訓練自有模型

整合圖片標籤和自訂模型的方法有兩種:透過組合 或者使用 。如果選取未組合的管道 小。詳情請參閱下表。

組合未分類
程式庫名稱com.google.mlkit:image-labeling-customcom.google.android.gms:play-services-mlkit-image-labeling-custom
導入作業管道會在建構期間以靜態方式連結至您的應用程式。管道是透過 Google Play 服務動態下載。
應用程式大小大小增加約 3.8 MB。大小增加約 200 KB。
初始化時間管道可立即使用。可能必須先等待管道下載完畢,才能開始使用。
API 生命週期階段正式發布版Beta 版

整合自訂模型的方式有兩種:將模型按照 將檔案放入應用程式的素材資源資料夾,或以動態方式下載 。下表比較這兩個選項。

套裝組合模型 託管模型
這個模型是應用程式 APK 的一部分,會增加其大小。 這個模型並非 APK 的一部分。這是由上傳至 Firebase 機器學習
可立即使用型號,即使 Android 裝置離線也沒問題 模型會隨選下載
不需要 Firebase 專案 需要 Firebase 專案
您必須重新發布應用程式才能更新模型 不必重新發布應用程式即可推送模型更新
沒有內建 A/B 測試 使用 Firebase 遠端設定輕鬆進行 A/B 測試

立即試用

事前準備

  1. 請務必在專案層級的 build.gradle 檔案中納入 位於 buildscript 和 的 Google Maven 存放區 allprojects 個版面。

  2. 將 ML Kit Android 程式庫的依附元件新增至模組的 應用程式層級的 Gradle 檔案,通常為 app/build.gradle。選擇下列其中一個選項: 您可以視需求選取下列依附元件:

    將管道與應用程式搭配使用:

    dependencies {
      // ...
      // Use this dependency to bundle the pipeline with your app
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
    }
    

    在 Google Play 服務中使用管道的步驟如下:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded pipeline in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5'
    }
    
  3. 如果您選擇在 Google Play 服務中使用管道,您可以 將應用程式設為自動下載管道 您的應用程式是從 Play 商店安裝若要執行此操作,請新增下列程式碼 新增至應用程式的 AndroidManifest.xml 檔案:

    <application ...>
        ...
        <meta-data
            android:name="com.google.mlkit.vision.DEPENDENCIES"
            android:value="custom_ica" />
        <!-- To use multiple downloads: android:value="custom_ica,download2,download3" -->
    </application>
    

    也可以明確檢查管道是否可用並要求下載 Google Play 服務 ModuleInstallClient API

    如果沒有啟用安裝期間管道下載功能或要求明確下載, 首次執行標籤人員時,系統會下載管道。您提出的要求 就無法取得任何結果。

  4. 如要動態下載,請新增 linkFirebase 依附元件 建立 Vertex AI 模型

    如要從 Firebase 動態下載模型,請新增 linkFirebase 依附元件:

    dependencies {
      // ...
      // Image labeling feature with model downloaded from Firebase
      implementation 'com.google.mlkit:image-labeling-custom:17.0.3'
      // Or use the dynamically downloaded pipeline in Google Play Services
      // implementation 'com.google.android.gms:play-services-mlkit-image-labeling-custom:16.0.0-beta5'
      implementation 'com.google.mlkit:linkfirebase:17.0.0'
    }
    
  5. 如要下載模型,請務必 將 Firebase 新增至您的 Android 專案, 如果尚未建立當您要組合模型時,不必做出任何動作。

1. 載入模型

設定本機模型來源

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

  1. 將模型檔案 (通常結尾為 .tflite.lite) 複製到應用程式的 assets/資料夾。(您可能需要先建立資料夾, 在 app/ 資料夾上按一下滑鼠右鍵,然後點選 新增 >資料夾 >素材資源資料夾)。

  2. 接著,請將以下內容新增至應用程式的 build.gradle 檔案,確保 Gradle 不會在建構應用程式時壓縮模型檔案:

    android {
        // ...
        aaptOptions {
            noCompress "tflite"
            // or noCompress "lite"
        }
    }
    

    模型檔案將包含在應用程式套件中,並可供 ML Kit 使用 做為原始素材資源

  3. 建立 LocalModel 物件,指定模型檔案的路徑:

    Kotlin

    val localModel = LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build()

    Java

    LocalModel localModel =
        new LocalModel.Builder()
            .setAssetFilePath("model.tflite")
            // or .setAbsoluteFilePath(absolute file path to model file)
            // or .setUri(URI to model file)
            .build();

設定 Firebase 託管的模型來源

如要使用遠端託管模型,請建立 RemoteModel 物件: FirebaseModelSource,指定您在建立模型時指派模型的名稱 已發布:

Kotlin

// Specify the name you assigned in the Firebase console.
val remoteModel =
    CustomRemoteModel
        .Builder(FirebaseModelSource.Builder("your_model_name").build())
        .build()

Java

// Specify the name you assigned in the Firebase console.
CustomRemoteModel remoteModel =
    new CustomRemoteModel
        .Builder(new FirebaseModelSource.Builder("your_model_name").build())
        .build();

接著,啟動模型下載工作,並指定在 您要允許下載的應用程式。如果裝置上沒有該型號,或是新型號 就能以非同步方式下載該模型 建立 Vertex AI 模型

Kotlin

val downloadConditions = DownloadConditions.Builder()
    .requireWifi()
    .build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
    .addOnSuccessListener {
        // Success.
    }

Java

DownloadConditions downloadConditions = new DownloadConditions.Builder()
        .requireWifi()
        .build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(@NonNull Task task) {
                // Success.
            }
        });

許多應用程式會在初始化程式碼中啟動下載工作,但您 這個模型會在您需要使用模型前執行

設定圖片標籤人員

設定模型來源後,請建立 ImageLabeler 物件,來源如下: 其中之一

可用選項如下所示:

選項
confidenceThreshold

偵測到標籤的最低可信度分數。如果未設定 系統會使用模型中繼資料指定的分類器門檻。 如果模型未包含任何中繼資料,或中繼資料不包含 指定分類器門檻,預設門檻為 0.0

maxResultCount

要傳回的標籤數量上限。如果未設定,系統會採用預設值 將使用 10。

如果您只有本機組合模型,只要從 LocalModel 物件:

Kotlin

val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
    .setConfidenceThreshold(0.5f)
    .setMaxResultCount(5)
    .build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)

Java

CustomImageLabelerOptions customImageLabelerOptions =
        new CustomImageLabelerOptions.Builder(localModel)
            .setConfidenceThreshold(0.5f)
            .setMaxResultCount(5)
            .build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);

如果您使用的是遠端託管的模型,則須檢查該模型是否已 執行前已下載完成您可以查看模型下載狀態 使用模型管理員的 isModelDownloaded() 方法完成任務

雖然您不必在執行標籤人員前確認 同時擁有遠端託管和本機封裝模型 要將圖片標籤器例項化時執行這項檢查,請建立 從遠端模型下載標籤人員 反之。

Kotlin

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded ->
    val optionsBuilder =
        if (isDownloaded) {
            CustomImageLabelerOptions.Builder(remoteModel)
        } else {
            CustomImageLabelerOptions.Builder(localModel)
        }
    val options = optionsBuilder
                  .setConfidenceThreshold(0.5f)
                  .setMaxResultCount(5)
                  .build()
    val labeler = ImageLabeling.getClient(options)
}

Java

RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                CustomImageLabelerOptions.Builder optionsBuilder;
                if (isDownloaded) {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(remoteModel);
                } else {
                    optionsBuilder = new CustomImageLabelerOptions.Builder(localModel);
                }
                CustomImageLabelerOptions options = optionsBuilder
                    .setConfidenceThreshold(0.5f)
                    .setMaxResultCount(5)
                    .build();
                ImageLabeler labeler = ImageLabeling.getClient(options);
            }
        });

如果只有遠端託管的模型,請停用模型相關 或隱藏部分 UI,直到 您確認模型已下載完成附加監聽器即可 複製到模型管理工具的 download() 方法:

Kotlin

RemoteModelManager.getInstance().download(remoteModel, conditions)
    .addOnSuccessListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

Java

RemoteModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener() {
            @Override
            public void onSuccess(Void v) {
              // Download complete. Depending on your app, you could enable
              // the ML feature, or switch from the local model to the remote
              // model, etc.
            }
        });

2. 準備輸入圖片

接著,為要加上標籤的每張圖片建立 InputImage。 物件中的物件名稱當您使用 Bitmap 時,圖片標籤工具執行速度最快 或者,如果您使用 camera2 API,則 YUV_420_888 media.Image,這些是 建議選取

您可以建立InputImage 不同來源的 ANR 物件,說明如下。

使用 media.Image

如要建立InputImage 物件,例如從 media.Image 物件擷取圖片 裝置的相機,請傳遞 media.Image 物件和映像檔的 旋轉為 InputImage.fromMediaImage()

如果您使用 CameraX 程式庫、OnImageCapturedListenerImageAnalysis.Analyzer 類別會計算旋轉值 不必確保憑證管理是否適當 因為 Google Cloud 會為您管理安全性

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 意圖提示使用者選取 取自圖片庫應用程式中的圖片。

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. 執行映像檔標籤工具

如要為圖片中的物件加上標籤,請將 image 物件傳遞至 ImageLabelerprocess() 方法。

Kotlin

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

Java

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

4. 取得已加上標籤實體的相關資訊

如果圖片標籤作業成功,系統會顯示 ImageLabel 清單。 會將物件傳遞到成功事件監聽器每個 ImageLabel 物件都代表已在圖片中加上標籤的內容。可以取得每個標籤的文字 說明 (若適用於 TensorFlow Lite 模型檔案的中繼資料)、可信度分數和索引。例如:

Kotlin

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

Java

for (ImageLabel label : labels) {
    String text = label.getText();
    float confidence = label.getConfidence();
    int index = label.getIndex();
}

即時效能改善訣竅

如要在即時應用程式中為圖片加上標籤,請按照下列步驟操作: 遵循下列原則,才能達到最佳畫面更新率

  • 如果您使用 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 格式。