在 iOS 上使用 ML Kit 偵測臉孔

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

事前準備

  1. 如果您尚未將 Firebase 加入應用程式,請按照入門指南中的步驟進行。
  2. 在 Podfile 中加入 ML Kit 程式庫:
    pod 'Firebase/MLVision', '6.25.0'
    # If you want to detect face contours (landmark detection and classification
    # don't require this additional model):
    pod 'Firebase/MLVisionFaceModel', '6.25.0'
    
    安裝或更新專案的 Pod 後,請務必使用 .xcworkspace 開啟 Xcode 專案。
  3. 在應用程式中匯入 Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;

輸入圖片規範

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

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

圖片焦點不佳可能會降低準確性。如果您仍未取得可接受的結果,請嘗試要求使用者重新拍攝圖片。

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

1. 設定臉部偵測工具

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

設定
performanceMode fast (預設) | accurate

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

landmarkMode none (預設) | all

是否要嘗試偵測所有偵測到的臉孔「地標」,包括眼睛、耳朵、鼻子、臉頰、嘴巴。

contourMode none (預設) | all

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

classificationMode none (預設) | all

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

minFaceSize CGFloat (預設:0.1)

待偵測臉孔的最小尺寸 (相對於圖片)。

isTrackingEnabled false (預設) | true

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

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

例如,您可以依照下列範例建構 VisionFaceDetectorOptions 物件:

Swift

// High-accuracy landmark detection and face classification
let options = VisionFaceDetectorOptions()
options.performanceMode = .accurate
options.landmarkMode = .all
options.classificationMode = .all

// Real-time contour detection of multiple faces
let options = VisionFaceDetectorOptions()
options.contourMode = .all

Objective-C

// High-accuracy landmark detection and face classification
FIRVisionFaceDetectorOptions *options = [[FIRVisionFaceDetectorOptions alloc] init];
options.performanceMode = FIRVisionFaceDetectorPerformanceModeAccurate;
options.landmarkMode = FIRVisionFaceDetectorLandmarkModeAll;
options.classificationMode = FIRVisionFaceDetectorClassificationModeAll;

// Real-time contour detection of multiple faces
FIRVisionFaceDetectorOptions *options = [[FIRVisionFaceDetectorOptions alloc] init];
options.contourMode = FIRVisionFaceDetectorContourModeAll;

2. 執行臉部偵測工具

如要偵測圖片中的臉孔,請將圖片以 UIImageCMSampleBufferRef 的形式傳遞至 VisionFaceDetectordetect(in:) 方法:

  1. 取得 VisionFaceDetector 的例項:

    Swift

    lazy var vision = Vision.vision()
    
    let faceDetector = vision.faceDetector(options: options)
    

    Objective-C

    FIRVision *vision = [FIRVision vision];
    FIRVisionFaceDetector *faceDetector = [vision faceDetector];
    // Or, to change the default settings:
    // FIRVisionFaceDetector *faceDetector =
    //     [vision faceDetectorWithOptions:options];
    
  2. 使用 UIImageCMSampleBufferRef 建立 VisionImage 物件。

    如何使用 UIImage

    1. 視需要旋轉圖片,讓 imageOrientation 屬性為 .up
    2. 使用正確旋轉的 UIImage 建立 VisionImage 物件。請勿指定任何旋轉中繼資料,必須使用預設值 .topLeft

      Swift

      let image = VisionImage(image: uiImage)

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    如何使用 CMSampleBufferRef

    1. 建立 VisionImageMetadata 物件,指定 CMSampleBufferRef 緩衝區中圖片資料的方向。

      如何取得圖片方向:

      Swift

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      Objective-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      然後,建立中繼資料物件:

      Swift

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      Objective-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. 使用 CMSampleBufferRef 物件和旋轉中繼資料建立 VisionImage 物件:

      Swift

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. 接著,將圖片傳遞至 detect(in:) 方法:

    Swift

    faceDetector.process(visionImage) { faces, error in
      guard error == nil, let faces = faces, !faces.isEmpty else {
        // ...
        return
      }
    
      // Faces detected
      // ...
    }
    

    Objective-C

    [faceDetector detectInImage:image
                     completion:^(NSArray<FIRVisionFace *> *faces,
                                  NSError *error) {
      if (error != nil) {
        return;
      } else if (faces != nil) {
        // Recognized faces
      }
    }];
    

