Mendeteksi Wajah dengan ML Kit di Android

Anda dapat menggunakan ML Kit untuk mendeteksi wajah dalam gambar dan video.

Sebelum memulai

  1. Tambahkan Firebase ke project Android jika Anda belum melakukannya.
  2. Tambahkan dependensi untuk library Android ML Kit ke file Gradle modul (level aplikasi), biasanya 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. Opsional tetapi direkomendasikan: Konfigurasi aplikasi agar mendownload model ML secara otomatis ke perangkat setelah aplikasi diinstal dari Play Store.

    Untuk melakukannya, tambahkan deklarasi berikut ke file AndroidManifest.xml aplikasi Anda:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="face" />
      <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    
    Jika Anda tidak mengaktifkan download model waktu-instal, model ini akan didownload saat pertama kali Anda menjalankan detektor. Permintaan yang Anda buat sebelum download selesai tidak akan memberikan hasil apa pun.

Pedoman gambar input

Agar ML Kit dapat secara akurat mengenali wajah, gambar input harus berisi wajah yang diwakili oleh data piksel yang memadai. Secara umum, setiap wajah yang ingin dideteksi dalam suatu gambar harus berukuran minimal 100x100 piksel. Jika Anda ingin mendeteksi kontur wajah, ML Kit membutuhkan input resolusi yang lebih tinggi: setiap wajah harus berukuran minimal 200x200 piksel.

Jika ingin mendeteksi wajah dalam aplikasi real-time, Anda mungkin perlu mempertimbangkan dimensi gambar input secara keseluruhan. Gambar yang lebih kecil dapat diproses lebih cepat. Jadi, untuk mengurangi latensi, ambil gambar dengan resolusi yang lebih rendah (dengan tetap memperhatikan persyaratan akurasi di atas), dan pastikan bahwa wajah subjek menempati gambar seluas mungkin. Baca juga Tips untuk meningkatkan performa real-time.

Fokus gambar yang buruk dapat mengurangi akurasi. Jika tidak memperoleh hasil yang dapat diterima, coba minta pengguna untuk mengambil ulang gambar.

Orientasi wajah terhadap arah kamera juga dapat memengaruhi fitur wajah yang terdeteksi oleh ML Kit. Baca Konsep Deteksi Wajah.

1. Mengonfigurasi detektor wajah

Jika ingin mengubah salah satu setelan default detektor wajah sebelum menerapkan deteksi wajah ke suatu gambar, tentukan setelan tersebut dengan objek FirebaseVisionFaceDetectorOptions. Anda dapat mengubah setelan berikut:

Setelan
Mode performa FAST (default) | ACCURATE

Mendukung kecepatan atau akurasi saat mendeteksi wajah.

Deteksi struktur NO_LANDMARKS (default) | ALL_LANDMARKS

Mencoba mengidentifikasi "struktur" wajah (mata, telinga, hidung, pipi, mulut, dll.) atau tidak.

Deteksi kontur NO_CONTOURS (default) | ALL_CONTOURS

Mencoba mendeteksi kontur fitur wajah atau tidak. Kontur dideteksi hanya untuk wajah yang paling tampil beda dalam suatu gambar.

Klasifikasikan wajah NO_CLASSIFICATIONS (default) | ALL_CLASSIFICATIONS

Mengklasifikasi wajah menjadi beberapa kategori atau tidak, seperti "tersenyum" dan "mata terbuka".

Ukuran wajah minimum float (default: 0.1f)

Ukuran minimum wajah yang ingin dideteksi dibandingkan ukuran gambar.

Aktifkan pelacakan wajah false (default) | true

Menetapkan ID pada wajah atau tidak, yang dapat digunakan untuk melacak wajah di banyak gambar.

Perlu diperhatikan bahwa hanya satu wajah yang terdeteksi jika deteksi kontur diaktifkan, sehingga pelacakan wajah tidak memberikan hasil yang memuaskan. Karena alasan ini, dan untuk meningkatkan kecepatan deteksi, jangan aktifkan deteksi kontur maupun pelacakan wajah.

Contoh:

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. Menjalankan detektor wajah

Untuk mendeteksi wajah dalam gambar, buat objek FirebaseVisionImage dari Bitmap, media.Image, ByteBuffer, array byte, atau file di perangkat. Lalu, teruskan objek FirebaseVisionImage ke metode detectInImage FirebaseVisionFaceDetector.

