IOS人臉識別開發入門教程--人臉檢測篇

feishixin發表於2017-08-25

引言

人臉識別當前比較熱門的技術,作為開發者的我們,如果不實現人臉識別的功能就太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

準備工作

在開始之前,我們需要先下載我們用到的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的是否是同一個人。在重要的安全領域,甚至我們可以通過對比人臉和身份證資訊是否一致來進行實名認證。正如虹軟人臉識別引擎在介紹頁面中所說的,“未來”已來到 更多實用產品等我們來共同創造!

相關文章