本文主要内容
一.iOS上人脸辨认的战略剖析
二.AVFoundation人脸辨认完成
三.AVFoundation二维码辨认
一.iOS上人脸辨认的战略剖析
- CoreIamge
- face++(2014阿里收购,收费)
- OpenCV(图片处理,银行卡号、身份证号辨认)
- libefacedetection(C++)
- AV Foundation(腾讯原生)
- vision(苹果模型:iOS11.0)
- 腾讯:优图项目组
人脸辨认系统组成
二.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滚动
三.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
有任何问题,欢迎各位评论指出!觉得博主写的还不错的麻烦点个赞喽