【iOS】自定义相机(七)人脸检测

人脸检测

自定义相机中我们可以进行聚焦操作,我们也能检测人脸之后对目标人脸进行聚焦,这样看上去会更加智能。在 iOS 中,我们可以通过 Core Image 中的CIDetectorCIFaceFeature进行人脸检测功能。但是,在拍照软件中,人间检测对帧率有较高的要求,刚才提到的基于静态图片人脸检测的类似乎不太够用。幸运的是,除了他们可以检测人脸,在 AVFoundation 中的AVCaptureMetadataOutput也支持人脸检测,并且他直接在捕捉会话中工作,使用起来也十分方便。本文主要介绍AVCaptureMetadataOutput在人脸检测上的应用。

本部分的主要代码在SCCamera中的SCCameraController.m与SCVideoPreviewView.m

AVCaptureMetadataOutput 使用

AVCaptureMetadataOutput的使用和其他AVCaptureOutput很相似,主要分为下面:

  1. 创建AVCaptureMetadataOutput对象
  2. 将该输出类添加到捕捉会话中
  3. 开启AVCaptureMetadataOutput的人脸检测功能
    • [self.metaOutput setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
  4. 设置AVCaptureMetadataOutputObjectsDelegate代理
    • [self.metaOutput setMetadataObjectsDelegate:self queue:self.metaQueue];

PS: 必须在添加进捕捉会话后再设置metadataObjectTypes,否则程序将会因为该输出类暂时不支持人脸检测而崩溃。

AVCaptureMetadataOutputObjectsDelegate

AVCaptureMetadataOutput中其实最重要的就是AVCaptureMetadataOutputObjectsDelegate代理,我们也是通过一下代理方法来获取识别结果的:

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection;

在这个方法中,我们最需要关注的就是metadataObjects数组,在这个数组中,我们能能拿到当前帧识别出来的所有对象。他的数组元素类型是AVMetadataObject,也是我们最终需要的AVMetadataFaceObject类的父类。在AVMetadataFaceObject中,我们可以获取以下信息:

  • time:识别到人脸的时间
  • duration:该人脸的维持时间
  • bounds:人脸在镜头中的位置(基于设备坐标)
    • 需要使用AVCaptureVideoPreviewLayer中的坐标转换
  • faceID:人脸ID
  • rollAngle:倾斜角
    • 表示人的头部向肩膀方向的侧斜角度
  • yawAngle:偏转角
    • 人脸绕 y 轴旋转的角度

PS:浮点数在运算的时候会出现精度丢失的问题,这些细小的误差在视频处理中会无限放大,因此 AVFoundation 中时间的表示不用NSTimeInterval(double),而是使用CMTime

CMTime其实就是使用分数来表示时间,即。比如CMTimeMake(1,2)表示0.5秒

检测显示

因为有帧率和性能的要求,我们不能使用UIView显示。为了方便,本文使用CALayer进行显示:

人脸检测结果AVMetadataFaceObject中有faceID,我们需要将使用faceID区分不同人脸的人脸框显示CALayer,因此,需要下面的这个NSMutableDictionary进行存储:

@property (nonatomic, strong) NSMutableDictionary *faceLayers;

为了方便管理,我们统一将人脸框的CALayer添加到指定的CALayer(overlayLayer)上。在初始化的时候,需要注意以下细节:

  • overlayLayerframe需要和预览视图一致
  • overlayLayer需要设置相应的sublayerTransform
    • 为了显示yawAngle的效果,需要让子层绕 Y 轴旋转
    • self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);

CATransform3DMakePerspective

static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0 / eyePosition;
    return transform;
}

人脸框显示前操作

上文说到,我们拿到的AVMetadataFaceObject中的bounds是基于设备坐标系的,但是显示的时候我们需要是普通视图坐标系中的。因此,需要先做下列的转换操作:

AVMetadataObject *transfromedFace = [self.videoPreviewLayer transformedMetadataObjectForMetadataObject:face]

人脸框显示操作

// 记录离开镜头的人脸框
NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
// 遍历识别到的所有人脸数据
for (AVMetadataFaceObject *face in transformedFaces) {
    NSNumber *faceID = @(face.faceID);
    [lostFaces removeObject:faceID];
    // 获取人脸框
    CALayer *layer = self.faceLayers[faceID];
    if (!layer) {
        layer = [self makeFaceLayer];
        [self.overlayLayer addSublayer:layer];
        self.faceLayers[faceID] = layer;
    }
    // 重置 transform
    layer.transform = CATransform3DIdentity;
    // 显示位置
    layer.frame = face.bounds;
    // 显示倾斜角
    if (face.hasRollAngle) {
        CATransform3D t = [face transformFromRollAngle];
        layer.transform = CATransform3DConcat(layer.transform, t);
    }
    // 显示偏转角
    if (face.hasYawAngle) {
        CATransform3D t = [face transformFromYawAngle];
        layer.transform = CATransform3DConcat(layer.transform, t);
    }
}

// 移除已经离开镜头的人脸框
for (NSNumber *faceID in lostFaces) {
    CALayer *layer = self.faceLayers[faceID];
    [layer removeFromSuperlayer];
    [self.faceLayers removeObjectForKey:faceID];
}

关于代码中transformFromRollAngletransformFromYawAngle方法可以参考AVMetadataFaceObject+Transform.m

你可能感兴趣的:(【iOS】自定义相机(七)人脸检测)