自定义相机中我们可以进行聚焦操作,我们也能检测人脸之后对目标人脸进行聚焦,这样看上去会更加智能。在 iOS 中,我们可以通过 Core Image 中的CIDetector
和CIFaceFeature
进行人脸检测功能。但是,在拍照软件中,人间检测对帧率有较高的要求,刚才提到的基于静态图片人脸检测的类似乎不太够用。幸运的是,除了他们可以检测人脸,在 AVFoundation 中的AVCaptureMetadataOutput
也支持人脸检测,并且他直接在捕捉会话中工作,使用起来也十分方便。本文主要介绍AVCaptureMetadataOutput
在人脸检测上的应用。
本部分的主要代码在SCCamera中的SCCameraController.m与SCVideoPreviewView.m
AVCaptureMetadataOutput 使用
AVCaptureMetadataOutput
的使用和其他AVCaptureOutput
很相似,主要分为下面:
- 创建
AVCaptureMetadataOutput
对象 - 将该输出类添加到捕捉会话中
- 开启
AVCaptureMetadataOutput
的人脸检测功能[self.metaOutput setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
- 设置
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<NSNumber*,CALayer*> *faceLayers;
为了方便管理,我们统一将人脸框的CALayer
添加到指定的CALayer
(overlayLayer
)上。在初始化的时候,需要注意以下细节:
-
overlayLayer
的frame
需要和预览视图一致 -
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];
}
关于代码中
transformFromRollAngle
和transformFromYawAngle
方法可以参考AVMetadataFaceObject+Transform.m