Wykrywaj twarze za pomocą zestawu ML na Androida

Za pomocą zestawu ML Kit możesz wykrywać twarze na zdjęciach i filmach.

Zanim zaczniesz

  1. Jeśli jeszcze tego nie zrobiłeś, dodaj Firebase do swojego projektu na Androida .
  2. Dodaj zależności dla bibliotek ML Kit Android 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. Opcjonalne, ale zalecane : skonfiguruj aplikację tak, aby automatycznie pobierała model ML na urządzenie po zainstalowaniu aplikacji ze Sklepu Play.

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

    <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 modelu w czasie instalacji, model zostanie pobrany przy pierwszym uruchomieniu detektora. Żądania złożone przed zakończeniem pobierania nie przyniosą żadnych rezultatów.

Wytyczne dotyczące obrazu wejściowego

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

Jeśli wykrywasz twarze w aplikacji czasu rzeczywistego, możesz również rozważyć ogólne wymiary obrazów wejściowych. Mniejsze obrazy można przetwarzać szybciej, dlatego aby zmniejszyć opóźnienia, należy rejestrować obrazy w niższych rozdzielczościach (pamiętając o powyższych wymaganiach dotyczących dokładności) i upewnić się, że twarz obiektu zajmuje jak największą część obrazu. Zobacz także Wskazówki dotyczące poprawy wydajności w czasie rzeczywistym .

Słaba ostrość obrazu może zaszkodzić dokładności. Jeśli wyniki nie są akceptowalne, spróbuj poprosić użytkownika o ponowne wykonanie zdjęcia.

Orientacja twarzy względem aparatu może również mieć wpływ na to, jakie cechy twarzy wykrywa ML Kit. Zobacz Pojęcia dotyczące wykrywania twarzy .

1. Skonfiguruj detektor twarzy

Jeśli przed zastosowaniem wykrywania twarzy do obrazu chcesz zmienić dowolne ustawienia domyślne detektora twarzy, określ te ustawienia za pomocą obiektu FirebaseVisionFaceDetectorOptions . Możesz zmienić następujące ustawienia:

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

Preferuj szybkość lub dokładność podczas wykrywania twarzy.

Wykryj punkty orientacyjne NO_LANDMARKS (domyślnie) | ALL_LANDMARKS

Czy próbować zidentyfikować „punkty orientacyjne” twarzy: oczy, uszy, nos, policzki, usta i tak dalej.

Wykryj kontury NO_CONTOURS (domyślnie) | ALL_CONTOURS

Czy wykrywać kontury rysów twarzy. Kontury są wykrywane tylko dla najbardziej widocznej twarzy na obrazie.

Klasyfikuj twarze NO_CLASSIFICATIONS (domyślnie) | ALL_CLASSIFICATIONS

Określa, czy klasyfikować twarze w kategorie takie jak „uśmiechnięte” i „otwarte oczy”.

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

Minimalny rozmiar wykrywanych twarzy w stosunku do obrazu.

Włącz śledzenie twarzy false (domyślnie) | true

Określa, czy przypisać twarzom identyfikator, którego można używać do śledzenia twarzy na obrazach.

Należy pamiętać, że gdy włączone jest wykrywanie konturów, wykrywana jest tylko jedna twarz, więc śledzenie twarzy nie daje użytecznych wyników. Z tego powodu oraz w celu zwiększenia szybkości wykrywania nie włączaj jednocześnie wykrywania konturów i śledzenia twarzy.

Na 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+KTX

// 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 detektor twarzy

Aby wykryć twarze na obrazie, utwórz obiekt FirebaseVisionImage z Bitmap , media.Image , ByteBuffer , tablicy bajtów lub pliku na urządzeniu. Następnie przekaż obiekt FirebaseVisionImage do metody detectInImage FirebaseVisionFaceDetector .

