Обнаружение лиц с помощью ML Kit на Android

Вы можете использовать ML Kit для обнаружения лиц на изображениях и видео.

ФункцияБез пакетаПакетный
РеализацияМодель динамически скачивается через сервисы Google Play.Модель статически связана с приложением во время сборки.
Размер приложенияРазмер увеличится примерно на 800 КБ.Увеличение размера примерно на 6,9 МБ.
Время инициализацииПеред первым использованием может потребоваться подождать, пока модель скачается.Модель доступна сразу

Попробовать

Подготовка

  1. В файле build.gradle на уровне проекта убедитесь, что репозиторий Google Maven включен в разделы buildscript и allprojects.

  2. Добавьте зависимости для библиотек ML Kit для Android в файл Gradle на уровне приложения, который обычно называется app/build.gradle. Выберите одну из следующих зависимостей в зависимости от ваших потребностей:

    Для включения модели в приложение:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:face-detection:16.1.7'
    }
    

    Для использования модели в сервисах Google Play:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-face-detection:17.1.0'
    }
    
  3. Если вы решите использовать модель в сервисах Google Play, вы можете настроить автоматическую загрузку модели на устройство после установки приложения из Google Play. Для этого добавьте в файл AndroidManifest.xml приложения следующее объявление:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="face" >
          <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    

    Вы также можете проверить доступность модели и запросить ее скачивание с помощью ModuleInstallClient API сервисов Google Play.

    Если вы не включите скачивание модели при установке или не запросите явное скачивание, модель будет скачана при первом запуске детектора. Запросы, сделанные до завершения скачивания, не принесут результатов.

Требования к исходным изображениям

Для распознавания лиц следует использовать изображения с размером не менее 480 x 360 пикселей. Чтобы ML Kit мог точно обнаруживать лица, входные изображения должны содержать лица, представленные достаточным количеством пиксельных данных. В целом, каждое лицо, которое вы хотите обнаружить на изображении, должно быть не менее 100x100 пикселей. Если вы хотите обнаружить контуры лиц, ML Kit требует более высокого разрешения входных данных: каждое лицо должно быть не менее 200 x 200 пикселей.

Если вы распознаете лица в приложении, работающем в реальном времени, вам также может потребоваться учитывать общие размеры входных изображений. Небольшие изображения обрабатываются быстрее, поэтому, чтобы уменьшить задержку, снимайте с более низким разрешением. Однако учитывайте требования к точности, указанные выше, и убедитесь, что лицо занимает как можно большую часть изображения. Также ознакомьтесь с советами по повышению эффективности в реальном времени.

Точность также может снизиться из-за плохого фокуса. Если результаты не соответствуют требованиям, попросите пользователя сделать снимок ещё раз.

Ориентация лица относительно камеры также может влиять на то, какие черты лица обнаруживает ML Kit. Подробнее о концепциях распознавания лиц…

1. Как настроить детектор лиц

Если вы хотите изменить настройки детектора лиц по умолчанию, укажите их в объекте FaceDetectorOptions до того, как применить к изображению функцию распознавания лиц. Укажите следующие параметры:

Настройки
setPerformanceMode PERFORMANCE_MODE_FAST (по умолчанию) | PERFORMANCE_MODE_ACCURATE

Выбирайте между скоростью и точностью распознавания лиц.

setLandmarkMode LANDMARK_MODE_NONE (по умолчанию) | LANDMARK_MODE_ALL

Попытаться ли определить "ориентиры" лица: глаза, уши, нос, щеки, рот и т. д.

setContourMode CONTOUR_MODE_NONE (по умолчанию) | CONTOUR_MODE_ALL

Обнаруживать ли контуры черт лица. Контуры определяются только для самого заметного лица на изображении.

setClassificationMode CLASSIFICATION_MODE_NONE (по умолчанию) | CLASSIFICATION_MODE_ALL

Нужно ли классифицировать лица по категориям, например "улыбается" и "глаза открыты".

setMinFaceSize float (по умолчанию: 0.1f)

Устанавливает наименьший желаемый размер лица, выраженный как отношение ширины головы к ширине изображения.

enableTracking false (по умолчанию) | true

Нужно ли присваивать лицам идентификаторы, которые можно использовать для отслеживания лиц на разных изображениях.

Обратите внимание, что при включенном распознавании контуров обнаруживается только одно лицо, поэтому отслеживание лиц не дает полезных результатов. По этой причине, а также для повышения скорости обнаружения не включайте одновременно обнаружение контуров и отслеживание лиц.

Пример:

Kotlin

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FaceDetectorOptions.Builder()
        .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
        .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
        .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
        .build()

// Real-time contour detection
val realTimeOpts = FaceDetectorOptions.Builder()
        .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
        .build()

Java

// High-accuracy landmark detection and face classification
FaceDetectorOptions highAccuracyOpts =
        new FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
                .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
                .build();

// Real-time contour detection
FaceDetectorOptions realTimeOpts =
        new FaceDetectorOptions.Builder()
                .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
                .build();

2. Подготовьте исходное изображение

Чтобы распознать лица на изображении, создайте объект InputImage из Bitmap, media.Image, ByteBuffer, массива байтов или файла на устройстве. Затем передайте объект InputImage методу process объекта FaceDetector.

Для распознавания лиц следует использовать изображения с размерами не менее 480 x 360 пикселей. Если вы обнаруживаете лица в реальном времени, захват кадров с минимальным разрешением поможет уменьшить задержку.

Объект 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 вместе с градусами поворота.

3. Получение экземпляра FaceDetector

Kotlin

