После обучения собственной модели с помощью AutoML Vision Edge вы сможете использовать ее в своем приложении для маркировки изображений.
Прежде чем начать
- Если вы еще этого не сделали, добавьте Firebase в свой Android-проект .
- Добавьте зависимости для библиотек 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. Загрузите модель
ML Kit запускает модели, созданные AutoML, на устройстве. Однако вы можете настроить ML Kit для удалённой загрузки модели из Firebase, из локального хранилища или обоими способами.
Разместив модель на Firebase, вы можете обновлять ее, не выпуская новую версию приложения, а также использовать Remote Config и A/B Testing для динамического предоставления различных моделей различным группам пользователей.
Если вы решите предоставить модель, разместив её в Firebase и не включив её в пакет приложения, вы можете уменьшить размер загружаемого файла. Однако имейте в виду, что если модель не включена в пакет приложения, все функции, связанные с моделью, будут недоступны до тех пор, пока приложение не загрузит её в первый раз.
Объединив модель с приложением, вы можете гарантировать, что функции машинного обучения вашего приложения продолжат работать, даже если модель, размещенная в Firebase, недоступна.
Настройте источник модели, размещенный в 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.
}
Многие приложения запускают задачу загрузки в своем коде инициализации, но вы можете сделать это в любой момент до того, как вам понадобится использовать модель.
Настройте локальный источник модели
Чтобы связать модель с вашим приложением:
- Извлеките модель и её метаданные из ZIP-архива, скачанного из консоли Firebase . Рекомендуем использовать файлы в том виде, в котором вы их скачали, без изменений (включая названия файлов).
Включите вашу модель и ее файлы метаданных в пакет вашего приложения:
- Если в вашем проекте нет папки с ресурсами, создайте ее, щелкнув правой кнопкой мыши папку
app/
и выбрав Создать > Папка > Папка ресурсов . - Создайте подпапку в папке «Assets» для хранения файлов модели.
- Скопируйте файлы
model.tflite
,dict.txt
иmanifest.json
в подпапку (все три файла должны находиться в одной папке).
- Если в вашем проекте нет папки с ресурсами, создайте ее, щелкнув правой кнопкой мыши папку
- Добавьте следующее в файл
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)
}
Если у вас есть только удалённо размещённая модель, следует отключить связанные с ней функции (например, сделать её серой или скрыть часть пользовательского интерфейса) до подтверждения загрузки модели. Это можно сделать, добавив прослушиватель к методу 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
(описанному в следующем разделе).
Вы можете создать объект FirebaseVisionImage
из объекта media.Image
, файла на устройстве, массива байтов или объекта Bitmap
:
Чтобы создать объект
FirebaseVisionImage
из объектаmedia.Image
, например, при захвате изображения с камеры устройства, передайте объектmedia.Image
и поворот изображения вFirebaseVisionImage.fromMediaImage()
.Если вы используете библиотеку CameraX , классы
OnImageCapturedListener
иImageAnalysis.Analyzer
вычисляют значение поворота автоматически, поэтому вам просто нужно преобразовать поворот в одну из константROTATION_
ML Kit перед вызовом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
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)
- Чтобы создать объект
FirebaseVisionImage
из URI файла, передайте контекст приложения и 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() }
- Чтобы создать объект
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
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)
- Чтобы создать объект
FirebaseVisionImage
из объектаBitmap
:Изображение, представленное объектомJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
, должно быть вертикальным, без необходимости дополнительного поворота.
3. Запустите маркировщик изображений.
Чтобы маркировать объекты на изображении, передайте объект FirebaseVisionImage
методу processImage()
объекта FirebaseVisionImageLabeler
.
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, а затем визуализируйте изображение и наложение за один шаг. Таким образом, визуализация на поверхности дисплея выполняется только один раз для каждого входного кадра.
Если вы используете API Camera2, снимайте изображения в формате
ImageFormat.YUV_420_888
.Если вы используете старый API камеры, снимайте изображения в формате
ImageFormat.NV21
.