Wykrywanie twarzy za pomocą ML Kit na Androidzie

Możesz używać ML Kit do wykrywania twarzy na zdjęciach i w filmach.

Zanim zaczniesz

  1. Jeśli jeszcze tego nie zrobiono, dodaj Firebase do projektu na Androida.
  2. Dodaj zależności do bibliotek ML Kit na Androida do pliku Gradle modułu (na poziomie aplikacji) (zwykle app/build.gradle):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      // If you want to detect face contours (landmark detection and classification
      // don't require this additional model):
      implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
    }
  3. Opcjonalnie, ale zalecane: skonfiguruj aplikację tak, aby po zainstalowaniu jej ze Sklepu Play automatycznie pobierała model ML na urządzenie.

    Aby to zrobić, dodaj do pliku AndroidManifest.xml aplikacji następującą deklarację:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="face" />
      <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    Jeśli nie włączysz pobierania modeli w czasie instalacji, model zostanie pobrany przy pierwszym uruchomieniu funkcji wykrywania. Żądania wysłane przed zakończeniem pobierania nie przyniosą żadnych wyników.

Wskazówki dotyczące obrazów wejściowych

Aby ML Kit mógł dokładnie wykrywać twarze, obrazy wejściowe muszą zawierać twarze reprezentowane przez wystarczającą ilość danych pikseli. Ogólnie rzecz biorąc, każda twarz, którą chcesz wykryć na zdjęciu, powinna mieć co najmniej 100 × 100 pikseli. Jeśli chcesz wykrywać kontury twarzy, ML Kit wymaga podania danych o wyższej rozdzielczości: każda twarz powinna mieć co najmniej 200 x 200 pikseli.

Jeśli wykrywanie twarzy odbywa się w aplikacji w czasie rzeczywistym, warto też wziąć pod uwagę ogólne wymiary obrazów wejściowych. Mniejsze obrazy można przetwarzać szybciej, więc aby zmniejszyć opóźnienie, rób zdjęcia w niższej rozdzielczości (z uwzględnieniem powyższych wymagań dotyczących dokładności) i upewnij się, że twarz osoby na zdjęciu zajmuje jak największą część obrazu. Zobacz też wskazówki dotyczące zwiększania skuteczności w czasie rzeczywistym.

Nieostre zdjęcie może obniżyć dokładność. Jeśli nie uzyskujesz zadowalających wyników, poproś użytkownika o ponowne zrobienie zdjęcia.

Na to, jakie cechy twarzy wykryje ML Kit, może mieć wpływ orientacja twarzy względem aparatu. Zobacz pojęcia związane z wykrywaniem twarzy.

1. Konfigurowanie modułu wykrywania twarzy

Jeśli przed zastosowaniem wykrywania twarzy na obrazie chcesz zmienić jakieś ustawienia domyślne wykrywacza twarzy, określ te ustawienia za pomocą obiektu FirebaseVisionFaceDetectorOptions. Możesz zmienić te ustawienia:

Ustawienia
Tryb wydajności FAST (domyślnie) | ACCURATE

preferować szybkość lub dokładność wykrywania twarzy;

Wykrywanie punktów orientacyjnych NO_LANDMARKS (domyślnie) | ALL_LANDMARKS

Czy należy próbować zidentyfikować „punkty orientacyjne” twarzy: oczy, uszy, nos, policzki, usta itp.

Wykrywanie kontur NO_CONTOURS (domyślnie) | ALL_CONTOURS

Określa, czy mają być wykrywane kontury elementów twarzy. Kontury są wykrywane tylko w przypadku najbardziej widocznej twarzy na zdjęciu.

Klasyfikowanie twarzy NO_CLASSIFICATIONS (domyślnie) | ALL_CLASSIFICATIONS

Określa, czy twarze mają być klasyfikowane według kategorii takich jak „uśmiech” czy „otwarte oczy”.

Minimalny rozmiar twarzy float (domyślnie: 0.1f)

Minimalny rozmiar twarzy do wykrycia w stosunku do obrazu.

Włączanie śledzenia twarzy false (domyślnie) | true

czy chcesz przypisywać twarzom identyfikatory, które mogą służyć do śledzenia twarzy na zdjęciach.

Pamiętaj, że po włączeniu wykrywania kontur wykrywane jest tylko jedno oblicze, więc śledzenie twarzy nie przynosi przydatnych rezultatów. Z tego powodu oraz aby zwiększyć szybkość wykrywania, nie włączaj jednocześnie wykrywania kontur i śledzenia twarzy.

Przykład:

Java

// High-accuracy landmark detection and face classification
FirebaseVisionFaceDetectorOptions highAccuracyOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
                .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
                .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
                .build();

// Real-time contour detection of multiple faces
FirebaseVisionFaceDetectorOptions realTimeOpts =
        new FirebaseVisionFaceDetectorOptions.Builder()
                .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
                .build();

Kotlin

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
        .setLandmarkMode(FirebaseVisionFaceDetectorOptions.ALL_LANDMARKS)
        .setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
        .build()

// Real-time contour detection of multiple faces
val realTimeOpts = FirebaseVisionFaceDetectorOptions.Builder()
        .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
        .build()

2. Uruchom wykrywanie twarzy

Aby wykrywać twarze na obrazie, utwórz obiekt FirebaseVisionImage z elementu Bitmap, media.Image, ByteBuffer, tablicy bajtów lub pliku na urządzeniu. Następnie prześlij obiekt FirebaseVisionImage do metody detectInImage obiektu FirebaseVisionFaceDetector.