Untuk pengenalan wajah, Anda harus menggunakan gambar dengan dimensi yang berukuran minimal 480x360 piksel. Jika Anda mengenali wajah secara real-time, pengambilan frame pada resolusi minimum ini dapat membantu mengurangi latensi.

  1. Buat objek FirebaseVisionImage dari gambar Anda.

    • Untuk membuat objek FirebaseVisionImage dari objek media.Image, seperti saat mengambil gambar dari kamera perangkat, teruskan objek media.Image dan nilai rotasi gambar ke FirebaseVisionImage.fromMediaImage().

      Jika Anda menggunakan library CameraX, class OnImageCapturedListener dan ImageAnalysis.Analyzer menghitung nilai rotasi, sehingga Anda hanya perlu mengonversi rotasi ke salah satu konstanta ROTATION_ ML Kit sebelum memanggil 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
                  // ...
              }
          }
      }
      

      Jika tidak menggunakan library kamera yang memberikan nilai rotasi gambar, Anda dapat menghitungnya dari rotasi perangkat dan orientasi sensor kamera pada perangkat:

      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
      }

      Lalu, teruskan objek media.Image dan nilai rotasi ke FirebaseVisionImage.fromMediaImage():

      Java

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

      Kotlin+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Untuk membuat objek FirebaseVisionImage dari URI file, teruskan konteks aplikasi dan URI file ke FirebaseVisionImage.fromFilePath(). Hal ini berguna saat Anda menggunakan intent ACTION_GET_CONTENT untuk meminta pengguna memilih gambar dari aplikasi galeri mereka.

      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()
      }
    • Untuk membuat objek FirebaseVisionImage dari ByteBuffer atau array byte, pertama-tama hitung rotasi gambar seperti yang dijelaskan di atas untuk input media.Image.

      Lalu, buat objek FirebaseVisionImageMetadata yang berisi tinggi, lebar, format encoding warna, dan rotasi gambar:

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

      Gunakan buffering atau array, dan objek metadata, untuk membuat objek 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)
    • Untuk membuat objek FirebaseVisionImage dari objek Bitmap:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Gambar yang diwakili oleh objek Bitmap harus berposisi tegak, tanpa perlu rotasi tambahan.
  2. Dapatkan instance FirebaseVisionFaceDetector:

    Java

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

    Kotlin+KTX

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. Terakhir, teruskan gambar ke metode 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. Mendapatkan informasi tentang wajah yang terdeteksi

Jika operasi pengenalan wajah berhasil, daftar objek FirebaseVisionFace akan diteruskan ke pemroses peristiwa sukses. Setiap objek FirebaseVisionFace mewakili wajah yang terdeteksi dalam gambar. Untuk setiap wajah, Anda bisa mendapatkan koordinat pembatasnya di gambar input, serta informasi lain yang dapat ditemukan oleh detektor wajah sesuai dengan konfigurasi yang Anda tetapkan. Contoh:

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

Contoh kontur wajah

Ketika mengaktifkan deteksi kontur wajah, Anda juga akan melihat sekumpulan titik untuk setiap fitur wajah yang terdeteksi Titik-titik ini mengikuti bentuk fitur wajah. Baca Ringkasan Konsep Deteksi Wajah untuk mengetahui informasi detail tentang cara kontur direpresentasikan.

Gambar berikut mengilustrasikan bagaimana titik-titik ini dipetakan ke wajah (klik gambar untuk memperbesar):

Deteksi wajah real-time

Jika ingin menggunakan deteksi wajah dalam aplikasi real-time, ikuti pedoman ini untuk mencapai frekuensi frame terbaik:

  • Konfigurasi detektor wajah untuk menggunakan deteksi kontur wajah atau klasifikasi dan deteksi struktur, tetapi tidak kedua-duanya:

    Deteksi kontur
    Deteksi struktur
    Klasifikasi
    Deteksi struktur dan klasifikasi
    Deteksi kontur dan deteksi struktur
    Deteksi kontur dan klasifikasi
    Deteksi kontur, deteksi struktur, dan klasifikasi

  • Aktifkan mode FAST (diaktifkan secara default).

  • Pertimbangkan untuk mengambil foto dengan resolusi lebih rendah. Namun, perhatikan juga persyaratan dimensi gambar API ini.

  • Batasi panggilan ke detektor. Jika frame video baru tersedia saat detektor sedang berjalan, hapus frame tersebut.
  • Jika Anda menggunakan output detektor untuk menempatkan grafis pada gambar input, pertama-tama dapatkan hasilnya dari ML Kit, lalu render gambar dan tempatkan grafis dalam satu langkah. Dengan demikian, Anda hanya merender ke permukaan tampilan sekali untuk setiap frame input.
  • Jika Anda menggunakan Camera2 API, ambil gambar dalam format ImageFormat.YUV_420_888.

    Jika Anda menggunakan Camera API versi lama, ambil gambar dalam format ImageFormat.NV21.