我是如何用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 就是這樣把, 這個速度也可以了,移動端在進行比對的時候,加一個旋轉菊花就行(這是高科技,短暫的耗時用戶也能接受)。

我覺得過程中還有或者方案,還有能優化的方法,歡迎大家一起討論!


分享到:


相關文章: