Существует два способа интеграции моделей, обученных с помощью AutoML Vision Edge: вы можете связать модель, поместив ее в папку активов вашего приложения, или вы можете динамически загрузить ее из Firebase.
Варианты комплектации модели | |
---|---|
Связано с вашим приложением |
|
Хостинг с Firebase |
|
Прежде чем вы начнете
Добавьте зависимости для библиотек ML Kit Android в файл gradle вашего модуля на уровне приложения, обычно это
app/build.gradle
:Для связывания модели с вашим приложением:
dependencies { // ... // Image labeling feature with bundled automl model implementation 'com.google.mlkit:image-labeling-custom:16.3.1' }
Для динамической загрузки модели из Firebase добавьте зависимость
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' }
Если вы хотите загрузить модель , убедитесь, что вы добавили Firebase в свой проект Android , если вы еще этого не сделали. Это не требуется, когда вы связываете модель.
1. Загрузите модель
Настройте локальный источник модели
Чтобы связать модель с вашим приложением:
Извлеките модель и ее метаданные из zip-архива, загруженного из консоли Firebase. Мы рекомендуем вам использовать файлы в том виде, в каком вы их загрузили, без изменений (включая имена файлов).
Включите модель и файлы ее метаданных в пакет приложения:
- Если в вашем проекте нет папки ресурсов, создайте ее, щелкнув правой кнопкой мыши
app/
папку и выбрав « Создать» > «Папка» > «Папка ресурсов» . - Создайте подпапку в папке ресурсов, чтобы содержать файлы модели.
- Скопируйте файлы
model.tflite
,dict.txt
иmanifest.json
в подпапку (все три файла должны находиться в одной папке).
- Если в вашем проекте нет папки ресурсов, создайте ее, щелкнув правой кнопкой мыши
Добавьте следующее в файл
build.gradle
вашего приложения, чтобы убедиться, что Gradle не сжимает файл модели при сборке приложения:android { // ... aaptOptions { noCompress "tflite" } }
Файл модели будет включен в пакет приложения и доступен для ML Kit в качестве необработанного актива.
Создайте объект
LocalModel
, указав путь к файлу манифеста модели:Джава
AutoMLImageLabelerLocalModel localModel = new AutoMLImageLabelerLocalModel.Builder() .setAssetFilePath("manifest.json") // or .setAbsoluteFilePath(absolute file path to manifest file) .build();
Котлин
val localModel = LocalModel.Builder() .setAssetManifestFilePath("manifest.json") // or .setAbsoluteManifestFilePath(absolute file path to manifest file) .build()
Настройте источник модели, размещенный в Firebase.
Чтобы использовать удаленно размещенную модель, создайте объект CustomRemoteModel
, указав имя, которое вы присвоили модели при ее публикации:
Джава
// 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();
Котлин
// Specify the name you assigned in the Firebase console.
val firebaseModelSource = FirebaseModelSource.Builder("your_model_name")
.build()
val remoteModel = CustomRemoteModel.Builder(firebaseModelSource).build()
Затем запустите задачу загрузки модели, указав условия, при которых вы хотите разрешить загрузку. Если модели нет на устройстве или доступна более новая версия модели, задача асинхронно загрузит модель из Firebase:
Джава
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.
}
});
Котлин
val downloadConditions = DownloadConditions.Builder()
.requireWifi()
.build()
RemoteModelManager.getInstance().download(remoteModel, downloadConditions)
.addOnSuccessListener {
// Success.
}
Многие приложения запускают задачу загрузки в своем коде инициализации, но вы можете сделать это в любой момент, прежде чем вам понадобится использовать модель.
Создайте этикетировщик изображений из вашей модели
После настройки источников модели создайте объект ImageLabeler
из одного из них.
Если у вас есть только локально связанная модель, просто создайте метку из вашего объекта CustomImageLabelerOptions
и настройте пороговое значение оценки достоверности, которое вы хотите потребовать (см. Оценка вашей модели ):
Джава
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);
Котлин
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)
Если у вас есть удаленно размещенная модель, вам нужно будет убедиться, что она была загружена, прежде чем запускать ее. Вы можете проверить статус задачи загрузки модели, используя метод isModelDownloaded()
диспетчера моделей.
Хотя вам нужно только подтвердить это перед запуском средства нанесения надписей, если у вас есть как удаленно размещенная модель, так и локальная связанная модель, может иметь смысл выполнить эту проверку при создании экземпляра средства нанесения надписей: создайте средство нанесения надписей из удаленной модели, если это было загружено, а с локальной модели иначе.
Джава
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);
}
});
Котлин
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)
}
Если у вас есть только удаленно размещенная модель, вам следует отключить функции, связанные с моделью, например затенить или скрыть часть пользовательского интерфейса, пока вы не подтвердите загрузку модели. Вы можете сделать это, подключив прослушиватель к методу download()
менеджера модели:
Джава
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.
}
});
Котлин
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. Подготовьте входное изображение
Затем для каждого изображения, которое вы хотите пометить, создайте объектInputImage
из вашего изображения. Средство маркировки изображений работает быстрее всего, когда вы используете Bitmap
или, если вы используете camera2 API, YUV_420_888 media.Image
, которые рекомендуются, когда это возможно. Вы можете создать InputImage
из разных источников, каждый из которых описан ниже.
Использование media.Image
Чтобы создать объект InputImage
из объекта media.Image
, например, когда вы захватываете изображение с камеры устройства, передайте объект media.Image
и поворот изображения в InputImage.fromMediaImage()
.
Если вы используете библиотеку CameraX , классы OnImageCapturedListener
и ImageAnalysis.Analyzer
вычисляют для вас значение поворота.
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 // ... } }
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 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+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 }
Затем передайте объект media.Image
и значение степени поворота в InputImage.fromMediaImage()
:
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Kotlin+KTX
val image = InputImage.fromMediaImage(mediaImage, rotation)
Использование URI файла
Чтобы создать объект InputImage
из URI файла, передайте контекст приложения и URI файла в InputImage.fromFilePath()
. Это полезно, когда вы используете намерение ACTION_GET_CONTENT
, чтобы предложить пользователю выбрать изображение из своего приложения-галереи.
Java
InputImage image; try { image = InputImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin+KTX
val image: InputImage try { image = InputImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
Использование ByteBuffer
или ByteArray
Чтобы создать объект InputImage
из ByteBuffer
или ByteArray
, сначала вычислите степень поворота изображения, как описано ранее для ввода media.Image
. Затем создайте объект InputImage
с буфером или массивом вместе с высотой изображения, шириной, форматом кодирования цвета и степенью поворота:
Java
InputImage image = InputImage.fromByteBuffer(byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
Kotlin+KTX
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 )
Использование Bitmap
Чтобы создать объект InputImage
из объекта Bitmap
, сделайте следующее объявление:
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Kotlin+KTX
val image = InputImage.fromBitmap(bitmap, 0)
Изображение представлено объектом Bitmap
вместе с градусами поворота.
3. Запустите этикетировщик изображений
Чтобы пометить объекты на изображении, передайте объект image
методу process()
объекта ImageLabeler
.
Джава
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
// ...
}
});
Котлин
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
// ...
}
.addOnFailureListener { e ->
// Task failed with an exception
// ...
}
4. Получить информацию о помеченных объектах
Если операция маркировки изображения завершается успешно, прослушивателю успеха передается список объектовImageLabel
. Каждый объект ImageLabel
представляет что-то, что было помечено на изображении. Вы можете получить текстовое описание каждой метки, показатель достоверности совпадения и индекс совпадения. Например: Джава
for (ImageLabel label : labels) {
String text = label.getText();
float confidence = label.getConfidence();
int index = label.getIndex();
}
Котлин
for (label in labels) {
val text = label.text
val confidence = label.confidence
val index = label.index
}
Советы по улучшению производительности в реальном времени
Если вы хотите маркировать изображения в приложении реального времени, следуйте этим рекомендациям для достижения наилучшей частоты кадров:- Дроссельные вызовы для средства нанесения меток на изображения. Если новый видеокадр становится доступным во время работы средства маркировки изображений, отбросьте этот кадр. В качестве примера см. класс
VisionProcessorBase
в примере приложения для быстрого старта. - Если вы используете выходные данные средства маркировки изображений для наложения графики на входное изображение, сначала получите результат, а затем выполните визуализацию изображения и наложение за один шаг. Поступая таким образом, вы визуализируете на поверхность дисплея только один раз для каждого входного кадра. В качестве примера см. классы
CameraSourcePreview
иGraphicOverlay
в примере приложения с кратким руководством. Если вы используете Camera2 API, захватывайте изображения в формате
ImageFormat.YUV_420_888
.Если вы используете старый API камеры, захватывайте изображения в формате
ImageFormat.NV21
.