Do rozpoznawania twarzy należy użyć obrazu o wymiarach co najmniej 480 x 360 pikseli. Jeśli rozpoznawanie twarzy odbywa się w czasie rzeczywistym, rejestrowanie klatek w tym minimalnym rozmiarze może pomóc w zmniejszeniu opóźnienia.

  1. Utwórz obiekt FirebaseVisionImage na podstawie swojego obrazu.

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

      Jeśli używasz biblioteki CameraX, klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość rotacji za Ciebie, więc przed wywołaniem funkcji FirebaseVisionImage.fromMediaImage() musisz tylko przekonwertować rotację na jedną z konstant ROTATION_ w ML Kit:

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }

      Kotlin

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }

      Jeśli nie używasz biblioteki aparatu, która zapewnia obrócenie obrazu, możesz obliczyć je na podstawie obrotu urządzenia i orientacji czujnika aparatu na urządzeniu:

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * 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, Context context)
              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);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * 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, context: Context): 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)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      Następnie prześlij obiekt media.Image i wartość obrotu do funkcji FirebaseVisionImage.fromMediaImage():

      Java

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

      Kotlin

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Aby utworzyć obiekt FirebaseVisionImage na podstawie identyfikatora URI pliku, prześlij kontekst aplikacji i identyfikator URI pliku do funkcji FirebaseVisionImage.fromFilePath(). Jest to przydatne, gdy używasz intencji ACTION_GET_CONTENT, aby poprosić użytkownika o wybranie obrazu z aplikacji Galeria.

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • Aby utworzyć obiekt FirebaseVisionImageByteBuffer lub tablicy bajtów, najpierw oblicz obrót obrazu w sposób opisany powyżej w przypadku wejścia media.Image.

      Następnie utwórz obiekt FirebaseVisionImageMetadata, który zawiera wysokość, szerokość, format kodowania kolorów oraz obrót obrazu:

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      Użyj bufora lub tablicy oraz obiektu metadanych, aby utworzyć obiekt FirebaseVisionImage:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • Aby utworzyć obiekt FirebaseVisionImage z obiektu Bitmap:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Obraz reprezentowany przez obiekt Bitmap musi być pionowy i nie wymagać dodatkowego obracania.
  2. Pobieranie instancji FirebaseVisionFaceDetector:

    Java

    FirebaseVisionFaceDetector detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options);

    Kotlin

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. Na koniec przekaż obraz do metody detectInImage:

    Java

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

    Kotlin

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

3. Uzyskiwanie informacji o wykryć twarze

Jeśli operacja rozpoznawania twarzy się powiedzie, do listenera success zostanie przekazana lista obiektów FirebaseVisionFace. Każdy obiekt FirebaseVisionFace reprezentuje twarz wykrytą na zdjęciu. W przypadku każdej twarzy możesz uzyskać współrzędne ograniczające w podawanym obrazie, a także wszelkie inne informacje, które skonfigurowałeś/skonfigurowałaś w detektorze twarzy. Przykład:

Java

for (FirebaseVisionFace 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):
    FirebaseVisionFaceLandmark leftEar = face.getLandmark(FirebaseVisionFaceLandmark.LEFT_EAR);
    if (leftEar != null) {
        FirebaseVisionPoint leftEarPos = leftEar.getPosition();
    }

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

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

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

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(FirebaseVisionFaceLandmark.LEFT_EAR)
    leftEar?.let {
        val leftEarPos = leftEar.position
    }

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

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

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

Przykład kontur twarzy

Gdy włączysz wykrywanie konturu twarzy, otrzymasz listę punktów dla każdego wykrytego elementu twarzy. Te punkty reprezentują kształt funkcji. Szczegółowe informacje o reprezentowaniu kontur znajdziesz w artykule Face Detection Concepts Overview (omówienie koncepcji wykrywania twarzy).

Na poniższym obrazie widać, jak te punkty są mapowane na twarz (kliknij obraz, aby go powiększyć):

Wykrywanie twarzy w czasie rzeczywistym

Jeśli chcesz używać funkcji wykrywania twarzy w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Skonfiguruj wykrywacz twarzy, aby używać wykrywania kontur twarzy lub klasyfikacji i wykrywania punktów orientacyjnych, ale nie obu tych funkcji jednocześnie:

    Wykrywanie kontur
    Wykrywanie punktów orientacyjnych
    Klasyfikacja
    Wykrywanie i klasyfikacja punktów orientacyjnych
    Wykrywanie kontur i punktów orientacyjnych
    Wykrywanie kontur i klasyfikacja
    Wykrywanie kontur, punktów orientacyjnych i klasyfikacja

  • Włącz tryb FAST (domyślnie włączony).

  • Rozważ robienie zdjęć w niższej rozdzielczości. Pamiętaj jednak o wymaganiach dotyczących wymiarów obrazu w tym interfejsie API.

  • ograniczać wywołania do tego urządzenia. Jeśli podczas działania detektora pojawi się nowa klatka wideo, odrzuć ją.
  • Jeśli używasz danych wyjściowych z detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a potem wyrenderuj obraz i nałóż go w jednym kroku. W ten sposób renderujesz na powierzchni wyświetlacza tylko raz w przypadku każdej ramki wejściowej.
  • Jeśli używasz interfejsu Camera2 API, rób zdjęcia w formacie ImageFormat.YUV_420_888.

    Jeśli używasz starszej wersji interfejsu Camera API, rób zdjęcia w formacie ImageFormat.NV21.