TensorFlow工程實戰(六):在iPhone手機上識別男女並進行活體檢測

本文將介紹如何利用TensorFlow實現一個活體檢測程序。

本文摘選自電子工業出版社出版、李金洪編著的《深度學習之TensorFlow工程化項目實戰》一書的實例54:在iPhone手機上識別男女並進行活體檢測。

實例描述

在iOS上實現一個活體檢測程序。

要求:在進行活體檢測之前,能夠識別出人物性別,並根據性別顯示問候語。

本實例可以分爲兩部分功能:第1部分是性別識別,第2部分是活體檢測。

一、搭建iOS開發環境

在實現功能開發之前,先通過本節的操作將iOS開發環境搭建起來。

  1. 下載iOS開發工具

Xcode是iOS的集成開發工具,並且免費向大衆提供。可以通過AppStore下載它。

在AppStore中搜索Xcode,然後單擊“安裝”按鈕,如圖1所示。


圖1 Xcode的安裝界面

  1. 安裝CocoaPods

CocoaPods是一個負責管理iOS項目中第三方開源庫的工具。

CocoaPods能讓我們、統一地管理第三方開源庫,從而節省設置和更新第三方開源庫的時間。具體安裝方法如下:

(1)安裝CocoaPods需要用到Ruby。雖然Mac系統自帶Ruby,但是需要將其更新到最新版本。更新方法是,在命令行模式下輸入以下命令:

sudo gem update --system

(2)更換Ruby的軟件源。有時會因爲網絡原因無法訪問到Ruby的軟件源“rubygems.org”,所以需要將“rubygems.org”地址更換爲更容易訪問的地址,即把Ruby的軟件源切換至ruby-china。執行命令:

gem sources --add https://gems.ruby-china.com/
gem sources --remove https://rubygems.org/

(3)檢查源路徑是否替換成功。執行命令:

gem sources –l

該命令執行完後,可以看到Ruby的軟件源已經更新,如圖2所示。


圖2 Ruby軟件源已經更新

(4)安裝CocoaPods,執行命令:

sudo gem install cocoapods

(5)安裝本地庫,執行命令:

pod setup

二、部署工程代碼並編譯

下面使用13.3.1小節所下載的工程代碼tensorflow-for-poets-2。導入及編譯該工程中的iOS代碼的步驟如下:

  1. 更新工程代碼所需的第三方庫

因爲工程代碼tensorflow-for-poets-2中隱藏了.xcworkspace的配置,所以,在運行前需要用CocoaPods更新管理的第三方庫。更新步驟如下:

(1)打開Mac操作系統的終端窗口。
(2)輸入“cd”,並且按空格鍵。
(3)將工程目錄下的文件夾拖入終端窗口,按Enter鍵。
(4)輸入“pod update”指令來更新第三方庫。
完整的流程如圖3所示。


圖3 更新代碼第三方庫

  1. 打開工程代碼,並編譯程序

完成更新之後,在項目目錄下會生成一個.xcworkspace文件。雙擊該文件打開Xcode工具。在Xcode工具中選擇需要運行的模擬器(見圖4中標註1部分),並單擊“運行”按鈕(見圖4中標註2部分)在模擬器中啓動應用程序,如圖4所示。


圖4 運行APP程序

  1. 常見錯誤及解決辦法

在最新的Xcode10中運行此工程代碼會報錯,如圖5所示。


圖5 錯誤異常

解決方法是:單擊TARGETS下的tflite_photos-example,然後單擊Build Phases,將Copy Bundle Resources下的Info.plist文件刪掉,如圖6所示。


圖6 解決錯誤方法

提示:
在打開工程項目時,需要雙擊的是.xcworkspace文件,而不是.xcproject文件。
另外,如果在運行過程中,如果因爲找不到tensorflow/contrib/lite/tools/mutable_op_ resolver.h文件而報錯,則可以使用以下方式來解決:
在代碼文件“CameraExampleViewController.mm”中的開頭部分,找到如下代碼:

#include "tensorflow/contrib/lite/tools/mutable_op_resolver.h"

將該行代碼刪除即可。

三、載入Lite模型,實現識別男女功能

搭建好環境之後,便可以將13.3.4小節製作好的lite模型集成進來,實現識別男女功能。

  1. 將自編譯模型導入工程

將13.3.4小節製作好的lite模型和13.3.2小節中生成的標籤文件,拖到工程代碼tensorflow-for-poets-2-master/ios/tflite/data目錄下,並替換原有文件,如圖7左側的箭頭部分。


圖7 替換文件

