Phát hiện khuôn mặt bằng ML Kit trên Android

Bạn có thể sử dụng ML Kit để phát hiện khuôn mặt trong hình ảnh và video.

Trước khi bắt đầu

  1. Nếu bạn chưa có, hãy thêm Firebase vào dự án Android của bạn .
  2. Thêm các phần phụ thuộc cho thư viện ML Kit Android vào tệp Gradle mô-đun (cấp ứng dụng) của bạn (thường là 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. Tùy chọn nhưng được khuyến nghị : Định cấu hình ứng dụng của bạn để tự động tải mô hình ML xuống thiết bị sau khi ứng dụng của bạn được cài đặt từ Cửa hàng Play.

    Để làm như vậy, hãy thêm phần khai báo sau vào tệp AndroidManifest.xml của ứng dụng của bạn:

    <application ...>
      ...
      <meta-data
          android:name="com.google.firebase.ml.vision.DEPENDENCIES"
          android:value="face" />
      <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    
    Nếu bạn không bật tính năng tải xuống mô hình tại thời điểm cài đặt, mô hình sẽ được tải xuống vào lần đầu tiên bạn chạy trình phát hiện. Những yêu cầu bạn thực hiện trước khi quá trình tải xuống hoàn tất sẽ không mang lại kết quả.

Hướng dẫn nhập hình ảnh

Để Bộ ML phát hiện chính xác khuôn mặt, hình ảnh đầu vào phải chứa các khuôn mặt được thể hiện bằng đủ dữ liệu pixel. Nói chung, mỗi khuôn mặt bạn muốn phát hiện trong ảnh phải có kích thước tối thiểu là 100x100 pixel. Nếu bạn muốn phát hiện đường viền của khuôn mặt, ML Kit yêu cầu đầu vào có độ phân giải cao hơn: mỗi khuôn mặt phải có kích thước tối thiểu là 200x200 pixel.

Nếu bạn đang phát hiện khuôn mặt trong một ứng dụng thời gian thực, bạn cũng có thể muốn xem xét kích thước tổng thể của hình ảnh đầu vào. Hình ảnh nhỏ hơn có thể được xử lý nhanh hơn, do đó để giảm độ trễ, hãy chụp ảnh ở độ phân giải thấp hơn (lưu ý các yêu cầu về độ chính xác ở trên) và đảm bảo rằng khuôn mặt của chủ thể chiếm càng nhiều diện tích trong hình ảnh càng tốt. Đồng thời xem Mẹo để cải thiện hiệu suất thời gian thực .

Lấy nét hình ảnh kém có thể ảnh hưởng đến độ chính xác. Nếu bạn không nhận được kết quả chấp nhận được, hãy thử yêu cầu người dùng chụp lại hình ảnh.

Hướng của khuôn mặt so với máy ảnh cũng có thể ảnh hưởng đến những đặc điểm khuôn mặt mà Bộ công cụ ML phát hiện. Xem Khái niệm nhận diện khuôn mặt .

1. Cấu hình tính năng dò tìm khuôn mặt

Trước khi áp dụng tính năng dò tìm khuôn mặt cho một hình ảnh, nếu bạn muốn thay đổi bất kỳ cài đặt mặc định nào của tính năng dò tìm khuôn mặt, hãy chỉ định các cài đặt đó bằng đối tượng FirebaseVisionFaceDetectorOptions . Bạn có thể thay đổi các cài đặt sau:

Cài đặt
Chế độ hiệu suất FAST (mặc định) | ACCURATE

Ưu tiên tốc độ hoặc độ chính xác khi phát hiện khuôn mặt.

Phát hiện mốc NO_LANDMARKS (mặc định) | ALL_LANDMARKS

Có nên cố gắng xác định các "điểm mốc" trên khuôn mặt: mắt, tai, mũi, má, miệng, v.v.

Phát hiện đường viền NO_CONTOURS (mặc định) | ALL_CONTOURS

Có phát hiện các đường nét của đặc điểm khuôn mặt hay không. Đường viền chỉ được phát hiện đối với khuôn mặt nổi bật nhất trong ảnh.

Phân loại khuôn mặt NO_CLASSIFICATIONS (mặc định) | ALL_CLASSIFICATIONS

Có phân loại khuôn mặt thành các loại như "mỉm cười" và "mở mắt" hay không.

Kích thước khuôn mặt tối thiểu float (mặc định: 0.1f )

Kích thước tối thiểu, liên quan đến hình ảnh, của khuôn mặt cần phát hiện.

Bật tính năng theo dõi khuôn mặt false (mặc định) | true

Có gán ID khuôn mặt hay không, ID này có thể được sử dụng để theo dõi khuôn mặt trên các hình ảnh.

Lưu ý rằng khi bật tính năng phát hiện đường viền, chỉ một khuôn mặt được phát hiện, do đó tính năng theo dõi khuôn mặt không mang lại kết quả hữu ích. Vì lý do này và để cải thiện tốc độ phát hiện, không bật cả tính năng phát hiện đường viền và theo dõi khuôn mặt.

Ví dụ:

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. Chạy máy dò khuôn mặt

Để phát hiện khuôn mặt trong hình ảnh, hãy tạo đối tượng FirebaseVisionImage từ Bitmap , media.Image , ByteBuffer , mảng byte hoặc tệp trên thiết bị. Sau đó, chuyển đối tượng FirebaseVisionImage sang phương thức detectInImage của FirebaseVisionFaceDetector .

Để nhận dạng khuôn mặt, bạn nên sử dụng hình ảnh có kích thước tối thiểu 480x360 pixel. Nếu bạn đang nhận dạng khuôn mặt trong thời gian thực thì việc chụp khung hình ở độ phân giải tối thiểu này có thể giúp giảm độ trễ.

  1. Tạo đối tượng FirebaseVisionImage từ hình ảnh của bạn.

    • Để tạo đối tượng FirebaseVisionImage từ đối tượng media.Image , chẳng hạn như khi chụp ảnh từ camera của thiết bị, hãy chuyển đối tượng media.Image và góc xoay của hình ảnh tới FirebaseVisionImage.fromMediaImage() .

      Nếu bạn sử dụng thư viện CameraX , các lớp OnImageCapturedListenerImageAnalysis.Analyzer sẽ tính toán giá trị xoay cho bạn, vì vậy bạn chỉ cần chuyển đổi phép xoay thành một trong các hằng số ROTATION_ của ML Kit trước khi gọi 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
                  // ...
              }
          }
      }
      

      Nếu bạn không sử dụng thư viện camera cung cấp cho bạn góc quay của hình ảnh, bạn có thể tính toán nó từ góc quay của thiết bị và hướng của cảm biến camera trong thiết bị:

      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
      }

      Sau đó, chuyển đối tượng media.Image và giá trị xoay cho FirebaseVisionImage.fromMediaImage() :

      Java

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

      Kotlin+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Để tạo đối tượng FirebaseVisionImage từ URI tệp, hãy chuyển ngữ cảnh ứng dụng và URI tệp tới FirebaseVisionImage.fromFilePath() . Điều này hữu ích khi bạn sử dụng ý định ACTION_GET_CONTENT để nhắc người dùng chọn hình ảnh từ ứng dụng thư viện của họ.

      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()
      }
    • Để tạo đối tượng FirebaseVisionImage từ ByteBuffer hoặc mảng byte, trước tiên hãy tính toán góc xoay hình ảnh như mô tả ở trên cho đầu vào media.Image .

      Sau đó, tạo một đối tượng FirebaseVisionImageMetadata chứa chiều cao, chiều rộng, định dạng mã hóa màu và xoay của hình ảnh:

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

      Sử dụng bộ đệm hoặc mảng và đối tượng siêu dữ liệu để tạo đối tượng 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)
    • Để tạo đối tượng FirebaseVisionImage từ đối tượng Bitmap :

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      Hình ảnh được đại diện bởi đối tượng Bitmap phải thẳng đứng, không cần xoay thêm.
  2. Lấy một phiên bản của FirebaseVisionFaceDetector :

    Java

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

    Kotlin+KTX

    val detector = FirebaseVision.getInstance()
            .getVisionFaceDetector(options)
  3. Cuối cùng, chuyển hình ảnh sang phương thức 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. Nhận thông tin về khuôn mặt được phát hiện