val detector = FaceDetection.getClient(options)
// Or, to use the default option:
// val detector = FaceDetection.getClient();

Java

FaceDetector detector = FaceDetection.getClient(options);
// Or use the default options:
// FaceDetector detector = FaceDetection.getClient();

4. Обработка изображения

Передайте изображение методу process:

Kotlin

val result = detector.process(image)
        .addOnSuccessListener { faces ->
            // Task completed successfully
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Java

Task<List<Face>> result =
        detector.process(image)
                .addOnSuccessListener(
                        new OnSuccessListener<List<Face>>() {
                            @Override
                            public void onSuccess(List<Face> faces) {
                                // Task completed successfully
                                // ...
                            }
                        })
                .addOnFailureListener(
                        new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                // Task failed with an exception
                                // ...
                            }
                        });

5. Как получить информацию об обнаруженных лицах

Если распознавание лиц прошло успешно, список объектов Face передается в прослушиватель успешного выполнения. Каждый объект Face представляет лицо, обнаруженное на изображении. Для каждого лица вы можете получить его ограничивающие координаты на входном изображении, а также любую другую информацию, которую вы настроили для детектора лиц. Пример:

Kotlin

for (face in faces) {
    val bounds = face.boundingBox
    val rotY = face.headEulerAngleY // Head is rotated to the right rotY degrees
    val rotZ = face.headEulerAngleZ // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    val leftEar = face.getLandmark(FaceLandmark.LEFT_EAR)
    leftEar?.let {
        val leftEarPos = leftEar.position
    }

    // If contour detection was enabled:
    val leftEyeContour = face.getContour(FaceContour.LEFT_EYE)?.points
    val upperLipBottomContour = face.getContour(FaceContour.UPPER_LIP_BOTTOM)?.points

    // If classification was enabled:
    if (face.smilingProbability != null) {
        val smileProb = face.smilingProbability
    }
    if (face.rightEyeOpenProbability != null) {
        val rightEyeOpenProb = face.rightEyeOpenProbability
    }

    // If face tracking was enabled:
    if (face.trackingId != null) {
        val id = face.trackingId
    }
}

Java

for (Face face : faces) {
    Rect bounds = face.getBoundingBox();
    float rotY = face.getHeadEulerAngleY();  // Head is rotated to the right rotY degrees
    float rotZ = face.getHeadEulerAngleZ();  // Head is tilted sideways rotZ degrees

    // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
    // nose available):
    FaceLandmark leftEar = face.getLandmark(FaceLandmark.LEFT_EAR);
    if (leftEar != null) {
        PointF leftEarPos = leftEar.getPosition();
    }

    // If contour detection was enabled:
    List<PointF> leftEyeContour =
            face.getContour(FaceContour.LEFT_EYE).getPoints();
    List<PointF> upperLipBottomContour =
            face.getContour(FaceContour.UPPER_LIP_BOTTOM).getPoints();

    // If classification was enabled:
    if (face.getSmilingProbability() != null) {
        float smileProb = face.getSmilingProbability();
    }
    if (face.getRightEyeOpenProbability() != null) {
        float rightEyeOpenProb = face.getRightEyeOpenProbability();
    }

    // If face tracking was enabled:
    if (face.getTrackingId() != null) {
        int id = face.getTrackingId();
    }
}

Пример контуров лица

Если обнаружение контуров лица включено, вы получите список точек для каждой обнаруженной черты лица. Эти точки представляют форму объекта. Подробные сведения о том, как представлены контуры, можно найти в статье Основные понятия, связанные с распознаванием лиц.

На изображении ниже показано, как эти точки соотносятся с лицом. Нажмите на изображение, чтобы увеличить его.

Пример обнаруженной сетки контура лица

Распознавание лиц в реальном времени

Если вы хотите использовать распознавание лиц в приложении, работающем в реальном времени, следуйте этим рекомендациям, чтобы достичь оптимальной частоты кадров:

  • Настройте детектор лиц на использование либо обнаружения контуров лица, либо классификации и обнаружения ориентиров, но не того и другого одновременно:

    Обнаружение контуров
    Обнаружение ориентиров
    Классификация
    Обнаружение и классификация ориентиров
    Обнаружение контуров и ориентиров
    Обнаружение контуров и классификация
    Обнаружение контуров и ориентиров, а также классификация

  • Включите режим FAST (включен по умолчанию).

  • Попробуйте снимать с более низким разрешением. Однако помните о требованиях к размеру изображений, которые предъявляются в этом API.

  • Если вы используете API Camera или camera2, ограничьте количество вызовов детектора. Если во время работы детектора становится доступен новый кадр видео, отбросьте его. Пример можно найти в классе VisionProcessorBase в примере приложения из краткого руководства.
  • Если вы используете API CameraX, убедитесь, что для стратегии противодействия избыточному давлению задано значение по умолчанию ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Это гарантирует, что для анализа будет передаваться только одно изображение за раз. Если во время работы анализатора будет создано больше изображений, они будут автоматически удалены и не будут поставлены в очередь на отправку. После того как анализируемое изображение будет закрыто вызовом ImageProxy.close(), будет доставлено следующее последнее изображение.
  • Если вы используете выходные данные детектора для наложения графики на входное изображение, сначала получите результат из ML Kit, а затем визуализируйте изображение и наложение за один шаг. Оно отрисовывается на экране только один раз для каждого входного кадра. Пример можно найти в классах CameraSourcePreview и GraphicOverlay в кратком руководстве по работе с примером приложения.
  • Если вы используете Camera2 API, делайте снимки в формате ImageFormat.YUV_420_888. Если вы используете старый Camera API, делайте снимки в формате ImageFormat.NV21.