Rileva le informazioni sul mesh per i volti con ML Kit su Android

Puoi utilizzare ML Kit per rilevare i volti in immagini e video simili a selfie.

API di rilevamento della mesh del viso
Nome dell'SDKface-mesh-detection
ImplementazioneIl codice e gli asset vengono collegati in modo statico all'app in fase di compilazione.
Impatto delle dimensioni dell'app~6,4 MB
PrestazioniIn tempo reale sulla maggior parte dei dispositivi.

Prova

  • Prova l'app di esempio per vedere un esempio di utilizzo di questa API.

Prima di iniziare

  1. Nel file build.gradle a livello di progetto, assicurati di includere il repository Maven di Google sia nelle sezioni buildscript che in allprojects.

  2. Aggiungi la dipendenza per la libreria di rilevamento della mesh del viso di ML Kit al file Gradle a livello di app del modulo, che in genere è app/build.gradle:

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

Linee guida per le immagini di input

  1. Le immagini devono essere scattate a una distanza di circa 2 metri dalla fotocamera del dispositivo, in modo che i volti siano sufficientemente grandi per un riconoscimento ottimale della mesh del viso. In generale, più grande è il volto, migliore è il riconoscimento della mesh del volto.

  2. Il volto deve essere rivolto verso la fotocamera con almeno metà del viso visibile. Qualsiasi oggetto di grandi dimensioni tra il viso e la fotocamera potrebbe comportare una minore precisione.

Se vuoi rilevare i volti in un'applicazione in tempo reale, devi anche considerare le dimensioni complessive dell'immagine di input. Le immagini più piccole possono essere elaborate più velocemente, quindi acquisire immagini a risoluzioni inferiori riduce la latenza. Tuttavia, tieni presente i requisiti di accuratezza riportati sopra e assicurati che il volto del soggetto occupi la maggior parte possibile dell'immagine.

Configurare il rilevatore di mesh del viso

Se vuoi modificare le impostazioni predefinite del rilevatore di mesh del viso, specificale con un oggetto FaceMeshDetectorOptions. Puoi modificare le seguenti impostazioni:

  1. setUseCase

    • BOUNDING_BOX_ONLY: fornisce solo un riquadro delimitante per una mesh del viso rilevata. Si tratta del rilevatore di volti più veloce, ma ha una limitazione di raggio d'azione(i volti devono trovarsi a circa 2 metri dalla videocamera).

    • FACE_MESH (opzione predefinita): fornisce una scatola delimitante e informazioni aggiuntive sul mesh del volto (468 punti 3D e informazioni sui triangoli). Rispetto al caso d'uso BOUNDING_BOX_ONLY, la latenza aumenta di circa il 15%, come misurato su Pixel 3.

Ad esempio:

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

Prepara l'immagine di input

Per rilevare i volti in un'immagine, crea un oggetto InputImage da un array di byte Bitmap, media.Image, ByteBuffer o da un file sul dispositivo. Quindi, passa l'oggetto InputImage al metodo process di FaceDetector.

Per il rilevamento della mesh del viso, devi utilizzare un'immagine con dimensioni di almeno 480 x 360 pixel. Se stai rilevando i volti in tempo reale, acquisire fotogrammi con questa risoluzione minima può contribuire a ridurre la latenza.

Puoi creare un oggetto InputImage da diverse origini, ognuna delle quali è descritta di seguito.

Utilizzo di un media.Image

Per creare un oggetto InputImage da un oggetto media.Image, ad esempio quando acquisisci un'immagine dalla fotocamera di un dispositivo, passa l'oggetto media.Image e la rotazione dell'immagine a InputImage.fromMediaImage().

Se utilizzi la libreria CameraX, le classi OnImageCapturedListener e ImageAnalysis.Analyzer calcolano il valore di rotazione per te.

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 non utilizzi una libreria della fotocamera che ti fornisca il grado di rotazione dell'immagine, puoi calcolarlo dal grado di rotazione del dispositivo e dall'orientamento del sensore della fotocamera al suo interno:

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

Poi, passa l'oggetto media.Image e il valore del grado di rotazione a InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Utilizzo di un URI file

Per creare un oggetto InputImage da un URI file, passa il contesto dell'app e l'URI file a InputImage.fromFilePath(). Questa operazione è utile quando utilizzi un'intenzione ACTION_GET_CONTENT per chiedere all'utente di selezionare un'immagine dalla sua app Galleria.

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

Utilizzo di un ByteBuffer o ByteArray

Per creare un oggetto InputImage da un ByteBuffer o un ByteArray, calcola prima il grado di rotazione dell'immagine come descritto in precedenza per l'input media.Image. Quindi, crea l'oggetto InputImage con il buffer o l'array, insieme all'altezza, alla larghezza, al formato di codifica dei colori e al grado di rotazione dell'immagine:

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

Utilizzo di un Bitmap

Per creare un oggetto InputImage da un oggetto Bitmap, esegui la seguente dichiarazione:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

L'immagine è rappresentata da un oggetto Bitmap insieme ai gradi di rotazione.

Elabora l'immagine

Passa l'immagine al metodo 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
                        // …
                    }
                });

Ottenere informazioni sulla mesh del volto rilevata

Se nell'immagine viene rilevato un volto, un elenco di oggetti FaceMesh viene passato all'ascoltatore di eventi di successo. Ogni FaceMesh rappresenta un volto rilevato nell'immagine. Per ogni mesh del viso, puoi ottenere le coordinate di delimitazione nell'immagine di input, nonché qualsiasi altra informazione che hai configurato per il rilevamento del mesh del viso.

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