在 iOS 系统中使用机器学习套件检测人脸

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

试试看

准备工作

  1. 在 Podfile 中添加以下机器学习套件 Pod:
    pod 'GoogleMLKit/FaceDetection', '15.5.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 对象,如以下示例中所示:

// 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
// 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. 准备输入图片

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

使用 UIImageVisionImage CMSampleBuffer

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

  • 使用 UIImage 创建一个 VisionImage 对象。请务必指定正确的 .orientation
    let image = VisionImage(image: UIImage)
    visionImage
    .orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage
    .orientation = image.imageOrientation;

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

  • 指定 CMSampleBuffer

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

    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
     
    }
    }
         
    - (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;
     
    }
    }
         
  • 使用VisionImage CMSampleBuffer 对象和方向:
    let image = VisionImage(buffer: sampleBuffer)
    image
    .orientation = imageOrientation(
      deviceOrientation
    : UIDevice.current.orientation,
      cameraPosition
    : cameraPosition)
     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image
    .orientation =
       
    [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition
    :cameraPosition];

3. 获取 FaceDetector 的一个实例

获取 FaceDetector 的一个实例:

let faceDetector = FaceDetector.faceDetector(options: options)
MLKFaceDetector *faceDetector = [MLKFaceDetector faceDetectorWithOptions:options];
     

4. 处理图片

然后,将图片传递给 process() 方法:
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
 
// ...
}
[faceDetector processImage:image
                completion
:^(NSArray<MLKFace *> *faces,
                             
NSError *error) {
 
if (error != nil) {
   
return;
 
}
 
if (faces.count > 0) {
   
// Recognized faces
 
}
}];

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

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

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
 
}
}
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。致电 此方法(可从 获取) AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) 函数,用于同步获取指定视频的结果 帧。保留 AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames 设置为 true,以限制对检测器的调用。如果新的 视频帧在检测器运行时可用,则会丢失。
  • 如果您使用检测器的输出在图像上叠加显示 输入图片,首先从机器学习套件获取结果, 和叠加层。通过这种方式,您可以在显示屏上呈现 只对每个已处理的输入帧运行一次。请参阅 updatePreviewOverlayViewWithLastFrame