提示:
在替換過程中,需確保文件名與代碼中配置的一致。在運行App過程中,一旦發生不一致的情況,則找不到文件,導致App進程崩潰。
另外,lite模型輸入尺寸應與代碼中保持一致,否則影響識別率。

  1. 修改分類代碼

因爲標籤文件中只有男女兩個標籤(在屏幕上最多隻能顯示兩個結果),所以將圖8中的kNumResults值設爲2。


圖8 調整顯示的分類個數

  1. 運行程序,查看效果

這一環節是在模擬器上實現的。事先將圖片保存至模擬器相冊,然後從模擬器相冊中獲取圖片來進行人物性別識別。

模擬器運行之後,顯示的結果如圖9所示。


圖9 在iPhone 8上App的運行結果

四、代碼實現:調用攝像頭並採集視頻流

因爲活體檢測功能需要用到攝像頭,所以需要在原來工程代碼中添加攝像頭功能。具體操作如下:

  1. 增加GoogleMobileVision庫

活體檢測主要是通過計算人臉特徵點的位置變化來判斷被檢測人是否完成了指定的行爲動作。該功能是藉助谷歌訓練好的人臉特徵API來實現的。該API爲GoogleMobileVision。將其引入到工程中的操作如下:

(1)雙擊打開工程代碼tensorflow-for-poets-2-master/ios/tflite下的Podfile,如圖10所示。


圖10 Podfile文件

(2)增加“pod ꞌGoogleMobileVisionꞌ”,如圖11所示。


圖11 增加GoogleMobileVision後的Podfile文件

(3)按照13.4.2小節的“1. 更新工程代碼所需的第三方庫”中的內容更新第三方庫。

  1. 自定義相機

(1)進入工程中,在左側工程目錄下右擊文件,在彈出的菜單中選擇“New File”命令,如圖12所示。


圖12 新建文件

(2)彈出如圖13所示界面,在其中選擇需要創建的平臺。這裏選擇iOS,然後選擇Source下的Cocoa Touch Class。

(3)進入Choose options for your new file界面,在“Class:”文本框中輸入要創建文件的名字,在“Subclass of:”文本框中輸入繼承的父類名稱,在“Language:”文本框中選擇Objective-C,如圖14所示。

(4)在創建的代碼文件“13-3 CameraExampleViewController.mm”中聲明自定義相機變量。具體代碼如下:

代碼1 CameraExampleViewController

//AVCaptureSession對象來執行輸入設備和輸出設備之間的數據傳遞
@property(nonatomic,strong)AVCaptureSession *session;
//視頻輸出流
@property(nonatomic,strong)AVCaptureVideoDataOutput *videoDataOutput;
//預覽圖層
@property(nonatomic,strong)AVCaptureVideoPreviewLayer *previewLayer;
//顯示方向
@property(nonatomic,assign)UIDeviceOrientation lastDeviceOrientation;

(5)添加自定義相機的初始化變量。具體代碼如下:

代碼1 CameraExampleViewController(續)

self.session = [[AVCaptureSession alloc] init];
//設置session顯示分辨率
self.session.sessionPreset = AVCaptureSessionPresetMedium;
[self.session beginConfiguration];
NSArray *oldInputs = [self.session inputs];
//移除AVCaptureSession對象中原有的輸入設備
for (AVCaptureInput *oldInput in oldInputs) {
     [self.session removeInput:oldInput];
}
//設置攝像頭方向
AVCaptureDevicePosition desiredPosition =
AVCaptureDevicePositionFront;
AVCaptureDeviceInput *input = [self cameraForPosition:desiredPosition];
//添加輸入設備
if (!input) {
    for (AVCaptureInput *oldInput in oldInputs) {
       [self.session addInput:oldInput];
    }
} else {
    [self.session addInput:input];
}
[self.session commitConfiguration];
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
//設置像素輸出格式
NSDictionary *rgbOutputSettings = @{
          (__bridge NSString*)kCVPixelBufferPixelFormatTypeKey:
          @(kCVPixelFormatType_32BGRA)
                                        	};
