С помощью ML Kit можно легко добавить в приложение функции сегментации объектов.
| Функция | Подробнее… |
|---|---|
| Название SDK | play-services-mlkit-subject-segmentation |
| Реализация | Отдельная модель: модель динамически скачивается с помощью сервисов Google Play. |
| Влияние размера приложения | Увеличение размера примерно на 200 КБ. |
| Время инициализации | Перед первым использованием модели пользователям может потребоваться подождать, пока она скачается. |
Попробовать
- Попробуйте поработать с примером приложения, чтобы понять, как использовать этот API.
Подготовка
- В файле
build.gradleна уровне проекта убедитесь, что репозиторий Google Maven указан в разделахbuildscriptиallprojects. - Добавьте зависимость для библиотеки сегментации объектов ML Kit в файл Gradle на уровне приложения, который обычно называется
app/build.gradle:
dependencies {
implementation 'com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1'
}
Как упоминалось выше, модель предоставляется сервисами Google Play.
Вы можете настроить автоматическую загрузку модели на устройство после установки приложения из Google Play. Для этого добавьте в файл AndroidManifest.xml приложения следующее объявление:
<application ...>
...
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="subject_segment" >
<!-- To use multiple models: android:value="subject_segment,model2,model3" -->
</application>
Вы также можете проверить доступность модели и запросить ее скачивание через сервисы Google Play с помощью ModuleInstallClient API.
Если вы не включите скачивание модели во время установки или не запросите явное скачивание, модель будет скачана при первом запуске сегментатора. Запросы, отправленные до завершения скачивания, не будут обработаны.
1. Подготовьте исходное изображение
Чтобы выполнить сегментацию изображения, создайте объект InputImage из Bitmap, media.Image, ByteBuffer, массива байтов или файла на устройстве.
Объект InputImage можно создать из разных источников. Ниже описаны все способы.
Используется media.Image
Чтобы создать объект InputImage из объекта media.Image, например при съемке изображения камерой устройства, передайте объект media.Image и поворот изображения в InputImage.fromMediaImage().
Если вы используете библиотеку
CameraX, классы OnImageCapturedListener и ImageAnalysis.Analyzer рассчитают значение поворота за вас.
Kotlin
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) { Image mediaImage = imageProxy.getImage(); if (mediaImage != null) { InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); // Pass image to an ML Kit Vision API // ... } } }
Если вы не используете библиотеку камеры, которая предоставляет угол поворота изображения, вы можете рассчитать его на основе угла поворота устройства и ориентации датчика камеры в устройстве:
Kotlin
private val ORIENTATIONS = SparseIntArray() init { ORIENTATIONS.append(Surface.ROTATION_0, 0) ORIENTATIONS.append(Surface.ROTATION_90, 90) ORIENTATIONS.append(Surface.ROTATION_180, 180) ORIENTATIONS.append(Surface.ROTATION_270, 270) } /** * 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, isFrontFacing: Boolean): 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) // Get the device's sensor orientation. val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager val sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION)!! if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360 } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360 } return rotationCompensation }
Java
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270); } /** * 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, boolean isFrontFacing) 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); // Get the device's sensor orientation. CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE); int sensorOrientation = cameraManager .getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SENSOR_ORIENTATION); if (isFrontFacing) { rotationCompensation = (sensorOrientation + rotationCompensation) % 360; } else { // back-facing rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360; } return rotationCompensation; }
Затем передайте объект media.Image и значение угла поворота в InputImage.fromMediaImage():
Kotlin
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Как использовать URI файла
Чтобы создать объект InputImage на основе URI файла, передайте контекст приложения и URI файла в InputImage.fromFilePath(). Это полезно, когда вы используете намерение ACTION_GET_CONTENT, чтобы предложить пользователю выбрать изображение из галереи.
Kotlin
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(); }
Использование ByteBuffer или ByteArray
Чтобы создать объект InputImage из ByteBuffer или ByteArray, сначала рассчитайте угол поворота изображения, как описано выше для входных данных media.Image.
Затем создайте объект InputImage с буфером или массивом, а также высотой, шириной, форматом кодирования цвета и углом поворота изображения:
Kotlin
val image = InputImage.fromByteBuffer( byteBuffer, /* image width */ 480, /* image height */ 360, rotationDegrees, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 ) // Or: val image = InputImage.fromByteArray( byteArray, /* 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 ); // Or: InputImage image = InputImage.fromByteArray( byteArray, /* image width */480, /* image height */360, rotation, InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12 );
Используется Bitmap
Чтобы создать объект InputImage из объекта Bitmap, используйте следующее объявление:
Kotlin
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Изображение представлено объектом Bitmap и градусами поворота.
2. Создайте экземпляр SubjectSegmenter
Как задать параметры сегментатора
Чтобы сегментировать изображение, сначала создайте экземпляр SubjectSegmenterOptions следующим образом:
Kotlin
val options = SubjectSegmenterOptions.Builder()
// enable options
.build()Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
// enable options
.build();Ниже приведена подробная информация о каждом варианте.
Маска уверенности для переднего плана
Маска уверенности для переднего плана позволяет отделить объект на переднем плане от фона.
Вызов enableForegroundConfidenceMask() в параметрах позволяет позже получить маску переднего плана, вызвав getForegroundMask() в объекте SubjectSegmentationResult, возвращенном после обработки изображения.
Kotlin
val options = SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundConfidenceMask() .build();
Растровое изображение переднего плана
Аналогичным образом можно получить растровое изображение объекта на переднем плане.
Вызов enableForegroundBitmap() в параметрах позволяет позже получить фоновое растровое изображение, вызвав getForegroundBitmap() в объекте SubjectSegmentationResult, возвращенном после обработки изображения.
Kotlin
val options = SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build()
Java
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder() .enableForegroundBitmap() .build();
Маска уверенности для нескольких объектов
Как и в случае с вариантами на переднем плане, вы можете использовать SubjectResultOptions, чтобы включить маску уверенности для каждого объекта на переднем плане, как показано ниже:
Kotlin
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableConfidenceMask()
.build()
val options = SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Java
SubjectResultOptions subjectResultOptions =
new SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableConfidenceMask()
.build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Растровое изображение с несколькими объектами
Аналогичным образом можно включить битовую карту для каждого объекта:
Kotlin
val subjectResultOptions = SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableSubjectBitmap()
.build()
val options = SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Java
SubjectResultOptions subjectResultOptions =
new SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableSubjectBitmap()
.build()
SubjectSegmenterOptions options = new SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(subjectResultOptions)
.build()Как создать сегментатор объектов
После того как вы задали параметры SubjectSegmenterOptions, создайте экземпляр SubjectSegmenter, вызвав getClient() и передав параметры в качестве аргумента:
Kotlin
val segmenter = SubjectSegmentation.getClient(options)
Java
SubjectSegmenter segmenter = SubjectSegmentation.getClient(options);
3. Как обработать изображение
Передайте подготовленный объект InputImage методу process объекта SubjectSegmenter:
Kotlin
segmenter.process(inputImage) .addOnSuccessListener { result -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Java
segmenter.process(inputImage) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(SubjectSegmentationResult result) { // Task completed successfully // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Получение результата сегментации объекта
Получение масок переднего плана и растровых изображений
После обработки вы можете получить маску переднего плана для изображения, вызвав функцию getForegroundConfidenceMask() следующим образом:
Kotlin
val colors = IntArray(image.width * image.height) val foregroundMask = result.foregroundConfidenceMask for (i in 0 until image.width * image.height) { if (foregroundMask[i] > 0.5f) { colors[i] = Color.argb(128, 255, 0, 255) } } val bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 )
Java
int[] colors = new int[image.getWidth() * image.getHeight()]; FloatBuffer foregroundMask = result.getForegroundConfidenceMask(); for (int i = 0; i < image.getWidth() * image.getHeight(); i++) { if (foregroundMask.get() > 0.5f) { colors[i] = Color.argb(128, 255, 0, 255); } } Bitmap bitmapMask = Bitmap.createBitmap( colors, image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888 );
Вы также можете получить растровое изображение переднего плана, вызвав getForegroundBitmap():
Kotlin
val foregroundBitmap = result.foregroundBitmap
Java
Bitmap foregroundBitmap = result.getForegroundBitmap();
Получение масок и растровых изображений для каждого объекта.
Аналогичным образом можно получить маску для сегментированных объектов, вызвав getConfidenceMask() для каждого объекта:
Kotlin
val subjects = result.subjects val colors = IntArray(image.width * image.height) for (subject in subjects) { val mask = subject.confidenceMask for (i in 0 until subject.width * subject.height) { val confidence = mask[i] if (confidence > 0.5f) { colors[image.width * (subject.startY - 1) + subject.startX] = Color.argb(128, 255, 0, 255) } } } val bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 )
Java
Listsubjects = result.getSubjects(); int[] colors = new int[image.getWidth() * image.getHeight()]; for (Subject subject : subjects) { FloatBuffer mask = subject.getConfidenceMask(); for (int i = 0; i < subject.getWidth() * subject.getHeight(); i++) { float confidence = mask.get(); if (confidence > 0.5f) { colors[width * (subject.getStartY() - 1) + subject.getStartX()] = Color.argb(128, 255, 0, 255); } } } Bitmap bitmapMask = Bitmap.createBitmap( colors, image.width, image.height, Bitmap.Config.ARGB_8888 );
Вы также можете получить доступ к битовой карте каждого сегментированного объекта следующим образом:
Kotlin
val bitmaps = mutableListOf() for (subject in subjects) { bitmaps.add(subject.bitmap) }
Java
Listbitmaps = new ArrayList<>(); for (Subject subject : subjects) { bitmaps.add(subject.getBitmap()); }
Советы по повышению эффективности
В каждом сеансе приложения первый вывод часто выполняется медленнее, чем последующие, из-за инициализации модели. Если низкая задержка критична, попробуйте вызвать "фиктивный" инференс заранее.
Качество результатов зависит от качества исходного изображения:
- Чтобы ML Kit мог получить точный результат сегментации, изображение должно быть не менее 512x512 пикселей.
- Точность также может снизиться из-за плохого фокуса. Если результаты вас не устроят, попросите пользователя сделать снимок ещё раз.