iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别)

本文主要内容

一.iOS上人脸辨认的战略剖析
二.AVFoundation人脸辨认完成
三.AVFoundation二维码辨认

一.iOS上人脸辨认的战略剖析

  • CoreIamge
  • face++(2014阿里收购,收费)
  • OpenCV(图片处理,银行卡号、身份证号辨认)
  • libefacedetection(C++)
  • AV Foundation(腾讯原生)
  • vision(苹果模型:iOS11.0)
  • 腾讯:优图项目组

人脸辨认系统组成

iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别)

iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别)

二.AVFoundation人脸辨认完成

人脸辨认流程

  • 1.视频收集(上一篇胪陈,属于耗时工作)
  • 2.为session增加一个元数据的输出AVCaptureMetadataOutput
  • 3.设置元数据的范围(人脸数据、二维码数据、一维码等)
  • 4.开端捕捉:设置捕捉完结署理didOutputMetadataObjects
  • 5.获取到捕捉人脸相关信息:署理办法中能够获取
  • 6.对人脸数据的处理:将人脸框出来

THCameraController.m

#import "THCameraController.h"
#import <AVFoundation/AVFoundation.h>
@interface THCameraController ()<AVCaptureMetadataOutputObjectsDelegate>
@property(nonatomic, strong)AVCaptureMetadataOutput *metadataOutput;
@end
@implementation THCameraController
// 创立session
- (BOOL)setupSessionOutputs:(NSError **)error {
    self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    // 为session增加一个metadataOutput
    if ([self.captureSession canAddOutput: self.metadataOutput]) {
        [self.captureSession addOutput: self.metadataOutput];
        // 输出数据 --> 人脸数据
        // 优化:指定元数据类型,减少辨认爱好,人脸辨认有爱好
        NSArray *metadataObjectType = @[AVMetadataObjectTypeFace];
        self.metadataOutput.metadataObjectTypes = metadataObjectType;
        // 创立主行列: 人脸检测运用硬件加速器,使命需求在主线程履行
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        // 设置metadataOutput署理办法,检测视频中每一帧数据是否包含人脸数据,包含则调用回调办法
        [self.metadataOutput setMetadataObjectsDelegate: self queue: mainQueue];
        return YES;
    } else {
        // 打印错误信息
    }
    return NO;
}
// 署理办法:捕获到你设置元数据目标
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    // metadataObjects:包含捕获到人脸数据(人脸数据或许重复,人脸方位不变)
    // 运用循环,打印人脸数据
    for (AVMetadataFaceObject *face in metadataObjects) {
        // faceID, bounds
        NSLog(@"Face ID: %li", (long)face.faceID);
        NSLog(@"Face bounds %@", NSStringFromCGRect(face.bounds));
    }
    // 现已获取视频中的人脸个数、人脸方位,处理人脸
    // 在预览图层上进行处理:THPreviewView类
    // 经过署理将捕捉的人脸元数据传递给THPreviewView.m,将元数据转化为layer
    [self.faceDetectionDelegate didDetectFaces: metadataObjects];
}
@end

需求先进行一些必要的初始化 THPreviewView.m

