美文网首页iOS 模式识别iOS那点事儿iOS新手学习
IOS人脸识别开发入门教程--人脸检测篇

IOS人脸识别开发入门教程--人脸检测篇

作者: 随风而逝的心情 | 来源:发表于2017-08-24 21:26 被阅读767次

    引言

    人脸识别当前比较热门的技术,作为开发者的我们,如果不实现人脸识别的功能就太Low了,从头开始发明轮子不可取,我们可以用很多现成的人脸识别技术来实现。
    当前的人脸识别技术分为WEBAPI和SDK调用两种方式,WEBAPI需要实时联网,SDK调用可以离线使用。

    本次我们使用的虹软免费开发的离线版本的SDK,离线版本的特点就是我们可以随时在本地使用,而不用担心联网的问题。最生要的是SDK免费,也就是说不用担心后面使用着使用着收费的问题。

    有关本文章的示例代码,请到http://download.csdn.net/download/feishixin/9954948 下载示例项目,下载后,需要把http://www.arcsoft.com.cn/ai/arcface.html 下载的.a文件引入到项目中。你也可以到http://www.arcsoft.com.cn/bbs/forum.php?mod=forumdisplay&fid=45 下载其它语言的demo。

    项目目标

    我们需要实现一个人脸识别功能,通过调用手机的前置设想头,识别摄像头中实时拍到的人脸信息。如果人脸信息已经在人脸库中,则显示出人脸的识别后的数据信息,如果不在,提示未注册,并询问用户是否把人脸信息加入到人脸库中。

    人脸注册

    人脸注册是指,我们在输入完用户名和密码后,调用摄像头,把保存的人脸特征和系统中的一个用户相关联。

    人脸检测是指一个场景,在这个场景中,检测是否存在一个人脸,如果存在,它检测的方式是通过人脸的关键点数据来进行定位的,通常的人脸检测程序中,人脸的检测结果会返回一个框。人脸识别引擎通过对框内的图片进行分析,提取被称为人脸特征点的数据并存入数据库,这个过程称为人脸信息注册,人脸信息注册后,在识别到新的人脸后,再调用人脸识别引擎,通过对比库中的人脸信息,获得不同人脸的相似度值,就完成了人脸识别功能。

    建立项目

    设计视图

    视图很简单,由于我们主要是识别人脸,我们使用一个子视图来显示人脸信息数据。

    主视图

    显示人脸部分我们直接使用自定义的OPENGL的View,因为这里是一个视频识别的功能,所以我们需要使用OPENGL/硬件加速,以实现显示的快速和高效。
    这个是一个自定义的View。你可以先下载本教程的源码,在GlView中找到这部分代码并把它拖到我们的项目中。

    打开设计器,找到Main.storyboard.拉控件到Storboard窗口,Class选择我们使用的GLView.
    设置视图高度和宽度为填满整个窗口。


    定义视图

    子视图

    我们还需要一个子视图用于显示识别的框。这个视图比较简单,我们新增一个Controller,Class选择默认类。

    设计视图

    这里面我们可以定义几个Label是用于显示人脸识别信息,类似于美国大片中的那些显示信息的效果,比如 刘德华 CIA-HongKong之类

    我们后面甚至可以将系统中注册的人脸显示出来,供人脸比对。不过这又需要另外一个子视图,有兴趣的读者可以自行尝试

    实现业务逻辑

    首先,我们打开默认的ViewController

    我们在.h文件中增加GlView.h的头文件。

    #import "GLView.h"
    

    定义OpenGL视图接口属性,这个是我们的主视图。

    @property (weak, nonatomic) IBOutlet GLView *glView;
    

    用于存放人脸特征小试图的集合

    @property (nonatomic, strong) NSMutableArray* arrayAllFaceRectView;
    

    定义图像视频的处理大小,由于是手机使用,我们使用720p的大小就够了

    #define IMAGE_WIDTH     720
    #define IMAGE_HEIGHT    1280
    

    找到ViewDidLoad方法,我们在这里定义业务逻辑。

    我们准备使用手机的前置摄像头的视频,并且希望我们的人脸框信息和手机的屏幕方向一致。

     //根据当前手机的方向自动确认图像识别的方向
        UIInterfaceOrientation uiOrientation = [[UIApplication sharedApplication] statusBarOrientation];
        AVCaptureVideoOrientation videoOrientation = (AVCaptureVideoOrientation)uiOrientation;
        
       
    
    

    我们希望摄像头的视频能够充满全屏,获得一种良好的体验效果。
    这部分的代码具有代表性,但逻辑比较简单。

     //计算OpenGL窗口大小
        CGSize sizeTemp = CGSizeZero;
        if(uiOrientation == UIInterfaceOrientationPortrait || uiOrientation == UIInterfaceOrientationPortraitUpsideDown)
        {
            sizeTemp.width = MIN(IMAGE_WIDTH, IMAGE_HEIGHT);
            sizeTemp.height = MAX(IMAGE_WIDTH, IMAGE_HEIGHT);
        }
        else
        {
            sizeTemp.width = MAX(IMAGE_WIDTH, IMAGE_HEIGHT);
            sizeTemp.height = MIN(IMAGE_WIDTH, IMAGE_HEIGHT);
        }
    
        CGFloat fWidth = self.view.bounds.size.width;
        CGFloat fHeight = self.view.bounds.size.height;
        
        [Utility CalcFitOutSize:sizeTemp.width oldH:sizeTemp.height newW:&fWidth newH:&fHeight];
        self.glView.frame = CGRectMake((self.view.bounds.size.width-fWidth)/2,(self.view.bounds.size.width-fWidth)/2,fWidth,fHeight);
        [self.glView setInputSize:sizeTemp orientation:videoOrientation];
    

    初始化人脸识别子视图数据

    self.arrayAllFaceRectView = [NSMutableArray arrayWithCapacity:0];
    
    //TODO:监视摄像头变化。检测摄像头中的人脸信息
    
    

    处理摄像头交互逻辑

    IOS提供了AVFundation用于处理视频和音频捕捉相关的工作,其中的AVCaptureSession是AVFoundation的核心类,用于捕捉视频和音频,协调视频和音频的输入和输出流

    image

    AFCameraController

    为了方便处理这些过程,我们把这些部分单独独立为一个类。我们来定义一个类
    AFCameraController

    你可以通过xcode的新增文件,选择cocoa Touch Class,填写类名和对应的父类名称,生成此类。

    @interface AFCameraController : NSObject
    
    - (BOOL) setupCaptureSession:(AVCaptureVideoOrientation)videoOrientation;
    - (void) startCaptureSession;
    - (void) stopCaptureSession;
    
    @end
    
    

    AVCaptureVideoDataOutputSampleBufferDelegate

    这个委托包含一个回调函数,它提供了处理视频的接口机制,视频是由业务逻辑直接处理的,我们需要把AVCapptureSession中的委托AVCaptureVideoDataOutputSampleBufferDelegate重新定义出来

    @protocol AFCameraControllerDelegate <NSObject>
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
    @end
    

    在类定义中增加委托

    @property (nonatomic, weak)     id <AFCameraControllerDelegate>    delegate;
    

    定义居部变量

        AVCaptureSession    *captureSession;
        AVCaptureConnection *videoConnection;
    

    我们来实现setupCaptureSession方法

     captureSession = [[AVCaptureSession alloc] init];
        
        [captureSession beginConfiguration];
        
        AVCaptureDevice *videoDevice = [self videoDeviceWithPosition:AVCaptureDevicePositionFront];
        
        AVCaptureDeviceInput *videoIn = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:nil];
        if ([captureSession canAddInput:videoIn])
            [captureSession addInput:videoIn];
        
        AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init];
        [videoOut setAlwaysDiscardsLateVideoFrames:YES];
        
    
        NSDictionary *dic = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
    
        [videoOut setVideoSettings:dic];
        
        
        /*处理并定义视频输出委托处理*/
        
        dispatch_queue_t videoCaptureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
        [videoOut setSampleBufferDelegate:self queue:videoCaptureQueue];
        
        if ([captureSession canAddOutput:videoOut])
            [captureSession addOutput:videoOut];
        videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];
        
        if (videoConnection.supportsVideoMirroring) {
            [videoConnection setVideoMirrored:TRUE];
        }
        
        if ([videoConnection isVideoOrientationSupported]) {
            [videoConnection setVideoOrientation:videoOrientation];
        }
        
        if ([captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
            [captureSession setSessionPreset:AVCaptureSessionPreset1280x720];
        }
        
        [captureSession commitConfiguration];
        
        return YES;
    
    

    在上面的代码中我们定义了本地处理setSampleBufferDelegate
    因此,我们需要在.m的类名中增加AVCaptureVideoDataOutputSampleBufferDelegate委托处理,代码如下所示

    @interface AFCameraController ()<AVCaptureVideoDataOutputSampleBufferDelegate>
    

    我们需要实现这个委托对应的处理方法

    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    {
        if (connection == videoConnection) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(captureOutput:didOutputSampleBuffer:fromConnection:)]) {
                [self.delegate captureOutput:captureOutput didOutputSampleBuffer:sampleBuffer fromConnection:connection];
            }
        }
    }
    

    然后我们填写session的start和stop方法,就比较简单了

    
    - (void) startCaptureSession
    {
        if ( !captureSession )
            return;
        
        if (!captureSession.isRunning )
            [captureSession startRunning];
    }
    
    - (void) stopCaptureSession
    {
        [captureSession stopRunning];
        captureSession = nil;
    }
    

    添加AFCamaraController引用

    让我们回到业务ViewController类,在.h中增加AFCamaraController.h的引用,并且增加变量camaraController

    #import "AFCameraController.h"
    
    ...
    
    @property (nonatomic, strong) AFCameraController* cameraController;
    
    

    在viewDidLoad方法中,增加camaraController的的处理逻辑。

     // 利用另外的Controller的方式启动摄像夈监听线程
        self.cameraController = [[AFCameraController alloc]init];
        self.cameraController.delegate = self;
        [self.cameraController setupCaptureSession:videoOrientation];
        [self.cameraController startCaptureSession];
    
    

    由于我们使用了另外的类,因此我们需要在dealloc方法中主动调用uninit的方法来完成内存对象的释放。

    - (void)dealloc
    {
       
        [self.cameraController stopCaptureSession];
       
    }
    
    

    我们在AFCameraController定义了委托,用于处理摄像头获取视频后的处理操作。首先和AFCameraController类一样,我们的ViewController也需要实现AFCameraControllerDelegate委托。

    @interface ViewController : UIViewController<AFCameraControllerDelegate>
    

    我们来实现这个委托

    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    {
        //获取图像
        //在视图上显示捕获到的图像
        //调用人脸检测引擎,得到人脸的范围
        //使用子视图的方式,显示捕获到的人脸信息,对,还得加上一个框标示人脸的位置
    }
    

    看到上面的注释,是不是觉得这个委托才是我们主角。

    构造ASVLOFFSCREEN 结构体

    打开下载的API文档,可以看到视频处理需要一个结构体ASVLOFFSCREEN,需要处理的图像格式为

    ASVL_PAF_NV12视频格式,是IOS摄像头提供的preview callback的YUV格式的两种之一


    image

    这个结构体的定义在asvloffscreen.h文件中。它的定义如下

    typedef struct __tag_ASVL_OFFSCREEN
    {
        MUInt32 u32PixelArrayFormat;
        MInt32  i32Width;
        MInt32  i32Height;
        MUInt8* ppu8Plane[4];
        MInt32  pi32Pitch[4];
    }ASVLOFFSCREEN, *LPASVLOFFSCREEN;
    

    其中ppu8Plane存储的是图像的数据。从上面的结构中可知道,要想实现功能,首先得向这个结构体传递正确的值。ppu8Plane保存的就是PixelArrayFormat对应的图像的二制数据信息

    继续来处理委托,在captureOutput中增加下面的代码

        CVImageBufferRef cameraFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
        int bufferWidth = (int) CVPixelBufferGetWidth(cameraFrame);
        int bufferHeight = (int) CVPixelBufferGetHeight(cameraFrame);
        LPASVLOFFSCREEN pOffscreenIn = [self offscreenFromSampleBuffer:sampleBuffer];
        
        //显示采集到的视频
        dispatch_sync(dispatch_get_main_queue(), ^{
        [self.glView render:bufferWidth height:bufferHeight yData:pOffscreenIn->ppu8Plane[0] uvData:pOffscreenIn->ppu8Plane[1]];
        }
        
    

    将图像信息转为化offscreen

    我们来实现offscreenFromSampleBuffer方法

    这个方法中通过获取摄像头的数据sampleBuffer,构造ASVLOFFSCREEN结构体。

    - (LPASVLOFFSCREEN)offscreenFromSampleBuffer:(CMSampleBufferRef)sampleBuffer
    {
        if (NULL == sampleBuffer)
            return NULL;
        
        CVImageBufferRef cameraFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
        int bufferWidth = (int) CVPixelBufferGetWidth(cameraFrame);
        int bufferHeight = (int) CVPixelBufferGetHeight(cameraFrame);
        OSType pixelType =  CVPixelBufferGetPixelFormatType(cameraFrame);
        
        CVPixelBufferLockBaseAddress(cameraFrame, 0);
        
     
            /*判断是否已经有内容,有内容清空*/
            if (_offscreenIn != NULL)
            {
                if (_offscreenIn->i32Width != bufferWidth || _offscreenIn->i32Height != bufferHeight || ASVL_PAF_NV12 != _offscreenIn->u32PixelArrayFormat) {
                    [Utility freeOffscreen:_offscreenIn];
                    _offscreenIn = NULL;
                }
            }
        
            /*先构造结构体*/
            if (_offscreenIn == NULL) {
                _offscreenIn = [Utility createOffscreen:bufferWidth height:bufferHeight format:ASVL_PAF_NV12];
            }
        
        
            //获取camaraFrame数据信息并复制到ppu8Plane[0]
        
            ASVLOFFSCREEN* pOff = _offscreenIn;
            
            uint8_t  *baseAddress0 = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(cameraFrame, 0); // Y
            uint8_t  *baseAddress1 = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(cameraFrame, 1); // UV
            
            size_t   rowBytePlane0 = CVPixelBufferGetBytesPerRowOfPlane(cameraFrame, 0);
            size_t   rowBytePlane1 = CVPixelBufferGetBytesPerRowOfPlane(cameraFrame, 1);
            
            // YData
            if (rowBytePlane0 == pOff->pi32Pitch[0])
            {
                memcpy(pOff->ppu8Plane[0], baseAddress0, rowBytePlane0*bufferHeight);
            }
            else
            {
                for (int i = 0; i < bufferHeight; ++i) {
                    memcpy(pOff->ppu8Plane[0] + i * bufferWidth, baseAddress0 + i * rowBytePlane0, bufferWidth);
                }
            }
            // uv data
            if (rowBytePlane1 == pOff->pi32Pitch[1])
            {
                memcpy(pOff->ppu8Plane[1], baseAddress1, rowBytePlane1 * bufferHeight / 2);
            }
            else
            {
                uint8_t  *pPlanUV = pOff->ppu8Plane[1];
                for (int i = 0; i < bufferHeight / 2; ++i) {
                    memcpy(pPlanUV + i * bufferWidth, baseAddress1+ i * rowBytePlane1, bufferWidth);
                }
            }
        
          CVPixelBufferUnlockBaseAddress(cameraFrame, 0);
        
        return _offscreenIn;
    }
    
    

    在上面的代码中我们使用到的createOffscreen是我们定义一个工具类Utility中的方法,它的作用是创建指定大小的Offscreen,并返回指针。

    
    + (LPASVLOFFSCREEN) createOffscreen:(MInt32) width height:( MInt32) height format:( MUInt32) format
    {
        
        ASVLOFFSCREEN* pOffscreen = MNull;
        do
        {
            pOffscreen = (ASVLOFFSCREEN*)malloc(sizeof(ASVLOFFSCREEN));
            if(!pOffscreen)
                break;
            
            memset(pOffscreen, 0, sizeof(ASVLOFFSCREEN));
            
            pOffscreen->u32PixelArrayFormat = format;
            pOffscreen->i32Width = width;
            pOffscreen->i32Height = height;
            
           
                pOffscreen->pi32Pitch[0] = pOffscreen->i32Width;        //Y
                pOffscreen->pi32Pitch[1] = pOffscreen->i32Width;        //UV
                
                pOffscreen->ppu8Plane[0] = (MUInt8*)malloc(height * pOffscreen->pi32Pitch[0] ) ;    // Y
                memset(pOffscreen->ppu8Plane[0], 0, height * pOffscreen->pi32Pitch[0]);
                
                pOffscreen->ppu8Plane[1] = (MUInt8*)malloc(height / 2 * pOffscreen->pi32Pitch[1]);  // UV
                memset(pOffscreen->ppu8Plane[1], 0, height * pOffscreen->pi32Pitch[0] / 2);
            
        }while(false);
        
        return pOffscreen;
    }
    
    

    这部分代码可以直接拷贝到你的程序中使用,要理解这部分代码,需要比较多的图形图像方面的知识,在本文章中将不再赘述。

    接入虹软人脸检测引擎

    从这里开始,我们正式接入人脸检测引擎,我们把人脸相关的功能单独建一个类AFVideoProcessor。用于编写和人脸识别SDK相关的代码

    添加必要的引用

    我们可以直接跟踪示例代码来添加必要的引用,因此我们把下载到的SDK中的头文件按需添加引用。

    #import "AFVideoProcessor.h"
    #import "ammem.h"
    #import "merror.h"
    #import "arcsoft_fsdk_face_tracking.h"
    #import "Utility.h"
    #import "AFRManager.h"
    
    #define AFR_DEMO_APP_ID         "bCx99etK9Ns4Saou1EbFdC8JMYnMmmLmpw1***"
    #define AFR_DEMO_SDK_FT_KEY     "FE9XjUgYTNXyBHiapTApnFydX4PpXB2ZaxhvtkD***"
    
    
    #define AFR_FT_MEM_SIZE         1024*1024*5
    
    

    上面的APPID和FT KEY,请到下载引擎页面查看,你可以在用户中心的申请历史中查到你申请到的Key的信息。

    在interface段定义变量

    MHandle          _arcsoftFT;
    MVoid*           _memBufferFT;
    

    定义人脸结构体变量

    @property(nonatomic,assign) MRECT faceRect;
    

    定义用ASVLOFFSCREEN变量

    ASVLOFFSCREEN*   _offscreenForProcess;
    

    定义三个主要方法

    - (void)initProcessor;
    - (void)uninitProcessor;
    - (NSArray*)process:(LPASVLOFFSCREEN)offscreen;
    

    initProcessor

    此方法用于初始化虹软引擎,应用程序需要主动调用此方法。

    - (void)initProcessor
    {
    _memBufferFT = MMemAlloc(MNull,AFR_FT_MEM_SIZE);
        AFT_FSDK_InitialFaceEngine((MPChar)AFR_DEMO_APP_ID, (MPChar)AFR_DEMO_SDK_FT_KEY, (MByte*)_memBufferFT, AFR_FT_MEM_SIZE, &_arcsoftFT, AFT_FSDK_OPF_0_HIGHER_EXT, 16, AFR_FD_MAX_FACE_NUM);
    }
    

    注:虹软这次库中提供了内存操作的一些函数,定义在ammem.h中,我们的程序在需要分配内存时,优先调用这里面的方法。

    uninitProcessor

    此方法用于反初始化引擎,主要是释放占用的内存。

    - (void)uninitProcessor
    {
        AFT_FSDK_UninitialFaceEngine(_arcsoftFT);
        _arcsoftFT = MNull;
        if(_memBufferFT != MNull)
        {
            MMemFree(MNull, _memBufferFT);
            _memBufferFT = MNull;
            
        }
    }
    

    process 识别人脸位置

    这个方法就是识别人脸位置的主要方法,我们可参考API文档中的示例代码来完成。

    - (NSArray*)process:(LPASVLOFFSCREEN)offscreen
    {
        MInt32 nFaceNum = 0;
        MRECT* pRectFace = MNull;
        
        __block AFR_FSDK_FACEINPUT faceInput = {0};
        
            LPAFT_FSDK_FACERES pFaceResFT = MNull;
            AFT_FSDK_FaceFeatureDetect(_arcsoftFT, offscreen, &pFaceResFT);
            if (pFaceResFT) {
                nFaceNum = pFaceResFT->nFace;
                pRectFace = pFaceResFT->rcFace;
            }
            
            if (nFaceNum > 0)
            {
                faceInput.rcFace = pFaceResFT->rcFace[0];
                faceInput.lOrient = pFaceResFT->lfaceOrient;
            }
        
        
        NSMutableArray *arrayFaceRect = [NSMutableArray arrayWithCapacity:0];
        for (int face=0; face<nFaceNum; face++) {
            AFVideoFaceRect *faceRect = [[AFVideoFaceRect alloc] init];
            faceRect.faceRect = pRectFace[face];
            [arrayFaceRect addObject:faceRect];
        }
    }
    

    这个方法返回一个数组,数组中保存人脸的识别信息,程序中可以利用这个信息来显示人脸。

    如上所述,引入头文件,并定义变量

    #import "AFVideoProcessor.h"
    @property (nonatomic, strong) AFVideoProcessor* videoProcessor;
    

    在viewDidLoad方法中初始化引擎

    self.videoProcessor = [[AFVideoProcessor alloc] init];
    [self.videoProcessor initProcessor];
    

    回到captureOutput方法,获取识别到的人脸数据

     NSArray *arrayFaceRect = [self.videoProcessor process:pOffscreenIn];
    

    由于虹软人脸引擎是支持多个人脸识别的,因此返回给我们的是一个数据,我们需要对每个人脸进行处理显示,这里的显示 是加一个框,并且把我们自定义的子试图中的数据显示出来 。所以这里是一个循环。

    for (NSUInteger face=self.arrayAllFaceRectView.count; face<arrayFaceRect.count; face++) {
                    //定位到自定义的View
                    UIStoryboard *faceRectStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
                    UIView *faceRectView = [faceRectStoryboard instantiateViewControllerWithIdentifier:@"FaceRectVideoController"].view;
                    
                    //我们的视图需要显示为绿色的框框
                    faceRectView.layer.borderColor=[UIColor greenColor].CGColor;
                    faceRectView.layer.borderWidth=2;
                    
                    [self.view addSubview:faceRectView];
                    [self.arrayAllFaceRectView addObject:faceRectView];
                }
    

    这里只是显示一个框,如果我们的框需要跟踪到人脸的位置并能自动缩放大小,还需要我们做一些工作。我们需要根据返回的人脸数据来设定view.frame的属性。

    人脸框的定位需要进行一系列简单的数学计算。也就是将视图的窗口人脸的窗口大小进行拟合。
    代码如下:

    - (CGRect)dataFaceRect2ViewFaceRect:(MRECT)faceRect
    {
        CGRect frameFaceRect = {0};
        CGRect frameGLView = self.glView.frame;
        frameFaceRect.size.width = CGRectGetWidth(frameGLView)*(faceRect.right-faceRect.left)/IMAGE_WIDTH;
        frameFaceRect.size.height = CGRectGetHeight(frameGLView)*(faceRect.bottom-faceRect.top)/IMAGE_HEIGHT;
        frameFaceRect.origin.x = CGRectGetWidth(frameGLView)*faceRect.left/IMAGE_WIDTH;
        frameFaceRect.origin.y = CGRectGetHeight(frameGLView)*faceRect.top/IMAGE_HEIGHT;
        
        return frameFaceRect;
    }
    

    我们回到captureOutput针对每一个人脸试图,来定义显示

     for (NSUInteger face=0; face<arrayFaceRect.count; face++) {
                UIView *faceRectView = [self.arrayAllFaceRectView objectAtIndex:face];
                faceRectView.hidden = NO;
                faceRectView.frame = [self dataFaceRect2ViewFaceRect:((AFVideoFaceRect*)[arrayFaceRect objectAtIndex:face]).faceRect];
                
                NSLog(@"Frame:(%.2f,%.2f,%.2f,%.2f",faceRectView.frame.origin.x,faceRectView.frame.origin.y,faceRectView.frame.size.width,faceRectView.frame.size.height);
                
            }
    

    我们还需要对人脸移出的情况下进行处理,当人脸视图数据大于识别到的人脸数目时,隐藏显示

            if(self.arrayAllFaceRectView.count >= arrayFaceRect.count)
            {
                for (NSUInteger face=arrayFaceRect.count; face<self.arrayAllFaceRectView.count; face++) {
                    UIView *faceRectView = [self.arrayAllFaceRectView objectAtIndex:face];
                    faceRectView.hidden = YES;
                }
            }
    
    

    运行测试

    这个时候代码终于完成了,你可以运行测试了。效果如下:

    应该还是不错的吧。

    示例效果图

    当然,这里面我们只使用最基本的人脸检测的功能,那个酷炫的信息框也是界面上硬编码的。
    如果要实现真实的信息,就需要对人脸特征进行提取,建立 自己的人脸库,然后使用人脸识别引擎,对比这些人脸信息。这些是在虹软的face_recongation的 SDK中提供的。

    提取人脸特征信息

    face_recongation提取人脸信息是相当简单的。

                    AFR_FSDK_FACEMODEL faceModel = {0};
                    AFR_FSDK_ExtractFRFeature(_arcsoftFR, pOffscreenForProcess, &faceInput, &faceModel);
    

    提取到的信息保存在faceModel结构体中。

    typedef struct {
        MByte       *pbFeature;     // The extracted features
        MInt32      lFeatureSize;   // The size of pbFeature    
    }AFR_FSDK_FACEMODEL, *LPAFR_FSDK_FACEMODEL;
    

    我们可以通过下面的代码获取到faceModel中的feature数据

      AFR_FSDK_FACEMODEL currentFaceModel = {0};
                    currentFaceModel.pbFeature = (MByte*)[currentPerson.faceFeatureData bytes];
                    currentFaceModel.lFeatureSize = (MInt32)[currentPerson.faceFeatureData length];
    

    这个结构体中的pbFeature就是人脸特征数据。我们的程序中需要取到这个数据并将其保存到的数据库就可以了。

    不同人脸数据之间的比对

    不同人脸之间的对比,我们使用的是AFR_FSDK_FacePairMatching接口,我们将 currentFaceModel和refFaceModel进行比对。代码如下:

     AFR_FSDK_FACEMODEL refFaceModel = {0};
                        refFaceModel.pbFeature = (MByte*)[person.faceFeatureData bytes];
                        refFaceModel.lFeatureSize = (MInt32)[person.faceFeatureData length];
                        
                        MFloat fMimilScore =  0.0;
                        MRESULT mr = AFR_FSDK_FacePairMatching(_arcsoftFR, &refFaceModel, &currentFaceModel, &fMimilScore);
    
     MFloat scoreThreshold = 0.56;
                    if (fMimilScore > scoreThreshold) {
                        //TODO:处理人脸识别到的逻辑
                    }
    
    

    在虹软人脸比较引擎中,认为0.56是同一个人脸的相对值。高于0.56就认为匹配到了同一个人。关于人脸比对及识别方面的具体处理,请持续关注我的博客,我会在后面的博客中来完整介绍实现这个功能的步骤和方法。

    后记

    基于人脸的技术是人工智能的重要组成部分,系统中集成人脸可以有效的扩展我们程序的现有功能,比如可以根据人脸识别来判断使用APP的是否是同一个人。在重要的安全领域,甚至我们可以通过对比人脸和身份证信息是否一致来进行实名认证。正如虹软人脸识别引擎在介绍页面中所说的,“未来”已来到 更多实用产品等我们来共同创造!

    相关文章

      网友评论

      • Gunks:有小demo吗
      • 果汁果肉:你好,怎样将UIImage转换成ASVLOFFSCREEN结构体啊。题主有解决办法吗。我看了例子都是从摄像头动态的提取图像数据,怎样注册静态的图片啊?
      • 糊涂0:能识别活体吗??
        随风而逝的心情:@糊涂0 我试了好多活体的,都不太理想。虹软的暂时也没有放出活体的feature,听说后面的更新会有活体的。
      • 伦敦乡下的小作家:谢谢楼主,可否提供一份demo让我们学习下。
        随风而逝的心情:有关本文章的示例代码,请到http://download.csdn.net/download/feishixin/9954948 下载示例项目,下载后,需要把http://www.arcsoft.com.cn/ai/arcface.html 下载的.a文件引入到项目中。你也可以到http://www.arcsoft.com.cn/bbs/forum.php?mod=forumdisplay&fid=45 下载其它语言的demo。
        伦敦乡下的小作家:@随风而逝的心情 谢啦
        随风而逝的心情:demo稍后放出,到时候更新文章。

      本文标题:IOS人脸识别开发入门教程--人脸检测篇

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