AVFoundation 人脸识别

阅读这篇文章请先了解 AVFoundation 拍照/录制视频 文章中视频的捕获流程。

先看下人脸捕获的流程图:
捕获人脸数据流程
  • AVCaptureSession的初始化配置流程跟 AVFoundation 拍照/录制视频 文章中的基本一致,只是少用了 音频设备输入、照片输出、电影文件输出 相关的类。
  • AVCaptureMetadataOutput:捕获元数据输出类。该功能的核心类,用来识别人脸并且输出人脸数据(faceID、bounds...)。

注意:AVFoundation最多同时支持识别10个人脸。

AVCaptureSession的初始化配置的核心代码:

    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;

    AVCaptureDevice *videoDevice =
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *videoInput =
        [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    if (videoInput) {
        if ([self.captureSession canAddInput:videoInput]) {
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        } 
    }

    self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    if ([self.captureSession canAddOutput:self.metadataOutput]) {
        [self.captureSession addOutput:self.metadataOutput];
        //设置识别的元数据类型
        self.metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeFace];
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
    } 
    self.videoQueue = dispatch_queue_create("CQCamera.Video.Queue", NULL);

AVCaptureSession的初始化配置结束后,将captureSession设置为AVCaptureVideoPreviewLayersession

[(AVCaptureVideoPreviewLayer*)self.layer setSession:session];

然后在全局串行队列内异步执行startRunning:

    if (![self.captureSession isRunning]) {
        dispatch_async(self.videoQueue, ^{
          [self.captureSession startRunning];
        });
    }

执行startRunning方法后,一旦检测到人脸就会执行AVCaptureMetadataOutputObjectsDelegate代理方法:

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    if ([self.faceDetectDelegate respondsToSelector:@selector(didDetectFaces:)]) {
        [self.faceDetectDelegate didDetectFaces:metadataObjects];
    }
}
  • 注意我们这里将faceDetectDelegate代理设置为previewView,将元数据对象集合(metadataObjects)传递到预览图层。因为预览图层里面有我们需要的UI数据。
  • metadataObject元数据对象中有我们需要的人脸基本数据faceIDboundsrollAngleyawAngle...

走到这一步,我们已经将人脸识别的核心功能做完了,下面就是如何根据检测到的人脸数据,进行UI处理,也就是将人脸框住。随着人脸的移动,框也作出响应的调整。


下面我们看下代理方法didDetectFaces:中的核心代码:

- (void)didDetectFaces:(NSArray *)faces {
    //转换坐标系
    NSArray *transformedFaces = [self transformedFromFace:faces];
    //
    NSMutableArray *lostFaceKeys = [self.faceLayers.allKeys mutableCopy];
    [transformedFaces enumerateObjectsUsingBlock:^(AVMetadataFaceObject * _Nonnull faceObj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        NSNumber *faceID = @(faceObj.faceID);
        //1.移除: lostFaceKeys中移除当前捕获到的人脸
        [lostFaceKeys removeObject:faceID];
        //2.当前faceID对应的layer
        CALayer *layer = self.faceLayers[faceID];
        if (!layer) {
            //2.1 如果没有 创建Layer
            layer = [self makeLayer];
            //2.2 添加
            [self.overlayLayer addSublayer:layer];
            //2.3 保存到字典中
            self.faceLayers[faceID] = layer;
        }
        //3.配置属性
        layer.transform = CATransform3DIdentity;//初始值
        layer.frame = faceObj.bounds;
        if (faceObj.hasRollAngle) {
            CATransform3D rollTransform3D = [self transformRollAngle:faceObj.rollAngle];
            layer.transform = CATransform3DConcat(layer.transform, rollTransform3D);
        }
        if (faceObj.hasYawAngle) {
            CATransform3D yawTransform3D = [self transformYawAngle:faceObj.yawAngle];
            layer.transform = CATransform3DConcat(layer.transform, yawTransform3D);
        }
    }];
    //将剩下的人脸ID集合从上一个图层和faceLayers字典中移除
    [lostFaceKeys enumerateObjectsUsingBlock:^(NSNumber * _Nonnull faceID, NSUInteger idx, BOOL * _Nonnull stop) {
        CALayer *layer = self.faceLayers[faceID];
        [layer removeFromSuperlayer];
        [self.faceLayers removeObjectForKey:faceID];
    }];
}
  • 1、首先,我们需要转换坐标系,因为 元数据对象(AVMetadataObject中一开始返回来的是摄像头的坐标数据,我们需要转换成视图上可展示的数据(也就是转换成UIKit坐标数据)。

看下转换代码:

- (NSArray *)transformedFromFace:(NSArray *)faces {
    NSMutableArray *transformedFaces = [NSMutableArray arrayWithCapacity:faces.count];
    [faces enumerateObjectsUsingBlock:^(AVMetadataObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 
        //转换需要考虑图层、镜像、视频重力、方向等因素 在iOS6.0之前需要开发者自己计算,但iOS6.0后提供方法
        AVMetadataFaceObject *newObj = (AVMetadataFaceObject *)[self.previewLayer transformedMetadataObjectForMetadataObject:obj];
        [transformedFaces addObject:newObj];
    }];
    return transformedFaces;
}
  • 2、layer:就是我们用来框住人脸的框框。
  • 3、rollAngle、yawAngle在3D坐标系中分别代表的是沿 Z轴 和 Y轴旋转的角度,被称为 欧拉角。我们这里并未用到的沿 X轴 旋转的角度称为Pitch
    需要将我们获取到的 元数据对象(AVMetadataFaceObject 中的rollAngle、yawAngle转为弧度然后再转为CATransform3D
//将rollAngle(欧拉角中的rollAngle围绕Z轴旋转)转换为CATransform3D
- (CATransform3D)transformRollAngle:(CGFloat)rollAngle {
    CGFloat degrees = CQDegreesToRadians(rollAngle);//角度转弧度
    return CATransform3DMakeRotation(degrees, 0.0f, 0.0f, 1.0f);
}
//将yawAngle(欧拉角中的yawAngle围绕Y轴旋转)转换为CATransform3D
- (CATransform3D)transformYawAngle:(CGFloat)yawAngle {
    CGFloat degrees = CQDegreesToRadians(yawAngle);//角度转弧度
    //由于overlayer 需要应用sublayerTransform,所以图层会投射到z轴上,人脸从一侧转向另一侧会有3D 效果
    CATransform3D yawTransform3D = CATransform3DMakeRotation(degrees, 0.0f, -1.0f, 0.0f);
    //因为应用程序的界面固定为垂直方向,但需要为设备方向计算一个相应的旋转变换
    //如果不这样,会造成人脸图层的偏转效果不正确
    CATransform3D orientationTransform3D = [self orientationTransform];
    return CATransform3DConcat(yawTransform3D, orientationTransform3D);
}

代码中转换yawAngle时调用了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);
}

注意:该文章只贴出了核心代码。

最后看下效果:


人脸识别

你可能感兴趣的:(AVFoundation 人脸识别)