首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

如何实现一个“人脸识别墨镜”的原型系统

在网上看到一条报道:英媒:中国警察戴上人脸识别墨镜,逃犯无处可逃

报道称:该技术允许警员拍摄可疑人员的照片,然后与内部数据库中的照片进行对比。如果有匹配结果,相关人员的姓名和地址等个人信息将被发送给警员。

https://m.sohu.com/a/221764119_114911

这是怎么实现的呢?从技术上分析,“人脸识别墨镜”的工作流程如下:摄像头实时录制现场视频⇒从视频中抽取一帧图像⇒对图像进行处理,检测出其中所有的人脸⇒将检测出的人脸与数据库中的目标照片进行对比⇒若匹配,输出相关结果

很明显,关键的步骤是第3和第4步。其中,第3步是一个目标检测问题,第4步则是一个人脸识别问题。

人脸目标检测:

我正好刚折腾过一轮Google的目标检测API,可以用来实现人脸检测。不过Google提供的现成模型大都是基于coco数据集的,针对检测的目标都是人、车、动物等常见事物,无法直接检测人脸,需要做迁移学习。这里简单说一下模型和训练集的选取及处理。

模型:因为后面想把这个原型搬到手机或谷歌眼镜这样的嵌入式平台上跑,肯定需要一个轻量级的模型,不然像VGG 16这种动辄几百兆的庞然大物光是加载估计都不够内存。这里选择了Google提供的最小的ssd mobilenet模型,训练完pb文件不到30MB。

),收集了来源于美联社和路透社新闻报道的2845张图片,并对图片中的人脸进行了椭圆形标注。由于数据集太小,只能用类似训练好的网络来做fine tune; 同时需要对原有标注进行处理,将椭圆标注统一转换为pascal voc 2007格式的矩形框标注。

