「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
AVFoundation
- AVFoundation是苹果OS X和iOS系统中用于处理基于时间的媒体数据的高级OC框架。通过开发所需的工具提供了大量功能集,让开发者能够基于苹果平台创建当下最先进的媒体应用程序。AVFoundation充分利用了苹果A系列处理器的优势...好了,不扯了,总而言之就是很厉害,做iOS音视频开发,你就是必须得学。
这篇文章主要讲解使用AVFoundation做人脸识别,关于AVFoundation的基础捕捉实现,可以看我之前的文章【iOS音视频学习】AVFoundation捕捉
人脸识别
目前客户端人脸识别框架很多,苹果自家的AVFoundation、CoreImage、Vision,知名开源框架OpenCV,腾讯阿里等三方框架,AVFoundation是这些里学习成本使用成本最低的,非常方便在业务初期实现简单的人脸识别功能。
- 静态人脸识别: 识别单张图片
- 动态人脸识别: 拍摄预览,视频
静态动态AVFoundation均支持
思路
- 使用
AVCaptureMetadataOutput获取人脸识别元数据 - 通过CALayer展现人脸捕捉框,其实操作AVCaptureMetadataOutput获取数据很简单,把获取到的数据展示出来会比较麻烦,设计到各种坐标转换
一 给捕捉会话设置AVCaptureMetadataOutput
- 注意,人脸检测使用了硬件加速器,所以任务需要在主线程执行
- (BOOL)configMetadataOutputWithType:(NSArray<AVMetadataObjectType> *)metadatObjectTypes {
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
// 人脸检测使用了硬件加速器,所以任务需要在主线程执行
if ([self.captureSession canAddOutput:self.metadataOutput]) {
[self.captureSession beginConfiguration];
[self.captureSession addOutput:self.metadataOutput];
// 限制检查到元数据类型集合的做法是一种优化处理方法。可以减少我们实际感兴趣的对象数量
self.metadataOutput.metadataObjectTypes = metadatObjectTypes;
[self.metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[self.captureSession commitConfiguration];
return YES;
} else {
return NO;
}
}
- (void)removeMetadataOutput {
if (self.metadataOutput) [self.captureSession removeOutput:self.metadataOutput];
}
- 在回调中可以拿到元数据
- 注意这个回调是一直回调的
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureMetadataSuccessWithMetadataObjects:)]) {
[self.delegate mediaCaptureMetadataSuccessWithMetadataObjects:metadataObjects];
}
}
二 将元数据给到展示Layer
- 因为回调是一直回调,例如人脸A一直捕捉到,那就会一直给到数据,人脸大小倾斜角等可能会变化,元数据中有个faceID是人脸唯一的,所以这里可以使用字典存储人脸,有相同的直接展示,不用一直创建浪费性能
- 这里要做到人脸捕捉框跟随人脸的变动倾斜等,需要对
YawAngle(偏移)、RollAngle(转动)设置 RollAngle(转动)围绕Z轴旋转,例如脸部向肩膀转动RollAngle(转动)围绕Y轴,例如点头
@property(nonatomic, strong) **CALayer** *overlayLayer; /// *< 承载人脸识别layer*
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, CALayer *> *faceLayers; /// *< 人脸识别layer集合,key是faceID*
[self.layer addSublayer:self.overlayLayer];
- (void)setFaceMetadataObjects:(**NSArray**<**AVMetadataFaceObject** *> *)faceMetadataObjects {
_faceMetadataObjects = faceMetadataObjects;
NSArray *transformedFaces = [self transformedMetadataObjects:faceMetadataObjects];
NSMutableArray *needRemoveFaces = [self.faceLayers.allKeys mutableCopy]; // 需要删除的
for (AVMetadataFaceObject** *faceObj in transformedFaces) {
NSNumber *faceID = @(faceObj.faceID);
// 存在的不需要删除,从删除数组中移除
[needRemoveFaces removeObject:faceID];
// 拿到当前faceID对应的layer
CALayer *faceLayer = self.faceLayers[faceID];
// 如果给定的faceID 没有找到对应的图层,创建一个新的,找的则直接使用
if (!faceLayer) {
faceLayer = [**CALayer** layer];
faceLayer.borderWidth = 5.0f;
faceLayer.borderColor = [UIColor redColor].CGColor;
[self.overlayLayer addSublayer:faceLayer];
self.faceLayers[faceID] = faceLayer;
}
// 图层的大小 = 人脸的大小
faceLayer.frame = faceObj.bounds;
// 设置图层的transform属性 CATransform3DIdentity 先创建一个3D单元矩阵
faceLayer.transform = CATransform3DIdentity;
// 判断人脸对象是否具有有效的倾斜旋转。围绕Z轴,例人脸向肩膀转动
if (faceObj.hasRollAngle) {
// 获取相应的CATransform3D值,将它与标识变化关联在一起,并设置transform属性
CATransform3D t = [self transformForRollAngle:faceObj.rollAngle];
faceLayer.transform = CATransform3DConcat(faceLayer.transform, t);
}
// 判断人脸对象是否具有有效的偏转角
if (faceObj.hasYawAngle) {
// 获取相应的CATransform3D值
CATransform3D t = [self transformForYawAngle:faceObj.yawAngle];
faceLayer.transform = CATransform3DConcat(faceLayer.transform, t);
}
}
for (NSNumber *faceID in needRemoveFaces) {
CALayer *layer = self.faceLayers[faceID];
[layer removeFromSuperlayer];
[self.faceLayers removeObjectForKey:faceID];
}
}
- 投影方式 正投影,无法体现立体效果 透视投影,体现立体效果
- eyePosition 观察者到投射面的距离,一般建议500-1000
- 这里需要注意
RollAngle(转动)的转换处理要考虑设备方向,否则识别框会有点不太自然。
#pragma mark - 人脸识别layer相关函数
/**
透视效果
@param eyePosition 观察者到投射面的距离
*/
- (CATransform3D)transform3DMakePerspective:(CGFloat)eyePosition {
// CATransform3D 图层的旋转,缩放,偏移,歪斜和应用的透*
// CATransform3DIdentity是单位矩阵,该矩阵没有缩放,旋转,歪斜,透视。该矩阵应用到图层上,就是设置默认值。*
CATransform3D transform = CATransform3DIdentity;
// 透视效果(就是近大远小),是通过设置m34 m34 = -1.0/D 默认是0.D越小透视效果越明显
// eyePosition 观察者到投射面的距离*
transform.m34 = -1.0/eyePosition;
return transform;
}
/// 摄像头元数据转换
- (NSArray<AVMetadataObject *> *)transformedMetadataObjects:(NSArray<AVMetadataObject *> *)medMetadataObjects {
NSMutableArray *transformedMetadatas = [NSMutableArray array];
for (AVMetadataObject *metadataObject in medMetadataObjects) {
// 将摄像头的元数据 转换为 视图上的可展示的元数据
// 简单说:UIKit的坐标 与 摄像头坐标系统(0,0)-(1,1)不一样。所以需要转换
// 转换需要考虑图层、镜像、视频重力、方向等因素 在iOS6.0之前需要开发者自己计算,但iOS6.0后提供方法
AVMetadataObject *transformedMetadata = [(AVCaptureVideoPreviewLayer *)self.layer transformedMetadataObjectForMetadataObject:metadataObject];
[transformedMetadatas addObject:transformedMetadata];
}
return transformedMetadatas;
}
/// 将RollAngle 的 度数转弧度 再生成 CATransform3D
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegrees {
// 将人脸对象得到的RollAngle 单位“度” 转为Core Animation需要的弧度值
CGFloat rollAngleInRadians = [self degreesToRadians:rollAngleInDegrees];
// RollAngle围绕Z旋转 x,y,z轴为0,0,1 得到绕Z轴倾斜角旋转转换
return CATransform3DMakeRotation(rollAngleInRadians, 0.0f, 0.0f, 1.0f);
}
/// 将YawAngle 的 度数转弧度 再生成 CATransform3D
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegrees {
// 绕Y轴旋转
// 由于overlayer 需要应用sublayerTransform,所以图层会投射到z轴上,人脸从一侧转向另一侧会有3D 效果
CGFloat yawAngleInRaians = [self degreesToRadians: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]);
}
/// 度数转弧度
- (CGFloat)degreesToRadians:(CGFloat)degrees {
return degrees * M_PI / 180;
}
/// 根据设备方向调整角度
- (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);
}