二、AVFoundation使用:人脸识别、二维码识别

人脸识别

目前人脸识别有多种解决方案,现在来说的是AV Foundation解决方案
1、CoreImage :CIDetector / CIFiter
2、face++ :阿里
3、OpenCV
4、libefacedetection:基于C++
5、AV Foundation
6、vision :基于Core ML 需要iOS11.0
腾讯: 优图项目组

AVFoundation

AVFoundation最多支持十张人脸识别,基于GPU运算

获取人脸范围


- (BOOL)setupSessionOutputs:(NSError **)error {

    
    self.metadataOutput = [[AVCaptureMetadataOutput alloc]init];
    
   
    
    //为捕捉会话添加设备
    if ([self.captureSession canAddOutput:self.metadataOutput]){
        [self.captureSession addOutput:self.metadataOutput];
        
        
        //获得人脸属性
        NSArray *metadatObjectTypes = @[AVMetadataObjectTypeFace];
        
        //设置metadataObjectTypes 指定对象输出的元数据类型。
        /*
         限制检查到元数据类型集合的做法是一种优化处理方法。可以减少我们实际感兴趣的对象数量
         支持多种元数据。这里只保留对人脸元数据感兴趣
         */
        self.metadataOutput.metadataObjectTypes = metadatObjectTypes;
        
        //创建主队列: 因为人脸检测用到了硬件加速,而且许多重要的任务都在主线程中执行,所以需要为这次参数指定主队列。
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        
        
        //通过设置AVCaptureVideoDataOutput的代理,就能获取捕获到一帧一帧数据
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
     
        return YES;
    }else
    {
        //报错
        if (error) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Failed to still image output"};
            
            *error = [NSError errorWithDomain:THCameraErrorDomain code:THCameraErrorFailedToAddOutput userInfo:userInfo];
            
        }
        return NO;
    }

}



//捕捉数据
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {
    //使用循环,打印人脸数据
    //metadataObjects 包含了捕获到的人脸数据(人脸数据重复,上一秒捕获的人脸数据,下一秒还会捕获)
    for (AVMetadataFaceObject *face in metadataObjects) {
        //faceID唯一
        NSLog(@"Face detected with ID:%li",(long)face.faceID);
        NSLog(@"Face bounds:%@",NSStringFromCGRect(face.bounds));
        
    }
    
    
    //将元数据 传递给 THPreviewView.m   将元数据转换为layer
    [self.faceDetectionDelegate didDetectFaces:metadataObjects];
}

视图层处理


+ (Class)layerClass {
    //重写layerClass方法
    return [AVCaptureVideoPreviewLayer class];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupView];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self setupView];
    }
    return self;
}

- (void)setupView {

    //初始化faceLayers属性  字典
    self.faceLayers = [NSMutableDictionary dictionary];
    
    //设置videoGravity 使用AVLayerVideoGravityResizeAspectFill 铺满整个预览层的边界范围
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    //初始化overlayLayer
    self.overlayLayer = [CALayer layer];
    
    //设置它的frame
    self.overlayLayer.frame = self.bounds;
    
    //子图层形变 sublayerTransform属性   Core  Animation动画
    self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
    
    //将子图层添加到预览图层来
    [self.previewLayer addSublayer:self.overlayLayer];
    
}

//会话的get方法
- (AVCaptureSession*)session {

    return self.previewLayer.session;
}


//会话的set方法
- (void)setSession:(AVCaptureSession *)session {

    
    self.previewLayer.session = session;
    

}

//获得layer
- (AVCaptureVideoPreviewLayer *)previewLayer {

    return (AVCaptureVideoPreviewLayer *)self.layer;
}



//将检测到的人脸进行可视化
- (void)didDetectFaces:(NSArray *)faces {

    //创建一个本地数组 保存转换后的人脸数据
    NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
    
    //获取faceLayers的key,用于确定哪些人移除了视图并将对应的图层移出界面。
    /*
        支持同时识别10个人脸
     */
    NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
    
    //遍历每个转换的人脸对象
    for (AVMetadataFaceObject *face in transformedFaces) {
        
        //获取关联的faceID。这个属性唯一标识一个检测到的人脸
        NSNumber *faceID = @(face.faceID);
        
        //将对象从lostFaces 移除
        [lostFaces removeObject:faceID];
        
        //拿到当前faceID对应的layer
        CALayer *layer = self.faceLayers[faceID];
        
        //如果给定的faceID 没有找到对应的图层
        if (!layer) {
            
            //调用makeFaceLayer 创建一个新的人脸图层
            layer = [self makeFaceLayer];
            
            //将新的人脸图层添加到 overlayLayer上
            [self.overlayLayer addSublayer:layer];
            
            //将layer加入到字典中
            self.faceLayers[faceID] = layer;
            
        }
        
        //设置图层的transform属性 CATransform3DIdentity 图层默认变化 这样可以重新设置之前应用的变化
        layer.transform = CATransform3DIdentity;
        
        //图层的大小 = 人脸的大小
        layer.frame = face.bounds;
        
        //判断人脸对象是否具有有效的斜倾交。
        if (face.hasRollAngle) {
            
            //如果为YES,则获取相应的CATransform3D 值
            CATransform3D t = [self transformForRollAngle:face.rollAngle];
            
            //将它与标识变化关联在一起,并设置transform属性
            layer.transform = CATransform3DConcat(layer.transform, t);
        }
        
        
        //判断人脸对象是否具有有效的偏转角
        if (face.hasYawAngle) {
            
            //如果为YES,则获取相应的CATransform3D 值
            CATransform3D  t = [self transformForYawAngle:face.yawAngle];
            layer.transform = CATransform3DConcat(layer.transform, t);
            
        }
    }
    
    
    //遍历数组将剩下的人脸ID集合从上一个图层和faceLayers字典中移除
    for (NSNumber *faceID in lostFaces) {
        
        CALayer *layer = self.faceLayers[faceID];
        [layer removeFromSuperlayer];
        [self.faceLayers  removeObjectForKey:faceID];
    }
    
}


//将设备的坐标空间的人脸转换为视图空间的对象集合
- (NSArray *)transformedFacesFromFaces:(NSArray *)faces {

    NSMutableArray *transformeFaces = [NSMutableArray array];
    
    for (AVMetadataObject *face in faces) {
        
        //将摄像头的人脸数据 转换为 视图上的可展示的数据
        //简单说:UIKit的坐标 与 摄像头坐标系统(0,0)-(1,1)不一样。所以需要转换
        //转换需要考虑图层、镜像、视频重力、方向等因素 在iOS6.0之前需要开发者自己计算,但iOS6.0后提供方法
        AVMetadataObject *transformedFace = [self.previewLayer transformedMetadataObjectForMetadataObject:face];
        
        //转换成功后,加入到数组中
        [transformeFaces addObject:transformedFace];
        
        
    }
    
    return transformeFaces;
    
    
    
}

- (CALayer *)makeFaceLayer {

    //创建一个layer
    CALayer *layer = [CALayer layer];
    
    //边框宽度为5.0f
    layer.borderWidth = 5.0f;
    
    //边框颜色为红色
    layer.borderColor = [UIColor redColor].CGColor;
    
    layer.contents = (id)[UIImage imageNamed:@"551.png"].CGImage;
    
    //返回layer
    return layer;
    
}



//将 RollAngle 的 rollAngleInDegrees 值转换为 CATransform3D
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegrees {

    //将人脸对象得到的RollAngle 单位“度” 转为Core Animation需要的弧度值
    CGFloat rollAngleInRadians = THDegreesToRadians(rollAngleInDegrees);

    //将结果赋给CATransform3DMakeRotation x,y,z轴为0,0,1 得到绕Z轴倾斜角旋转转换
    return CATransform3DMakeRotation(rollAngleInRadians, 0.0f, 0.0f, 1.0f);
    
}


//将 YawAngle 的 yawAngleInDegrees 值转换为 CATransform3D
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegrees {

    //将角度转换为弧度值
     CGFloat yawAngleInRaians = THDegreesToRadians(yawAngleInDegrees);
    
    //将结果CATransform3DMakeRotation x,y,z轴为0,-1,0 得到绕Y轴选择。
    //由于overlayer 需要应用sublayerTransform,所以图层会投射到z轴上,人脸从一侧转向另一侧会有3D 效果
    CATransform3D yawTransform = CATransform3DMakeRotation(yawAngleInRaians, 0.0f, -1.0f, 0.0f);
    
    //因为应用程序的界面固定为垂直方向,但需要为设备方向计算一个相应的旋转变换
    //如果不这样,会造成人脸图层的偏转效果不正确
    return CATransform3DConcat(yawTransform, [self orientationTransform]);
}

