ML Kit предоставляет оптимизированный SDK для сегментации селфи.
Ресурсы Selfie Segmenter статически связаны с вашим приложением во время сборки. Это увеличит размер загрузки вашего приложения примерно на 4,5 МБ, а задержка API может варьироваться от 25 до 65 мс в зависимости от размера входного изображения, измеренного на Pixel 4.
Попробуйте это
- Поэкспериментируйте с примером приложения, чтобы увидеть пример использования этого API.
Прежде чем начать
- В файле
build.gradle
на уровне проекта обязательно включите репозиторий Google Maven как вbuildscript
, так и в разделыallprojects
. - Добавьте зависимости для библиотек Android ML Kit в файл градиента уровня приложения вашего модуля, который обычно имеет
app/build.gradle
:
dependencies {
implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta6'
}
1. Создайте экземпляр сегментатора.
Опции сегментатора
Чтобы выполнить сегментацию изображения, сначала создайте экземпляр Segmenter
, указав следующие параметры.
Режим детектора
Segmenter
работает в двух режимах. Убедитесь, что вы выбрали тот, который соответствует вашему варианту использования.
STREAM_MODE (default)
Этот режим предназначен для потоковой передачи кадров с видео или камеры. В этом режиме сегментатор будет использовать результаты предыдущих кадров для получения более плавных результатов сегментации.
SINGLE_IMAGE_MODE
Этот режим предназначен для отдельных изображений, не связанных друг с другом. В этом режиме сегментатор будет обрабатывать каждое изображение независимо, без сглаживания кадров.
Включить маску необработанного размера
Просит сегментатор вернуть необработанную маску размера, соответствующую выходному размеру модели.
Размер необработанной маски (например, 256x256) обычно меньше размера входного изображения. Пожалуйста, вызовите SegmentationMask#getWidth()
и SegmentationMask#getHeight()
чтобы получить размер маски при включении этой опции.
Без указания этой опции сегментатор изменит масштаб необработанной маски в соответствии с размером входного изображения. Рассмотрите возможность использования этой опции, если вы хотите применить настраиваемую логику масштабирования или изменение масштаба не требуется для вашего варианта использования.
Укажите параметры сегментатора:
Котлин
val options = SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build()
Ява
SelfieSegmenterOptions options = new SelfieSegmenterOptions.Builder() .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) .enableRawSizeMask() .build();
Создайте экземпляр Segmenter
. Передайте указанные вами параметры:
Котлин
val segmenter = Segmentation.getClient(options)
Ява
Segmenter segmenter = Segmentation.getClient(options);
2. Подготовьте входное изображение
Чтобы выполнить сегментацию изображения, создайте объект InputImage
из Bitmap
, media.Image
, ByteBuffer
, массива байтов или файла на устройстве.
Вы можете создать объект InputImage
из разных источников, каждый из которых описан ниже.
Использование media.Image
Чтобы создать объект InputImage
из объекта media.Image
, например, при захвате изображения с камеры устройства, передайте объект media.Image
и поворот изображения в InputImage.fromMediaImage()
.
Если вы используете библиотеку CameraX , классы OnImageCapturedListener
и ImageAnalysis.Analyzer
вычисляют значение поворота за вас.
Котлин
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 // ... } } }
Ява
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 // ... } } }
Если вы не используете библиотеку камер, которая дает вам степень поворота изображения, вы можете рассчитать ее на основе степени поворота устройства и ориентации датчика камеры в устройстве:
Котлин
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 }
Ява
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()
:
Котлин
val image = InputImage.fromMediaImage(mediaImage, rotation)
Java
InputImage image = InputImage.fromMediaImage(mediaImage, rotation);
Использование URI файла
Чтобы создать объект InputImage
из URI файла, передайте контекст приложения и URI файла в InputImage.fromFilePath()
. Это полезно, когда вы используете намерение ACTION_GET_CONTENT
, чтобы предложить пользователю выбрать изображение из приложения галереи.
Котлин
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
с буфером или массивом вместе с высотой, шириной изображения, форматом цветовой кодировки и степенью поворота:
Котлин
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 )
Ява
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
, сделайте следующее объявление:
Котлин
val image = InputImage.fromBitmap(bitmap, 0)
Java
InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);
Изображение представлено объектом Bitmap
вместе с градусами поворота.
3. Обработка изображения
Передайте подготовленный объект InputImage
в метод process
Segmenter
.
Котлин
Task<SegmentationMask> result = segmenter.process(image) .addOnSuccessListener { results -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }
Ява
Task<SegmentationMask> result = segmenter.process(image) .addOnSuccessListener( new OnSuccessListener<SegmentationMask>() { @Override public void onSuccess(SegmentationMask mask) { // Task completed successfully // ... } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
4. Получите результат сегментации
Получить результат сегментации можно следующим образом:
Котлин
val mask = segmentationMask.getBuffer() val maskWidth = segmentationMask.getWidth() val maskHeight = segmentationMask.getHeight() for (val y = 0; y < maskHeight; y++) { for (val x = 0; x < maskWidth; x++) { // Gets the confidence of the (x,y) pixel in the mask being in the foreground. val foregroundConfidence = mask.getFloat() } }
Ява
ByteBuffer mask = segmentationMask.getBuffer(); int maskWidth = segmentationMask.getWidth(); int maskHeight = segmentationMask.getHeight(); for (int y = 0; y < maskHeight; y++) { for (int x = 0; x < maskWidth; x++) { // Gets the confidence of the (x,y) pixel in the mask being in the foreground. float foregroundConfidence = mask.getFloat(); } }
Полный пример использования результатов сегментации см. в образце быстрого запуска ML Kit .
Советы по повышению производительности
Качество результатов зависит от качества входного изображения:
- Чтобы ML Kit мог получить точный результат сегментации, изображение должно быть не менее 256x256 пикселей.
- Плохая фокусировка изображения также может повлиять на точность. Если вы не получили приемлемых результатов, попросите пользователя повторно сделать снимок.
Если вы хотите использовать сегментацию в приложении реального времени, следуйте этим рекомендациям для достижения наилучшей частоты кадров:
- Используйте
STREAM_MODE
. - Рассмотрите возможность захвата изображений с более низким разрешением. Однако также имейте в виду требования к размеру изображения этого API.
- Рассмотрите возможность включения опции маски необработанного размера и объединения всей логики масштабирования. Например, вместо того, чтобы позволить API сначала изменить масштаб маски в соответствии с размером входного изображения, а затем снова масштабировать ее, чтобы она соответствовала размеру представления для отображения, просто запросите необработанную маску размера и объедините эти два шага в один.
- Если вы используете API-интерфейс
Camera
илиcamera2
, регулируйте вызовы детектора. Если новый видеокадр становится доступным во время работы детектора, удалите этот кадр. Пример см. в классеVisionProcessorBase
в примере приложения для быстрого запуска. - Если вы используете API
CameraX
, убедитесь, что для стратегии обратного давления установлено значение по умолчаниюImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Это гарантирует, что для анализа одновременно будет передано только одно изображение. Если во время занятости анализатора создаются дополнительные изображения, они будут автоматически удалены и не будут поставлены в очередь для доставки. Как только анализируемое изображение будет закрыто с помощью вызова ImageProxy.close(), будет доставлено следующее последнее изображение. - Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат из ML Kit, затем визуализируйте изображение и наложите его за один шаг. Это визуализируется на поверхности дисплея только один раз для каждого входного кадра. Пример см. в классах
CameraSourcePreview
иGraphicOverlay
в примере приложения для быстрого запуска. - Если вы используете API Camera2, захватывайте изображения в формате
ImageFormat.YUV_420_888
. Если вы используете более старый API камеры, захватывайте изображения в форматеImageFormat.NV21
.