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

人脸检测

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

本部分的主要代码在SCCamera中的SCCameraController.mSCVideoPreviewView.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其实就是使用分数来表示时间,即\frac{value}{timescale}。比如CMTimeMake(1,2)表示0.5秒

检测显示

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

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

@property (nonatomic, strong) NSMutableDictionary<NSNumber*,CALayer*> *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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容