- (CATransform3D)orientationTransform {

    CGFloat angle = 0.0;
    //拿到设备方向
    switch ([UIDevice currentDevice].orientation) {
            
            //方向:下
        case UIDeviceOrientationPortraitUpsideDown:
            angle = M_PI;
            break;
            
            //方向:右
        case UIDeviceOrientationLandscapeRight:
            angle = -M_PI / 2.0f;
            break;
        
            //方向:左
        case UIDeviceOrientationLandscapeLeft:
            angle = M_PI /2.0f;
            break;

            //其他
        default:
            angle = 0.0f;
            break;
    }
    
    return CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
    
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused"


static CGFloat THDegreesToRadians(CGFloat degrees) {

    return degrees * M_PI / 180;
}


static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
    
    
    //CATransform3D 图层的旋转,缩放,偏移,歪斜和应用的透
    //CATransform3DIdentity是单位矩阵,该矩阵没有缩放,旋转,歪斜,透视。该矩阵应用到图层上,就是设置默认值。
    CATransform3D  transform = CATransform3DIdentity;
    
    
    //透视效果(就是近大远小),是通过设置m34 m34 = -1.0/D 默认是0.D越小透视效果越明显
    //D:eyePosition 观察者到投射面的距离
    transform.m34 = -1.0/eyePosition;
    
    return transform;
    
    
}
欧拉角

欧拉角是由3个角组成,这3个角分别是yaw,Pitch,Roll。很难翻译这3个词。Yaw
表示绕Y轴旋转的角度,Pitch表示绕X轴旋转角度,Roll表示绕Z轴旋转的角度。

欧拉角.png

二维码识别

二维码分类:

  • QR码:移动营销
  • Aztec:登机牌
  • PDF417: 商品运输

- (NSString *)sessionPreset {
    
    //重写sessionPreset方法,可以选择最适合应用程序捕捉预设类型。
    //苹果公司建议开发者使用最低合理解决方案以提高性能
    return AVCaptureSessionPreset640x480;
}

- (BOOL)setupSessionInputs:(NSError *__autoreleasing *)error {

    //设置相机自动对焦,这样可以在任何距离都可以进行扫描。
    BOOL success = [super setupSessionInputs:error];
    if(success)
    {
        //判断是否能自动聚焦
        if (self.activeCamera.autoFocusRangeRestrictionSupported) {
            
            //锁定设备
            if ([self.activeCamera lockForConfiguration:error]) {
                
                //自动聚焦
                /*
                    iOS 7.0新增属性 允许使用范围约束来对功能进行定制。
                  因为扫描条码,距离都比较近。所以AVCaptureAutoFocusRangeRestrictionNear,
                 通过缩小距离,来提高识别成功率。
                 */
                self.activeCamera.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
                
                //释放排他锁
                [self.activeCamera  unlockForConfiguration];
            }
        }
    }

    return YES;
}

- (BOOL)setupSessionOutputs:(NSError **)error {

    //获取输出设备
    self.metadataOutput = [[AVCaptureMetadataOutput alloc]init];
    
    //判断是否能添加输出设备
    if ([self.captureSession canAddOutput:self.metadataOutput]) {
        
        //添加输出设备
        [self.captureSession addOutput:self.metadataOutput];
        
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        
        //设置委托代理
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
        
        //指定扫描对是OR码 & Aztec 码 (移动营销)
        NSArray *types = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeDataMatrixCode,AVMetadataObjectTypePDF417Code];
        
        self.metadataOutput.metadataObjectTypes = types;
        
    }else
    {
        //错误时,存储错误信息
        NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Faild to add metadata output."};
        *error = [NSError errorWithDomain:THCameraErrorDomain code:THCameraErrorFailedToAddOutput userInfo:userInfo];
    
        return NO;
        
        
    
    }
    
    
    
    return YES;
}


//委托代理回掉。处理条码
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {
    
    if (metadataObjects.count > 0) {
        
        NSLog(@"%@",metadataObjects[0]);
        
        /*
         corners { 0.4,0.6 0.6,0.6 0.6,0.4 0.4,0.4 }, time 122373330766250, stringValue ""http://www.echargenet.com/portal/csService/html/app.html
         */

    }
    
    
    //获取了
    [self.codeDetectionDelegate didDetectCodes:metadataObjects];

}

+ (Class)layerClass {

    //目的让视图的备用层成为AVCaptureVideoPreviewLayer 实例
    return [AVCaptureVideoPreviewLayer class];//获的预览图层
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupView];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self setupView];
    }
    return self;
}

- (void)setupView {

    //保存一组表示识别编码的几何信息图层。
    _codeLayers = [NSMutableDictionary dictionary];
    
    //设置图层的videoGravity 属性,保证宽高比在边界范围之内
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    
}

- (AVCaptureSession*)session {

    return [[self previewLayer]session];
}

//重写setSession 方法,将AVCaptureSession 作为预览层的session 属性
- (void)setSession:(AVCaptureSession *)session {
    
    self.previewLayer.session = session;
}

- (AVCaptureVideoPreviewLayer *)previewLayer {

    return (AVCaptureVideoPreviewLayer *)self.layer;
}

