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

引言

人脸识别当前比较热门的技术,作为开发者的我们,如果不实现人脸识别的功能就太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
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的是否是同一个人。在重要的安全领域,甚至我们可以通过对比人脸和身份证信息是否一致来进行实名认证。正如虹软人脸识别引擎在介绍页面中所说的,“未来”已来到 更多实用产品等我们来共同创造!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容