Wykrywanie informacji o siatce twarzy za pomocą ML Kit na Androida

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Możesz używać ML Kit, aby wykrywać twarze na obrazach i filmach przypominających selfie.

Interfejs API do wykrywania sieci typu mesh
Nazwa pakietu SDKface-mesh-detection
WdrażanieKod i zasoby są statycznie połączone z aplikacją podczas tworzenia.
Wpływ na rozmiar aplikacji~6,4 MB
WydajnośćW czasie rzeczywistym na większości urządzeń.

Wypróbuj

Zanim zaczniesz

  1. W pliku build.gradle na poziomie projektu umieść repozytorium Maven w sekcjach buildscript i allprojects.

  2. Dodaj zależność od biblioteki wykrywania siatki ML Kit do pliku Gradle na poziomie aplikacji, który zwykle wynosi app/build.gradle:

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

Wskazówki dotyczące obrazu wejściowego

  1. Zdjęcia powinny znajdować się w odległości ok. 2 metrów od aparatu urządzenia, tak by twarze były dostatecznie duże, by można było ich optymalnie wykryć. Ogólnie im większa twarz, tym lepsze rozpoznawanie sieci typu mesh.

  2. Twarz powinna być skierowana w stronę kamery z co najmniej 50% widocznej części twarzy. Każdy duży obiekt między twarzą a kamerą może obniżyć dokładność.

Jeśli chcesz wykrywać twarze w aplikacji w czasie rzeczywistym, weź pod uwagę również ogólne wymiary obrazu wejściowego. Mniejsze obrazy można przetwarzać szybciej, więc ich rejestrowanie w niższych rozdzielczościach skraca czas oczekiwania. Pamiętaj jednak o powyższych wymaganiach dotyczących dokładności i upewnij się, że twarz osoby zajmuje jak najwięcej miejsca.

Skonfiguruj detektor twarzy

Jeśli chcesz zmienić dowolne z domyślnych ustawień funkcji wykrywania siatki typu mesh, określ te ustawienia za pomocą obiektu FaceMeshDetectorOptions. Możesz zmienić te ustawienia:

  1. setUseCase

    • BOUNDING_BOX_ONLY: udostępnia tylko ramkę ograniczającą wykrywaną siatkę twarzy. Jest to najszybszy wykrywacz twarzy, ale ma pewne ograniczenia zasięgu(twarzy musi znajdować się w zasięgu ok. 2 metrów od kamery).

    • FACE_MESH (opcja domyślna): udostępnia ramki ograniczające oraz dodatkowe informacje o siatce twarzy (468 punktów 3D i trójkąty). W porównaniu z tym przypadkiem użycia BOUNDING_BOX_ONLY czas oczekiwania zwiększa się o około 15%, zgodnie z pomiarami Pixela 3.

Przykład:

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

Przygotuj obraz wejściowy

Aby wykrywać twarze na obrazie, utwórz obiekt InputImage z Bitmap, media.Image, ByteBuffer, tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt InputImage do metody process elementu FaceDetector.

W przypadku wykrywania siatki siatki należy użyć obrazu o wymiarach co najmniej 480 x 360 pikseli. Jeśli wykrywasz twarze w czasie rzeczywistym, rejestrowanie klatek przy minimalnej rozdzielczości może zmniejszyć opóźnienia.

Obiekt InputImage możesz utworzyć z różnych źródeł. Poniżej objaśniamy poszczególne z nich.

Korzystanie z: media.Image

Aby utworzyć obiekt InputImage z obiektu media.Image, na przykład podczas robienia zdjęcia aparatem urządzenia, przekaż obiekt media.Image i obrót obrazu do InputImage.fromMediaImage().

Jeśli używasz biblioteki Aparat X, klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość rotacji.

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

Jeśli nie używasz biblioteki zdjęć, która zapewnia kąt obrotu zdjęcia, możesz obliczyć ją na podstawie stopnia obrotu urządzenia i orientacji czujnika aparatu w urządzeniu.

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

Następnie przekaż obiekt media.Image i wartość stopni obrotu do InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Używanie identyfikatora URI pliku

Aby utworzyć obiekt InputImage z identyfikatora URI pliku, przekaż kontekst aplikacji i identyfikator URI pliku do InputImage.fromFilePath(). Jest to przydatne, gdy użyjesz intencji ACTION_GET_CONTENT, aby poprosić użytkownika o wybranie obrazu z galerii.

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

Korzystanie z ByteBuffer lub ByteArray

Aby utworzyć obiekt InputImage z ByteBuffer lub ByteArray, najpierw oblicz stopień obrotu obrazu, jak opisano wcześniej w przypadku danych wejściowych media.Image. Następnie utwórz obiekt InputImage z buforem lub tablicą wraz z wysokością, szerokością, formatem kolorów i stopniem rotacji:

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

Korzystanie z: Bitmap

Aby utworzyć obiekt InputImage z obiektu Bitmap, złóż tę deklarację:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Obraz jest reprezentowany przez obiekt Bitmap wraz ze stopniami obrotu.

Przetwórz obraz

Przekaż obraz za pomocą metody 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
                        // …
                    }
                });

Uzyskiwanie informacji o wykrytym siatce twarzy

Jeśli na zdjęciu zostanie wykryta twarz, do odbiornika uda się przekazać listę FaceMesh obiektów. Każdy FaceMesh reprezentuje twarz wykryte na zdjęciu. W przypadku każdej sieci typu mesh możesz określić współrzędne jej ograniczenia na obrazie wejściowym oraz wszelkie inne informacje skonfigurowane w tym celu.

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