3. 取得系統偵測到的臉孔資訊

如果臉部偵測作業成功,臉部偵測工具會將 VisionFace 物件陣列傳遞至完成處理常式。每個 VisionFace 物件都代表在圖片中偵測到的臉孔。您可以在輸入圖片中取得每個臉孔的邊界座標,以及您設定臉部偵測工具尋找的其他資訊。例如:

Swift

for face in faces {
  let frame = face.frame
  if face.hasHeadEulerAngleY {
    let rotY = face.headEulerAngleY  // Head is rotated to the right rotY degrees
  }
  if face.hasHeadEulerAngleZ {
    let rotZ = face.headEulerAngleZ  // Head is rotated upward rotZ degrees
  }

  // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
  // nose available):
  if let leftEye = face.landmark(ofType: .leftEye) {
    let leftEyePosition = leftEye.position
  }

  // If contour detection was enabled:
  if let leftEyeContour = face.contour(ofType: .leftEye) {
    let leftEyePoints = leftEyeContour.points
  }
  if let upperLipBottomContour = face.contour(ofType: .upperLipBottom) {
    let upperLipBottomPoints = upperLipBottomContour.points
  }

  // If classification was enabled:
  if face.hasSmilingProbability {
    let smileProb = face.smilingProbability
  }
  if face.hasRightEyeOpenProbability {
    let rightEyeOpenProb = face.rightEyeOpenProbability
  }

  // If face tracking was enabled:
  if face.hasTrackingID {
    let trackingId = face.trackingID
  }
}

Objective-C

for (FIRVisionFace *face in faces) {
  // Boundaries of face in image
  CGRect frame = face.frame;

  if (face.hasHeadEulerAngleY) {
    CGFloat rotY = face.headEulerAngleY;  // Head is rotated to the right rotY degrees
  }
  if (face.hasHeadEulerAngleZ) {
    CGFloat rotZ = face.headEulerAngleZ;  // Head is tilted sideways rotZ degrees
  }

  // If landmark detection was enabled (mouth, ears, eyes, cheeks, and
  // nose available):
  FIRVisionFaceLandmark *leftEar = [face landmarkOfType:FIRFaceLandmarkTypeLeftEar];
  if (leftEar != nil) {
    FIRVisionPoint *leftEarPosition = leftEar.position;
  }

  // If contour detection was enabled:
  FIRVisionFaceContour *upperLipBottomContour = [face contourOfType:FIRFaceContourTypeUpperLipBottom];
  if (upperLipBottomContour != nil) {
    NSArray<FIRVisionPoint *> *upperLipBottomPoints = upperLipBottomContour.points;
    if (upperLipBottomPoints.count > 0) {
      NSLog("Detected the bottom contour of the subject's upper lip.")
    }
  }

  // If classification was enabled:
  if (face.hasSmilingProbability) {
    CGFloat smileProb = face.smilingProbability;
  }
  if (face.hasRightEyeOpenProbability) {
    CGFloat rightEyeOpenProb = face.rightEyeOpenProbability;
  }

  // If face tracking was enabled:
  if (face.hasTrackingID) {
    NSInteger trackingID = face.trackingID;
  }
}

臉部輪廓範例

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

下圖說明這些點如何對應到臉孔 (點選圖片即可放大):

即時臉部偵測

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

  • 設定臉部偵測工具來使用臉部輪廓偵測、分類和地標偵測,但不要同時使用兩者:

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

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

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

  • 限制對偵測工具的呼叫。如果在偵測工具執行時有新的影片影格,請捨棄影格。
  • 如果您使用偵測工具的輸出內容,在輸入圖片上疊加圖像,請先從 ML Kit 取得結果,然後透過一個步驟算繪圖像和疊加層。如此一來,每個輸入影格都只會算繪到顯示介面一次。如需範例,請參閱展示範例應用程式中的 previewOverlayViewFIRDetectionOverlayView 類別。