É possível usar o Kit de ML para detectar rostos em imagens e vídeos com aparência de selfie.
API de detecção de malha facial | |
---|---|
Nome do SDK | face-mesh-detection |
Implementação | O código e os recursos são vinculados estaticamente ao app no tempo de compilação. |
Impacto no tamanho do app | Aprox.6,4 MB |
Desempenho | Em tempo real na maioria dos dispositivos. |
Testar
- Teste o app de exemplo para conferir um exemplo de uso dessa API.
Antes de começar
No arquivo
build.gradle
no nível do projeto, inclua o repositório Maven do Google nas seções do buildscript e do allprojects.Adicione a dependência da biblioteca de detecção de malha facial do Kit de ML ao arquivo Gradle do módulo no nível do app, que geralmente é
app/build.gradle
:dependencies { // ... implementation 'com.google.mlkit:face-mesh-detection:16.0.0-beta1' }
Diretrizes para imagens de entrada
As imagens precisam ser tiradas a cerca de dois metros da câmera do dispositivo. Assim, os rostos ficam grandes o suficiente para o reconhecimento de malha facial. Em geral, quanto maior o rosto, melhor o reconhecimento da malha facial.
O rosto precisa estar de frente para a câmera com pelo menos metade do rosto visível. Qualquer objeto grande entre o rosto e a câmera pode reduzir a precisão.
Se você quiser detectar rostos em um aplicativo em tempo real, considere também as dimensões gerais da imagem de entrada. Como imagens menores podem ser processadas mais rapidamente, a captura de imagens em resoluções mais baixas reduz a latência. No entanto, tenha em mente os 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 essas configurações com um objeto FaceMeshDetectorOptions. Você pode alterar as seguintes configurações:
setUseCase
BOUNDING_BOX_ONLY
: fornece apenas uma caixa delimitadora para uma malha de rostos 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 de malha de rosto (468 pontos 3D e informações de triângulo). Em comparação com o caso de uso deBOUNDING_BOX_ONLY
, a latência aumenta cerca de 15%, conforme medido 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 poderá 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
usando 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);
Como 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
usando um ByteBuffer
ou ByteArray
, primeiro calcule o grau de rotação da imagem, conforme descrito anteriormente para a entrada media.Image
.
Em seguida, crie o objeto InputImage
com o buffer ou a matriz, 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
usando 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 de rostos detectada
Se algum rosto for detectado na imagem, uma lista de objetos FaceMesh
será transmitida ao listener de êxito. Cada FaceMesh
representa um rosto detectado na imagem. Para cada malha de rosto, é possível receber as coordenadas delimitadoras na imagem
de entrada, bem como qualquer outra informação configurada no detector
de malha facial a ser encontrada.
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(); } }