在 Android 上使用 ML Kit 偵測臉孔

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

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

立即試用

事前準備

  1. 在專案層級的 build.gradle 檔案中,請務必加入 Google 的 buildscriptallprojects 區段內的 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.7'
    }
    

    在 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 服務中使用模型,可以 讓應用程式自動下載至裝置, 安裝如果要這麼做,請將下列宣告加入 應用程式的 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 準確偵測臉孔,輸入圖片必須包含臉孔 以充足的像素資料表示基本上, 至少需要 100x100 像素如要偵測 臉部輪廓線,則 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,以用於追蹤 圖像中的人物臉孔。

請注意,啟用輪廓偵測功能後,只有一張臉孔 因此臉部追蹤功能無法產生實用的結果。為此 原因及加快偵測速度,請勿同時啟用兩個輪廓線 偵測及臉部追蹤

例如:

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

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

如要使用臉部偵測功能,圖片尺寸應至少為 480x360 像素。如果正在即時偵測臉孔,請擷取畫面 達到這個最低解析度將有助於縮短延遲時間

您可以建立InputImage 不同來源的 ANR 物件,說明如下。

使用 media.Image

如要建立InputImage 物件,例如從 media.Image 物件擷取圖片 裝置的相機,請傳遞 media.Image 物件和映像檔的 旋轉為 InputImage.fromMediaImage()

如果您使用 CameraX 程式庫、OnImageCapturedListenerImageAnalysis.Analyzer 類別會計算旋轉值 不必確保憑證管理是否適當 因為 Google Cloud 會為您管理安全性

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

如要建立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

如要建立InputImage ByteBufferByteArray 的物件,請先計算圖片 與先前 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

如要建立InputImage 物件中,Bitmap 物件,請做出以下宣告:

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 格式。