前言
最近新公司剛來就一個(gè)新項(xiàng)目啟動(dòng),讓實(shí)現(xiàn)一個(gè)人臉識(shí)別的平板APP用于門店開門(人臉識(shí)別只是其中一個(gè)模塊),剛好現(xiàn)在AI大潮來襲早晚都要接觸這些,這也是個(gè)契機(jī),二話不說直接開干。
目前市面上做人臉方面的公司非常多,列舉幾個(gè):
- 百度人臉識(shí)別
- Face++
- 商湯科技
- 騰訊
- 虹軟
當(dāng)然還有一些其他的,目前比較出名的大概就這些,差別其實(shí)更多是在技術(shù)支持上(至少我是這么認(rèn)為的)以及費(fèi)用,我接觸的幾個(gè):百度、Face++、虹軟,大概對(duì)比下: - 百度人臉識(shí)
優(yōu)點(diǎn):價(jià)格便宜
缺點(diǎn):技術(shù)支持真的懶得說,常年不在線,找技術(shù)基本都是讓看看這個(gè)看看那個(gè)沒點(diǎn)用 - Face++
優(yōu)點(diǎn):技術(shù)更好,文檔很清晰,因?yàn)闆]有接入暫且不知道技術(shù)支持怎么樣不過應(yīng)該不差
缺點(diǎn):有點(diǎn)小貴 - 虹軟
優(yōu)點(diǎn):全套離線,后臺(tái)前端都可以離線實(shí)現(xiàn),文檔也挺詳細(xì)的
缺點(diǎn):看了下他們技術(shù)論壇,貌似問題也不少,而且回復(fù)也不是很及時(shí)的樣子,最主要是需要自己搭建一套,他們的人臉對(duì)比居然也是放在本地APP數(shù)據(jù)庫的(當(dāng)然這不算是缺點(diǎn)了)
說了這么多,想用啥自己選擇就行,我們公司目前用的百度人臉識(shí)別,進(jìn)入正題不多BB。
一、注冊(cè)百度開發(fā)者賬號(hào)
這個(gè)注冊(cè)稍微要點(diǎn)時(shí)間,大概需要幾個(gè)工作日,而且需要公司的資質(zhì)信息,我們當(dāng)時(shí)已經(jīng)有了所以我就直接拿來用
二、新建項(xiàng)目獲取授權(quán)文件
這一步算是前期測(cè)試的重要步驟,先要到控制臺(tái)(默認(rèn)第一步已經(jīng)完成了)鏈接
這個(gè)控制臺(tái)基本就是人臉識(shí)別的所有開發(fā)SDK,技術(shù)資料的地方了,前端的SDK在SDK管理里面進(jìn)行下載
首先你需要在采集SDK管理里面下載授權(quán)文件,他會(huì)讓你跟你據(jù)包名跟key的MD5來進(jìn)行生成,具體步驟按著他們的操作就行了

