引言
人臉識別當前比較熱門的技術,作為開發者的我們,如果不實現人臉識別的功能就太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格式的兩種之一
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, ¤tFaceModel, &fMimilScore);
MFloat scoreThreshold = 0.56;
if (fMimilScore > scoreThreshold) {
//TODO:處理人臉識別到的邏輯
}
在虹軟人臉比較引擎中,認為0.56是同一個人臉的相對值。高于0.56就認為匹配到了同一個人。關于人臉比對及識別方面的具體處理,請持續關注我的博客,我會在后面的博客中來完整介紹實現這個功能的步驟和方法。
后記
基于人臉的技術是人工智能的重要組成部分,系統中集成人臉可以有效的擴展我們程序的現有功能,比如可以根據人臉識別來判斷使用APP的是否是同一個人。在重要的安全領域,甚至我們可以通過對比人臉和身份證信息是否一致來進行實名認證。正如虹軟人臉識別引擎在介紹頁面中所說的,“未來”已來到 更多實用產品等我們來共同創造!