AVFoundation开发秘籍笔记-07高级捕捉功能之人脸识别

一、概述

这里的人脸检测是通过AVFoundation实现的实时人脸检测功能,会在检测到人脸自动建立相应的焦点。

AVFoundation中通过特定的AVCaptureOutput类型的AVCaptureMetadataOutput实现这个功能。它的输出同之前类似,输出的不是静态图片或影片,而是元数据。定义了用来处理多种元数据类型的接口,当使用人脸检测时,会输出一个具体子类类型AVMetadataFaceObject

AVMetadataFaceObject几个重要属性:

  • rollAngle:倾斜角,表示人的头部向肩膀方向的侧倾角度。
  • yawAngle:偏转角,表示人脸绕y轴旋转的角度。
  • bounds:边界,对应的是设备坐标。

人脸识别的整个流程与之前用到的静态图片和视频捕捉是一样的,不同的是一些配置的不同,以及对获取到的脸部数据对象的处理。

二、实现流程

基本功能

  • 1、创建会话,并配置输入输出
self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;

AVCaptureDevice *videoDevice =
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

AVCaptureDeviceInput *videoInput =
    [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
    if ([self.captureSession canAddInput:videoInput]) {
        [self.captureSession addInput:videoInput];
        self.activeVideoInput = videoInput;
    } else {
        if (error) {
           
        }
    }
} 

// Setup the still image output
self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
//self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};

if ([self.captureSession canAddOutput:self.imageOutput]) {
    [self.captureSession addOutput:self.imageOutput];
} else {
    if (error) {
    
    }
}

// 添加元数据输出捕捉
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
if ([self.captureSession canAddOutput:self.metadataOutput]) {
    [self.captureSession addOutput:self.metadataOutput];
    // 添加新的捕捉会话输出
    
    NSArray *metadataObjectTypes = @[AVMetadataObjectTypeFace];
    self.metadataOutput.metadataObjectTypes = metadataObjectTypes;
    //指定输出的元数据类型。
    
    dispatch_queue_t mainqueue = dispatch_get_main_queue();
    [self.metadataOutput setMetadataObjectsDelegate:self queue:mainqueue];
    //有新的元数据被检测到时,会都回调代理AVCaptureMetadataOutputObjectsDelegate中的方法
    //可以自定义系列的调度队列,不过由于人脸检测用到硬件加速,而且许多人物都要在主线程中执行,所以需要为这个参数指定主队列。
    

  • 2、设置回调代理方法
#pragma  -- mark AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

   //metadataObjects 就是人脸检测结果的元数据,
   //包含多个人脸数据信息,可以做相应处理,
   // 比如将要实现的,在人脸上画框标记。

}

  • 3、开始会话和结束会话
- (void)startSession {
    if (![self.captureSession isRunning]) {
        dispatch_async(self.videoQueue, ^{
            [self.captureSession startRunning];
        });
    }
}

- (void)stopSession {
    if ([self.captureSession isRunning]) {
        dispatch_async(self.videoQueue, ^{
            [self.captureSession stopRunning];
        });
    }
}

  • 4、设置必要的预览层

    视频预览层,和将要标记人脸的数据集合以及标记人脸方框的父layer。

self.faceLayers = [NSMutableDictionary dictionary];
// 存放人脸数据:@{faceId:layer}

    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    self.overlayLayer = [CALayer layer];
    self.overlayLayer.frame = self.bounds;
    self.overlayLayer.sublayerTransform = THMakePerspectiveTransform(10000);
    //设置sublayerTransform属性为CATransform3D,可以对所有子层应用视角转换。
    [self.previewLayer addSublayer:self.overlayLayer];
static CATransform3D THMakePerspectiveTransform(CGFloat eyePosition) {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = -1.0/eyePosition;
    return transform;
    
    // CoreAnimation中所使用的transformation matrix类型,用于进行缩放和旋转等转换。
    // 设置m34可以应用视角转换,即让子层绕Y轴旋转。
}
  • 5、元数据处理
NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
    // Listing 7.11

    
NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
for (AVMetadataFaceObject *face in transformedFaces) {
    NSNumber *faceId = @(face.faceID);
    [lostFaces removeObject:faceId];
    // 如果对应faceId还在,将它从要删除视图的数组中移除。
    
    CALayer *layer = self.faceLayers[faceId];
    // 查找faceId对应的Layer
    if (!layer) {
        //如果没有对应layer,说明是新加入的faceId,需要新建对应来layer
        layer = [self makeFaceLayer];
        [self.overlayLayer addSublayer:layer];
        self.faceLayers[faceId] = layer;
    }
    layer.transform = CATransform3DIdentity;
    //对每个人脸图层,先将他的tansform属性设置为CATransform3DIdentity
    //然后重新设置之前的用过的变换
    
    layer.frame = face.bounds;
    
    
}
    
// 删除已经移除人脸对应的图层
for (NSNumber *faceId in lostFaces) {
    CALayer *layer = self.faceLayers[faceId];
    [layer removeFromSuperlayer];
    [self.faceLayers removeObjectForKey:faceId];
}

