轉載請註明作者及出處:https://www.jianshu.com/p/ca3a12bc4911
引言
人臉識別這件事想來早已經不新鮮,在 Android 中的應用也並不廣泛,所以網上相關資料乏善可陳。但是在面對特殊的應用場景時,人臉識別的功能還是有一定的用處的,比如在考勤領域。
網上能搜到的很多示例比較多的是基於科大訊飛或者face++實現的,其中有一個示例做的非常漂亮,推薦大家看一看,SwFace。該項目基於訊飛SDK實現的人臉檢測,使用face++的webapi實現的人臉註冊以及人臉識別。
這些示例都有一個缺點,就是不支持動態識別(可以通過一些巧妙的方法,使用戶無法感知這一過程),無論訊飛的SDK還是face++的webapi都是通過拍攝上傳一張圖片來進行人臉識別,其中訊飛的SDK使用起來很麻煩,官方的接入文檔語焉不詳,但是用來做人臉檢測還是不錯的。
這些平臺都有一個共同的缺點,就是依賴網絡,所有操作都是調用雲端接口,需要良好的網絡環境才能實現人臉的註冊與識別。這對於簽到考勤這一場景(需要較快的識別速度、設備可能處於無網絡狀態)還是很不方便的,另外他們都是收費的。
所以本文將介紹另一個功能完備,性能還算不錯的第三方開發工具,虹軟中國,而且它是免費的。
鑑於本文實質是我理解人臉識別這一需求的一個思維過程,所謂文章整體會比較墨跡,乾貨部分我會加黑處理,大家可以選擇性閱讀。
該項目的地址爲:https://github.com/asdfqwrasdf/ArcFaceDemo
我整理並加註釋的項目地址爲:https://github.com/junerver/ArcFaceDemo (clone 到本地後可以直接 import 後運行)
人臉識別的幾個重要的概念
人臉識別,我們可以理解爲從一個專門保存人臉特徵值的數據集合中找到最匹配的一組特徵值。所以在整個流程中應該包含以下幾個步驟
- 人臉檢測 (FD引擎)
即從攝像頭預覽中檢測到人臉的存在,並且使用一個矩形框出人臉的範圍。 - 人臉註冊
即將一張圖片中的人臉信息,提取出特徵值,將該特徵值與人員信息建立聯繫。 - 人臉識別 (FR引擎)
當檢測出人臉時,對人臉進行識別,如果人臉特徵集合中存在該人臉信息,讀取出該人臉信息及人員信息。
人臉註冊
人臉註冊可以說是整個識別流程的基礎,原因不言而喻,來看看官方demo是如何處理的。
PS:demo非常簡單,我們不做過於詳細的解釋,只介紹流程。
所有人臉註冊的流程都在 RegsiterActivity 文件中處理的,該頁面啓動的時候接受 Intent 中傳來的 imagePath
信息(圖片地址);
第一步:
將拍照獲得的圖片轉爲 Bitmap,然後將其轉化成 NV21 格式的 Byte 數組,因爲我們使用的sdk只能處理 NV21 格式的數據,NV21 格式限制高度不能爲奇數;
mBitmap = Application.decodeImage(mFilePath);
//創建字節數組 大小由拍照傳來的圖片尺寸決定
byte[] data = new byte[mBitmap.getWidth() * mBitmap.getHeight() * 3 / 2];
try {
//將bitmap轉換成nv21,結果保存到data數組中
ImageConverter convert = new ImageConverter();
convert.initial(mBitmap.getWidth(), mBitmap.getHeight(), ImageConverter.CP_PAF_NV21);//此處高度不能爲奇數
if (convert.convert(mBitmap, data)) {
Log.d(TAG, "convert ok!");
}
convert.destroy();
} catch (Exception e) {
e.printStackTrace();
}
第二步:
獲得 NV21 格式的圖片信息數據後,我們使用sdk提供的 FD 人臉檢測引擎,檢測圖片中的人臉信息(人臉 Rect、角度),此處的 Rect 是圖片中人臉位置的矩形。
//創建FD人臉檢測引擎
AFD_FSDKEngine engine = new AFD_FSDKEngine();
AFD_FSDKVersion version = new AFD_FSDKVersion();
List<AFD_FSDKFace> result = new ArrayList<AFD_FSDKFace>(); //人臉探測結果(探測引擎可以識別圖片中的全部人聯繫信息,所以此處是一個List)
//初始化引擎
AFD_FSDKError err = engine.AFD_FSDK_InitialFaceEngine(
FaceDB.appid, FaceDB.fd_key, AFD_FSDKEngine.AFD_OPF_0_HIGHER_EXT, 16, 300);
if (err.getCode() != AFD_FSDKError.MOK) {
//引擎初始化失敗
Message reg = Message.obtain();
reg.what = MSG_CODE;
reg.arg1 = MSG_EVENT_FD_ERROR;
reg.arg2 = err.getCode();
mUIHandler.sendMessage(reg);
}
err = engine.AFD_FSDK_GetVersion(version);
//FD人臉探測,轉化的nv21數據數組,傳入圖片的寬度、高度、NV21、探測結果
err = engine.AFD_FSDK_StillImageFaceDetection(data, mBitmap.getWidth(), mBitmap.getHeight(), AFD_FSDKEngine.CP_PAF_NV21, result);
至此我們就獲得了一張圖片中的全部人臉數據了,他們都被保存在result這個List列表中了。
第三步:
經過上述的兩部,我們已經成功的從圖片中識別到了人臉,並且將該人臉在圖片中的位置獲取到了,接下來我們要做的就是使用 FR 人臉識別引擎識別該位置人臉中的特徵信息。
if (!result.isEmpty()) {
//探測結果不爲空-存在人臉,初始化FR人臉識別引擎
AFR_FSDKVersion version1 = new AFR_FSDKVersion();
AFR_FSDKEngine engine1 = new AFR_FSDKEngine();
AFR_FSDKFace result1 = new AFR_FSDKFace(); //人臉特徵實例
AFR_FSDKError error1 = engine1.AFR_FSDK_InitialEngine(FaceDB.appid, FaceDB.fr_key);
if (error1.getCode() != AFD_FSDKError.MOK) {
//人臉識別引擎初始化失敗
Message reg = Message.obtain();
reg.what = MSG_CODE;
reg.arg1 = MSG_EVENT_FR_ERROR;
reg.arg2 = error1.getCode();
mUIHandler.sendMessage(reg);
}
error1 = engine1.AFR_FSDK_GetVersion(version1);
//提取人臉識別特徵 傳入值爲:傳入的圖片(NV21轉換後)、圖片的寬度、高度、格式、人臉檢測結果列表中取出的人臉Rect、角度、提取出的人臉特徵
error1 = engine1.AFR_FSDK_ExtractFRFeature(data, mBitmap.getWidth(), mBitmap.getHeight(), AFR_FSDKEngine.CP_PAF_NV21, new Rect(result.get(0).getRect()), result.get(0).getDegree(), result1);
if(error1.getCode() == error1.MOK) {
//提取出了特徵
mAFR_FSDKFace = result1.clone(); //clone提取出的人臉特徵
int width = result.get(0).getRect().width();
int height = result.get(0).getRect().height();
Bitmap face_bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);//人臉位置Rect的bitmap
Canvas face_canvas = new Canvas(face_bitmap);
face_canvas.drawBitmap(mBitmap, result.get(0).getRect(), new Rect(0, 0, width, height), null); //將檢測到的人臉位置圖片提取到face_bitmap中
Message reg = Message.obtain();
reg.what = MSG_CODE;
reg.arg1 = MSG_EVENT_REG;
reg.obj = face_bitmap;
mUIHandler.sendMessage(reg);
} else {
//沒有提取出特徵
Message reg = Message.obtain();
reg.what = MSG_CODE;
reg.arg1 = MSG_EVENT_NO_FEATURE;
mUIHandler.sendMessage(reg);
}
error1 = engine1.AFR_FSDK_UninitialEngine();//結束人臉識別FR引擎
} else {
//人臉檢測結果爲空,圖片中不存在人臉
Message reg = Message.obtain();
reg.what = MSG_CODE;
reg.arg1 = MSG_EVENT_NO_FACE;
mUIHandler.sendMessage(reg);
}
err = engine.AFD_FSDK_UninitialFaceEngine(); //結束人臉檢測FD引擎
第四步:
到此我們已經獲得了整個人臉註冊流程中所需要的幾個關鍵值了:
- 人臉位置 Rect 及該 Rect 的 Bitmap;
- 人臉特徵信息實例 mAFR_FSDKFace;
接下來我們來將人臉特徵信息與人員信息建立關聯,並且將人臉特徵信息保存到本地,這個數據將會用於人臉識別獲取人員信息的流程。
我們先來看看官方的 Demo 是如何處理的:
if (msg.arg1 == MSG_EVENT_REG) {
//人臉特徵信息識別成功,彈出一個對話框,輸入該特徵的註冊名字(關聯的人員信息,此處根據業務需求處理)
//**********省略*********
//****關鍵代碼****添加人臉結果 用名字作爲key
((Application)RegisterActivity.this.getApplicationContext())
.mFaceDB.addFace(mEditText.getText().toString(), mAFR_FSDKFace);
//**********省略*********
}
獲取 Application 中的 mFaceDB 對象,調用其中的 addFace 方法。FaceDb 類是 demo 中官方寫的一個人臉特徵管理類,其實現是文件方式實現的,當然我們可以採用其他方式來實現暫且按下不表。
//addface
public void addFace(String name, AFR_FSDKFace face) {
try {
//檢查全部已註冊的人臉特徵信息
boolean add = true;//默認需要註冊新增人員
for (FaceRegist frface : mRegister) {
if (frface.mName.equals(name)) {
//存在該人員的信息,直接向其特徵list中添加新的特徵
frface.mFaceList.add(face);
add = false;
break;
}
}
if (add) { //該人員信息沒有註冊過
FaceRegist frface = new FaceRegist(name); //創建一個人員人臉特徵類
frface.mFaceList.add(face);//添加一個人臉特徵
mRegister.add(frface);
}
//saveInfo()方法會清空原有txt文件,重新向其中添加sdk版本信息
if (saveInfo()) {
//update all names
//把當前全部數據的人員名稱重新添加到txt文件
FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true);
ExtOutputStream bos = new ExtOutputStream(fs);
for (FaceRegist frface : mRegister) {
bos.writeString(frface.mName);
}
bos.close();
fs.close();
//將人臉特徵信息寫入.data文件中
fs = new FileOutputStream(mDBPath + "/" + name + ".data", true);
bos = new ExtOutputStream(fs);
bos.writeBytes(face.getFeatureData());
bos.close();
fs.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
至此人臉註冊的整個流程就已經完畢了,其中有很多方法我們不必細究其實現細節,只要先了解其流程即可,畢竟我們第一步是把項目運行起來,並且能參照官方的 Demo 集成到自己項目中去。
在下一篇中,我們再來看看官方 Demo 中人臉識別是如何實現的。