在 iOS 上使用机器学习套件检测人脸

您可以使用机器学习套件检测图片和视频中的人脸。

  • 请试用示例应用,以查看此 API 的用法示例。
  • 使用 Codelab 亲自试用代码。

准备工作

  1. 在 Podfile 中添加以下机器学习套件 pod:
    pod 'GoogleMLKit/FaceDetection', '3.2.0'
    
  2. 安装或更新项目的 Pod 之后,使用 Xcode 项目的 .xcworkspace 打开该项目。Xcode 12.4 版或更高版本支持机器学习套件。

输入图片指南

对于人脸识别,您应该使用尺寸至少为 480x360 像素的图片。 为了使机器学习套件准确检测人脸,输入图片必须包含由足够像素数据表示的人脸。一般来说,要在图片中检测的每张人脸应至少为 100x100 像素。如果要检测人脸轮廓,机器学习套件需要更高的分辨率输入:每张人脸应至少为 200x200 像素。

如果您在实时应用中检测人脸,则可能还需要考虑输入图片的整体尺寸。较小图片的处理速度相对较快,因此,为了缩短延迟时间,请以较低的分辨率捕获图片,但请牢记上述准确性要求,并确保拍摄对象在图片中占据尽可能多的图片位置。另请参阅提高实时性能的相关提示

图片聚焦不良也会影响准确性。如果您未获得可接受的结果,请让用户重新拍摄图片。

人脸相对于相机的方向也会影响机器学习套件检测到的面部特征。请参阅人脸检测概念

1. 配置人脸检测器

在对图片应用人脸检测之前,如果要更改人脸检测器的任何默认设置,请使用 FaceDetectorOptions 对象指定这些设置。您可以更改以下设置:

设置
performanceMode fast(默认)| accurate

在检测人脸时更注重速度还是准确性。

landmarkMode none(默认)| all

是否尝试检测检测到的所有人脸的“特征点”:眼睛、耳朵、鼻子、脸颊、嘴巴。

contourMode none(默认)| all

是否检测面部特征的轮廓。仅检测图片中最突出的人脸的轮廓。

classificationMode none(默认)| all

是否将人脸分为不同类别(例如“微笑”和“睁眼”)。

minFaceSize CGFloat(默认值:0.1

设置所需的最小人脸大小,表示为头部宽度与图片宽度之比。

isTrackingEnabled false(默认)| true

是否为人脸分配 ID,以用于跨图片跟踪人脸。

请注意,启用轮廓检测后,仅检测到一张人脸,因此人脸跟踪不会产生有用的结果。因此,若要提高检测速度,请勿同时启用轮廓检测和人脸跟踪。

例如,如以下某个示例所示,构建 FaceDetectorOptions 对象:

Swift

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

// Real-time contour detection of multiple faces
// options.contourMode = .all

Objective-C

// High-accuracy landmark detection and face classification
MLKFaceDetectorOptions *options = [[MLKFaceDetectorOptions alloc] init];
options.performanceMode = MLKFaceDetectorPerformanceModeAccurate;
options.landmarkMode = MLKFaceDetectorLandmarkModeAll;
options.classificationMode = MLKFaceDetectorClassificationModeAll;

// Real-time contour detection of multiple faces
// options.contourMode = MLKFaceDetectorContourModeAll;

2. 准备输入图片

如需检测图片中的人脸,请使用 process(_:completion:)results(in:) 方法将图片作为 UIImageCMSampleBufferRef 传递给 FaceDetector

使用 UIImageCMSampleBuffer 创建 VisionImage 对象。

如果您使用的是 UIImage,请按以下步骤操作:

  • 使用 UIImage 创建一个 VisionImage 对象。请务必指定正确的 .orientation

    Swift

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

如果您使用的是 CMSampleBuffer,请按以下步骤操作:

  • 指定 CMSampleBuffer 中包含的图片数据的方向。

    要获取图片方向,请执行以下操作:

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • 使用 CMSampleBuffer 对象和屏幕方向创建 VisionImage 对象:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. 获取 FaceDetector 的实例

获取 FaceDetector 的一个实例:

Swift

let faceDetector = FaceDetector.faceDetector(options: options)

Objective-C

MLKFaceDetector *faceDetector = [MLKFaceDetector faceDetectorWithOptions:options];
      

4. 处理图片

然后,将图片传递给 process() 方法:

Swift

weak var weakSelf = self
faceDetector.process(visionImage) { faces, error in
  guard let strongSelf = weakSelf else {
    print("Self is nil!")
    return
  }
  guard error == nil, let faces = faces, !faces.isEmpty else {
    // ...
    return
  }

  // Faces detected
  // ...
}

Objective-C

[faceDetector processImage:image
                completion:^(NSArray<MLKFace *> *faces,
                             NSError *error) {
  if (error != nil) {
    return;
  }
  if (faces.count > 0) {
    // Recognized faces
  }
}];

5. 获取有关检测到的人脸的信息

如果人脸检测操作成功,则人脸检测器会向完成处理程序传递一组 Face 对象。每个 Face 对象代表一张在图片中检测到的面孔。对于每张人脸,您可以获取它在输入图片中的边界坐标,以及您已配置人脸检测器所查找的任何其他信息。例如:

Swift

for face in faces {
  let frame = face.frame
  if face.hasHeadEulerAngleX {
    let rotX = face.headEulerAngleX  // Head is rotated to the uptoward rotX degrees
  }
  if face.hasHeadEulerAngleY {
    let rotY = face.headEulerAngleY  // Head is rotated to the right rotY degrees
  }
  if face.hasHeadEulerAngleZ {
    let rotZ = face.headEulerAngleZ  // Head is tilted sideways 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 (MLKFace *face in faces) {
  // Boundaries of face in image
  CGRect frame = face.frame;
  if (face.hasHeadEulerAngleX) {
    CGFloat rotX = face.headEulerAngleX;  // Head is rotated to the upward rotX degrees
  }
  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):
  MLKFaceLandmark *leftEar = [face landmarkOfType:FIRFaceLandmarkTypeLeftEar];
  if (leftEar != nil) {
    MLKVisionPoint *leftEarPosition = leftEar.position;
  }

  // If contour detection was enabled:
  MLKFaceContour *upperLipBottomContour = [face contourOfType:FIRFaceContourTypeUpperLipBottom];
  if (upperLipBottomContour != nil) {
    NSArray<MLKVisionPoint *> *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 的图片尺寸要求。

  • 如需处理视频帧,请使用检测器的 results(in:) 同步 API。从 AVCaptureVideoDataOutputSampleBufferDelegatecaptureOutput(_, didOutput:from:) 函数中调用此方法,以同步获取给定视频帧的结果。将 AVCaptureVideoDataOutputalwaysDiscardsLateVideoFrames 保留为 true,以限制对检测器的调用。如果在检测器运行时有新的视频帧可用,将被丢弃。
  • 如果您使用检测器的输出在输入图片上叠加图形,请先从机器学习套件获取结果,然后在一个步骤中完成图片的渲染和叠加。采用这一方法,每个已处理的输入帧只需在显示表面呈现一次。如需查看示例,请参阅机器学习套件快速入门示例中的 updatePreviewOverlayViewWithLastFrame