Nếu thao tác nhận dạng khuôn mặt thành công, danh sách các đối tượng FirebaseVisionFace sẽ được chuyển cho người nghe thành công. Mỗi đối tượng FirebaseVisionFace đại diện cho một khuôn mặt được phát hiện trong ảnh. Đối với mỗi khuôn mặt, bạn có thể lấy tọa độ giới hạn của nó trong hình ảnh đầu vào, cũng như bất kỳ thông tin nào khác mà bạn đã định cấu hình trình dò ​​tìm khuôn mặt để tìm. Ví dụ:

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

Ví dụ về đường nét khuôn mặt

Khi bật tính năng phát hiện đường viền khuôn mặt, bạn sẽ nhận được danh sách các điểm cho từng đặc điểm khuôn mặt được phát hiện. Những điểm này đại diện cho hình dạng của đối tượng địa lý. Xem Tổng quan về Khái niệm Nhận diện Khuôn mặt để biết chi tiết về cách thể hiện các đường viền.

Hình ảnh sau đây minh họa cách các điểm này ánh xạ tới một khuôn mặt (nhấp vào hình ảnh để phóng to):

Nhận diện khuôn mặt theo thời gian thực

Nếu bạn muốn sử dụng tính năng nhận diện khuôn mặt trong ứng dụng thời gian thực, hãy làm theo các nguyên tắc sau để đạt được tốc độ khung hình tốt nhất:

  • Định cấu hình trình dò ​​tìm khuôn mặt để sử dụng tính năng phát hiện hoặc phân loại đường viền khuôn mặt và phát hiện mốc chứ không phải cả hai:

    Phát hiện đường viền
    Phát hiện mốc
    Phân loại
    Phát hiện và phân loại mốc
    Phát hiện đường viền và phát hiện mốc
    Phát hiện và phân loại đường viền
    Phát hiện đường viền, phát hiện mốc và phân loại

  • Bật chế độ FAST (được bật theo mặc định).

  • Hãy cân nhắc việc chụp ảnh ở độ phân giải thấp hơn. Tuy nhiên, cũng hãy ghi nhớ các yêu cầu về kích thước hình ảnh của API này.

  • Van tiết lưu gọi tới máy dò. Nếu có khung hình video mới trong khi trình phát hiện đang chạy, hãy thả khung hình đó xuống.
  • Nếu bạn đang sử dụng đầu ra của bộ dò để phủ đồ họa lên hình ảnh đầu vào, trước tiên hãy lấy kết quả từ Bộ công cụ ML, sau đó kết xuất hình ảnh và lớp phủ trong một bước duy nhất. Bằng cách đó, bạn chỉ hiển thị trên bề mặt hiển thị một lần cho mỗi khung hình đầu vào.
  • Nếu bạn sử dụng API Camera2, hãy chụp ảnh ở định dạng ImageFormat.YUV_420_888 .

    Nếu bạn sử dụng API máy ảnh cũ hơn, hãy chụp ảnh ở định dạng ImageFormat.NV21 .