//元数据转换
- (void)didDetectCodes:(NSArray *)codes {
    
    //保存转换完成的元数据对象
    NSArray *transformedCodes = [self transformedCodesFromCodes:codes];
    
    //从codeLayers字典中获得key,用来判断那个图层应该在方法尾部移除
    NSMutableArray *lostCodes = [self.codeLayers.allKeys mutableCopy];
    
    //遍历数组
    for (AVMetadataMachineReadableCodeObject *code in transformedCodes) {
        
        //获得code.stringValue
        NSString *stringValue = code.stringValue;
        
        if (stringValue) {
         
            [lostCodes removeObject:stringValue];
            
        }else
        {
            continue;
        }
        
        //根据当前的stringValue 查找图层。
        NSArray *layers = self.codeLayers[stringValue];
        
        //如果没有对应的类目
        if (!layers) {
            
            //新建图层 方、圆
            layers = @[[self makeBoundsLayer],[self makeCornersLayer]];
            
            //将图层以stringValue 为key 存入字典中
            self.codeLayers[stringValue] = layers;
            
            //在预览图层上添加 图层0、图层1
            [self.previewLayer addSublayer:layers[0]];
            [self.previewLayer addSublayer:layers[1]];
        }
        
        //创建一个和对象的bounds关联的 UIBezierPath
        //画方框
        CAShapeLayer *boundsLayer = layers[0];
        boundsLayer.path = [self bezierPathForBounds:code.bounds].CGPath;
        
        //对于cornersLayer 构建一个CGPath
        CAShapeLayer *cornersLayer = layers[1];
        cornersLayer.path = [self bezierPathForCorners:code.corners].CGPath;
    }
    
    //遍历lostCodes
    for (NSString *stringValue in lostCodes) {
        
        //将里面的条目图层从 previewLayer 中移除
        for (CALayer *layer in self.codeLayers[stringValue]) {
         
            [layer removeFromSuperlayer];
            
        }
        //数组条目中也要移除
        [self.codeLayers removeObjectForKey:stringValue];
    }
    
}

- (NSArray *)transformedCodesFromCodes:(NSArray *)codes {

    
    NSMutableArray *transformedCodes = [NSMutableArray array];
    
    //遍历数组
    for (AVMetadataObject *code in codes) {
        
        //讲设备坐标空间元数据对象 转换为 视图坐标空间对象
        AVMetadataObject *transformedCode = [self.previewLayer transformedMetadataObjectForMetadataObject:code];
        
        //将转换好的数据 添加到 数组中
        [transformedCodes addObject:transformedCode];
        
    }
    
    //返回已经处理好的数据
    return transformedCodes;
}

- (UIBezierPath *)bezierPathForBounds:(CGRect)bounds {

    //绘制一个方框
    return [UIBezierPath bezierPathWithOvalInRect:bounds];
    
}

//CAShapeLayer 是CALayer 子类,用于绘制UIBezierPath  绘制boudns矩形
- (CAShapeLayer *)makeBoundsLayer {

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [[UIColor colorWithRed:0.95f green:0.75f blue:0.06f alpha:1.0f]CGColor];
    shapeLayer.fillColor = nil;
    shapeLayer.lineWidth = 4.0f;
    return shapeLayer;

}

//CAShapeLayer 是CALayer 子类,用于绘制UIBezierPath  绘制coners路径
- (CAShapeLayer *)makeCornersLayer {

    CAShapeLayer *cornerLayer = [CAShapeLayer layer];
    cornerLayer.lineWidth = 2.0f;
    cornerLayer.strokeColor  = [UIColor colorWithRed:0.172f green:0.671f blue:0.48f alpha:1.000f].CGColor;
    cornerLayer.fillColor = [UIColor colorWithRed:0.190f green:0.753f blue:0.489f alpha:0.5f].CGColor;
    
    return cornerLayer;
}

- (UIBezierPath *)bezierPathForCorners:(NSArray *)corners {
    //创建一个空的UIBezierPath
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    //遍历数组中的条目,为每个条目构建一个CGPoint
     for (int i = 0; i < corners.count; i++)
    {
        CGPoint point = [self pointForCorner:corners[i]];
        
        if (i == 0) {
            [path moveToPoint:point];
        }else
        {
            [path addLineToPoint:point];
        }
    }

    [path closePath];
    return  path;

}

//从字典corner 中拿到需要的点point
- (CGPoint)pointForCorner:(NSDictionary *)corner {

    CGPoint point;
    CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corner, &point);
    return point;
}

你可能感兴趣的:(二、AVFoundation使用:人脸识别、二维码识别)