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
- Nếu bạn chưa có, hãy thêm Firebase vào dự án Android của bạn .
- 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' }
- 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ượngFirebaseVisionFaceDetectorOptions
. 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ượngFirebaseVisionImage
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ễ.
Tạo đối tượng
FirebaseVisionImage
từ hình ảnh của bạn.Để tạo đối tượng
FirebaseVisionImage
từ đối tượngmedia.Image
, chẳng hạn như khi chụp ảnh từ camera của thiết bị, hãy chuyển đối tượngmedia.Image
và góc xoay của hình ảnh tớiFirebaseVisionImage.fromMediaImage()
.Nếu bạn sử dụng thư viện CameraX , các lớp
OnImageCapturedListener
vàImageAnalysis.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ọiFirebaseVisionImage.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 choFirebaseVisionImage.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ớiFirebaseVisionImage.fromFilePath()
. Điều này hữu ích khi bạn sử dụng ý địnhACTION_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àomedia.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ượngBitmap
:Hình ảnh được đại diện bởi đối tượngJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin+KTX
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmap
phải thẳng đứng, không cần xoay thêm.
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)
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ượngFirebaseVisionFace
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ạiBậ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
.