Сегментация селфи с помощью ML Kit на Android

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

ML Kit предоставляет оптимизированный SDK для сегментации селфи.

Ресурсы Selfie Segmenter статически связаны с вашим приложением во время сборки. Это увеличит размер загрузки вашего приложения примерно на 4,5 МБ, а задержка API может варьироваться от 25 до 65 мс в зависимости от размера входного изображения, измеренного на Pixel 4.

Попробуйте

Прежде чем вы начнете

  1. В файле build.gradle на уровне проекта обязательно включите репозиторий Google Maven как в buildscript и в разделы allprojects .
  2. Добавьте зависимости для библиотек ML Kit Android в файл gradle вашего модуля на уровне приложения, обычно это app/build.gradle :
dependencies {
  implementation 'com.google.mlkit:segmentation-selfie:16.0.0-beta4'
}

1. Создайте экземпляр Segmenter

Параметры сегмента

Чтобы выполнить сегментацию изображения, сначала создайте экземпляр 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 сначала изменить масштаб маски, чтобы она соответствовала размеру входного изображения, а затем снова изменить масштаб, чтобы он соответствовал размеру представления для отображения, просто запросите маску исходного размера и объедините эти два шага в один.
  • Если вы используете Camera или camera2 API, дросселируйте вызовы детектора. Если новый видеокадр становится доступным во время работы детектора, удалите кадр. В качестве примера см. класс VisionProcessorBase в примере приложения для быстрого старта.
  • Если вы используете CameraX API, убедитесь, что для стратегии обратного давления установлено значение по умолчанию ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST . Это гарантирует, что только одно изображение будет доставлено для анализа за раз. Если в то время, когда анализатор занят, создается больше изображений, они будут автоматически удалены и не будут поставлены в очередь на доставку. Как только анализируемое изображение будет закрыто вызовом ImageProxy.close(), будет доставлено следующее последнее изображение.
  • Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат от ML Kit, а затем выполните визуализацию изображения и наложение за один шаг. Это визуализирует поверхность дисплея только один раз для каждого входного кадра. В качестве примера см. классы CameraSourcePreview и GraphicOverlay в примере приложения с кратким руководством.
  • Если вы используете Camera2 API, захватывайте изображения в формате ImageFormat.YUV_420_888 . Если вы используете старый API камеры, захватывайте изображения в формате ImageFormat.NV21 .