将取得的人脸元数据的坐标做相应转换。

- (NSArray *)transformedFacesFromFaces:(NSArray *)faces {

    // Listing 7.11
    NSMutableArray *transformedFaces = [[NSMutableArray alloc] init];
    for (AVMetadataObject *face  in faces) {
        AVMetadataObject *transformedFace = [self.previewLayer transformedMetadataObjectForMetadataObject:face];
        //将设备坐标空间的人脸对象转化为视图空间对象集合
        
        [transformedFaces addObject:transformedFace];
        //得到一个由AVMetadataFaceObject实例组成的集合,其中有创建用户界面所需要的坐标点
        
    }
    return transformedFaces;
    
}
// 创建标记人脸的方框
- (CALayer *)makeFaceLayer {

    CALayer *layer= [CALayer layer];
    layer.borderWidth = 5.0f;
    layer.borderColor = [UIColor colorWithRed:0.188 green:0.517 blue:0.877 alpha:1.0].CGColor;
    return layer;

}

这样基本已经实现了人脸识别,以及标记功能。本书作者还对这个功能做了拓展,实现角度检测的变换。相对比较复杂。

拓展

这里的拓展,增加了对标记人脸方框的角度变换实现,它会随着人脸的转动和倾斜,方框也发生相应的变换。

  • 1、在上述元数据处理方法中加入方框角度变换的是实现方法即可

添加在for循环之中,创建重置完layer.transform之后。

if (face.hasRollAngle) {
    //检测人脸是否具有有效的倾斜角,如果没有获取属性会有异常。
    //如果有rollAngle,则获取相应的CATransform3D
    //将它与标识变换关联在一起,并设置图层的transform属性
    CATransform3D t = [self transformForRollAngle:face.rollAngle];

    layer.transform = CATransform3DConcat(layer.transform, t);
}

if (face.hasYawAngle) {

    //检测人脸是否具有有效的偏转角,如果没有获取属性会有异常。
    //如果有hasYawAngle,则获取相应的CATransform3D
    //将它与标识变换关联在一起,并设置图层的transform属性
    CATransform3D t = [self transformForYawAngle:face.hasYawAngle];
    layer.transform = CATransform3DConcat(layer.transform, t);
}

  • 2、Z轴的角度
// Rotate around Z-axis
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegrees {
    
    
    CGFloat rollAngleInRadians = THDegreesToRadians(rollAngleInDegrees);
    //从对象得到rollAngle的单位是度,需要转换为弧度制。
    //将转换结果赋值给CATransform3DMakeRotation函数
    //x,y,z轴对应参数分别以0,0,1,得到的就是绕Z轴的倾斜角旋转转换、
    return CATransform3DMakeRotation(rollAngleInRadians, 0.f, 0.f, 1.f);

}
  • 3、Y轴的角度
// Rotate around Y-axis
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegrees {

    // Listing 7.13
    
    //从对象得到hasYawAngle的单位是度,需要转换为弧度制。
    //将转换结果赋值给CATransform3DMakeRotation函数
    //x,y,z轴对应参数分别以0,-1,0,得到的就是绕Y轴的倾斜角旋转转换、
    
    CGFloat yawAngleInRadians = THDegreesToRadians(yawAngleInDegrees);
    CATransform3D yawAngleTransform = CATransform3DMakeRotation(yawAngleInRadians, 0.f, -1.f, 0.f);
    
    //由于overlayer需要应用sublayerTransform,图层hi投影到Z轴
    //人脸从一次移动到另一侧时就会出现3D效果
    return CATransform3DConcat(yawAngleTransform, [self orientationTransform]);
    
    //应用程序用户界面固定为垂直方向,不过需要为设备方向计算一个相应的旋转变换。
    //如果不这样做,会导致人脸图层的便宜效果不正确,这一转换会同其他变换关联。
    
}
  • 4、设备方向的调整

- (CATransform3D)orientationTransform {

    // Listing 7.13
    CGFloat angle = 0.0;
    
    switch ([UIDevice currentDevice].orientation) {
        case UIDeviceOrientationPortraitUpsideDown:
            angle = M_PI;
            break;
        case UIDeviceOrientationLandscapeRight:
            angle = -M_PI;
            break;
        case UIDeviceOrientationLandscapeLeft:
            angle = M_PI;
            break;
        case UIDeviceOrientationPortrait:
            angle = 0;
            break;
            
            
        default:
            break;
    }

    return CATransform3DMakeRotation(angle, 0.f, 0.f, 1.f);
//    return CATransform3DIdentity;
}

角度变换:

static CGFloat THDegreesToRadians(CGFloat degrees) {

    // Listing 7.13
    return degrees * M_PI / 180;
}

三、总结

只是实现简单地人脸识别,如果要实现更多的功能,相关的还有CoreAnimation以及Quartz框架的知识需要了解学习,还在努力中~~~

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

推荐阅读更多精彩内容