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

引言

人臉識(shí)別當(dāng)前比較熱門的技術(shù),作為開發(fā)者的我們,如果不實(shí)現(xiàn)人臉識(shí)別的功能就太Low了,從頭開始發(fā)明輪子不可取,我們可以用很多現(xiàn)成的人臉識(shí)別技術(shù)來實(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)的問題。最生要的是SDK免費(fèi),也就是說不用擔(dān)心后面使用著使用著收費(fèi)的問題。

有關(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 下載其它語言的demo。

項(xiàng)目目標(biāo)

我們需要實(shí)現(xiàn)一個(gè)人臉識(shí)別功能,通過調(diào)用手機(jī)的前置設(shè)想頭,識(shí)別攝像頭中實(shí)時(shí)拍到的人臉信息。如果人臉信息已經(jīng)在人臉庫(kù)中,則顯示出人臉的識(shí)別后的數(shù)據(jù)信息,如果不在,提示未注冊(cè),并詢問用戶是否把人臉信息加入到人臉庫(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ān)鍵點(diǎn)數(shù)據(jù)來進(jìn)行定位的,通常的人臉檢測(cè)程序中,人臉的檢測(cè)結(jié)果會(huì)返回一個(gè)框。人臉識(shí)別引擎通過對(duì)框內(nèi)的圖片進(jìn)行分析,提取被稱為人臉特征點(diǎn)的數(shù)據(jù)并存入數(shù)據(jù)庫(kù),這個(gè)過程稱為人臉信息注冊(cè),人臉信息注冊(cè)后,在識(shí)別到新的人臉后,再調(diào)用人臉識(shí)別引擎,通過對(duì)比庫(kù)中的人臉信息,獲得不同人臉的相似度值,就完成了人臉識(shí)別功能。

image
image

準(zhǔn)備工作

在開始之前,我們需要先下載我們用到的IOS庫(kù),我們使用的是虹軟的人臉識(shí)別庫(kù),你可以在 http://www.arcsoft.com.cn/ai/arcface.html 下載最新的版本,下載后我們得到了三個(gè)包,分別是

face_tracking用于人臉信息跟蹤,它用來定位并追蹤面部區(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)主要用來分析兩張臉的相似度,多用于用戶認(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文檔,是說明文檔,里面介紹了公開發(fā)布的一些API,并提供了示例代碼。不過,這個(gè)示例代碼比較簡(jiǎn)單,如果你沒有經(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)去,具體的作用可以參見各個(gè)文件的注釋。lib是mpbase庫(kù),也需要把它包含到我們項(xiàng)目中。
  • sampleCode 示例代碼

建立項(xiàng)目

我們的項(xiàng)目比較簡(jiǎn)單,所以我們就建立普通的SingleViewApplaction.

建立項(xiàng)目

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

視圖很簡(jiǎn)單,由于我們主要是識(shí)別人臉,我們使用一個(gè)子視圖來顯示人臉信息數(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è)的人臉顯示出來,供人臉比對(duì)。不過這又需要另外一個(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

為了方便處理這些過程,我們把這些部分單獨(dú)獨(dú)立為一個(gè)類。我們來定義一個(gè)類
AFCameraController

你可以通過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重新定義出來

@protocol AFCameraControllerDelegate <NSObject>
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
@end

在類定義中增加委托

@property (nonatomic, weak)     id <AFCameraControllerDelegate>    delegate;

定義居部變量

    AVCaptureSession    *captureSession;
    AVCaptureConnection *videoConnection;

我們來實(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)聽線程
    self.cameraController = [[AFCameraController alloc]init];
    self.cameraController.delegate = self;
    [self.cameraController setupCaptureSession:videoOrientation];
    [self.cameraController startCaptureSession];

由于我們使用了另外的類,因此我們需要在dealloc方法中主動(dòng)調(diào)用uninit的方法來完成內(nèi)存對(duì)象的釋放。

- (void)dealloc
{
   
    [self.cameraController stopCaptureSession];
   
}

我們?cè)贏FCameraController定義了委托,用于處理攝像頭獲取視頻后的處理操作。首先和AFCameraController類一樣,我們的ViewController也需要實(shí)現(xiàn)AFCameraControllerDelegate委托。

@interface ViewController : UIViewController<AFCameraControllerDelegate>

我們來實(shí)現(xiàn)這個(gè)委托

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    //獲取圖像
    //在視圖上顯示捕獲到的圖像
    //調(diào)用人臉檢測(cè)引擎,得到人臉的范圍
    //使用子視圖的方式,顯示捕獲到的人臉信息,對(duì),還得加上一個(gè)框標(biāo)示人臉的位置
}

看到上面的注釋,是不是覺得這個(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ù)來處理委托,在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

我們來實(shí)現(xiàn)offscreenFromSampleBuffer方法

這個(gè)方法中通過獲取攝像頭的數(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)的代碼

添加必要的引用

我們可以直接跟蹤示例代碼來添加必要的引用,因此我們把下載到的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)到下載引擎頁面查看,你可以在用戶中心的申請(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文檔中的示例代碼來完成。

- (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è)信息來顯示人臉。

如上所述,引入頭文件,并定義變量

#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ù)顯示出來 。所以這里是一個(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ù)來設(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è)人臉試圖,來定義顯示

 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;

我們可以通過下面的代碼獲取到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ì)在后面的博客中來完整介紹實(shí)現(xiàn)這個(gè)功能的步驟和方法。

后記

基于人臉的技術(shù)是人工智能的重要組成部分,系統(tǒng)中集成人臉可以有效的擴(kuò)展我們程序的現(xiàn)有功能,比如可以根據(jù)人臉識(shí)別來判斷使用APP的是否是同一個(gè)人。在重要的安全領(lǐng)域,甚至我們可以通過對(duì)比人臉和身份證信息是否一致來進(jìn)行實(shí)名認(rèn)證。正如虹軟人臉識(shí)別引擎在介紹頁面中所說的,“未來”已來到 更多實(shí)用產(chǎn)品等我們來共同創(chuàng)造!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容