在 Android 上使用 ML Kit 偵測臉孔

你可以使用 ML Kit 偵測圖片和影片中的臉孔。

特徵未分類組合
導入作業模型會透過 Google Play 服務動態下載。模型會在建構期間以靜態方式連結至應用程式。
應用程式大小大小增加約 800 KB。大小增加約 6.9 MB。
初始化時間可能要等到模型下載完畢再開始使用。模型可立即使用

馬上試試

事前準備

  1. 在專案層級的 build.gradle 檔案中,請務必在 buildscriptallprojects 區段中納入 Google 的 Maven 存放區。

  2. 將 ML Kit Android 程式庫的依附元件,新增至模組的應用程式層級 Gradle 檔案,通常為 app/build.gradle。請根據您的需求選擇下列其中一種依附元件:

    將模型與應用程式搭配使用:

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

    在 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. 如果您選擇在 Google Play 服務中使用模型,可以設定讓應用程式在從 Play 商店安裝應用程式後,自動將模型下載至裝置。如要這麼做,請在應用程式的 AndroidManifest.xml 檔案中新增下列宣告:

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

    您也可以明確檢查模型可用性,並透過 Google Play 服務 ModuleInstallClient API 要求下載。

    如果您不啟用安裝期間模型下載功能,或要求明確下載,系統就會在您首次執行偵測工具時下載模型。下載完成前發出的要求不會產生任何結果。

輸入圖片規範

如要使用臉部辨識功能,圖片尺寸應至少設為 480x360 像素。 為了讓 ML Kit 準確偵測臉孔,輸入圖片必須包含以充足像素資料表示的臉孔。一般來說,您要在圖片中偵測的每個臉孔都必須至少為 100 x 100 像素。如要偵測臉孔的等差,ML Kit 需要較高的輸入解析度:每個臉部至少應為 200 x 200 像素。

如果您在即時應用程式中偵測到臉部,建議您也考量輸入圖片的整體尺寸。系統可更快處理較小的圖片,因此為了縮短延遲時間,以較低解析度拍攝圖片,但請留意上述準確率要求,並盡可能讓拍攝對象的臉孔盡可能佔滿。另請參閱「即時效能改善提示」。

圖像對焦品質不佳也可能會影響準確度。如果您未能取得可接受的結果,請要求使用者重新拍攝圖片。

相對於攝影機的臉部方向,也可能會影響 ML Kit 偵測到的臉部特徵。請參閱「臉部偵測概念」一文。

1. 設定臉部偵測工具

如要在圖片中套用臉部偵測功能,如要變更任何臉部偵測工具的預設設定,請使用 FaceDetectorOptions 物件指定這些設定。您可以變更下列設定:

設定
setPerformanceMode PERFORMANCE_MODE_FAST (預設) | PERFORMANCE_MODE_ACCURATE

改善偵測臉孔的速度或精確度。

setLandmarkMode LANDMARK_MODE_NONE (預設) | LANDMARK_MODE_ALL

是否嘗試辨識臉部「地標」:眼睛、耳朵、鼻子、臉頰、嘴巴等。

setContourMode CONTOUR_MODE_NONE (預設) | CONTOUR_MODE_ALL

是否偵測臉部特徵的輪廓。系統只會針對圖片中最顯眼的臉孔偵測輪廓。

setClassificationMode CLASSIFICATION_MODE_NONE (預設) | CLASSIFICATION_MODE_ALL

是否將臉孔分類,例如「微笑」和「睜開雙眼」。

setMinFaceSize float (預設:0.1f)

設定所需的最小面尺寸,以頭部寬度與圖片寬度的比例表示。

enableTracking false (預設) | true

是否指派臉孔 ID,這組 ID 可用於追蹤圖片中的臉孔。

請注意,啟用輪廓偵測功能後,系統只會偵測到一個臉孔,因此臉部追蹤功能無法產生實用的結果。因此,如要加快偵測速度,請勿同時啟用輪廓偵測和臉部追蹤功能。

例如:

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. 準備輸入圖片

如要偵測圖片中的臉孔,請透過 Bitmapmedia.ImageByteBuffer、位元組陣列或裝置上的檔案建立 InputImage 物件。然後將 InputImage 物件傳遞至 FaceDetectorprocess 方法。

對於臉部偵測,建議使用尺寸至少為 480x360 像素的圖片。如果正在即時偵測臉孔,以這個最低解析度擷取影格有助於減少延遲。

您可以從不同來源建立 InputImage 物件,詳情請見下文。

使用 media.Image

如要從 media.Image 物件建立 InputImage 物件 (例如使用裝置相機拍照時),請將 media.Image 物件以及圖片的旋轉角度傳遞至 InputImage.fromMediaImage()

如果使用 CameraX 程式庫,OnImageCapturedListenerImageAnalysis.Analyzer 類別會為您計算旋轉值。

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

如果您未使用相機程式庫提供圖像的旋轉角度,可以將裝置旋轉角度和裝置相機感應器方向做為計算依據:

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

然後,將 media.Image 物件和旋轉角度值傳遞至 InputImage.fromMediaImage()

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

使用檔案 URI

如要從檔案 URI 建立 InputImage 物件,請將應用程式結構定義和檔案 URI 傳遞至 InputImage.fromFilePath()。使用 ACTION_GET_CONTENT 意圖提示使用者從圖片庫應用程式中選取圖片時,這項功能就很實用。

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

使用 ByteBufferByteArray

如要從 ByteBufferByteArray 建立 InputImage 物件,請先按照之前的 media.Image 輸入內容計算圖片旋轉角度。接著,使用緩衝區或陣列建立 InputImage 物件,以及圖片的高度、寬度、顏色編碼格式和旋轉角度:

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

使用 Bitmap

如要從 Bitmap 物件建立 InputImage 物件,請宣告下列宣告:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

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

圖像以 Bitmap 物件和旋轉角度表示。

3. 取得 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. 處理圖片

將圖片傳遞至 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. 取得系統偵測到的臉孔資訊

如果臉部偵測功能成功執行,系統會將 Face 物件清單傳送給成功事件監聽器。每個 Face 物件都代表在圖片中偵測到的臉孔。您可在輸入圖片中取得每個臉孔的定界座標,以及您設定臉部偵測工具要尋找的其他資訊。例如:

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

臉部輪廓範例

啟用臉部輪廓偵測功能後,系統會針對偵測到的各項臉部特徵顯示一份積分清單。這些點代表地圖項目的形狀。如要瞭解輪廓如何表示,請參閱「臉部偵測概念」一文。

下圖說明這些點如何對應到某個臉孔,按一下即可放大圖片:

偵測到的臉部輪廓網格示例

即時臉部偵測

如果想在即時應用程式中使用臉部偵測功能,請遵循下列準則,以達到最佳的影格速率:

  • 將臉部偵測工具設為使用臉部輪廓偵測、分類和地標偵測,但請勿同時使用兩者:

    線差偵測
    地標偵測
    分類
    地標偵測和分類
    路徑偵測和地標偵測
    輪廓偵測和分類
    校正偵測、地標偵測和分類

  • 啟用 FAST 模式 (預設為啟用)。

  • 建議以較低的解析度拍攝圖片。不過,也請留意這個 API 的圖片尺寸規定

  • 如果使用 Cameracamera2 API,請調節偵測工具的呼叫頻率。如果在偵測工具執行時有新的影片影格,請捨棄影格。如需範例,請參閱快速入門導覽課程範例應用程式中的 VisionProcessorBase 類別。
  • 如果使用 CameraX API,請確定背壓策略已設為其預設值 ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST。這麼做可保證系統一次只會傳送一張圖片進行分析。如果分析器忙碌時產生更多圖片,系統會自動捨棄這些圖片,不會排入傳送佇列。呼叫 ImageProxy.close() 關閉要分析的圖片後,就會傳送下一個最新的映像檔。
  • 如果使用偵測工具的輸出內容在輸入圖片上重疊圖像,請先從 ML Kit 取得結果,然後透過一個步驟算繪圖片和疊加層。在每個輸入影格中,這個操作只會轉譯一次到顯示介面一次。如需範例,請參閱快速入門導覽課程範例應用程式中的 CameraSourcePreview GraphicOverlay 類別。
  • 如果你使用 Camera2 API,請擷取 ImageFormat.YUV_420_888 格式的圖片。如果您使用舊版 Camera API,請拍攝 ImageFormat.NV21 格式的圖片。