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 gemupdate--system

(2)更换 Ruby 的软件源。有时会因为网络原因无法访问到 Ruby 的软件源“ rubygems.org ”,所以需要将“ rubygems.org ”地址更换为更容易访问的地址,即把 Ruby 的软件源切换至 ruby-china。执行命令:

复制代码

gem sources --addhttps://gems.ruby-china.com/
gem sources --removehttps://rubygems.org/

(3)检查源路径是否替换成功。执行命令:

复制代码

gemsources –l

该命令执行完后,可以看到 Ruby 的软件源已经更新,如图 2 所示。

图 2 Ruby 软件源已经更新

(4)安装 CocoaPods,执行命令:

复制代码

sudo geminstallcocoapods

(5)安装本地库,执行命令:

复制代码

podsetup

二、部署工程代码并编译

下面使用 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)UIDeviceOrientationlastDeviceOrientation;

(5)添加自定义相机的初始化变量。具体代码如下:

代码 1 CameraExampleViewController(续)

复制代码

self.session = [[AVCaptureSessionalloc] init];
// 设置 session 显示分辨率
self.session.sessionPreset =AVCaptureSessionPresetMedium;
[self.session beginConfiguration];
NSArray*oldInputs = [self.session inputs];
// 移除 AVCaptureSession 对象中原有的输入设备
for(AVCaptureInput*oldInputinoldInputs) {
[self.session removeInput:oldInput];
}
// 设置摄像头方向
AVCaptureDevicePositiondesiredPosition =
AVCaptureDevicePositionFront;
AVCaptureDeviceInput*input = [selfcameraForPosition:desiredPosition];
// 添加输入设备
if(!input) {
for(AVCaptureInput*oldInputinoldInputs) {
[self.session addInput:oldInput];
}
}else{
[self.session addInput:input];
}
[self.session commitConfiguration];
self.videoDataOutput = [[AVCaptureVideoDataOutputalloc] init];
// 设置像素输出格式
NSDictionary*rgbOutputSettings = @{
(__bridgeNSString*)kCVPixelBufferPixelFormatTypeKey:
@(kCVPixelFormatType_32BGRA)
};
[self.videoDataOutput setVideoSettings:rgbOutputSettings];
[self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
self.videoDataOutputQueue= dispatch_queue_create("VideoDataOutputQueue",DISPATCH_QUEUE_SERIAL);
[self.videoDataOutput setSampleBufferDelegate:selfqueue:self.videoDataOutputQueue];
// 添加输出设备
[self.session addOutput:self.videoDataOutput];
// 相机拍摄预览图层
self.previewLayer =
[[AVCaptureVideoPreviewLayeralloc] initWithSession:self.session];
[self.previewLayer setBackgroundColor:[[UIColorclearColor]CGColor]];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
self.overlayView = [[UIViewalloc]initWithFrame:self.view.bounds];
self.overlayView.backgroundColor = [UIColordarkGrayColor];
[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// 配置检测器
61NSDictionary *options = @{
62GMVDetectorFaceMinSize : @(0.1),
63GMVDetectorFaceTrackingEnabled : @(YES),
64GMVDetectorFaceLandmarkType : @(GMVDetectorFaceLandmarkAll),
65GMVDetectorFaceClassificationType : @(GMVDetectorFaceClassificationAll),
66GMVDetectorFaceMode : @(GMVDetectorFaceFastMode)
67};
68// 创建并返回已配置的检测器
69self.faceDetector = [GMVDetector detectorOfType:GMVDetectorTypeFace options:options];
  1. 获取人脸

在代码文件“ CameraExampleViewController.mm ”的相机代理方法中,调用 GoogleMobileVision 框架的 GMVDetector 检测功能,获取屏幕上所有的人脸。具体代码如下:

代码 1 CameraExampleViewController(续)

复制代码

70UIImage *image= [GMVUtility sampleBufferTo32RGBA:sampleBuffer];
71// 建立图像方向
72UIDeviceOrientation deviceOrientation = [[UIDevicecurrentDevice] orientation];
73GMVImageOrientation orientation = [GMVUtility imageOrientationFromOrientation:deviceOrientation withCaptureDevicePosition:AVCaptureDevicePositionFront defaultDeviceOrientation:self.lastKnownDeviceOrientation];
74// 定义图像显示方向,用于指定面部特征检测
75NSDictionary *options = @{GMVDetectorImageOrientation : @(orientation)};
76// 使用 GMVDetector 检测功能
77NSArray*faces = [self.faceDetector featuresInImage:imageoptions:options];
78CMFormatDescriptionRef fdesc = CMSampleBufferGetFormatDescription(sampleBuffer);
79CGRect clap = CMVideoFormatDescriptionGetCleanAperture(fdesc,false);
80// 计算比例因子和偏移量以正确显示特征
81CGSize parentFrameSize = self.previewLayer.frame.size;
82CGFloat cameraRatio = clap.size.height/ clap.size.width;
83CGFloat viewRatio = parentFrameSize.width/ parentFrameSize.height;
84CGFloat xScale =1;
85CGFloat yScale =1;
86CGRect videoBox = CGRectZero;
87// 判断视频预览尺寸与相机捕获视频帧尺寸
88if(viewRatio > cameraRatio) {
89videoBox.size.width= parentFrameSize.height* clap.size.width/ clap.size.height;
90videoBox.size.height= parentFrameSize.height;
91videoBox.origin.x = (parentFrameSize.width-videoBox.size.width) /2;
92videoBox.origin.y =(videoBox.size.height-parentFrameSize.height) /2;
93xScale = videoBox.size.width/ clap.size.width;
94yScale = videoBox.size.height/ clap.size.height;
95}else{
96videoBox.size.width= parentFrameSize.width;
97videoBox.size.height= clap.size.width* (parentFrameSize.width/ clap.size.height);
98videoBox.origin.x = (videoBox.size.width-parentFrameSize.width) /2;
99videoBox.origin.y =(parentFrameSize.height-videoBox.size.height) /2;
100xScale = videoBox.size.width/ clap.size.height;
101yScale = videoBox.size.height/ clap.size.width;
102}
103dispatch_sync(dispatch_get_main_queue(), ^{
104// 移除之前添加的功能视图
105for(UIView *featureView in self.overlayView.subviews) {
106[featureView removeFromSuperview];
107}
108for(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// 鼻子的坐标
113CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
114// 左脸颊的坐标
115CGPoint leftCheekPoint = [weakSelf scaledPoint:face.leftCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
116// 右脸颊的坐标
117CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
118// 鼻子与右脸颊之间的距离
119CGFloat leftRightFloat1 = rightCheekPoint.x - nosePoint.x;
120// 鼻子与左脸颊之间的距离
121CGFloat leftRightFloat2 = nosePoint.x - leftCheekPoint.x;
122if(leftRightFloat2 - leftRightFloat1 >20) {
123// 左转
124}elseif(leftRightFloat1 – leftRightFloat2 >20) {
125// 右转
126}else{
127// 没有转动,或者转动幅度小
128}
  1. 识别抬头、低头行为

通过计算鼻子和左眼的 y 坐标之差是否小于 24,来判断是否为抬头的行为。如果鼻子与右脸颊的 y 坐标之差大于 0,则为低头行为。具体代码如下:

代码 1 CameraExampleViewController(续)

复制代码

129// 鼻子的坐标
130CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
131// 左眼的坐标
132CGPoint leftEyePoint = [weakSelf scaledPoint:face.leftEyePosition xScale:xScale yScale:yScale offset:videoBox.origin];
133// 右脸颊的坐标
134CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
135if(nosePoint.y - leftEyePoint.y <24){
136// 抬头
137}elseif(nosePoint.y - rightCheekPoint.y >0){
138// 低头
139}
  1. 识别张嘴行为

通过计算上唇中心 y 坐标与下唇中心 y 坐标之差是否大于 18,来判定是否为张嘴的行为。具体代码如下:

代码 1 CameraExampleViewController(续)

复制代码

140// 下唇中心的坐标
141CGPoint bottomMouthPoint = [weakSelf scaledPoint:face.bottomMouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
142// 上唇中心的坐标
143CGPoint mouthPoint = [weakSelf scaledPoint:face.mouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
144if(bottomMouthPoint.y – mouthPoint.y >18){
145// 张嘴
146……
147}
4.识别微笑行为
微笑行为可直接通过 face.smilingProbability 属性判断出来。具体代码如下:
代码1CameraExampleViewController(续)
148// 微笑判断, 0.3 是经过验证后的经验值
149if(face.smilingProbability >0.3) {
150// 微笑
151……
152}

八、代码实现:完成整体功能并运行程序

将男女识别算法与所有的活体检测算法结合起来,完成完整流程。并在其中添加问候语。具体代码如下:

  1. 实现完整流程

代码 1 CameraExampleViewController(续)

复制代码

153for(GMVFaceFeature *faceinfaces) {
154CGRect faceRect = [weakSelf scaledRect:face.bounds xScale:xScale yScale:yScale offset:videoBox.origin];
155// 判断是否在指定的尺寸里
156if(CGRectContainsRect(weakSelf.bgView.frame, faceRect)) {
157// 如果 index 为 1,则表示微笑行为
158if(index ==1){
159if(face.smilingProbability >0.3){
160}
161// 如果 index 为 2,则表示左转、右转行为
162}elseif(index ==2){
163// 鼻子的坐标
164CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
165// 左脸颊的坐标
166CGPoint leftCheekPoint = [weakSelf scaledPoint:face.leftCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
167// 右脸颊的坐标
168CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
169// 鼻子与右脸颊之间的距离
170CGFloat leftRightFloat1 = rightCheekPoint.x - nosePoint.x;
171// 鼻子与左脸颊之间的距离
172CGFloat leftRightFloat2 = nosePoint.x - leftCheekPoint.x;
173if(leftRightFloat2 - leftRightFloat1 >20) {
174// 左转
175}elseif(leftRightFloat1 – leftRightFloat2 >20) {
176// 右转
177}
178// 如果 index 为 3,则表示张嘴行为
179}elseif(index ==3){
180// 下唇中心的坐标
181CGPoint bottomMouthPoint = [weakSelf scaledPoint:face. bottomMouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
182// 上唇中心的坐标
183CGPoint mouthPoint = [weakSelf scaledPoint:face.mouthPosition xScale:xScale yScale:yScale offset:videoBox.origin];
184if(bottomMouthPoint.y – mouthPoint.y >18){
185// 张嘴
186}
187// 如果 index 为 4,则表示抬头、低头行为
188}elseif(index ==4){
189// 鼻子的坐标
190CGPoint nosePoint = [weakSelf scaledPoint:face.noseBasePosition xScale:xScale yScale:yScale offset:videoBox.origin];
191// 左眼的坐标
192CGPoint leftEyePoint = [weakSelf scaledPoint:face.leftEyePosition xScale:xScale yScale:yScale offset:videoBox.origin];
193// 右脸颊的坐标
194CGPoint rightCheekPoint = [weakSelf scaledPoint:face.rightCheekPosition xScale:xScale yScale:yScale offset:videoBox.origin];
195if(nosePoint.y - leftEyePoint.y <24){
196// 抬头
197}elseif(nosePoint.y - rightCheekPoint.y >0){
198// 低头
199}
200}
201}
202}
  1. 添加问候语

在代码文件 CameraExampleViewController.mm 中添加下列代码,实现问候语的显示功能。具体代码如下:

代码 1 CameraExampleViewController(续)

复制代码

203// 遍历获取到的所有结果
204for(constauto& item : newValues) {
205std::stringlabel = item.second;
206constfloatvalue = item.first;
207if(value >0.5) {
208NSString *nsLabel = [NSStringstringWithCString:label.c_str() encoding:[NSString defaultCStringEncoding]];
209NSString *textString;
210if([nsLabel isEqualToString:@"man"]) {
211textString = @" 先生你好 ";
212}else{
213textString = @" 女士你好 ";
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 工程化项目实战》一书,更多实战内容 点此 查看。

本文经授权发布,转载请联系电子工业出版社。