就是上圖的樣子,這個(gè)里面的License ID和包名都很重要,包名要根據(jù)你自己項(xiàng)目的包名來寫。
然后下載License等下會(huì)用到
SDK下載跟示例工程下載
在采集SDK管理這里下拉會(huì)看到下載SDK跟示例工程這兩欄
但是我建議你可以直接根據(jù)你們公司的業(yè)務(wù)需求下載對(duì)應(yīng)的示例工程,比如我下載的就是人臉登陸/考勤這個(gè),這里面已經(jīng)包含了全套的離線SDK功能(活體檢測(cè),人臉追蹤,質(zhì)量檢測(cè)等等)
下載完示例工程后把項(xiàng)目導(dǎo)入AS,下面就是改動(dòng)下包名跟License
- 修改包名
在app build里面改成自己上圖里面的包名:com.test.facere - 修改License
把剛下載的License文件導(dǎo)入到對(duì)應(yīng)的 assets包下,修改工程里面Config.java的licenseID(上圖的)以及l(fā)icenseFileName(對(duì)應(yīng)License的文件名)
以上兩步做完沒啥問題示例工程就可以跑起來了
代碼講解
以我用的人臉登陸/考勤這個(gè)示例工程修改優(yōu)化后的項(xiàng)目做參考來大致講解下(其實(shí)每個(gè)示例工程都有集成文檔,可以自己看)
我們項(xiàng)目目前的業(yè)務(wù)邏輯就是用人臉對(duì)比(1:N)進(jìn)行人臉識(shí)別開門,所以要運(yùn)用到的就是人臉活體檢測(cè),人臉追蹤以及人臉對(duì)比這幾個(gè)技術(shù),其中人臉活體檢測(cè)跟人臉追蹤是在離線SDK里面實(shí)現(xiàn)的,人臉對(duì)比是跟百度云服務(wù)器進(jìn)行匹配的,當(dāng)然我們會(huì)在手機(jī)端先把人臉進(jìn)行注冊(cè),不過手機(jī)APP端的人臉注冊(cè)重心在我們自己的服務(wù)器調(diào)用百度的人臉注冊(cè),所以手機(jī)端就是上傳人臉就OK了。
人臉登陸/考勤這個(gè)示例工程這個(gè)項(xiàng)目里面有幾個(gè)Activity,主要是注冊(cè)人臉,人臉登陸,快速檢測(cè)人臉登陸,我用的是快速檢測(cè)人臉登陸(DetectLoginActivity.java)
1.FaceDetectManager
這個(gè)類封裝了人臉檢測(cè)的整體邏輯包括開啟人臉檢測(cè)start,關(guān)閉人臉檢測(cè)stop,設(shè)置人臉檢測(cè)監(jiān)聽器setOnFaceDetectListener,設(shè)置人檢跟蹤回調(diào)setOnTrackListener
2faceDetectManager.setOnFaceDetectListener設(shè)置人臉檢測(cè)監(jiān)聽器
這個(gè)監(jiān)聽器是人臉識(shí)別主要方法之一
public interface OnFaceDetectListener {
void onDetectFace(int status, FaceInfo[] infos, ImageFrame imageFrame);
}
這是它的回調(diào)方法,里面包含了人臉檢測(cè)狀態(tài) status(用于處理人臉距離角度方向等等檢測(cè)),人臉信息infos,這里面是一組人臉人信息不過只用到Infos[0]這個(gè)就好,還有封裝了一幀圖片的imageFrame,包含了該幀圖片的大小等信息

