引言
在如今的移动应用和直播场景中,我们常常能体验到一种颇具趣味与互动性的功能:无论是美颜相机中的萌趣贴纸精准附着于人脸关键点上,还是主播们在直播时实时戴上可爱的虚拟动物耳朵或动态装饰物,这些令人眼前一亮的效果背后,都离不开人脸识别技术的支持。自iOS 6起,苹果引入了对AV Foundation框架中的人脸检测功能,这一突破性更新使得应用程序能够实时捕捉并精确定位多达10张人脸的特征信息。
介绍
方案:
人脸识别的整体实现方案与其它媒体捕捉的实现方案并没有很大区别,或者说所有媒体捕捉的实现都是这个步骤:
- 配置会话:配置AVCaptureSession,添加AVCaptureDeviceInput,添加AVCaptureOutput
- 启动会话:AVCaptureSession startRunning
- 停止会话(如果需要)AVCaptureSession stopRunning。
具体实现在这里就贴代码了,有需要可以查看这篇文章媒体捕捉-iOS自定义二维码扫描功能-CSDN博客。
重点类
AVCaptureMetadataOutput
通过运用AVCaptureOutput家族中的特定类型AVCaptureMetadataOutput,开发者得以获取设备摄像头实时捕获的视频流,并从中提取出有价值的人脸元数据。这种输出并非普通的图像数据,而是封装在AVMetadataObject抽象类内的结构化信息,该类提供了一个通用接口来处理包括但不限于人脸在内的多种类型的元数据对象。
AVMetadataFaceObject
当开启人脸检测模式时,系统会智能识别出人脸区域,并以具体的子类AVMetadataFaceObject的形式输出详细的面部信息。这一技术层面的革新不仅丰富了用户在社交、娱乐等领域的交互体验,更为广泛应用如AR增强现实、虚拟妆容、表情识别等领域奠定了坚实的基础。
实现
因为实现的步骤和其它媒体捕捉的实现步骤几乎相同,所以我们把重点集中到添加会话输出和元数据处理这两个上面。
会话输出:
实现setupSessionOutputs:方法,创建一个新的AVCaptureMetaDataOutput实例并把它添加为捕捉会话的输出。
设置metadataobjectTypes属性指定对象输出的元数据类型为AVMetaObjectTypeFace。AV Foundation支持多种元数据类型的识别,所以当我们只对人脸元数据感兴趣时,需要明确指出。
//MARK:配置会话输出方法
- (BOOL)setupSessionOutputs:(NSError *__autoreleasing _Nullable *)error{
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
if ([self.captureSession canAddOutput:self.metadataOutput]) {
[self.captureSession addOutput:self.metadataOutput];
NSArray * metadataObjectTypes = @[AVMetadataObjectTypeFace];
self.metadataOutput.metadataObjectTypes = metadataObjectTypes;
dispatch_queue_t mainQueue = dispatch_get_main_queue();
[self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
return YES;
}else{
return NO;
}
}
数据处理:
当有新的人脸元数据被检测到之后,AVCaptureMetadataOutput会回调它代理的captureOutput:didOutputMetadataObjects:fromConection方法。
//MARK:检测到指定兴趣点的代理
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
for (AVMetadataFaceObject * face in metadataObjects) {
NSLog(@"Face detected with ID:%li",(long)face.faceID);
NSLog(@"Face bounds:%@",NSStringFromCGRect(face.bounds));
}
[self.faceDetectionDelegate didDetectFaces:metadataObjects];
}
我们将获取到的人脸元数据传递到预览视图previewView来进行处理,在预览视图中首先调用了一个transformedCodesFromCodes:方法,只是我们自己定义的方法,在里面我们只是调用了AVCaptureVideoPreviewLayer提供的坐标转发方法将设备坐标空间元数据对象转换为视图坐标空间对象。
//MARK:代理 检测到人脸数组
- (void)didDetectFaces:(nonnull NSArray *)faces {
NSArray * transformedFaces = [self transformedFacesFromFaces:faces];
}
//MARK:坐标转换
- (NSArray *)transformedFacesFromFaces:(NSArray *)faces{
NSMutableArray * transformedFaces = [NSMutableArray array];
for (AVMetadataObject * face in faces) {
AVMetadataObject * transformedFace = [self.previewLayer transformedMetadataObjectForMetadataObject:face];
[transformedFaces addObject:face];
}
return transformedFaces;
}
接下来从一个全局的人脸图层存储字典中获取所有的key组成一个待丢弃的人脸id数组。然后遍历数组将已经获取到的人脸id从带丢弃的人脸id数组中移除。然后获取或者创建新的人脸layer并设置frame及transform参数。此处代码主要实现的功能是让人脸layer追踪人脸移动,并标记已经消失的人脸layer。
- (void)didDetectFaces:(nonnull NSArray *)faces {
NSArray * transformedFaces = [self transformedFacesFromFaces:faces];
NSMutableArray * lostFaces = [self.faceLayers.allKeys mutableCopy];
for (AVMetadataFaceObject * face in transformedFaces) {
NSNumber * faceID = @(face.faceID);
[lostFaces removeObject:faceID];
CALayer * layer = self.faceLayers[faceID];
if (!layer) {
//没有则创建
layer = [self makeFaceLayer];
[self.overlayLayer addSublayer:layer];
self.faceLayers[faceID] = layer;
}
layer.transform = CATransform3DIdentity;
layer.frame = face.bounds;
}
for (NSNumber * faceID in lostFaces) {
CALayer * layer = self.faceLayers[faceID];
[layer removeFromSuperlayer];
[self.faceLayers removeObjectForKey:faceID];
}
}
//MARK:创建标记人脸layer
- (CALayer *)makeFaceLayer{
CALayer * layer = [CALayer layer];
layer.borderWidth = 5.0;
layer.borderColor = [UIColor colorWithRed:0.188 green:0.517 blue:0.877 alpha:1.00].CGColor;
return layer;
}
此时我们就已经可以绘制出人脸在预览视图中的frame了,但距离精确跟踪还差一点小步骤。
要实现这个功能,我们需要读取AVMetadataFaceObject中的rollAngle(滚动角-绕z轴)和yawAngle(偏转角-绕y轴),并将人脸图层layer进行相应的仿射变化。
for (AVMetadataFaceObject * face in transformedFaces) {
NSNumber * faceID = @(face.faceID);
[lostFaces removeObject:faceID];
CALayer * layer = self.faceLayers[faceID];
if (!layer) {
//没有则创建
layer = [self makeFaceLayer];
[self.overlayLayer addSublayer:layer];
self.faceLayers[faceID] = layer;
}
layer.transform = CATransform3DIdentity;
layer.frame = face.bounds;
if (face.hasRollAngle) {
CATransform3D t = [self transformForRollAngle:face.rollAngle];
layer.transform = CATransform3DConcat(layer.transform, t);
}
if (face.hasYawAngle) {
CATransform3D t = [self transformForYawAngle:face.yawAngle];
layer.transform = CATransform3DConcat(layer.transform, t);
}
}
仿射变化的代码如下:
//MARK:绕z轴旋转
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegress{
CGFloat rollAngleInRadians = THDegreesToRadians(rollAngleInDegress);
return CATransform3DMakeRotation(rollAngleInRadians, 0.0, 0.0, 1.0);
}
//MARK:绕y轴旋转
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegress{
CGFloat yawAngleInRadians = THDegreesToRadians(yawAngleInDegress);
CATransform3D yawTransform = CATransform3DMakeRotation(yawAngleInRadians, 0.0, -1.0, 0.0);
return CATransform3DConcat(yawTransform, [self orientationTransform]);
}
//MARK:角度转弧度
static CGFloat THDegreesToRadians(CGFloat degress){
return degress * 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.0;
break;
case UIDeviceOrientationLandscapeLeft:
angle = M_PI / 2.0;
break;
default:
angle = 0.0;
break;
}
return CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0);
}
结语
以上就是使用AV Foundation进行人脸检测的所有关键步骤,已经实现了一个比较粗糙的人脸识别的用户界面。要真正实现将帽子,贴纸等显示在人脸上并且拍照或者直播,还需要与其它框架的技术相结合,比如Quartz框架,或者OpenGL等。