Phát hiện khuôn mặt bằng Bộ công cụ học máy trên Android

Bạn có thể dùng Bộ công cụ học máy để phát hiện khuôn mặt trong hình ảnh và video.

Tính năngKhông được kết hợpTheo cụm
Triển khaiMô hình được tải xuống linh hoạt thông qua Dịch vụ Google Play.Mô hình được liên kết tĩnh với ứng dụng của bạn tại thời điểm tạo.
Kích thước ứng dụngKích thước tăng khoảng 800 KB.Kích thước tăng khoảng 6,9 MB.
Thời gian khởi chạyCó thể phải đợi mô hình tải xuống trước khi sử dụng lần đầu.Mô hình có sẵn ngay lập tức

Dùng thử

Trước khi bắt đầu

  1. Trong tệp build.gradle cấp dự án, hãy nhớ thêm kho lưu trữ Maven của Google vào cả hai mục buildscriptallprojects.

  2. Thêm các phần phụ thuộc cho thư viện ML Kit trên Android vào tệp gradle cấp ứng dụng của mô-đun, thường là app/build.gradle. Chọn một trong các phần phụ thuộc sau đây dựa trên nhu cầu của bạn:

    Để gói mô hình với ứng dụng của bạn:

    dependencies {
      // ...
      // Use this dependency to bundle the model with your app
      implementation 'com.google.mlkit:face-detection:16.1.7'
    }
    

    Để sử dụng mô hình này trong Dịch vụ Google Play:

    dependencies {
      // ...
      // Use this dependency to use the dynamically downloaded model in Google Play Services
      implementation 'com.google.android.gms:play-services-mlkit-face-detection:17.1.0'
    }
    
  3. Nếu chọn sử dụng mô hình trong Dịch vụ Google Play, bạn có thể định cấu hình ứng dụng để tự động tải mô hình xuống thiết bị sau khi ứng dụng được cài đặt từ Cửa hàng Play. Để thực hiện việc này, hãy thêm khai báo sau vào tệp AndroidManifest.xml của ứng dụng:

    <application ...>
          ...
          <meta-data
              android:name="com.google.mlkit.vision.DEPENDENCIES"
              android:value="face" >
          <!-- To use multiple models: android:value="face,model2,model3" -->
    </application>
    

    Bạn cũng có thể kiểm tra rõ ràng phạm vi cung cấp của mô hình và yêu cầu tải xuống thông qua ModuleInstallClient API của Dịch vụ Google Play.

    Nếu bạn không bật tính năng tải mô hình xuống tại thời điểm cài đặt hoặc yêu cầu tải xuống rõ ràng, thì 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. Các 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 có kết quả.

Nguyên tắc về hình ảnh đầu vào

Đối với tính năng 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 là 480x360 pixel. Để ML Kit phát hiện khuôn mặt một cách chính xác, hình ảnh đầu vào phải chứa khuôn mặt được biểu thị bằng đủ dữ liệu pixel. Nhìn chung, mỗi khuôn mặt mà bạn muốn phát hiện trong một hình ả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 phát hiện khuôn mặt trong một ứng dụng theo 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. Vì vậy, để giảm độ trễ, hãy chụp ảnh ở độ phân giải thấp hơn, nhưng hãy lưu ý các yêu cầu về độ chính xác nêu trên và đảm bảo rằng khuôn mặt của đối tượng chiếm phần lớn hình ảnh. Ngoài ra, hãy xem các mẹo để cải thiện hiệu suất theo thời gian thực.

Hình ảnh không rõ nét cũng 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 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 camera cũng có thể ảnh hưởng đến những đặc điểm trên khuôn mặt mà ML Kit phát hiện. Xem Các khái niệm về tính năng phát hiện khuôn mặt.

1. Định cấu hình trình phát hiện khuôn mặt

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

Cài đặt
setPerformanceMode PERFORMANCE_MODE_FAST (mặc định) | PERFORMANCE_MODE_ACCURATE

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

setLandmarkMode LANDMARK_MODE_NONE (mặc định) | LANDMARK_MODE_ALL

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

setContourMode CONTOUR_MODE_NONE (mặc định) | CONTOUR_MODE_ALL

Liệu có phát hiện đường nét của các đặc điểm trên khuôn mặt hay không. Đường viền chỉ được phát hiện cho khuôn mặt nổi bật nhất trong một hình ảnh.

setClassificationMode CLASSIFICATION_MODE_NONE (mặc định) | CLASSIFICATION_MODE_ALL

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

setMinFaceSize float (mặc định: 0.1f)

Đặt kích thước khuôn mặt nhỏ nhất mong muốn, được biểu thị dưới dạng tỷ lệ giữa chiều rộng đầu và chiều rộng hình ảnh.

enableTracking false (mặc định) | true

Có nên chỉ định mã nhận dạng cho khuôn mặt hay không. Mã nhận dạng này có thể dùng để theo dõi khuôn mặt trên nhiều hình ảnh.

Xin lưu ý rằng khi bật tính năng phát hiện đường viền, hệ thống sẽ chỉ phát hiện một khuôn mặt, vì vậy, tính năng theo dõi khuôn mặt sẽ 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, đừng bật cả tính năng phát hiện đường viền và tính năng theo dõi khuôn mặt.