我是在外圍做了一些動(dòng)畫效果,修改了一下它本來的這個(gè)圓形遮罩
這個(gè)類里面已經(jīng)有完善的提示功能其他代碼就暫時(shí)不貼了可以對(duì)照項(xiàng)目看。
說下遇到的問題
- 因?yàn)槲沂瞧桨遄龅臄z像頭用的USB所以距離檢測(cè)(其實(shí)就是人臉的長(zhǎng)寬高)就暫時(shí)屏蔽了(因?yàn)閿z像頭跟手機(jī)攝像頭相比還是有點(diǎn)模糊)
- 用的平板USB攝像頭所以會(huì)出現(xiàn)鏡像卡幀,解決辦法就是
ICameraControl control = cameraImageSource.getCameraControl();
control.setPreviewView(previewView);
control.setCameraFacing(ICameraControl.CAMERA_USB);
previewView.getTextureView().setScaleX(-1);
設(shè)置為USB模式以及previewView預(yù)覽反轉(zhuǎn)
這里設(shè)置USB模式有個(gè)問題,就是這個(gè)工程其實(shí)是一個(gè)手機(jī)端的項(xiàng)目,但是我用到了平板端,在ICameraControl這個(gè)接口中是沒有CAMERA_USB這個(gè)字段的,需要在里面加入
int CAMERA_FACING_BACK = 0;
int CAMERA_FACING_FRONT = 1;
int CAMERA_USB = 2;
@IntDef({CAMERA_FACING_FRONT, CAMERA_FACING_BACK, CAMERA_USB})
@interface CameraFacing {
}
這樣子就可以了
3.faceDetectManager.setOnTrackListener設(shè)置人臉檢測(cè)監(jiān)聽器
回調(diào)方法是
public void onTrack(FaceFilter.TrackedModel trackedModel)
乍一看這個(gè)回調(diào)跟
faceDetectManager.setOnFaceDetectListener
差不多,其實(shí)如果仔細(xì)看FaceDetectManager這個(gè)類的話會(huì)發(fā)現(xiàn)在
private void process(int[] argb, int width, int height, ArgbPool pool)
這個(gè)方法里面有這樣一段代碼
if (value == 0) {
faceFilter.filter(faces, frame);//等于0的時(shí)候才帶過去
}
if (listener != null) {
listener.onDetectFace(value, faces, frame); //檢測(cè)人臉把value值也帶過去,用于判斷人臉位置
}
當(dāng)value為0(表示是一張合格人臉)的時(shí)候會(huì)在FaceFilter中調(diào)用filter方法,并且在該方法中把一個(gè)單個(gè)face設(shè)置到onTrace回調(diào)中,如果listener不為空的話直接放到onDetectFace這個(gè)回調(diào)中,所以從這里也可以看出來其實(shí)
faceDetectManager.setOnFaceDetectListener
就是為了讓你獲取一張合格的人臉(可以在這個(gè)里面處理你具體的合格人臉操作)
回歸正題,既然onTrace回調(diào)是一個(gè)合格的人臉就好辦了,可以直接拿到TrackedModel里面的人臉圖片和服務(wù)器進(jìn)行比對(duì),具體代碼邏輯示例代碼里面也已經(jīng)實(shí)現(xiàn)了,對(duì)比結(jié)束后會(huì)返回一定的分?jǐn)?shù)給你,如果大于80或者你覺得的分?jǐn)?shù)就認(rèn)定這個(gè)是你在手機(jī)端注冊(cè)過的人臉,然后進(jìn)行邏輯處理(比如開門)
遇到的問題
- 由于我們的業(yè)務(wù)需求是要求平板一直運(yùn)行,就算是識(shí)別失敗或者成功也要返回到人臉識(shí)別的頁面(不finish人臉識(shí)別頁面),造成了性能影響很大,主要現(xiàn)象就是每次返回過來了在進(jìn)行識(shí)別會(huì)出現(xiàn)卡住動(dòng)不了,解決辦法就是在onPause和onResume里面加上一個(gè)是否在前臺(tái)的標(biāo)志,如果不在前臺(tái)就讓人臉識(shí)別停止識(shí)別,在前臺(tái)后繼續(xù)識(shí)別(不是單純的faceDetectManager.stop()這樣子會(huì)報(bào)錯(cuò))
- 由于這個(gè)示例工程是手機(jī)端的,我拿到平板端使用肯定需要修改,代碼講解部分已經(jīng)說了USB這個(gè)問題,其實(shí)雖然就是兩行代碼的事,但是當(dāng)時(shí)我試了很久才解決(問百度那邊一直在扯犢子,寫工單也是很久回復(fù)說些沒用的東西,拉了個(gè)微信群也是帶理不理的,最后放棄了,自己去研究)
- 裁剪處理器出現(xiàn)問題
// 設(shè)置檢測(cè)裁剪處理器
faceDetectManager.addPreProcessor(cropProcessor);
這行代碼放在平板端會(huì)報(bào)錯(cuò),所以需要進(jìn)行修改,經(jīng)過調(diào)試發(fā)現(xiàn)出錯(cuò)的原因在FaceCropper這個(gè)類里面
/**
* 裁剪argb中的一塊兒,裁剪框如果超出圖片范圍會(huì)被調(diào)整,所以記得檢查。
* @param argb 圖片argb數(shù)據(jù)
* @param width 圖片寬度
* @param rect 裁剪框
*/
public static int[] crop(int[] argb, int width, Rect rect) {
adjustRect(argb, width, rect);
int[] image = new int[rect.width() * rect.height()];
for (int i = rect.top; i < rect.bottom; i++) {
int rowIndex = width * i;//9
try {
System.arraycopy(argb, Math.abs(rowIndex + rect.left), image, rect.width() * (i - rect.top), rect.width());
} catch (Exception e) {
e.printStackTrace();
return argb;
}
}
return image;
}
以上是修改后的代碼,主要是
System.arraycopy(argb, Math.abs(rowIndex + rect.left), image, rect.width() * (i - rect.top), rect.width());
這段,需要把rowIndex+rect.left 變成正數(shù),之前是一個(gè)負(fù)數(shù)造成越界報(bào)錯(cuò)
- 判斷臉部的中心點(diǎn)位置用于處理臉是否在對(duì)應(yīng)的布局里面
faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() { //設(shè)置人臉檢測(cè)監(jiān)聽器,檢測(cè)后的結(jié)果會(huì)回調(diào)。
@Override
public void onDetectFace(final int retCode, FaceInfo[] infos, ImageFrame frame) {
上面代碼里里面的infos[0] 里面有兩個(gè)參數(shù),一個(gè)是臉中心點(diǎn)X軸坐標(biāo),一個(gè)是Y坐標(biāo)
public class FaceInfo {
public int mWidth;
public int mAngle;
public int mCenter_y;
public int mCenter_x;
public float mConf;
public int[] landmarks;
public int face_id;
public float[] headPose;
public int[] is_live;
也就是mCenter_x和mCenter_y,有了這倆神器你就可以輕松處理臉部位置,讓他在你想要的布局中,我自己邏輯的完整代碼:
if (infos[0]!= null) {
if(info.mCenter_x<218||info.mCenter_x>600){
headXY=false;
str ="請(qǐng)把臉移入框內(nèi)";
}else {
headXY=true;
}
}
以上,我也是剛接觸這塊,很多地方也是慢慢琢磨,有問題的地方還請(qǐng)大家多多指教