iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别)
1、iOS上人脸识别的策略分析
-
CoreIamge
-
face++(2014阿里收购,收费)
-
OpenCV(图片处理,银行卡号、身份证号识别)
-
libefacedetection(C++)
-
AV Foundation(腾讯原生)
-
vision(苹果模型:iOS11.0)
-
腾讯:优图项目组
1.1 人脸识别系统组成


2、AVFoundation人脸识别实现
人脸识别流程
-
1.视频采集(上一篇详述,属于耗时工作) -
2.为session添加一个元数据的输出AVCaptureMetadataOutput
-
3.设置元数据的范围(人脸数据、二维码数据、一维码等)
-
4.开始捕捉:设置捕捉完成代理didOutputMetadataObjects
-
5.获取到捕捉人脸相关信息:代理方法中可以获取
-
6.对人脸数据的处理:将人脸框出来
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

THCameraController.m
#import "THCameraController.h"
#import
@interface THCameraController ()
@property(nonatomic, strong)AVCaptureMetadataOutput *metadataOutput;
@end
@implementation THCameraController
// 创建session
- (BOOL)setupSessionOutputs:(NSError **)error {self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];// 为session添加一个metadataOutputif ([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, boundsNSLog(@"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 使用AVLayerVideoGravityResizeAspectFillself.previewView.videoGravity = AVLayerVideoGravityResizeAspectFill;// 一般在previewLayer上添加一个透明的图层:初始化overlayLayerself.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 属于 CoreAnimationCATransform3D transform = CATransform3DIdentity;// 透视效果(近大远小),通过设置m34(-1.0/D)默认时0,D越小透视效果越明显// eyePosition 500-1000transform.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对应的layerCALayer *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 {// 创建一个layerCALayer *layer = [CALayer layer];// 边框宽度为5.0flayer.borderWidth = 5.0f// 边框颜色为红色layer.borderColor = [UIColor redColor].CGColor;// 设置背景图片layer.contents = (id)[UIImage imageNamed:@"xxx.png"].CGImage;// 返回layerreturn 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);
}
2.1 欧拉角是什么?
欧拉角是由3个角组成,这3个角分别是Yaw、Pitch、Roll。Yaw表示绕Y轴旋转的角度,Pitch表示绕X轴旋转的角度,Roll表示绕Z轴旋转的角度 · Yaw偏移 · Pitch 投掷、倾斜、坠落 · Roll转动

三.AVFoundation二维码识别
分类
-
QR码:移动营销
-
Aztec码:登机牌
-
PDF417:商品运输
二维码识别部分代码实现
@protocol THCodeDetectionDelegate
- (void)didDetectCodes:(NSArray *)codes;
@end
THPreviewView.m
#import "THPreviewView.h"
@interface THPreviewView()
// 二维码图层
@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.stringValueNSString *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构建一个CGPathCAShapeLayer *cornersLayer = layers[1];cornersLayer.path = [self bezierPathForCorners: code.corners].CGPath;}// 遍历lostCodesfor (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 {
// 创建一个空的UIBezierPathUIBezierPath *path = [UIBezierPath bezierPath];// 遍历数组中的条目,为每个条目构建一个CGPointfor (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
原文链接:iOS音视频底层(二)之AVFoundation高级捕捉(人脸/二维码识别) - 掘金
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
