使用 AutoML Vision Edge 訓練專屬模型後,您就可以在應用程式中使用該模型來標記圖片。
事前準備
- 如果您尚未將 Firebase 新增至 Android 專案,請新增 Firebase。
- 將 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-automl:18.0.5' }
1. 載入模型
機器學習套件會在裝置上執行 AutoML 產生的模型。不過,您可以設定 ML Kit,讓它從 Firebase 或本機儲存空間遠端載入模型,也可以同時從這兩個位置載入模型。
在 Firebase 上代管模型後,您就能在不發布新版應用程式的情況下更新模型,並使用 Remote Config 和 A/B Testing 為不同使用者群組動態提供不同的模型。
如果您選擇只透過 Firebase 代管模型,而非與應用程式捆綁,就能縮減應用程式的初始下載大小。不過,請注意,如果模型未與應用程式捆綁,則必須等到應用程式首次下載模型,才能使用任何模型相關功能。
將模型與應用程式捆綁後,即使 Firebase 代管的模型無法使用,應用程式的 ML 功能仍可正常運作。
設定由 Firebase 代管的模型來源
如要使用遠端代管的模型,請建立 FirebaseAutoMLRemoteModel
物件,並指定您在發布模型時指派的名稱:
Java
// Specify the name you assigned in the Firebase console.
FirebaseAutoMLRemoteModel remoteModel =
new FirebaseAutoMLRemoteModel.Builder("your_remote_model").build();
Kotlin
// Specify the name you assigned in the Firebase console.
val remoteModel = FirebaseAutoMLRemoteModel.Builder("your_remote_model").build()
接著,啟動模型下載工作,並指定要允許下載的條件。如果裝置上沒有模型,或是有較新版本的模型可供使用,工作會從 Firebase 異步下載模型:
Java
FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
// Success.
}
});
Kotlin
val conditions = FirebaseModelDownloadConditions.Builder()
.requireWifi()
.build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Success.
}
許多應用程式會在初始化程式碼中啟動下載工作,但您可以在需要使用模型之前的任何時間啟動下載工作。
設定本機模型來源
如何將模型與應用程式組合:
- 從 Firebase 控制台下載的 ZIP 封存檔中,擷取模型及其中繼資料。建議您使用下載的檔案,不必修改 (包括檔案名稱)。
-
在應用程式套件中加入模型及其中繼資料檔案:
- 如果專案中沒有 Assets 資料夾,請在
app/
資料夾上按一下滑鼠右鍵,然後依序點選「New」>「Folder」>「Assets Folder」建立資料夾。 - 在 assets 資料夾下建立子資料夾,用於容納模型檔案。
- 將
model.tflite
、dict.txt
和manifest.json
檔案複製到子資料夾 (三個檔案必須位於同一個資料夾)。
- 如果專案中沒有 Assets 資料夾,請在
- 將以下內容加入應用程式的
build.gradle
檔案,確保 Gradle 在建構應用程式時不會壓縮模型檔案: 模型檔案會納入應用程式套件,並以原始素材資源的形式提供給 ML Kit。android { // ... aaptOptions { noCompress "tflite" } }
- 建立
FirebaseAutoMLLocalModel
物件,指定模型資訊清單檔案的路徑:Java
FirebaseAutoMLLocalModel localModel = new FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build();
Kotlin
val localModel = FirebaseAutoMLLocalModel.Builder() .setAssetFilePath("manifest.json") .build()
使用模型建立圖片標註器
設定模型來源後,請從其中一個來源建立 FirebaseVisionImageLabeler
物件。
如果您只有本機內建的模型,請從 FirebaseAutoMLLocalModel
物件建立標記器,然後設定所需的可信度分數門檻 (請參閱「評估模型」):
Java
FirebaseVisionImageLabeler labeler;
try {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options =
new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build();
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// ...
}
Kotlin
val options = FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0) // Evaluate your model in the Firebase console
// to determine an appropriate value.
.build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
如果您使用的是遠端代管模型,請務必先確認模型已下載,再執行模型。您可以使用模型管理員的 isModelDownloaded()
方法,查看模型下載作業的狀態。
雖然您只需要在執行標註器前確認這項資訊,但如果您同時擁有遠端代管模型和本機內建模型,在例項化圖片標註器時執行這項檢查可能會比較合理:如果已下載遠端模型,請從該模型建立標註器;如果未下載,請從本機模型建立標註器。
Java
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder optionsBuilder;
if (isDownloaded) {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel);
} else {
optionsBuilder = new FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel);
}
FirebaseVisionOnDeviceAutoMLImageLabelerOptions options = optionsBuilder
.setConfidenceThreshold(0.0f) // Evaluate your model in the Firebase console
// to determine an appropriate threshold.
.build();
FirebaseVisionImageLabeler labeler;
try {
labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options);
} catch (FirebaseMLException e) {
// Error.
}
}
});
Kotlin
FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(remoteModel)
} else {
FirebaseVisionOnDeviceAutoMLImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Firebase console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = FirebaseVision.getInstance().getOnDeviceAutoMLImageLabeler(options)
}
如果您只有遠端代管的模型,請在確認模型已下載前,停用模型相關功能 (例如將部分 UI 設為灰色或隱藏)。方法是將事件監聽器附加至模型管理員的 download()
方法:
Java
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@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.
}
});
Kotlin
FirebaseModelManager.getInstance().download(remoteModel, conditions)
.addOnCompleteListener {
// Download complete. Depending on your app, you could enable the ML
// feature, or switch from the local model to the remote model, etc.
}
2. 準備輸入圖片
接著,針對每張要標示的圖片,使用本節所述的其中一個選項建立 FirebaseVisionImage
物件,然後將其傳遞至 FirebaseVisionImageLabeler
的例項 (詳見下一節)。
您可以從 media.Image
物件、裝置上的檔案、位元組陣列或 Bitmap
物件建立 FirebaseVisionImage
物件:
-
如要從
media.Image
物件建立FirebaseVisionImage
物件 (例如從裝置相機擷取圖片時),請將media.Image
物件和圖片的旋轉角度傳遞至FirebaseVisionImage.fromMediaImage()
。如果您使用 CameraX 程式庫,
OnImageCapturedListener
和ImageAnalysis.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
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
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
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
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
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
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- 如要從
Bitmap
物件建立FirebaseVisionImage
物件,請按照下列步驟操作:Java
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
物件所代表的圖片必須是直立的,不需要額外旋轉。
3. 執行圖片標註工具
如要為圖片中的物件加上標籤,請將 FirebaseVisionImage
物件傳遞至 FirebaseVisionImageLabeler
的 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
labeler.processImage(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
如果圖片標示成功,系統會將 FirebaseVisionImageLabel
物件的陣列傳遞至成功事件監聽器。您可以從每個物件取得圖像中辨識到的特色資訊。
例如:
Java
for (FirebaseVisionImageLabel label: labels) {
String text = label.getText();
float confidence = label.getConfidence();
}
Kotlin
for (label in labels) {
val text = label.text
val confidence = label.confidence
}
改善即時成效的訣竅
- 限制對偵測器的呼叫。如果在偵測器執行期間有新的影片影格可用,請放棄該影格。
- 如果您要使用偵測器的輸出內容,在輸入圖片上疊加圖形,請先從 ML Kit 取得結果,然後在單一步驟中算繪圖片和疊加圖形。這樣一來,您只需為每個輸入影格轉譯一次顯示介面。
-
如果您使用 Camera2 API,請以
ImageFormat.YUV_420_888
格式擷取圖片。如果您使用舊版 Camera API,請以
ImageFormat.NV21
格式擷取圖片。