После обучения собственной модели с помощью 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 . Рекомендуем использовать файлы в том виде, в котором вы их скачали, без изменений (включая имена файлов).
Включите вашу модель и файлы метаданных в пакет вашего приложения:
- Если в вашем проекте нет папки 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 на основе одного из них.
Если у вас есть только локально упакованная модель, просто создайте объект Labeler из объекта 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.Если вы используете более старую версию Camera API, захватывайте изображения в формате
ImageFormat.NV21.