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

时间:2022-11-17 04:13:54

引言

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

项目目标

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

人脸注册

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

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

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

准备工作

在开始之前,我们需要先下载我们用到的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.

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

设计视图

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

主视图

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

打开设计器,找到Main.storyboard.拉控件到Storboard窗口,Class选择我们使用的GLView.
设置视图高度和宽度为填满整个窗口。
IOS人脸识别开发入门教程--人脸检测篇

子视图

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

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

这里面我们可以定义几个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的核心类,用于捕捉视频和音频,协调视频和音频的输入和输出流

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

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格式的两种之一
IOS人脸识别开发入门教程--人脸检测篇

这个结构体的定义在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;
}
}

运行测试

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

应该还是不错的吧。

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

当然,这里面我们只使用最基本的人脸检测的功能,那个酷炫的信息框也是界面上硬编码的。
如果要实现真实的信息,就需要对人脸特征进行提取,建立 自己的人脸库,然后使用人脸识别引擎,对比这些人脸信息。这些是在虹软的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的是否是同一个人。在重要的安全领域,甚至我们可以通过对比人脸和身份证信息是否一致来进行实名认证。正如虹软人脸识别引擎在介绍页面中所说的,“未来”已来到 更多实用产品等我们来共同创造!