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

来源:互联网 发布:ubuntu离线安装mysql 编辑:程序博客网 时间:2024/05/29 14:55

引言

人脸识别当前比较热门的技术,作为开发者的我们,如果不实现人脸识别的功能就太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。

项目目标

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

人脸注册

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

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

image

准备工作

在开始之前,我们需要先下载我们用到的IOS库,我们使用的是虹软的人脸识别库,你可以在 http://www.arcsoft.com.cn/ai/arcface.html 下载最新的版本,下载后我们得到了三个包,分别是

face_tracking用于人脸信息跟踪,它用来定位并追踪面部区域位置,随着人物脸部位置的变化能够快速定位人脸位置
face_detection用于静态照片中的人脸检测。人脸检测是人脸技术的基础,它检测并且定位到影像(图片或者视频)中的人脸。
face_recognition,face_tracking,face_detection
face_recognition用于人脸特征点提取及人脸信息比对,其中FR根据不同的应用场景又分为1:1和1:N 。

(1:1)主要用来分析两张脸的相似度,多用于用户认证及身份验证。

(1:N)针对一张输入的人脸,在已建立的人脸数据库中检索相似的人脸。
我们在本demo中使用的是1:1
由于FR的功能需要FD或者FT的检测数据,因此的下载包中是包含所有的三个库的。

这三包的结构基本相同,我们需要把它们解压。

  • doc 此目录中存放GUIDE文档,是说明文档,里面介绍了公开发布的一些API,并提供了示例代码。不过,这个示例代码比较简单,如果你没有经验,是很难理解的。
  • inc 保存的是供引用的库,一般是当前API对应的头文件
  • lib 共享库,这里面放的是SDK编译后的.a文件。你需要把它们全部引用到项目中。
  • platform 包括两个文件夹,其中inc为平台相关的头文件,这个是我们的SDK要引用到的,因此必须要包含进去,具体的作用可以参见各个文件的注释。lib是mpbase库,也需要把它包含到我们项目中。
  • sampleCode 示例代码

建立项目

我们的项目比较简单,所以我们就建立普通的SingleViewApplaction.

建立项目

设计视图

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

主视图

显示人脸部分我们直接使用自定义的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的是否是同一个人。在重要的安全领域,甚至我们可以通过对比人脸和身份证信息是否一致来进行实名认证。正如虹软人脸识别引擎在介绍页面中所说的,“未来”已来到 更多实用产品等我们来共同创造!

原创粉丝点击