我是如何用Java完成人脸识别的

前言

该问题是实际工作遇到的问题,需求很简单,移动端传入照片,后台判断是不是某用户。

考虑到这个在项目中,并不是很严格,因此并不打算集成阿里之类的SDK, 打算自己搞一个人脸识别。

人脸识别

简单介绍一下人脸识别,人脸识别第一步是把人脸图像给采集到(黑白的,不需要彩色照片),然后把人脸图像的特征给描点(把一块像素的东西,拆分出来,黑的是1,白的是0),最后根据这些点采用一定的算法,组成一组编码,我称之为人脸面部编码,这组编码描述了人脸的眼睛特征、嘴巴特征、脸部特征等。两张照片比对,就是比对这两个人脸的面部编码,怎么算是同一个人呢?两组编码的相似度在多少(一般可以配置,用来调整精度)就算是一个人了,是不是很简单呢!

准备工作

百度一下,很少是用Java做的,最简单的就是Python来做人脸识别,据说 face_recognition 有高达99.38%的准确率,但对小孩和亚洲人脸的识别准确率尚待提升(虽然我们项目的用户也是中国人,但是就算识别率低点,也能接受),而且这个用起来简单啊。于是就决定使用Python来做人脸识别,然后用Java调用Python,最后完成整个人脸识别功能

方案分析

方案一:使用Django搭建一个服务,供Java调用

为什么会第一个就想到这个呢?因为我其实并不怎么会Python,之前只用Python做了一个简单的web项目,当是用到的框架就是Django。首先,这个能解决当前的问题,其次,之前用过Django,学习成本不是很大。

方案二:使用 Java的 JPython三方库,调用Python

据说这个是Java调用Python最简单的方法,就是把Python用Java重写了一次。但是这个方法有个缺点,就是不能调用Python的第三方库,只能使用Python自带的包,所以这个方案被pass掉。

方案三 :写Python脚本文件,java直接调用脚本文件

该方法其实是最简单的,因为不需要Python去搭建一个服务,需要的时候,直接用Runtime.getRuntime().exec(..)调用脚本文件。

开始干

综合几种方案下来,最后决定用方案三,因为这个最简单。首先在本地创建一个py文件,就两个方法:1. 获取图片的人脸面部编码;2. 比对编码,是不是同一个人(人脸编码保存在Java项目的数据库中,比对的时候,尽量直接传人脸编码,不要传照片,因为face_recognition 去做人脸编码的过程挺耗时的)。

下面是部分代码:

<code># 获取图片的人脸编码
def get_encodings(fileUrl):
try:
temp_file = './temp.jpg'
urlretrieve(fileUrl, filename=temp_file)
encodings = face_recognition.face_encodings(face_recognition.load_image_file(temp_file))
face_num = len(encodings)
code = []
# 转换一下格式,方便传递参数给Java
for encoding in encodings:
code.append(str(encoding).replace("[", "").replace("]", ""))

return R(code=200, face_num=face_num, encodings=code)

except Exception as e:
return R(code=500, msg=e.__str__())

# 检查编码是不是同一个人
def check_encodings(un_know_encoding, know_encodings):
try:
check_result = face_recognition.compare_faces(know_encodings, un_know_encoding, 0.4)
final_result = 0
all_result = []
for r in check_result:
if r:
all_result.append(1)
final_result = 1
else:
all_result.append(0)
return R(code=200, all_result=all_result, final_result=bool(final_result))
except Exception as e:
return R(code=500, msg=e.__str__())/<code>

经过一番折腾,终于搞定了, 最后使用Runtime.getRuntime().exec(arguments);测试。哎呀,我的那个高兴啊,得到了自己想要的结果。

但是发现一个问题,时间很慢,我每次调用脚本文件,时间都会很长,大概在4s左右,在现在这个网络情况下,还能这么慢,简直不能忍啊。排查了一阵,发现问题所在,搞事的代码,不是上面的两个方法,而是 import face_recognition ,每次执行脚本的时候,都会执行该语句,这个导入语句耗时是真的长(心中默数2秒)。问题找到了,怎么解决呢?

解决耗时问题:

我试着思考,既然执行脚本文件import语句耗时,能不能只执行一次,以后需要的时候, 只需要执行指定的方法,这样就可以解决耗时长的问题,于是我去看Runtime.getRuntime().exec()源码,发现根本不行(可能是自己能力不够)。于是这条路发现不行,不能执行一个人脸识别,让用户等待4~5S吧。找遍各种方法,都不能对此进行优化了。哎,绝望了!

我打算还是用python做一个web服务,让Java通过http的形式调用,这样至少可以解决时间长的问题。于是有google了一下,发现python既然自带httpServer, 只需要简单几行代码就行,都不需要引入新的包(我用的python3)。感觉发现新大陆了,于是网上搜了一圈,很快就搭好了一个本地服务。

现在执行流程:

1.Java项目启动的时候,去执行python脚本,然后启动了python的web服务。

2.Java项目运行中,本地调用Python的http接口

3.Java项目关闭的时候,执行指定脚本,根据端口号(Python启动的时候指定的端口号)来杀掉python进程。

最后测试,发现识别一个照片,大概耗时700ms, 能接受吧,但是感觉还是不够快,可能face_recognition 就是这样把, 这个速度也可以了,移动端在进行比对的时候,加一个旋转菊花就行(这是高科技,短暂的耗时用户也能接受)。

我觉得过程中还有或者方案,还有能优化的方法,欢迎大家一起讨论!


分享到:


相關文章: