引言
人臉識(shí)別當(dāng)前比較熱門的技術(shù),作為開發(fā)者的我們津辩,如果不實(shí)現(xiàn)人臉識(shí)別的功能就太Low了,從頭開始發(fā)明輪子不可取,我們可以用很多現(xiàn)成的人臉識(shí)別技術(shù)來(lái)實(shí)現(xiàn)销部。
當(dāng)前的人臉識(shí)別技術(shù)分為WEBAPI和SDK調(diào)用兩種方式,WEBAPI需要實(shí)時(shí)聯(lián)網(wǎng)制跟,SDK調(diào)用可以離線使用舅桩。
本次我們使用的虹軟免費(fèi)開發(fā)的離線版本的SDK,離線版本的特點(diǎn)就是我們可以隨時(shí)在本地使用雨膨,而不用擔(dān)心聯(lián)網(wǎng)的問(wèn)題擂涛。最生要的是SDK免費(fèi),也就是說(shuō)不用擔(dān)心后面使用著使用著收費(fèi)的問(wèn)題聊记。
有關(guān)本文章的示例代碼撒妈,請(qǐng)到http://download.csdn.net/download/feishixin/9954948 下載示例項(xiàng)目,下載后排监,需要把http://www.arcsoft.com.cn/ai/arcface.html 下載的.a文件引入到項(xiàng)目中狰右。你也可以到http://www.arcsoft.com.cn/bbs/forum.php?mod=forumdisplay&fid=45 下載其它語(yǔ)言的demo。
項(xiàng)目目標(biāo)
我們需要實(shí)現(xiàn)一個(gè)人臉識(shí)別功能社露,通過(guò)調(diào)用手機(jī)的前置設(shè)想頭挟阻,識(shí)別攝像頭中實(shí)時(shí)拍到的人臉信息。如果人臉信息已經(jīng)在人臉庫(kù)中峭弟,則顯示出人臉的識(shí)別后的數(shù)據(jù)信息附鸽,如果不在,提示未注冊(cè)瞒瘸,并詢問(wèn)用戶是否把人臉信息加入到人臉庫(kù)中坷备。
人臉注冊(cè)
人臉注冊(cè)是指,我們?cè)谳斎胪暧脩裘兔艽a后情臭,調(diào)用攝像頭省撑,把保存的人臉特征和系統(tǒng)中的一個(gè)用戶相關(guān)聯(lián)。
人臉檢測(cè)是指一個(gè)場(chǎng)景俯在,在這個(gè)場(chǎng)景中竟秫,檢測(cè)是否存在一個(gè)人臉,如果存在跷乐,它檢測(cè)的方式是通過(guò)人臉的關(guān)鍵點(diǎn)數(shù)據(jù)來(lái)進(jìn)行定位的肥败,通常的人臉檢測(cè)程序中,人臉的檢測(cè)結(jié)果會(huì)返回一個(gè)框。人臉識(shí)別引擎通過(guò)對(duì)框內(nèi)的圖片進(jìn)行分析馒稍,提取被稱為人臉特征點(diǎn)的數(shù)據(jù)并存入數(shù)據(jù)庫(kù)皿哨,這個(gè)過(guò)程稱為人臉信息注冊(cè),人臉信息注冊(cè)后纽谒,在識(shí)別到新的人臉后证膨,再調(diào)用人臉識(shí)別引擎,通過(guò)對(duì)比庫(kù)中的人臉信息鼓黔,獲得不同人臉的相似度值央勒,就完成了人臉識(shí)別功能。
準(zhǔn)備工作
在開始之前请祖,我們需要先下載我們用到的IOS庫(kù)订歪,我們使用的是虹軟的人臉識(shí)別庫(kù),你可以在 http://www.arcsoft.com.cn/ai/arcface.html 下載最新的版本肆捕,下載后我們得到了三個(gè)包,分別是
face_tracking用于人臉信息跟蹤盖高,它用來(lái)定位并追蹤面部區(qū)域位置慎陵,隨著人物臉部位置的變化能夠快速定位人臉位置
face_detection用于靜態(tài)照片中的人臉檢測(cè)。人臉檢測(cè)是人臉技術(shù)的基礎(chǔ)喻奥,它檢測(cè)并且定位到影像(圖片或者視頻)中的人臉席纽。
face_recognition,face_tracking,face_detection
face_recognition用于人臉特征點(diǎn)提取及人臉信息比對(duì),其中FR根據(jù)不同的應(yīng)用場(chǎng)景又分為1:1和1:N 撞蚕。
(1:1)主要用來(lái)分析兩張臉的相似度润梯,多用于用戶認(rèn)證及身份驗(yàn)證。
(1:N)針對(duì)一張輸入的人臉甥厦,在已建立的人臉數(shù)據(jù)庫(kù)中檢索相似的人臉纺铭。
我們?cè)诒綿emo中使用的是1:1
由于FR的功能需要FD或者FT的檢測(cè)數(shù)據(jù),因此的下載包中是包含所有的三個(gè)庫(kù)的刀疙。
這三包的結(jié)構(gòu)基本相同舶赔,我們需要把它們解壓。
- doc 此目錄中存放GUIDE文檔谦秧,是說(shuō)明文檔竟纳,里面介紹了公開發(fā)布的一些API,并提供了示例代碼疚鲤。不過(guò)锥累,這個(gè)示例代碼比較簡(jiǎn)單,如果你沒(méi)有經(jīng)驗(yàn)集歇,是很難理解的桶略。
- inc 保存的是供引用的庫(kù),一般是當(dāng)前API對(duì)應(yīng)的頭文件
- lib 共享庫(kù),這里面放的是SDK編譯后的.a文件删性。你需要把它們?nèi)恳玫巾?xiàng)目中亏娜。
- platform 包括兩個(gè)文件夾,其中inc為平臺(tái)相關(guān)的頭文件蹬挺,這個(gè)是我們的SDK要引用到的维贺,因此必須要包含進(jìn)去,具體的作用可以參見(jiàn)各個(gè)文件的注釋巴帮。lib是mpbase庫(kù)溯泣,也需要把它包含到我們項(xiàng)目中。
- sampleCode 示例代碼
建立項(xiàng)目
我們的項(xiàng)目比較簡(jiǎn)單榕茧,所以我們就建立普通的SingleViewApplaction.
設(shè)計(jì)視圖
視圖很簡(jiǎn)單垃沦,由于我們主要是識(shí)別人臉,我們使用一個(gè)子視圖來(lái)顯示人臉信息數(shù)據(jù)用押。
主視圖
顯示人臉部分我們直接使用自定義的OPENGL的View肢簿,因?yàn)檫@里是一個(gè)視頻識(shí)別的功能,所以我們需要使用OPENGL/硬件加速蜻拨,以實(shí)現(xiàn)顯示的快速和高效池充。
這個(gè)是一個(gè)自定義的View。你可以先下載本教程的源碼缎讼,在GlView中找到這部分代碼并把它拖到我們的項(xiàng)目中收夸。
打開設(shè)計(jì)器,找到Main.storyboard.拉控件到Storboard窗口血崭,Class選擇我們使用的GLView.
設(shè)置視圖高度和寬度為填滿整個(gè)窗口卧惜。
子視圖
我們還需要一個(gè)子視圖用于顯示識(shí)別的框。這個(gè)視圖比較簡(jiǎn)單夹纫,我們新增一個(gè)Controller咽瓷,Class選擇默認(rèn)類。
這里面我們可以定義幾個(gè)Label是用于顯示人臉識(shí)別信息捷凄,類似于美國(guó)大片中的那些顯示信息的效果忱详,比如 劉德華 CIA-HongKong之類
我們后面甚至可以將系統(tǒng)中注冊(cè)的人臉顯示出來(lái),供人臉比對(duì)跺涤。不過(guò)這又需要另外一個(gè)子視圖匈睁,有興趣的讀者可以自行嘗試
實(shí)現(xiàn)業(yè)務(wù)邏輯
首先,我們打開默認(rèn)的ViewController
我們?cè)?h文件中增加GlView.h的頭文件桶错。
#import "GLView.h"
定義OpenGL視圖接口屬性航唆,這個(gè)是我們的主視圖。
@property (weak, nonatomic) IBOutlet GLView *glView;
用于存放人臉特征小試圖的集合
@property (nonatomic, strong) NSMutableArray* arrayAllFaceRectView;
定義圖像視頻的處理大小院刁,由于是手機(jī)使用糯钙,我們使用720p的大小就夠了
#define IMAGE_WIDTH 720
#define IMAGE_HEIGHT 1280
找到ViewDidLoad方法,我們?cè)谶@里定義業(yè)務(wù)邏輯。
我們準(zhǔn)備使用手機(jī)的前置攝像頭的視頻任岸,并且希望我們的人臉框信息和手機(jī)的屏幕方向一致再榄。
//根據(jù)當(dāng)前手機(jī)的方向自動(dòng)確認(rèn)圖像識(shí)別的方向
UIInterfaceOrientation uiOrientation = [[UIApplication sharedApplication] statusBarOrientation];
AVCaptureVideoOrientation videoOrientation = (AVCaptureVideoOrientation)uiOrientation;
我們希望攝像頭的視頻能夠充滿全屏,獲得一種良好的體驗(yàn)效果享潜。
這部分的代碼具有代表性困鸥,但邏輯比較簡(jiǎn)單。
//計(jì)算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];
初始化人臉識(shí)別子視圖數(shù)據(jù)
self.arrayAllFaceRectView = [NSMutableArray arrayWithCapacity:0];
//TODO:監(jiān)視攝像頭變化剑按。檢測(cè)攝像頭中的人臉信息
處理攝像頭交互邏輯
IOS提供了AVFundation用于處理視頻和音頻捕捉相關(guān)的工作疾就,其中的AVCaptureSession是AVFoundation的核心類,用于捕捉視頻和音頻,協(xié)調(diào)視頻和音頻的輸入和輸出流
AFCameraController
為了方便處理這些過(guò)程,我們把這些部分單獨(dú)獨(dú)立為一個(gè)類艺蝴。我們來(lái)定義一個(gè)類
AFCameraController
你可以通過(guò)xcode的新增文件猬腰,選擇cocoa Touch Class,填寫類名和對(duì)應(yīng)的父類名稱猜敢,生成此類姑荷。
@interface AFCameraController : NSObject
- (BOOL) setupCaptureSession:(AVCaptureVideoOrientation)videoOrientation;
- (void) startCaptureSession;
- (void) stopCaptureSession;
@end
AVCaptureVideoDataOutputSampleBufferDelegate
這個(gè)委托包含一個(gè)回調(diào)函數(shù),它提供了處理視頻的接口機(jī)制锣枝,視頻是由業(yè)務(wù)邏輯直接處理的厢拭,我們需要把AVCapptureSession中的委托AVCaptureVideoDataOutputSampleBufferDelegate重新定義出來(lái)
@protocol AFCameraControllerDelegate <NSObject>
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
@end
在類定義中增加委托
@property (nonatomic, weak) id <AFCameraControllerDelegate> delegate;
定義居部變量
AVCaptureSession *captureSession;
AVCaptureConnection *videoConnection;
我們來(lái)實(shí)現(xiàn)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>
我們需要實(shí)現(xiàn)這個(gè)委托對(duì)應(yīng)的處理方法
- (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方法,就比較簡(jiǎn)單了
- (void) startCaptureSession
{
if ( !captureSession )
return;
if (!captureSession.isRunning )
[captureSession startRunning];
}
- (void) stopCaptureSession
{
[captureSession stopRunning];
captureSession = nil;
}
添加AFCamaraController引用
讓我們回到業(yè)務(wù)ViewController類畦贸,在.h中增加AFCamaraController.h的引用陨闹,并且增加變量camaraController
#import "AFCameraController.h"
...
@property (nonatomic, strong) AFCameraController* cameraController;
在viewDidLoad方法中,增加camaraController的的處理邏輯薄坏。
// 利用另外的Controller的方式啟動(dòng)攝像夈監(jiān)聽(tīng)線程
self.cameraController = [[AFCameraController alloc]init];
self.cameraController.delegate = self;
[self.cameraController setupCaptureSession:videoOrientation];
[self.cameraController startCaptureSession];
由于我們使用了另外的類趋厉,因此我們需要在dealloc方法中主動(dòng)調(diào)用uninit的方法來(lái)完成內(nèi)存對(duì)象的釋放。
- (void)dealloc
{
[self.cameraController stopCaptureSession];
}
我們?cè)贏FCameraController定義了委托胶坠,用于處理攝像頭獲取視頻后的處理操作君账。首先和AFCameraController類一樣,我們的ViewController也需要實(shí)現(xiàn)AFCameraControllerDelegate委托沈善。
@interface ViewController : UIViewController<AFCameraControllerDelegate>
我們來(lái)實(shí)現(xiàn)這個(gè)委托
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
//獲取圖像
//在視圖上顯示捕獲到的圖像
//調(diào)用人臉檢測(cè)引擎乡数,得到人臉的范圍
//使用子視圖的方式,顯示捕獲到的人臉信息闻牡,對(duì)净赴,還得加上一個(gè)框標(biāo)示人臉的位置
}
看到上面的注釋,是不是覺(jué)得這個(gè)委托才是我們主角罩润。
構(gòu)造ASVLOFFSCREEN 結(jié)構(gòu)體
打開下載的API文檔玖翅,可以看到視頻處理需要一個(gè)結(jié)構(gòu)體ASVLOFFSCREEN,需要處理的圖像格式為
ASVL_PAF_NV12視頻格式,是IOS攝像頭提供的preview callback的YUV格式的兩種之一
這個(gè)結(jié)構(gòu)體的定義在asvloffscreen.h文件中金度。它的定義如下
typedef struct __tag_ASVL_OFFSCREEN
{
MUInt32 u32PixelArrayFormat;
MInt32 i32Width;
MInt32 i32Height;
MUInt8* ppu8Plane[4];
MInt32 pi32Pitch[4];
}ASVLOFFSCREEN, *LPASVLOFFSCREEN;
其中ppu8Plane存儲(chǔ)的是圖像的數(shù)據(jù)应媚。從上面的結(jié)構(gòu)中可知道,要想實(shí)現(xiàn)功能猜极,首先得向這個(gè)結(jié)構(gòu)體傳遞正確的值中姜。ppu8Plane保存的就是PixelArrayFormat對(duì)應(yīng)的圖像的二制數(shù)據(jù)信息
繼續(xù)來(lái)處理委托,在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]];
}
將圖像信息轉(zhuǎn)為化offscreen
我們來(lái)實(shí)現(xiàn)offscreenFromSampleBuffer方法
這個(gè)方法中通過(guò)獲取攝像頭的數(shù)據(jù)sampleBuffer魔吐,構(gòu)造ASVLOFFSCREEN結(jié)構(gòu)體扎筒。
- (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);
/*判斷是否已經(jīng)有內(nèi)容,有內(nèi)容清空*/
if (_offscreenIn != NULL)
{
if (_offscreenIn->i32Width != bufferWidth || _offscreenIn->i32Height != bufferHeight || ASVL_PAF_NV12 != _offscreenIn->u32PixelArrayFormat) {
[Utility freeOffscreen:_offscreenIn];
_offscreenIn = NULL;
}
}
/*先構(gòu)造結(jié)構(gòu)體*/
if (_offscreenIn == NULL) {
_offscreenIn = [Utility createOffscreen:bufferWidth height:bufferHeight format:ASVL_PAF_NV12];
}
//獲取camaraFrame數(shù)據(jù)信息并復(fù)制到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是我們定義一個(gè)工具類Utility中的方法酬姆,它的作用是創(chuàng)建指定大小的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;
}
這部分代碼可以直接拷貝到你的程序中使用,要理解這部分代碼辞色,需要比較多的圖形圖像方面的知識(shí)骨宠,在本文章中將不再贅述。
接入虹軟人臉檢測(cè)引擎
從這里開始相满,我們正式接入人臉檢測(cè)引擎层亿,我們把人臉相關(guān)的功能單獨(dú)建一個(gè)類AFVideoProcessor。用于編寫和人臉識(shí)別SDK相關(guān)的代碼
添加必要的引用
我們可以直接跟蹤示例代碼來(lái)添加必要的引用立美,因此我們把下載到的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,請(qǐng)到下載引擎頁(yè)面查看建蹄,你可以在用戶中心的申請(qǐng)歷史中查到你申請(qǐng)到的Key的信息碌更。
在interface段定義變量
MHandle _arcsoftFT;
MVoid* _memBufferFT;
定義人臉結(jié)構(gòu)體變量
@property(nonatomic,assign) MRECT faceRect;
定義用ASVLOFFSCREEN變量
ASVLOFFSCREEN* _offscreenForProcess;
定義三個(gè)主要方法
- (void)initProcessor;
- (void)uninitProcessor;
- (NSArray*)process:(LPASVLOFFSCREEN)offscreen;
initProcessor
此方法用于初始化虹軟引擎,應(yīng)用程序需要主動(dòng)調(diào)用此方法洞慎。
- (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);
}
注:虹軟這次庫(kù)中提供了內(nèi)存操作的一些函數(shù)痛单,定義在ammem.h中,我們的程序在需要分配內(nèi)存時(shí)劲腿,優(yōu)先調(diào)用這里面的方法旭绒。
uninitProcessor
此方法用于反初始化引擎,主要是釋放占用的內(nèi)存焦人。
- (void)uninitProcessor
{
AFT_FSDK_UninitialFaceEngine(_arcsoftFT);
_arcsoftFT = MNull;
if(_memBufferFT != MNull)
{
MMemFree(MNull, _memBufferFT);
_memBufferFT = MNull;
}
}
process 識(shí)別人臉位置
這個(gè)方法就是識(shí)別人臉位置的主要方法挥吵,我們可參考API文檔中的示例代碼來(lái)完成。
- (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];
}
}
這個(gè)方法返回一個(gè)數(shù)組垃瞧,數(shù)組中保存人臉的識(shí)別信息蔫劣,程序中可以利用這個(gè)信息來(lái)顯示人臉。
如上所述个从,引入頭文件脉幢,并定義變量
#import "AFVideoProcessor.h"
@property (nonatomic, strong) AFVideoProcessor* videoProcessor;
在viewDidLoad方法中初始化引擎
self.videoProcessor = [[AFVideoProcessor alloc] init];
[self.videoProcessor initProcessor];
回到captureOutput方法歪沃,獲取識(shí)別到的人臉數(shù)據(jù)
NSArray *arrayFaceRect = [self.videoProcessor process:pOffscreenIn];
由于虹軟人臉引擎是支持多個(gè)人臉識(shí)別的,因此返回給我們的是一個(gè)數(shù)據(jù)嫌松,我們需要對(duì)每個(gè)人臉進(jìn)行處理顯示沪曙,這里的顯示 是加一個(gè)框,并且把我們自定義的子試圖中的數(shù)據(jù)顯示出來(lái) 萎羔。所以這里是一個(gè)循環(huán)液走。
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];
}
這里只是顯示一個(gè)框,如果我們的框需要跟蹤到人臉的位置并能自動(dòng)縮放大小贾陷,還需要我們做一些工作缘眶。我們需要根據(jù)返回的人臉數(shù)據(jù)來(lái)設(shè)定view.frame的屬性。
人臉框的定位需要進(jìn)行一系列簡(jiǎn)單的數(shù)學(xué)計(jì)算髓废。也就是將視圖的窗口人臉的窗口大小進(jìn)行擬合巷懈。
代碼如下:
- (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針對(duì)每一個(gè)人臉試圖,來(lái)定義顯示
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);
}
我們還需要對(duì)人臉移出的情況下進(jìn)行處理慌洪,當(dāng)人臉視圖數(shù)據(jù)大于識(shí)別到的人臉數(shù)目時(shí)顶燕,隱藏顯示
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;
}
}
運(yùn)行測(cè)試
這個(gè)時(shí)候代碼終于完成了,你可以運(yùn)行測(cè)試了冈爹。效果如下:
應(yīng)該還是不錯(cuò)的吧涌攻。
當(dāng)然,這里面我們只使用最基本的人臉檢測(cè)的功能频伤,那個(gè)酷炫的信息框也是界面上硬編碼的恳谎。
如果要實(shí)現(xiàn)真實(shí)的信息,就需要對(duì)人臉特征進(jìn)行提取憋肖,建立 自己的人臉庫(kù)惠爽,然后使用人臉識(shí)別引擎,對(duì)比這些人臉信息瞬哼。這些是在虹軟的face_recongation的 SDK中提供的。
提取人臉特征信息
face_recongation提取人臉信息是相當(dāng)簡(jiǎn)單的租副。
AFR_FSDK_FACEMODEL faceModel = {0};
AFR_FSDK_ExtractFRFeature(_arcsoftFR, pOffscreenForProcess, &faceInput, &faceModel);
提取到的信息保存在faceModel結(jié)構(gòu)體中坐慰。
typedef struct {
MByte *pbFeature; // The extracted features
MInt32 lFeatureSize; // The size of pbFeature
}AFR_FSDK_FACEMODEL, *LPAFR_FSDK_FACEMODEL;
我們可以通過(guò)下面的代碼獲取到faceModel中的feature數(shù)據(jù)
AFR_FSDK_FACEMODEL currentFaceModel = {0};
currentFaceModel.pbFeature = (MByte*)[currentPerson.faceFeatureData bytes];
currentFaceModel.lFeatureSize = (MInt32)[currentPerson.faceFeatureData length];
這個(gè)結(jié)構(gòu)體中的pbFeature就是人臉特征數(shù)據(jù)。我們的程序中需要取到這個(gè)數(shù)據(jù)并將其保存到的數(shù)據(jù)庫(kù)就可以了用僧。
不同人臉數(shù)據(jù)之間的比對(duì)
不同人臉之間的對(duì)比结胀,我們使用的是AFR_FSDK_FacePairMatching接口,我們將 currentFaceModel和refFaceModel進(jìn)行比對(duì)责循。代碼如下:
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:處理人臉識(shí)別到的邏輯
}
在虹軟人臉比較引擎中糟港,認(rèn)為0.56是同一個(gè)人臉的相對(duì)值。高于0.56就認(rèn)為匹配到了同一個(gè)人院仿。關(guān)于人臉比對(duì)及識(shí)別方面的具體處理秸抚,請(qǐng)持續(xù)關(guān)注我的博客速和,我會(huì)在后面的博客中來(lái)完整介紹實(shí)現(xiàn)這個(gè)功能的步驟和方法。
后記
基于人臉的技術(shù)是人工智能的重要組成部分剥汤,系統(tǒng)中集成人臉可以有效的擴(kuò)展我們程序的現(xiàn)有功能颠放,比如可以根據(jù)人臉識(shí)別來(lái)判斷使用APP的是否是同一個(gè)人。在重要的安全領(lǐng)域吭敢,甚至我們可以通過(guò)對(duì)比人臉和身份證信息是否一致來(lái)進(jìn)行實(shí)名認(rèn)證碰凶。正如虹軟人臉識(shí)別引擎在介紹頁(yè)面中所說(shuō)的,“未來(lái)”已來(lái)到 更多實(shí)用產(chǎn)品等我們來(lái)共同創(chuàng)造鹿驼!