Do rozpoznawania twarzy należy użyć obrazu o wymiarach co najmniej 480x360 pikseli. Jeśli rozpoznajesz twarze w czasie rzeczywistym, przechwytywanie klatek w minimalnej rozdzielczości może pomóc w zmniejszeniu opóźnień.

  1. Utwórz obiekt FirebaseVisionImage ze swojego obrazu.

    • Aby utworzyć obiekt FirebaseVisionImage z obiektu media.Image , na przykład podczas przechwytywania obrazu z kamery urządzenia, przekaż obiekt media.Image i obrót obrazu do FirebaseVisionImage.fromMediaImage() .

      Jeśli korzystasz z biblioteki CameraX , klasy OnImageCapturedListener i ImageAnalysis.Analyzer obliczają wartość rotacji za Ciebie, więc wystarczy przekonwertować rotację na jedną ze stałych ROTATION_ ML Kit przed wywołaniem FirebaseVisionImage.fromMediaImage() :

      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+KTX

      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 korzystasz z biblioteki kamer, która umożliwia obrót obrazu, możesz go obliczyć na podstawie obrotu urządzenia i orientacji czujnika kamery w 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+KTX

      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 przekaż obiekt media.Image i wartość rotacji do FirebaseVisionImage.fromMediaImage() :

      Java

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

      Kotlin+KTX

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

      Java

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

      Kotlin+KTX

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

      Następnie utwórz obiekt FirebaseVisionImageMetadata zawierający wysokość, szerokość, format kodowania kolorów i 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+KTX

      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+KTX

      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+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Obraz reprezentowany przez obiekt Bitmap musi być ustawiony pionowo i nie jest wymagany żaden dodatkowy obrót.
  2. Pobierz instancję FirebaseVisionFaceDetector :

    Java

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

    Kotlin+KTX

    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+KTX

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

3. Uzyskaj informacje o wykrytych twarzach

Jeśli operacja rozpoznawania twarzy zakończy się pomyślnie, lista obiektów FirebaseVisionFace zostanie przekazana do odbiornika powodzenia. Każdy obiekt FirebaseVisionFace reprezentuje twarz wykrytą na obrazie. Dla każdej twarzy możesz uzyskać współrzędne ograniczające na obrazie wejściowym, a także wszelkie inne informacje, które skonfigurowałeś do wyszukiwania przez wykrywacz twarzy. Na 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+KTX

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ów twarzy

Jeśli masz włączone wykrywanie konturu twarzy, otrzymasz listę punktów za każdą wykrytą cechę twarzy. Punkty te reprezentują kształt obiektu. Aby uzyskać szczegółowe informacje na temat sposobu przedstawiania konturów, zobacz Przegląd koncepcji wykrywania twarzy .

Poniższy obraz ilustruje sposób, w jaki te punkty są przypisane do twarzy (kliknij obraz, aby powiększyć):

Wykrywanie twarzy w czasie rzeczywistym

Jeśli chcesz używać wykrywania twarzy w aplikacji czasu rzeczywistego, postępuj zgodnie z poniższymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Skonfiguruj wykrywacz twarzy tak, aby korzystał z wykrywania konturu twarzy lub wykrywania klasyfikacji i punktów orientacyjnych, ale nie obu jednocześnie:

    Wykrywanie konturu
    Wykrywanie punktów orientacyjnych
    Klasyfikacja
    Wykrywanie i klasyfikacja zabytków
    Wykrywanie konturów i wykrywanie punktów orientacyjnych
    Wykrywanie i klasyfikacja konturów
    Wykrywanie konturów, wykrywanie punktów orientacyjnych i klasyfikacja

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

  • Rozważ przechwytywanie obrazów w niższej rozdzielczości. Należy jednak pamiętać o wymaganiach dotyczących wymiarów obrazu tego interfejsu API.

  • Przepustnica wzywa do detektora. Jeżeli w trakcie działania detektora pojawi się nowa klatka wideo, usuń ją.
  • Jeśli używasz wyjścia detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a następnie wyrenderuj obraz i nakładkę w jednym kroku. W ten sposób renderujesz na powierzchnię wyświetlacza tylko raz dla każdej klatki wejściowej.
  • Jeśli korzystasz z interfejsu API Camera2, przechwytuj obrazy w formacie ImageFormat.YUV_420_888 .

    Jeśli używasz starszego interfejsu Camera API, przechwytuj obrazy w formacie ImageFormat.NV21 .