美文网首页
AVFoundation 人脸识别

AVFoundation 人脸识别

作者: Maji1 | 来源:发表于2020-09-22 09:06 被阅读0次

    阅读这篇文章请先了解 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<AVMetadataObject *> *)faces {
        //转换坐标系
        NSArray<AVMetadataFaceObject *> *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<AVMetadataFaceObject *> *)transformedFromFace:(NSArray<AVMetadataObject *> *)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 人脸识别

          本文链接:https://www.haomeiwen.com/subject/wbujyktx.html