引言
人脸识别当前比较热门的技术,作为开发者的我们,如果不实现人脸识别的功能就太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库,我们使用的是虹软的人脸识别库,你可以在 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的核心类,用于捕捉视频和音频,协调视频和音频的输入和输出流
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格式的两种之一
这个结构体的定义在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, ¤tFaceModel, &fMimilScore);
MFloat scoreThreshold = 0.56;
if (fMimilScore > scoreThreshold) {
//TODO:处理人脸识别到的逻辑
}
在虹软人脸比较引擎中,认为0.56是同一个人脸的相对值。高于0.56就认为匹配到了同一个人。关于人脸比对及识别方面的具体处理,请持续关注我的博客,我会在后面的博客中来完整介绍实现这个功能的步骤和方法。
后记
基于人脸的技术是人工智能的重要组成部分,系统中集成人脸可以有效的扩展我们程序的现有功能,比如可以根据人脸识别来判断使用APP的是否是同一个人。在重要的安全领域,甚至我们可以通过对比人脸和身份证信息是否一致来进行实名认证。正如虹软人脸识别引擎在介绍页面中所说的,“未来”已来到 更多实用产品等我们来共同创造!