Po wytrenowaniu własnego modelu za pomocą AutoML Vision Edge możesz użyć go w swojej aplikacji do oznaczania obrazów.
Istnieją dwa sposoby integrowania modeli wytrenowanych w AutoML Vision Edge: możesz połączyć model w pakiet, umieszczając go w folderze zasobów aplikacji, lub możesz go dynamicznie pobrać z Firebase.
Opcje łączenia modeli | |
---|---|
Dołączone do Twojej aplikacji |
|
Hostowane w Firebase |
|
Zanim zaczniesz
Dodaj zależności bibliotek ML Kit dla systemu Android do pliku gradle na poziomie aplikacji modułu, którym zwykle jest
app/build.gradle
:Aby połączyć model z aplikacją:
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-custom:16.3.1' }
Aby dynamicznie pobierać model z Firebase, dodaj zależność
linkFirebase
:dependencies { // ... // Image labeling feature with automl model downloaded // from firebase implementation 'com.google.mlkit:image-labeling-custom:16.3.1' implementation 'com.google.mlkit:linkfirebase:16.1.0' }
Jeśli chcesz pobrać model , pamiętaj o dodaniu Firebase do swojego projektu na Androida , jeśli jeszcze tego nie zrobiłeś. Nie jest to wymagane w przypadku spakowania modelu.
1. Załaduj model
Skonfiguruj lokalne źródło modelu
Aby połączyć model z aplikacją:
Wyodrębnij model i jego metadane z archiwum zip pobranego z konsoli Firebase. Zalecamy używanie plików w takiej postaci, w jakiej je pobrałeś, bez modyfikacji (w tym nazw plików).
Dołącz swój model i jego pliki metadanych do pakietu aplikacji:
- Jeśli nie masz folderu zasobów w swoim projekcie, utwórz go, klikając prawym przyciskiem myszy
app/
folder, a następnie klikając Nowy > Folder > Folder zasobów . - Utwórz podfolder w folderze zasobów, aby zawierać pliki modelu.
- Skopiuj pliki
model.tflite
,dict.txt
imanifest.json
do podfolderu (wszystkie trzy pliki muszą znajdować się w tym samym folderze).
- Jeśli nie masz folderu zasobów w swoim projekcie, utwórz go, klikając prawym przyciskiem myszy
Dodaj następujące elementy do pliku
build.gradle
swojej aplikacji, aby mieć pewność, że Gradle nie kompresuje pliku modelu podczas tworzenia aplikacji:android { // ... aaptOptions { noCompress "tflite" } }
Plik modelu zostanie zawarty w pakiecie aplikacji i będzie dostępny dla ML Kit jako surowy zasób.
Utwórz obiekt
LocalModel
, podając ścieżkę do pliku manifestu modelu:Jawa
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
Kotlina
val localModel = LocalModel.Builder() .setAssetManifestFilePath("manifest.json") // or .setAbsoluteManifestFilePath(absolute file path to manifest file) .build()
Skonfiguruj źródło modelu hostowane w Firebase
Aby użyć modelu hostowanego zdalnie, utwórz obiekt CustomRemoteModel
, podając nazwę, którą przypisałeś modelowi podczas jego publikacji:
Jawa
// Specify the name you assigned in the Firebase console.
FirebaseModelSource firebaseModelSource =
new FirebaseModelSource.Builder("your_model_name").build();
CustomRemoteModel remoteModel =
new CustomRemoteModel.Builder(firebaseModelSource).build();
Kotlina
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()
Następnie rozpocznij zadanie pobierania modelu, określając warunki, na jakich chcesz zezwolić na pobieranie. Jeśli modelu nie ma na urządzeniu lub jeśli dostępna jest nowsza wersja modelu, zadanie asynchronicznie pobierze model z Firebase:
Jawa
DownloadConditions downloadConditions = new DownloadConditions.Builder()
.requireWifi()
.build();
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(@NonNull Task<Void> task) {
// Success.
}
});
Kotlina
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// Success.
}
Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjującym, ale można to zrobić w dowolnym momencie, zanim będzie konieczne użycie modelu.
Utwórz etykietę obrazu ze swojego modelu
Po skonfigurowaniu źródeł modelu utwórz obiekt ImageLabeler
na podstawie jednego z nich.
Jeśli masz tylko model powiązany lokalnie, po prostu utwórz etykietę na podstawie obiektu CustomImageLabelerOptions
i skonfiguruj wymagany próg wyniku zaufania (zobacz Oceń swój model ):
Jawa
CustomImageLabelerOptions customImageLabelerOptions = new CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build();
ImageLabeler labeler = ImageLabeling.getClient(customImageLabelerOptions);
Kotlina
val customImageLabelerOptions = CustomImageLabelerOptions.Builder(localModel)
.setConfidenceThreshold(0.0f) // Evaluate your model in the Cloud console
// to determine an appropriate value.
.build()
val labeler = ImageLabeling.getClient(customImageLabelerOptions)
Jeśli masz model hostowany zdalnie, przed uruchomieniem musisz sprawdzić, czy został pobrany. Możesz sprawdzić status zadania pobierania modelu, korzystając z metody isModelDownloaded()
menedżera modeli.
Chociaż musisz to potwierdzić jedynie przed uruchomieniem modułu etykietowania, jeśli masz zarówno model hostowany zdalnie, jak i model powiązany lokalnie, sensowne może być wykonanie tej kontroli podczas tworzenia instancji modułu etykietowania obrazów: utwórz moduł etykietowania na podstawie modelu zdalnego, jeśli został pobrany, a w przeciwnym razie z modelu lokalnego.
Jawa
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener(new OnSuccessListener<Boolean>() {
@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.0f) // Evaluate your model in the Cloud console
// to determine an appropriate threshold.
.build();
ImageLabeler labeler = ImageLabeling.getClient(options);
}
});
Kotlina
RemoteModelManager.getInstance().isModelDownloaded(remoteModel)
.addOnSuccessListener { isDownloaded ->
val optionsBuilder =
if (isDownloaded) {
CustomImageLabelerOptions.Builder(remoteModel)
} else {
CustomImageLabelerOptions.Builder(localModel)
}
// Evaluate your model in the Cloud console to determine an appropriate threshold.
val options = optionsBuilder.setConfidenceThreshold(0.0f).build()
val labeler = ImageLabeling.getClient(options)
}
Jeśli masz tylko model hostowany zdalnie, wyłącz funkcje związane z modelem — na przykład wyszarz lub ukryj część interfejsu użytkownika — do czasu potwierdzenia, że model został pobrany. Można to zrobić, dołączając odbiornik do metody download()
menedżera modeli:
Jawa
RemoteModelManager.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.
}
});
Kotlina
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.
}
2. Przygotuj obraz wejściowy
Następnie dla każdego obrazu, który chcesz oznaczyć etykietą, utwórz na jego podstawie obiekt InputImage
. Narzędzie do etykietowania obrazów działa najszybciej, gdy używasz Bitmap
lub, jeśli używasz interfejsu API kamery2, media.Image
YUV_420_888, które są zalecane, jeśli to możliwe.
Możesz utworzyć InputImage
z różnych źródeł, każde z nich zostało wyjaśnione poniżej.
Korzystanie z media.Image
Aby utworzyć obiekt InputImage
z obiektu media.Image
, na przykład podczas przechwytywania obrazu z kamery urządzenia, przekaż obiekt media.Image
i obrót obrazu do InputImage.fromMediaImage()
.
Jeśli korzystasz z biblioteki CameraX , klasy OnImageCapturedListener
i ImageAnalysis.Analyzer
obliczają wartość rotacji za Ciebie.
Kotlin+KTX
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) { if (imageProxy == null || imageProxy.getImage() == null) { return; } Image mediaImage = imageProxy.getImage(); InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees); // Pass image to an ML Kit Vision API // ... } }
Jeśli nie korzystasz z biblioteki kamer, która podaje stopień obrotu obrazu, możesz go obliczyć na podstawie stopnia obrotu urządzenia i orientacji czujnika kamery w urządzeniu:
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 }
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; }
Następnie przekaż obiekt media.Image
i wartość stopnia obrotu do InputImage.fromMediaImage()
:
Kotlin+KTX
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Korzystanie z pliku URI
Aby utworzyć obiekt InputImage
na podstawie identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath()
. Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT
do monitowania użytkownika o wybranie obrazu z aplikacji galerii.
Kotlin+KTX
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(); }
Korzystanie z ByteBuffer
lub ByteArray
Aby utworzyć obiekt InputImage
z ByteBuffer
lub ByteArray
, najpierw oblicz stopień obrotu obrazu, jak opisano wcześniej dla wejścia media.Image
. Następnie utwórz obiekt InputImage
z buforem lub tablicą wraz z wysokością, szerokością, formatem kodowania kolorów i stopniem obrotu obrazu:
Kotlin+KTX
val image = InputImage.fromByteBuffer( byteBuffer, /* 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 );
Korzystanie z Bitmap
Aby utworzyć obiekt InputImage
z obiektu Bitmap
, wykonaj następującą deklarację:
Kotlin+KTX
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Obraz jest reprezentowany przez obiekt Bitmap
wraz ze stopniami obrotu.
3. Uruchom etykietę obrazu
Aby oznaczyć obiekty na obrazie, przekaż obiekt image
do metody process()
klasy ImageLabeler
.
Jawa
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
// ...
}
});
Kotlina
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
4. Zdobądź informacje o oznaczonych obiektach
Jeśli operacja etykietowania obrazu zakończy się pomyślnie, do odbiornika powodzenia zostanie przekazana lista obiektów ImageLabel
. Każdy obiekt ImageLabel
reprezentuje coś, co zostało oznaczone na obrazie. Możesz uzyskać opis tekstowy każdej etykiety, poziom pewności dopasowania i indeks dopasowania. Na przykład:
Jawa
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
Kotlina
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
Wskazówki, jak poprawić wydajność w czasie rzeczywistym
Jeśli chcesz oznaczać obrazy w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z poniższymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:
- Ogranicz wywołania do modułu etykietowania obrazów. Jeśli w trakcie działania narzędzia do etykietowania obrazów dostępna będzie nowa klatka wideo, usuń tę klatkę. Aby zapoznać się z przykładem, zobacz klasę
VisionProcessorBase
w przykładowej aplikacji szybkiego startu. - Jeśli używasz danych wyjściowych modułu etykietowania obrazów do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik, a następnie wyrenderuj obraz i nakładkę w jednym kroku. W ten sposób renderujesz na powierzchnię wyświetlacza tylko raz dla każdej klatki wejściowej. Aby zapoznać się z przykładem, zobacz klasy
CameraSourcePreview
iGraphicOverlay
w przykładowej aplikacji szybkiego startu. Jeśli korzystasz z interfejsu API Camera2, przechwytuj obrazy w formacie
ImageFormat.YUV_420_888
.Jeśli używasz starszego interfejsu Camera API, przechwytuj obrazy w formacie
ImageFormat.NV21
.