选好了模型和处理完训练集后,就可以调用Google的目标检测API训练所需的人脸检测网络啦~ 完整的过程我记录在这个帖子(http://mp.weixin.qq.com/s/HWk1cM7fCBfwkO-7saumHw)里了。训练完的目标检测效果如下:

人脸识别:

通常的CNN分类需要输入大量目标人脸来训练网络,才能实现人脸的分类。然而我们的目标是要筛查疑犯,通常能拿到的疑犯信息都很少,甚至只有单张照片;而且有可能是临时追加目标,没有时间预先训练。那么在这里我们就需要一个能实现单样本学习的模型了,我决定选用孪生网络来搞定这个问题。

虽然知道原理,从头搭一个孪生网络的工程还是太大了,于是我找到了这篇著名的论文:《FaceNet: A Unified Embedding for Face Recognition and Clustering》,一看卧槽又是Google发的,好吧深度学习这块谷歌真是相当的牛啊~

https://github.com/davidsandberg/facenet

上面的网址里提供有训练好的facenet模型,不过我还是打算自己训练一次。因为这个模型还是有点大,训练完生成的pb文件有近100M,并非用于移动终端设备的版本。回头有空打算用轻量级的mobilenet再定制一个移动版,所以现在先尝试一下训练过程。

1. 数据预处理

这一步是要用mtcnn将训练集中图片中的人脸裁剪出来,并将裁剪出的图片统一调整到160×160像素,预处理的命令如下:

python src/align/align_dataset_mtcnn.py ~/Work/Tensorflow/data/lfw/lfw ~/Work/Tensorflow/data/lfw/lfw_mtcnnpy_160 --image_size 160 --margin 32

2. 训练

训练过程中遇到的问题就比较多了,我用的facenet的代码毕竟不是官方提供给开发者使用的正式版,只是论文作者放出了自己用来试验的测试版,有不少小bug。其中最严重的就是显存溢出问题。

当使用GPU计算时,使用者需要控制所分配的显存数量不得超过设定的阈值,否则就会溢出。我的服务器有2块GTX 1080 Ti,共22GB的显存,直接用默认参数训练居然一直报OOM……然后github上的issues和wiki里也没有很明确的解决方案。只好一边查资料一边各种尝试,折腾了2天终于搞明白了。

训练参数中比较影响显存占用的有3个参数:images_per_personpeople_per_batchbatch_size,然后需要指定一个占用显存的比例gpu_memory_fraction,这个值需要小于1而大于实际占用值,否则都会出错,其中images_per_person × people_per_batch必须为3的倍数(因为facenet的训练数据最小单元为三元组),我最终用的训练参数是:

max_nrof_epochs=500

epoch_size=1000

batch_size=60

people_per_batch=30

images_per_person=20

gpu_memory_fraction=0.6

另外代码中有个函数漏了一个输入参数,改动如下图:

用如下命令进行训练:

python src/train_tripletloss.py --logs_base_dir ~/Work/Tensorflow/facenet/log/ --models_base_dir ~/Work/Tensorflow/facenet/models/ --data_dir ~/Work/Tensorflow/data/lfw/lfw_mtcnnpy_160 --image_size 160 --model_def models.inception_resnet_v1 --optimizer RMSPROP --learning_rate 0.01 --weight_decay 1e-4 --max_nrof_epochs 500 --epoch_size 1000 --batch_size 60 --people_per_batch 30 --images_per_person 20 --gpu_memory_fraction 0.6

整个训练过程耗时约2天半。

3. 生成pb格式模型文件

python src/freeze_graph.py ~/Work/Tensorflow/facenet/models/20180207-110417 model-20180207-110417.pb

4. 效果测试

用如下命令对2张人脸图片进行对比:

python src/compare.py ~/Work/Tensorflow/facenet/models/20180207-110417/model-20180207-110417.pb face_0001.jpg face_0002.jpg

会输出一个2×2的矩阵,其中2个元素为0,2个元素非零且相等,非零元素代表2张照片间的距离。若2张照片为同一个人,则该距离较小,通常在0.4~0.8之间;若为不同人,则该距离通常大于1。

代码实现:

搞定了2个模型,终于可以开始写代码了~

Google的Object Detection API和FaceNet项目中都提供了一些demo代码,可以完成人脸检测和识别的任务。不过这些代码有一个共同的问题,它们都是串行的,而人脸检测和识别的过程比较慢,对一帧图像完成处理需要上百毫秒甚至更多。而我希望这个“人脸识别墨镜”能够实时工作,做到看到哪里就检测到哪里,因此需要引入多线程并发处理。

代码的实现流程如下图所示:

采集并显示实时图像需要较高的处理速度,要让人眼感觉图像是连贯的,电影通常采用每秒24帧图像的放映速度。因此代码中单开了一个线程用于摄像头采集和显示连续图像,同时将采集到的每个图像帧都写入一个共享数据区,仅保留最新一帧。

主线程则进行人脸检测和识别的任务,完成识别后,将结果(包括人脸的区域标注框、识别结果)写入另一个共享数据区,同样仅保留最后一次结果,然后重新读取下一个图像帧,重复循环这一过程。

采集显示子线程在每次显示图像帧前,检查共享数据区是否存在标注框和识别结果,有的话读取该结果并在图像帧上绘制,然后送显,等待20ms后读取摄像头,重复循环。

这样,就能保证显示的实时视频画面是连续的,不会因为处理延时导致严重卡顿。但存在的问题是,绘制出的标识框位置存在滞后现象,若图像中人物移动较快,人脸标识框的位置可能会追不上人物实际的移动~ 只能靠优化处理速度来改善。

速度优化:

1. 数据格式转换问题

Google的demo里用PIL.Image来打开图片,处理前要先转换为numpy的array,对实时处理来说,每帧都要转换一次也是相当不小的开销。所以代码里改为用cv2调用摄像头并读取图像帧,读出来就是numpy的array,可以省掉格式转换的耗时。

2. Object Detection

Google的demo代码里,完成目标检测后,通过调用API的utils.visualization_utils来实现对图像帧中目标的矩形标注,并显示其类型和概率。但不知何故,这个接口的调用也非常耗时。

简单研究了一下目标检测返回的结果,原来谷歌按概率排序返回了数量高达100的3个数组,分别存储标识框、概率和分类。在我这个任务里分类只有“人脸”1种,可以不用管它;然后我自己做了一个循环判决,概率(即代码中的score值)低于门限(我取为0.7)的,一律丢弃;谷歌存储标识框的方式也绕了我一阵,返回的boxes值存储的居然是2个对角顶点坐标占图像帧长宽的百分比。所以实际的坐标值计算如下:

x1 = int(cap_size[0]boxes[0][i][1])

y1 = int(cap_size[1]boxes[0][i][0])

x2 = int(cap_size[0]boxes[0][i][3])

y2 = int(cap_size[1]boxes[0][i][2])

box_list[i] = np.array([x1,y1,x2,y2])

其中cap_size是图像帧的长宽,cap_size = (width, height)。

得到了具体坐标值,通过cv2的rectangle()函数来自己绘制目标框,有效加快了处理速度。

3. facenet的人脸识别

其实facenet处理中最需要改善的是换一个轻量级的模型,不过暂时没有时间弄,只能先用它自带的模型了。

facenet代码中进行人脸识别的步骤,也是要先调用mtcnn网络,对输入的2张图片进行预处理,裁剪出脸部,统一调整图像大小,预白化,然后才输入孪生网络,得到2组embedding值,通过计算其距离判决是否为同一个人。

由于前面调用了人脸检测,mtcnn的步骤可以省略,调整大小和预白化则不能省。用于匹配目标的预置照片的embedding值可以提前算好保存,这样实时处理中就仅需要处理图像帧中检测出的人脸,再计算距离进行判决即可。

最终实现的原型系统的效果:

直接上图:

在正常光线下,正脸识别效果良好。不过假如光线太暗,或背光太强,或人脸倾斜角度太大,装个夸张的鬼脸等,就识别不出来了。这些问题估计需要通过改善人脸检测和人脸识别的模型来解决,以后再慢慢研究~

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180304G17CPY00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券