Ví dụ:

Kotlin

// High-accuracy landmark detection and face classification
val highAccuracyOpts = FaceDetectorOptions.Builder()
        .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
        .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
        .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
        .build()

// Real-time contour detection
val realTimeOpts = FaceDetectorOptions.Builder()
        .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
        .build()

Java

// High-accuracy landmark detection and face classification
FaceDetectorOptions highAccuracyOpts =
        new FaceDetectorOptions.Builder()
                .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
                .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
                .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
                .build();

// Real-time contour detection
FaceDetectorOptions realTimeOpts =
        new FaceDetectorOptions.Builder()
                .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
                .build();

2. Chuẩn bị hình ảnh đầu vào

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

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

Bạn có thể tạo một đối tượng InputImage từ nhiều nguồn, mỗi nguồn được giải thích bên dưới.

Sử dụng media.Image

Để tạo một đối tượng InputImage từ một đối tượng media.Image, chẳng hạn như khi bạn chụp ảnh bằng camera của thiết bị, hãy truyền đối tượng media.Image và hướng xoay của hình ảnh đến InputImage.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.

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

Nếu không dùng thư viện máy ảnh cho bạn biết độ xoay của hình ảnh, bạn có thể tính độ xoay đó từ độ xoay của thiết bị và hướng của cảm biến camera trong thiết bị:

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

Sau đó, hãy truyền đối tượng media.Image và giá trị độ xoay đến InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Sử dụng URI tệp

Để tạo một đối tượng InputImage từ một URI tệp, hãy truyền ngữ cảnh ứng dụng và URI tệp đến InputImage.fromFilePath(). Điều này hữu ích khi bạn dùng ý định ACTION_GET_CONTENT để nhắc người dùng chọn một hình ảnh trong ứng dụng thư viện của họ.

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

Sử dụng ByteBuffer hoặc ByteArray

Để tạo một đối tượng InputImage từ ByteBuffer hoặc ByteArray, trước tiên, hãy tính độ xoay của hình ảnh như mô tả trước đó cho dữ liệu đầu vào media.Image. Sau đó, hãy tạo đối tượng InputImage bằng vùng đệm hoặc mảng, cùng với chiều cao, chiều rộng, định dạng mã hoá màu và độ xoay của hình ảnh:

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

Sử dụng Bitmap

Để tạo đối tượng InputImage từ đối tượng Bitmap, hãy khai báo như sau:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

Hình ảnh được biểu thị bằng một đối tượng Bitmap cùng với độ xoay.

3. Nhận một thực thể FaceDetector

Kotlin

val detector = FaceDetection.getClient(options)
// Or, to use the default option:
// val detector = FaceDetection.getClient();

Java

FaceDetector detector = FaceDetection.getClient(options);
// Or use the default options:
// FaceDetector detector = FaceDetection.getClient();

4. Xử lý hình ảnh

Truyền hình ảnh đến phương thức process:

Kotlin

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

Java

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

5. Xem thông tin về khuôn mặt được phát hiện

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

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

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

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

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

Java

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

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

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

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

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

Khi bật tính năng phát hiện đường nét 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. Các điểm này biểu thị hình dạng của đối tượng. Hãy xem phần Các khái niệm về tính năng phát hiện khuôn mặt để biết thông tin chi tiết về cách thể hiện đường viền.

Hình ảnh sau đây minh hoạ cách các điểm này ánh xạ đến một khuôn mặt, hãy nhấp vào hình ảnh để phóng to:

ví dụ về lưới đường viền khuôn mặt được phát hiện

Phát hiệ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 một ứng dụng theo 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 phát hiện khuôn mặt để sử dụng tính năng phát hiện đường viền khuôn mặt hoặc phân loại và phát hiện điểm đánh dấu, nhưng không được dùng cả hai:

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

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

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

  • Nếu bạn dùng API Camera hoặc camera2, hãy điều chỉnh tốc độ gọi đến trình phát hiện. Nếu có khung hình video mới trong khi bộ phát hiện đang chạy, hãy thả khung hình đó. Hãy xem lớp VisionProcessorBase trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ.
  • Nếu bạn sử dụng API CameraX, hãy đảm bảo rằng chiến lược áp suất ngược được đặt thành giá trị mặc định ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST. Điều này đảm bảo rằng chỉ có một hình ảnh được phân phối để phân tích tại một thời điểm. Nếu có nhiều hình ảnh được tạo ra khi trình phân tích đang bận, thì những hình ảnh đó sẽ tự động bị loại bỏ và không được đưa vào hàng đợi để phân phối. Sau khi hình ảnh đang được phân tích bị đóng bằng cách gọi ImageProxy.close(), hình ảnh mới nhất tiếp theo sẽ được phân phối.
  • Nếu bạn dùng đầu ra của bộ nhận diện để phủ đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả từ ML Kit, sau đó kết xuất hình ảnh và phủ trong một bước. Thao tác này chỉ kết xuất vào bề mặt hiển thị một lần cho mỗi khung hình đầu vào. Hãy xem các lớp CameraSourcePreview GraphicOverlay trong ứng dụng mẫu bắt đầu nhanh để biết ví dụ.
  • 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 Camera API cũ, hãy chụp ảnh ở định dạng ImageFormat.NV21.