- (void)setupView {
    // 初始化faceLayers 特点为字典
    self.faceLayers = [NSMutableDictionary dictionary];
    // 设置图层的填充方式videoGravity 运用AVLayerVideoGravityResizeAspectFill
    self.previewView.videoGravity = AVLayerVideoGravityResizeAspectFill;
    // 一般在previewLayer上增加一个通明的图层:初始化overlayLayer
    self.overLayer = [CALayer layer];
    self.overLayer.frame = self.bounds;
    //图层上的图形发生3D改换时,设置投影方式
    self.overLayer.sublayerTransform = CATransform3DMakePerspective(1000)
    [self.previewLayer addSublayer: self.overLayer];
}
static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
    // CATransform3D 图层的旋转、缩放、偏移、歪斜和使用的透视
    // CATransform3DIdentity时单元矩阵,该矩阵没有缩放、旋转等
    // CALayer 属于 CoreAnimation
    CATransform3D transform = CATransform3DIdentity;
    // 透视效果(近大远小),经过设置m34(-1.0/D)默许时0,D越小透视效果越显着
    // eyePosition 500-1000
    transform.m34 = -1.0 / eyePosition;
    return transform;
}
// 将检测到的人脸进行可视化
- (void)didDetectFace:(NSArray *)faces {
    // 1.创立一个本地数组保存转化后的人脸数据:人脸数据方位信息(摄像头坐标系) --> 屏幕坐标系
    NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
    /*
      2.获取faceLayers的key,用于确定哪些人移除了视图并将对应的图层移除界面
      支撑一起辨认10个人脸
    */
    // 假如人脸从摄像头消失,要删除它的图层,经过faceID
    // 假定所有的人脸都需求删除,然后再从删除列表中一一移除
    NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
    // 3.遍历每个转化的人脸目标
    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;
        // 判别人脸目标是否具有有用的歪斜角
        layer.transform = CATransform3DIdentity;
        // 了解为人的头部向肩膀方向歪斜
        if (face.hasRollAngle) {
            // 假如为YES,则获取相应的CATransform3D值
            CATransform3D t = [self transformForRollAngle: face.rollAngle];
            // 将它与标识改变相关在一起,并设置transform特点
            // CATransform3DConcat 矩阵相乘
            layer.transform = CATransform3DConcat(layer.transform, t);
        }
        // 判别人脸目标是否具有有用的偏转角
        if (face.hasYawAngle) {
            // 获取相应的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 {
    // 将摄像头的人脸数据转化为视图上的可展示的数据
    // 简单说:就是UIKit的坐标与摄像头坐标系统(0,0)-(1,1)不一样,需求转化
    // 转化需求考虑图层、镜像、视频重力、方向等因素,在iOS6.0之后才供给了办法
    NSMutableArray *transformFace = [NSMutableArray array];
    for (AVMetadataObject *face in faces) {
        AVMetadataObject *newFace = [self.previewLayer transformedMetadataObjectForMetadataObject: face];
        [transformFace addObject: newFace];
    }
    return transformFace;
}
//
- (CALayer *)makeFaceLayer {
    // 创立一个layer
    CALayer *layer = [CALayer layer];
    // 边框宽度为5.0f
    layer.borderWidth = 5.0f
    // 边框颜色为赤色
    layer.borderColor = [UIColor redColor].CGColor;
    // 设置背景图片
    layer.contents = (id)[UIImage imageNamed:@"xxx.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)yarAngleInDegrees {
    // 将视点转化为弧度
    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);
}
欧拉角是什么?
欧拉角是由3个角组成,这3个角分别是Yaw、Pitch、Roll。Yaw表明绕Y轴旋转的视点,Pitch表明绕X轴旋转的视点,Roll表明绕Z轴旋转的视点
 Yaw偏移
 Pitch 抛掷、歪斜、坠落
 Roll滚动

iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别)

三.AVFoundation二维码辨认

分类

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

二维码辨认部分代码完成

@protocol THCodeDetectionDelegate <NSObject>
- (void)didDetectCodes:(NSArray *)codes;
@end

THPreviewView.m

#import "THPreviewView.h"
@interface THPreviewView()<THCodeDetectionDelegate>
// 二维码图层
@property(strong,nonatomic)NSMutableDictionary *codeLayers;
@end
@implementation THPreviewView
- (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;
}
// 元数据转化
- (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];
}
- (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;
}
// CAShapeLayer 是CALayer子类,用于制作UIBezierPath,制作bounds矩形
- (CAShapeLayer *)makeBoundsLayer {
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.lineWidth = 4.0f;
    shapeLayer.strokeColor = [UIColor colorWithRed: 0.95f green: 0.75f blue: 0.06f alpha: 1.0f].CGColor;
    shapeLayer.fillColor = nil;
    return shapeLayer;
}
// CAShapeLayer 是CALayer子类,用于制作UIBezierPath,制作corners途径
- (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;
}
@end

有任何问题,欢迎各位评论指出!觉得博主写的还不错的麻烦点个赞喽