IOS人臉識(shí)別開發(fā)入門教程--人臉檢測(cè)篇

引言

人臉識(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í)別功能。

image
image

準(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.

建立項(xiàng)目

設(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)類。

設(shè)計(jì)視圖

這里面我們可以定義幾個(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)視頻和音頻的輸入和輸出流

image

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格式的兩種之一


image

這個(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, &currentFaceModel, &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)造鹿驼!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欲低,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子畜晰,更是在濱河造成了極大的恐慌砾莱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舷蟀,死亡現(xiàn)場(chǎng)離奇詭異恤磷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)野宜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門扫步,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匈子,你說(shuō)我怎么就攤上這事河胎。” “怎么了虎敦?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵游岳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我其徙,道長(zhǎng)胚迫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任唾那,我火速辦了婚禮访锻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闹获。我一直安慰自己期犬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布避诽。 她就那樣靜靜地躺著龟虎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沙庐。 梳的紋絲不亂的頭發(fā)上鲤妥,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天佳吞,我揣著相機(jī)與錄音,去河邊找鬼旭斥。 笑死容达,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垂券。 我是一名探鬼主播花盐,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼菇爪!你這毒婦竟也來(lái)了算芯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凳宙,失蹤者是張志新(化名)和其女友劉穎熙揍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氏涩,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡届囚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了是尖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意系。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饺汹,靈堂內(nèi)的尸體忽然破棺而出蛔添,到底是詐尸還是另有隱情,我是刑警寧澤兜辞,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布迎瞧,位于F島的核電站,受9級(jí)特大地震影響逸吵,放射性物質(zhì)發(fā)生泄漏凶硅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一扫皱、第九天 我趴在偏房一處隱蔽的房頂上張望咏尝。 院中可真熱鬧,春花似錦啸罢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至厕怜,卻和暖如春衩匣,著一層夾襖步出監(jiān)牢的瞬間蕾总,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工琅捏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留生百,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓柄延,卻偏偏與公主長(zhǎng)得像蚀浆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搜吧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容