[self.videoDataOutput setVideoSettings:rgbOutputSettings];
[self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
self.videoDataOutputQueue= dispatch_queue_create("VideoDataOutputQueue",DISPATCH_QUEUE_SERIAL);
[self.videoDataOutput setSampleBufferDelegate:self queue:self.videoDataOutputQueue];
//添加輸出設備
[self.session addOutput:self.videoDataOutput];  
//相機拍攝預覽圖層
self.previewLayer = 
[[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
[self.previewLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
self.overlayView = [[UIView alloc]initWithFrame:self.view.bounds];
self.overlayView.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:self.overlayView];
CALayer *overlayViewLayer = [self.overlayView layer];
[overlayViewLayer setMasksToBounds:YES];
[self.previewLayer setFrame:[overlayViewLayer bounds]];
[overlayViewLayer addSublayer:self.previewLayer];

(6)添加自定義相機的代理方法。具體代碼如下:

代碼1 CameraExampleViewController(續)

#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
-(void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
//將CMSampleBuffer轉換爲UIImage
UIImage *image = [GMVUtility sampleBufferTo32RGBA:sampleBuffer];
}

在上面代碼中,用AVCaptureVideoDataOutputSampleBufferDelegate代理方法獲取實時的image。在獲取到image之後,將其分爲兩個分支:一個用於識別男女性別,另一個用於活體檢測。

五、代碼實現:提取人臉特徵

本小節用GoogleMobileVision接口獲取人臉關鍵點,進行人臉特徵提取。

  1. 創建人臉檢測器

在代碼文件“13-3 CameraExampleViewController.mm”的初始化方法中創建人臉檢測器。具體代碼如下:

代碼1 CameraExampleViewController(續)

60	//配置檢測器
61	NSDictionary *options = @{
62	GMVDetectorFaceMinSize : @(0.1),
63	GMVDetectorFaceTrackingEnabled : @(YES),
64	GMVDetectorFaceLandmarkType : @(GMVDetectorFaceLandmarkAll),
65	GMVDetectorFaceClassificationType : @(GMVDetectorFaceClassificationAll),
66	GMVDetectorFaceMode : @(GMVDetectorFaceFastMode)
67	};
68	//創建並返回已配置的檢測器
69	self.faceDetector = [GMVDetector detectorOfType:GMVDetectorTypeFace options:options];
  1. 獲取人臉

在代碼文件“CameraExampleViewController.mm”的相機代理方法中,調用GoogleMobileVision框架的GMVDetector檢測功能,獲取屏幕上所有的人臉。具體代碼如下:

代碼1 CameraExampleViewController(續)

70	UIImage *image = [GMVUtility sampleBufferTo32RGBA:sampleBuffer];
71	//建立圖像方向
72	UIDeviceOrientation deviceOrientation = [[UIDevicecurrentDevice] orientation];
73	GMVImageOrientation orientation = [GMVUtility imageOrientationFromOrientation:deviceOrientation withCaptureDevicePosition:AVCaptureDevicePositionFront defaultDeviceOrientation:self.lastKnownDeviceOrientation];
74	//定義圖像顯示方向,用於指定面部特徵檢測
75	NSDictionary *options = @{GMVDetectorImageOrientation : @(orientation)};
76	//使用GMVDetector檢測功能
77	NSArray<GMVFaceFeature*>*faces = [self.faceDetector featuresInImage:image options:options];
78	CMFormatDescriptionRef fdesc = CMSampleBufferGetFormatDescription(sampleBuffer);
79	CGRect clap = CMVideoFormatDescriptionGetCleanAperture(fdesc, false);
80	//計算比例因子和偏移量以正確顯示特徵
81	CGSize parentFrameSize = self.previewLayer.frame.size;
82	CGFloat cameraRatio = clap.size.height / clap.size.width;
83	CGFloat viewRatio = parentFrameSize.width / parentFrameSize.height;
84	CGFloat xScale = 1;
85	CGFloat yScale = 1;
86	CGRect videoBox = CGRectZero;
87	//判斷視頻預覽尺寸與相機捕獲視頻幀尺寸
88	if (viewRatio > cameraRatio) {
89	     videoBox.size.width = parentFrameSize.height * clap.size.width / clap.size.height;
90	     videoBox.size.height = parentFrameSize.height;
91	     videoBox.origin.x = (parentFrameSize.width-videoBox.size.width) / 2;
92	     videoBox.origin.y =(videoBox.size.height-parentFrameSize.height) / 2;
93	     xScale = videoBox.size.width / clap.size.width;
94	     yScale = videoBox.size.height / clap.size.height;
95	     } else {
96	     videoBox.size.width = parentFrameSize.width;
97	     videoBox.size.height = clap.size.width * (parentFrameSize.width / clap.size.height);
98	     videoBox.origin.x = (videoBox.size.width-parentFrameSize.width) / 2;
99	     videoBox.origin.y =(parentFrameSize.height-videoBox.size.height) / 2;
100	     xScale = videoBox.size.width / clap.size.height;
101	     yScale = videoBox.size.height / clap.size.width;
102	     }
103	dispatch_sync(dispatch_get_main_queue(), ^{
104	    //移除之前添加的功能視圖
105	    for (UIView *featureView in self.overlayView.subviews) {
106	         [featureView removeFromSuperview];
107	    }
108	    for (GMVFaceFeature *face in faces) {
109	         //所有的face
110	     	......
111	    }

六、活體檢測算法介紹

通過獲取人臉的GMVFaceFeature對象可以得到五官參數,從而實現微笑檢測、向左轉、向右轉、擡頭、低頭、張嘴等功能。

代碼第77行,會返回一個GMVFaceFeature對象。該對象包含人臉的具體信息。其中所包括的字段及含義如下。

  • smilingProbability:用於檢測微笑,該字段是CGFloat類型,取值範圍爲0~1。微笑尺度越大,則smilingProbability字段越大。
  • noseBasePosition:檢測圖像在視圖座標系中的鼻子座標。
  • leftCheekPosition:檢測圖像在視圖座標系中的左臉頰座標。
  • rightCheekPosition:檢測圖像在視圖座標系中的右臉頰臉頰座標。
  • mouthPosition:檢測圖像在視圖座標系中的嘴角座標。
  • bottomMouthPosition:檢測圖像在視圖座標系中的下脣中心座標。
  • leftEyePosition:檢測圖像在視圖座標系中的左眼座標。

在活體檢測的行爲算法中,只有微笑行爲可以直接用smilingProbability進行判斷。其他的行爲需要多個字段聯合判斷,具體代碼如下。

  • 左轉、右轉:通過noseBasePosition、leftCheekPosition、rightCheekPosition三點之間的間距進行判斷。
  • 擡頭:通過noseBasePosition、leftEyePosition兩點之間的間距進行判斷。
  • 低頭:通過noseBasePosition、rightCheekPosition兩點之間的間距進行判斷。
  • 張嘴:通過mouthPosition、bottomMouthPosition兩點之間的間距進行判斷。

七、代碼實現:實現活體檢測算法

在瞭解原理之後,就可以編寫代碼實現人臉檢測算法。具體如下:

  1. 識別左轉、右轉行爲

左轉、右轉的識別行爲算法是通過鼻子與左、右臉頰x座標的間距之差來判斷的。如果左邊間距比右邊間距大20以上,即爲左轉;反之則爲右轉。具體代碼如下:

代碼1 CameraExampleViewController(續)

112	//鼻子的座標
113	CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
114	//左臉頰的座標
115	CGPoint leftCheekPoint = [weakSelf scaledPoint:face.leftCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
116	//右臉頰的座標
117	CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
118	//鼻子與右臉頰之間的距離
119	CGFloat leftRightFloat1 = rightCheekPoint.x - nosePoint.x;
120	//鼻子與左臉頰之間的距離
121	CGFloat leftRightFloat2 = nosePoint.x - leftCheekPoint.x;
122	if (leftRightFloat2 - leftRightFloat1 > 20) {
123	//左轉
124	}else if (leftRightFloat1 – leftRightFloat2 > 20) {
125	//右轉
126	}else{
127	//沒有轉動,或者轉動幅度小
128	}
  1. 識別擡頭、低頭行爲

通過計算鼻子和左眼的y座標之差是否小於24,來判斷是否爲擡頭的行爲。如果鼻子與右臉頰的y座標之差大於0,則爲低頭行爲。具體代碼如下:

代碼1 CameraExampleViewController(續)

129	//鼻子的座標
130	CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
131	//左眼的座標
132	CGPoint leftEyePoint = [weakSelf scaledPoint:face.leftEyePosition xScale:xScale yScale:yScale offset:videoBox.origin];
133	//右臉頰的座標
134	CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
135	if(nosePoint.y - leftEyePoint.y < 24){
136	//擡頭
137	}else if(nosePoint.y - rightCheekPoint.y > 0){
138	//低頭
139	}
  1. 識別張嘴行爲

通過計算上脣中心y座標與下脣中心y座標之差是否大於18,來判定是否爲張嘴的行爲。具體代碼如下:

代碼1 CameraExampleViewController(續)

140	//下脣中心的座標
141	CGPoint bottomMouthPoint = [weakSelf scaledPoint:face.bottomMouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
142	//上脣中心的座標
143	CGPoint mouthPoint = [weakSelf scaledPoint:face.mouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
144	if(bottomMouthPoint.y – mouthPoint.y > 18){
145	    //張嘴
146	    ……
147	}
4. 識別微笑行爲
微笑行爲可直接通過face.smilingProbability屬性判斷出來。具體代碼如下:
代碼1  CameraExampleViewController(續)
148	//微笑判斷, 0.3是經過驗證後的經驗值
149	if (face.smilingProbability > 0.3) {
150	    //微笑
151	    ……
152	}

八、代碼實現:完成整體功能並運行程序

將男女識別算法與所有的活體檢測算法結合起來,完成完整流程。並在其中添加問候語。具體代碼如下:

  1. 實現完整流程

代碼1 CameraExampleViewController(續)

153	for (GMVFaceFeature *face in faces) {
154	    CGRect faceRect = [weakSelf scaledRect:face.bounds xScale:xScale yScale:yScale offset:videoBox.origin];
155	    //判斷是否在指定的尺寸裏
156	    if (CGRectContainsRect(weakSelf.bgView.frame, faceRect)) {
157	        //如果index爲1,則表示微笑行爲
158	        if(index == 1){
159	            if(face.smilingProbability > 0.3){
160	            }
161	        //如果index爲2,則表示左轉、右轉行爲
162	        }else if(index == 2){
163	            //鼻子的座標
164	            CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
165	            //左臉頰的座標
166	            CGPoint leftCheekPoint = [weakSelf scaledPoint:face.leftCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
167	            //右臉頰的座標
168	            CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
169	            //鼻子與右臉頰之間的距離
170	            CGFloat leftRightFloat1 = rightCheekPoint.x - nosePoint.x;
171	            //鼻子與左臉頰之間的距離
172	            CGFloat leftRightFloat2 = nosePoint.x - leftCheekPoint.x;
173	            if (leftRightFloat2 - leftRightFloat1 > 20) {
174	            //左轉
175	            }else if (leftRightFloat1 – leftRightFloat2 > 20) {
176	            //右轉
177	           }
178	        //如果index爲3,則表示張嘴行爲
179	        }else if(index == 3){
180	             //下脣中心的座標
181	             CGPoint bottomMouthPoint = [weakSelf scaledPoint:face. bottomMouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
182	             //上脣中心的座標
183	             CGPoint mouthPoint = [weakSelf scaledPoint:face.mouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
184	             if(bottomMouthPoint.y – mouthPoint.y > 18){
185	             //張嘴
186	           }
187	        //如果index爲4,則表示擡頭、低頭行爲
188	        }else if(index == 4){
189	             //鼻子的座標
190	             CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
191	             //左眼的座標
192	             CGPoint leftEyePoint = [weakSelf scaledPoint:face.leftEyePosition xScale:xScale yScale:yScale offset:videoBox.origin];
193	            //右臉頰的座標
194	            CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
195	             if(nosePoint.y - leftEyePoint.y < 24){
196	             //擡頭
197	             }else if(nosePoint.y - rightCheekPoint.y > 0){
198	             //低頭
199	             }
200	        }
201	    }
202	}
  1. 添加問候語

在代碼文件CameraExampleViewController.mm中添加下列代碼,實現問候語的顯示功能。具體代碼如下:

代碼1 CameraExampleViewController(續)

203	//遍歷獲取到的所有結果
204	for  (const auto& item : newValues) {
205	   std::string label = item.second;
206	   const float value = item.first;
207	   if (value > 0.5) {
208	       NSString *nsLabel = [NSString stringWithCString:label.c_str() encoding:[NSString defaultCStringEncoding]];
209	       NSString *textString;
210	       if ([nsLabel isEqualToString:@"man"]) {
211	           textString = @"先生你好";
212	           }else{
213	           textString = @"女士你好";
214	           }
215	    }
216	    //創建UILaebl顯示對應的問候語
217	    ......
218	}
  1. 運行程序並顯示效果

將蘋果手機通過USB接口連接到電腦上。先選擇真機,然後單擊“運行”按鈕進行程序同步,如圖15所示。


圖15 選擇真機調試

在手機上打開App即可運行程序。當手機屏幕顯示綠色邊框時,表示正在檢測。手機屏幕離人臉50cm爲最佳距離。以檢測微笑、張嘴的行爲爲例,程序運行結果如圖16、圖17所示。

圖16表示程序識別出微笑行爲,圖17表示程序識別出張嘴行爲。

提示:
在iOS 9之後的操作系統中,使用相機功能需要在項目Info.plist文件中增加了“Privacy - Camera Usage Description”權限提示,否則會報出異常。

本文摘選自電子工業出版社出版、李金洪編著的《深度學習之TensorFlow工程化項目實戰》一書,更多實戰內容點此查看。

本文經授權發佈,轉載請聯繫電子工業出版社。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章