Detectar informações da malha de rostos com o Kit de ML no Android

Você pode usar o Kit de ML para detectar rostos em imagens e vídeos semelhantes a selfies.

API Face Mesh Detection
Nome do SDKface-mesh-detection
ImplementaçãoO código e os recursos são vinculados estaticamente ao app no tempo de build.
Impacto no tamanho do appAprox.6,4 MB
DesempenhoEm tempo real na maioria dos dispositivos.

Testar

Antes de começar

  1. No arquivo build.gradle no nível do projeto, inclua o repositório Maven do Google nas seções buildscript e allprojects.

  2. Adicione a dependência da biblioteca de detecção de malha facial do Kit de ML ao arquivo Gradle no nível do app do módulo, que geralmente é app/build.gradle:

    dependencies {
     // ...
    
     implementation 'com.google.mlkit:face-mesh-detection:16.0.0-beta1'
    }
    

Diretrizes de imagens de entrada

  1. As imagens precisam ser tiradas a aproximadamente dois metros da câmera do dispositivo, para que os rostos sejam grandes o suficiente para um reconhecimento de malha facial ideal. Em geral, quanto maior o rosto, melhor o reconhecimento de malha facial.

  2. O rosto precisa estar de frente para a câmera com pelo menos metade dele visível. Qualquer objeto grande entre o rosto e a câmera pode resultar em baixa precisão.

Para detectar rostos em um aplicativo em tempo real, considere também as dimensões gerais da imagem de entrada. Imagens menores podem ser processadas mais rapidamente. Portanto, capturar imagens em resoluções mais baixas reduz a latência. No entanto, lembre-se dos requisitos de precisão acima e garanta que o rosto da pessoa ocupe o máximo possível da imagem.

Configurar o detector de malha facial

Se você quiser alterar as configurações padrão do detector de malha facial, especifique-as com um objeto FaceMeshDetectorOptions. Você pode alterar as seguintes configurações:

  1. setUseCase

    • BOUNDING_BOX_ONLY: fornece apenas uma caixa delimitadora para uma malha facial detectada. Esse é o detector facial mais rápido, mas tem limitação de alcance. Os rostos precisam estar a cerca de 2 metros da câmera.

    • FACE_MESH (opção padrão): fornece uma caixa delimitadora e outras informações da malha facial (468 pontos em 3D e informações de triângulos). Em comparação com o caso de uso da BOUNDING_BOX_ONLY, a latência aumenta aproximadamente 15%, conforme medida no Pixel 3.

Exemplo:

Kotlin

val defaultDetector = FaceMeshDetection.getClient(
  FaceMeshDetectorOptions.DEFAULT_OPTIONS)

val boundingBoxDetector = FaceMeshDetection.getClient(
  FaceMeshDetectorOptions.Builder()
    .setUseCase(UseCase.BOUNDING_BOX_ONLY)
    .build()
)

Java

FaceMeshDetector defaultDetector =
        FaceMeshDetection.getClient(
                FaceMeshDetectorOptions.DEFAULT_OPTIONS);

FaceMeshDetector boundingBoxDetector = FaceMeshDetection.getClient(
        new FaceMeshDetectorOptions.Builder()
                .setUseCase(UseCase.BOUNDING_BOX_ONLY)
                .build()
        );

Preparar a imagem de entrada

Para detectar rostos em uma imagem, crie um objeto InputImage com base em um Bitmap, media.Image, ByteBuffer, matriz de bytes ou um arquivo no dispositivo. Em seguida, transmita o objeto InputImage para o método process do FaceDetector.

Para a detecção de malha facial, use uma imagem com dimensões de pelo menos 480 x 360 pixels. Se você estiver detectando rostos em tempo real, a captura de frames com essa resolução mínima pode ajudar a reduzir a latência.

Você pode criar um objeto InputImage de diferentes origens, cada uma explicada abaixo.

Como usar um media.Image

Para criar um objeto InputImage com base em um objeto media.Image, como quando você captura uma imagem da câmera de um dispositivo, transmita o objeto media.Image e a rotação da imagem para InputImage.fromMediaImage().

Se você usar a biblioteca CameraX, as classes OnImageCapturedListener e ImageAnalysis.Analyzer vão calcular o valor de rotação automaticamente.

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
          // ...
        }
    }
}

Se você não usar uma biblioteca de câmera que ofereça o grau de rotação da imagem, será possível calculá-lo usando o grau de rotação do dispositivo e a orientação do sensor da câmera:

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;
}

Em seguida, transmita o objeto media.Image e o valor do grau de rotação para InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

InputImage image = InputImage.fromMediaImage(mediaImage, rotation);

Usar um URI de arquivo

Para criar um objeto InputImage usando o URI de um arquivo, transmita o contexto do app e o URI do arquivo para InputImage.fromFilePath(). Isso é útil ao usar uma intent ACTION_GET_CONTENT para solicitar que o usuário selecione uma imagem do app de galeria dele.

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();
}

Como usar ByteBuffer ou ByteArray

Para criar um objeto InputImage com base em ByteBuffer ou ByteArray, primeiro calcule o grau de rotação da imagem conforme descrito anteriormente para a entrada de media.Image. Em seguida, crie o objeto InputImage com o buffer ou a matriz, junto com a altura, a largura, o formato de codificação de cores e o grau de rotação da imagem:

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
);

Como usar um Bitmap

Para criar um objeto InputImage com base em um objeto Bitmap, faça a seguinte declaração:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

A imagem é representada por um objeto Bitmap com os graus de rotação.

Processar a imagem

Transmita a imagem para o método process:

Kotlin

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

Java


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

Receber informações sobre a malha facial detectada

Se algum rosto for detectado na imagem, uma lista de objetos FaceMesh será transmitida para o listener de êxito. Cada FaceMesh representa um rosto detectado na imagem. Para cada malha facial, é possível receber as coordenadas delimitadoras na imagem de entrada, bem como qualquer outra informação configurada para ser encontrada pelo detector de malha facial.

Kotlin

for (faceMesh in faceMeshs) {
    val bounds: Rect = faceMesh.boundingBox()

    // Gets all points
    val faceMeshpoints = faceMesh.allPoints
    for (faceMeshpoint in faceMeshpoints) {
      val index: Int = faceMeshpoints.index()
      val position = faceMeshpoint.position
    }

    // Gets triangle info
    val triangles: List<Triangle<FaceMeshPoint>> = faceMesh.allTriangles
    for (triangle in triangles) {
      // 3 Points connecting to each other and representing a triangle area.
      val connectedPoints = triangle.allPoints()
    }
}

Java

for (FaceMesh faceMesh : faceMeshs) {
    Rect bounds = faceMesh.getBoundingBox();

    // Gets all points
    List<FaceMeshPoint> faceMeshpoints = faceMesh.getAllPoints();
    for (FaceMeshPoint faceMeshpoint : faceMeshpoints) {
        int index = faceMeshpoints.getIndex();
        PointF3D position = faceMeshpoint.getPosition();
    }

    // Gets triangle info
    List<Triangle<FaceMeshPoint>> triangles = faceMesh.getAllTriangles();
    for (Triangle<FaceMeshPoint> triangle : triangles) {
        // 3 Points connecting to each other and representing a triangle area.
        List<FaceMeshPoint> connectedPoints = triangle.getAllPoints();
    }
}