基於虹軟人臉識別引擎,在Android平臺上實現人臉識別功能,即便在離線的狀況下依舊運行,不被人採集我的照片的感受,仍是爽爽的。通過整個測試過來,虹軟的人臉識別仍是很強大的,人臉檢測能夠控制在20ms以內,人臉識別大概在200ms左右。今天就來分享一下開發經驗php
項目的目標html
咱們須要實現一我的臉識別功能。簡單來講,就是機的後置攝像頭,識別攝像頭中實時拍到的人臉信息,若是人庫註冊過,則顯示識別後的人臉信息,如登記的名字;若是不在,提示未註冊。
這個功能具備多個應用場景,好比,火車站或者打卡和門禁系統中。java
人臉識別的過程mysql
人臉識別包括兩個必備的過程,人臉註冊和實時識別。
人臉註冊是指把人臉的特徵信息註冊到人臉信息庫中。人臉註冊的來源能夠有不少種,好比android
國家身份證庫git
企業自建人臉識別庫github
互聯網大數據庫算法
人臉特徵提取是一個不可逆的過程,你沒法從人臉特徵信息還原一我的的臉部照片。
在線庫在使用時,須要傳遞照片信息,或者提取圖像特徵值,
離線的SDK相對安全,可是,在線的SDK一般提供更多的接入和調用方式,這個要結合實際狀況來選擇。
人臉註冊和識別的過程能夠用下面的圖來表示。 sql
準備工做數據庫
在開發以前須要到虹軟的官網
http://www.arcsoft.com.cn/ai/arcface.html
下載用到的android庫,下載的壓縮包中有3個壓縮包,以下圖:
其中,第一個Face Detection,人臉檢測。
人臉檢測是人臉技術的基礎,使用虹軟人臉引擎,可以幫助您檢測而且定位到影像(圖片或者視頻)中的人臉。
第二個Face Recognition,
人臉識別。引擎可獨立運行在終端設備或者獨立服務器中,應用端可獨立完成算法運行,能保證用戶數據的私密性,自主運營與保護用戶敏感信息。
第三個Face Tracking,
人臉跟蹤。精肯定位並追蹤面部區域位置,隨着人物臉部位置的變化可以快速定位人臉位置,而且適用於不一樣表情、性別、年齡、姿態、光照等條件。
這三包的結構基本相同,咱們須要把它們解壓。
注意:開發中還須要APP_Id和SDK_Key的激活碼,這些激活碼能夠在帳號管理--》您的申請記錄,對應的下載應用中找到相應的激活碼。
版本與環境要求
根據SDK的說明,咱們使用的版本爲android arm32,版本爲5.0.咱們使用的IDE爲android studio,你也可使用eclipse,不過依然建議你使用android studio,由於它如今已是事實上的標準。
一步一步實現人臉識別功能
本文將以這三個庫爲基礎,從人臉註冊開始,到人臉識別結束。全程演示人臉識別的流程。若是你不想從頭開始,你能夠到https://github.com/asdfqwrasdf/ArcFaceDemo 下載完整的示例程序
新建項目
打開android studio,創建項目,API兼容性選擇4.0。
導入依賴包
虹軟人臉SDK的包是so包,你能夠在下載的壓縮包中把這些文件找到並導入。
導入後的工程文件夾以下所示。
定義並實現人臉庫的相關功能
如前面所述,咱們但願定義本身 的人臉庫,人臉庫在程序中使用List存儲,在系統中保存爲txt文件。
經過查詢引擎,能夠知道人臉信息是保存在AFR_FSDKFace類中的。這的主要結構爲
public static final int FEATURE_SIZE = 22020;
byte[] mFeatureData;
咱們須要定義另一個類來把人臉信息和姓名關聯起來。
class FaceRegist {
String mName;
List<AFR_FSDKFace> mFaceList;
public FaceRegist(String name) {
mName = name;
mFaceList = new ArrayList<>();
}
}
包含特徵信息的長度和內容的byte數組。
咱們把這些功能定義在類FaceDB中。FaceDB須要包含引擎定義,初始化,把人臉信息保存在版本庫和從版本庫中讀出人臉信息這些功能
初始化引擎
爲了程序結構性考慮,咱們將人臉識別相關的代碼獨立出來一個類FaceDB,並定義必要的變量
public static String appid = "bCx99etK9Ns4Saou1EbFdC18xHdY9817EKw****";
public static String ft_key = "CopwZarSihp1VBu5AyGxfuLQdRMPyoGV2C2opc****";
public static String fd_key = "CopwZarSihp1VBu5AyGxfuLXnpccQbWAjd86S8****";
public static String fr_key = "CopwZarSihp1VBu5AyGxfuLexDsi8yyELdgsj4****";
String mDBPath;
List<FaceRegist> mRegister;
AFR_FSDKEngine mFREngine;
AFR_FSDKVersion mFRVersion;
定義有參數的構造函數來初始化引擎
public FaceDB(String path) {
mDBPath = path;
mRegister = new ArrayList<>();
mFRVersion = new AFR_FSDKVersion();
mUpgrade = false;
mFREngine = new AFR_FSDKEngine();
AFR_FSDKError error = mFREngine.AFR_FSDK_InitialEngine(FaceDB.appid, FaceDB.fr_key);
if (error.getCode() != AFR_FSDKError.MOK) {
Log.e(TAG, "AFR_FSDK_InitialEngine fail! error code :" + error.getCode());
} else {
mFREngine.AFR_FSDK_GetVersion(mFRVersion);
Log.d(TAG, "AFR_FSDK_GetVersion=" + mFRVersion.toString());
}
}
定義析構函數釋放引擎佔用的系統資源
public void destroy() {
if (mFREngine != null) {
mFREngine.AFR_FSDK_UninitialEngine();
}
}
實現人臉增長和讀取功能
一般人臉庫會存放在數據庫中,本次咱們使用List來進行簡單的模擬,並將其保存在文本文件中,須要時從文本中讀取,保存時寫入到文件中。
咱們使用addFace方法將待註冊的人臉信息添加到人臉庫中
public void addFace(String name, AFR_FSDKFace face) {
try {
//check if already registered.
boolean add = true;
for (FaceRegist frface : mRegister) {
if (frface.mName.equals(name)) {
frface.mFaceList.add(face);
add = false;
break;
}
}
if (add) { // not registered.
FaceRegist frface = new FaceRegist(name);
frface.mFaceList.add(face);
mRegister.add(frface);
}
if (!new File(mDBPath + "/face.txt").exists()) {
if (!saveInfo()) {
Log.e(TAG, "save fail!");
}
}
//save name
FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true);
ExtOutputStream bos = new ExtOutputStream(fs);
bos.writeString(name);
bos.close();
fs.close();
//save feature
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();
}
}
使用loadFaces從文件中讀取人臉
public boolean loadFaces(){
if (loadInfo()) {
try {
for (FaceRegist face : mRegister) {
Log.d(TAG, "load name:" + face.mName + "'s face feature data.");
FileInputStream fs = new FileInputStream(mDBPath + "/" + face.mName + ".data");
ExtInputStream bos = new ExtInputStream(fs);
AFR_FSDKFace afr = null;
do {
if (afr != null) {
if (mUpgrade) {
//upgrade data.
}
face.mFaceList.add(afr);
}
afr = new AFR_FSDKFace();
} while (bos.readBytes(afr.getFeatureData()));
bos.close();
fs.close();
}
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
if (!saveInfo()) {
Log.e(TAG, "save fail!");
}
}
return false;
}
實現業務邏輯
實現人臉註冊功能
人臉識別的前提條件就是人臉信息要先註冊到人臉庫中,註冊人臉庫
第一步固然是獲取待註冊的照片,咱們能夠可使用攝像頭,也可使用照片。咱們使用AlertDialog彈出選擇框
new AlertDialog.Builder(this)
.setTitle("請選擇註冊方式")
.setIcon(android.R.drawable.ic_dialog_info)
.setItems(new String[]{"打開圖片", "拍攝照片"}, this)
.show();
在對應的事件處理函數中進行處理
switch (which){
case 1://攝像頭
Intent getImageByCamera = new Intent("android.media.action.IMAGE_CAPTURE");
ContentValues values = new ContentValues(1);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
mPath = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
getImageByCamera.putExtra(MediaStore.EXTRA_OUTPUT, mPath);
startActivityForResult(getImageByCamera, REQUEST_CODE_IMAGE_CAMERA);
break;
case 0://圖片
Intent getImageByalbum = new Intent(Intent.ACTION_GET_CONTENT);
getImageByalbum.addCategory(Intent.CATEGORY_OPENABLE);
getImageByalbum.setType("image/jpeg");
startActivityForResult(getImageByalbum, REQUEST_CODE_IMAGE_OP);
break;
default:;
}
獲取一張照片後,後續咱們就須要實現人臉檢測功能。
if (requestCode == REQUEST_CODE_IMAGE_OP && resultCode == RESULT_OK) {
mPath = data.getData();
String file = getPath(mPath);
//TODO: add image coversion
}
在上面的代碼中,咱們獲取到了咱們須要的圖像數據bmp,把圖片取出來
咱們在Application類用函數 decodeImage中實現這段代碼
public static Bitmap decodeImage(String path) {
Bitmap res;
try {
ExifInterface exif = new ExifInterface(path);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
BitmapFactory.Options op = new BitmapFactory.Options();
op.inSampleSize = 1;
op.inJustDecodeBounds = false;
//op.inMutable = true;
res = BitmapFactory.decodeFile(path, op);
//rotate and scale.
Matrix matrix = new Matrix();
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
matrix.postRotate(90);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
matrix.postRotate(180);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
matrix.postRotate(270);
}
Bitmap temp = Bitmap.createBitmap(res, 0, 0, res.getWidth(), res.getHeight(), matrix, true);
Log.d("com.arcsoft", "check target Image:" + temp.getWidth() + "X" + temp.getHeight());
if (!temp.equals(res)) {
res.recycle();
}
return temp;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
調用AFD_FSDK_StillImageFaceDetection返回檢測到的人臉信息
人臉註冊 ,首先要先檢測出來人臉,對於靜態圖片,虹軟人臉SDK中對應的是FD,提供了一個方法名稱,叫AFD_FSDK_StillImageFaceDetection 。
咱們來看一下參數列表
類型 名稱 說明
byte[] data 輸入的圖像數據
int width 圖像寬度
int height 圖像高度
int format 圖像格式
List list 檢測到的人臉會放到到該列表裏。
注意AFD_FSDKFace對象引擎內部重複使用,如需保存,請clone一份AFD_FSDKFace對象或另外保存
AFD_FSDKFace是人臉識別的結果,定義以下
public class AFD_FSDKFace {
Rect mRect;
int mDegree;
}
mRect定義一個了一個矩形框Rect
在此以前咱們須要注意虹軟人臉SDK使用的圖像格式是NV21的格式,因此咱們須要將獲取到的圖像轉化爲對應的格式。在Android_extend.jar中提供了對應的轉換函數
byte[] data = new byte[mBitmap.getWidth() * mBitmap.getHeight() * 3 / 2];
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();
如今咱們就能夠調用AFD_FSDK_StillImageFaceDetection方法了
err = engine.AFD_FSDK_StillImageFaceDetection(data, mBitmap.getWidth(), mBitmap.getHeight(), AFD_FSDKEngine.CP_PAF_NV21, result);
繪出人臉框
在List中保存了檢測到的人臉的位置信息和深度信息。
咱們能夠將檢測到的人臉位置信息在圖片上用一個矩形框繪製出來表示檢測到的人臉信息。
Canvas canvas = mSurfaceHolder.lockCanvas();
if (canvas != null) {
Paint mPaint = new Paint();
boolean fit_horizontal = canvas.getWidth() / (float)src.width() < canvas.getHeight() / (float)src.height() ? true : false;
float scale = 1.0f;
if (fit_horizontal) {
scale = canvas.getWidth() / (float)src.width();
dst.left = 0;
dst.top = (canvas.getHeight() - (int)(src.height() * scale)) / 2;
dst.right = dst.left + canvas.getWidth();
dst.bottom = dst.top + (int)(src.height() * scale);
} else {
scale = canvas.getHeight() / (float)src.height();
dst.left = (canvas.getWidth() - (int)(src.width() * scale)) / 2;
dst.top = 0;
dst.right = dst.left + (int)(src.width() * scale);
dst.bottom = dst.top + canvas.getHeight();
}
canvas.drawBitmap(mBitmap, src, dst, mPaint);
canvas.save();
canvas.scale((float) dst.width() / (float) src.width(), (float) dst.height() / (float) src.height());
canvas.translate(dst.left / scale, dst.top / scale);
for (AFD_FSDKFace face : result) {
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10.0f);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(face.getRect(), mPaint);
}
canvas.restore();
mSurfaceHolder.unlockCanvasAndPost(canvas);
break;
}
}
將人臉註冊到人臉庫
檢測到了人臉,咱們能夠輸入相應的描述信息,加入到人臉庫中。
爲了提升識別的準確性,咱們能夠對一我的屢次註冊人臉信息。
public void addFace(String name, AFR_FSDKFace face) {
try {
//check if already registered.
boolean add = true;
for (FaceRegist frface : mRegister) {
if (frface.mName.equals(name)) {
frface.mFaceList.add(face);
add = false;
break;
}
}
if (add) { // not registered.
FaceRegist frface = new FaceRegist(name);
frface.mFaceList.add(face);
mRegister.add(frface);
}
if (!new File(mDBPath + "/face.txt").exists()) {
if (!saveInfo()) {
Log.e(TAG, "save fail!");
}
}
//save name
FileOutputStream fs = new FileOutputStream(mDBPath + "/face.txt", true);
ExtOutputStream bos = new ExtOutputStream(fs);
bos.writeString(name);
bos.close();
fs.close();
//save feature
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();
}
}
最後,別忘記了銷燬人臉檢測引擎哦
err = engine.AFD_FSDK_UninitialFaceEngine();
Log.d("com.arcsoft", "AFD_FSDK_UninitialFaceEngine =" + err.getCode());
實現人臉識別
上面的代碼準備完畢後,就能夠開始咱們的人臉識別的功能了。咱們使用一個第三方的擴展庫,ExtGLSurfaceView的擴展 庫CameraGLSurfaceView,用ImageView和TextView顯示檢測到的人臉和相應的描述信息。
首先是定義layout。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
<com.guo.android_extend.widget.CameraSurfaceView android:id="@+id/surfaceView" android:layout_width="1dp" android:layout_height="1dp"/>
<com.guo.android_extend.widget.CameraGLSurfaceView android:id="@+id/glsurfaceView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true"/>
<ImageView android:id="@+id/imageView" android:layout_width="120dp" android:layout_height="120dp" android:layout_marginLeft="10dp" android:layout_marginTop="10dp"/>
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/imageView" android:layout_alignRight="@+id/imageView" android:layout_below="@+id/imageView" android:layout_marginTop="10dp" android:text="@string/app_name" android:textAlignment="center"/>
<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/imageView" android:layout_alignRight="@+id/imageView" android:layout_below="@+id/textView" android:layout_marginTop="10dp" android:text="@string/app_name" android:textAlignment="center"/>
</RelativeLayout>
由於引擎須要的圖像格式是NV21的,因此須要將攝像頭中的圖像格式預設置爲NV21
public Camera setupCamera() {
// TODO Auto-generated method stub
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mWidth, mHeight);
parameters.setPreviewFormat(ImageFormat.NV21);
for( Camera.Size size : parameters.getSupportedPreviewSizes()) {
Log.d(TAG, "SIZE:" + size.width + "x" + size.height);
}
for( Integer format : parameters.getSupportedPreviewFormats()) {
Log.d(TAG, "FORMAT:" + format);
}
List<int[]> fps = parameters.getSupportedPreviewFpsRange();
for(int[] count : fps) {
Log.d(TAG, "T:");
for (int data : count) {
Log.d(TAG, "V=" + data);
}
}
mCamera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
}
if (mCamera != null) {
mWidth = mCamera.getParameters().getPreviewSize().width;
mHeight = mCamera.getParameters().getPreviewSize().height;
}
return mCamera;
}
從攝像頭識別人臉,須要使用FT庫,FT庫在人臉跟蹤算法上對人臉檢測部分進行了優化,是專門爲視頻處理而優化的庫。
初始化人臉檢測引擎(FT)
和FD同樣,咱們須要初始化人臉識別FT引擎。
Log.d(TAG, "AFT_FSDK_InitialFaceEngine =" + err.getCode());
err = engine.AFT_FSDK_GetVersion(version);
Log.d(TAG, "AFT_FSDK_GetVersion:" + version.toString() + "," + err.getCode());
在攝像頭的預覽事件處理函數中,先調用FT的人臉識函數函數,而後再調用FR中的人臉信息特徵提取數函數。
AFT_FSDKError err = engine.AFT_FSDK_FaceFeatureDetect(data, width, height, AFT_FSDKEngine.CP_PAF_NV21, result);
AFR_FSDKError error = engine.AFR_FSDK_ExtractFRFeature(mImageNV21, mWidth, mHeight, AFR_FSDKEngine.CP_PAF_NV21,mAFT_FSDKFace.getRect(), mAFT_FSDKFace.getDegree(), result);
這裏面的result中保存了人臉特徵信息。咱們能夠將其保存下來或下來並與系統中的其它信息進行對比。
AFR_FSDKMatching score = new AFR_FSDKMatching();
float max = 0.0f;
String name = null;
for (FaceDB.FaceRegist fr : mResgist) {
for (AFR_FSDKFace face : fr.mFaceList) {
error = engine.AFR_FSDK_FacePairMatching(result, face, score);
Log.d(TAG, "Score:" + score.getScore() + ", AFR_FSDK_FacePairMatching=" + error.getCode());
if (max < score.getScore()) {
max = score.getScore();
name = fr.mName;
}
}
}
當score的特徵信息大於0.6時,咱們就能夠認爲匹配到了人臉。顯示人臉匹配信息。
上面的循環中,能夠看到,是遍歷了真個庫進行尋找。咱們的目的是爲了演示,實際狀況下,咱們能夠在找到一個匹配值比較高的人臉後,就跳出循環。
運行結果
咱們來看一下運行的結果。
效果還不錯吧。鍾漢良帥哥一枚。
本文檔中全部的代碼均可以在https://github.com/asdfqwrasdf/ArcFaceDemo 下載。若是你須要尋找更多的人臉識別的demo,也能夠到虹軟的論壇中去尋找。
http://www.arcsoft.com.cn/bbs/forum.php?mod=forumdisplay&fid=45&page=1
附錄:會遇到的問題及解決方案
若是你使用的是github中的示例,你可能會遇到下面的問題。
Plugin with id ‘com.android.application’ not found
直接從github上下載的源代碼會有這個問題。
解決方案:打開 [項目文件夾]\app\build.gradle 文件
在文件末尾添加
buildscript { repositories { mavenCentral() }
dependencies { classpath 'com.android.tools.build:gradle:1.0.0' }
}
Failed to find build tools revision 25.0.0.2
這個主要是build 的版本和gradle中指定的版本不一致,按照提示下載或者修改版本指定就能夠了。
android {
compileSdkVersion 24
buildToolsVersion "25.0.2"
}
install_failed_no_maching_abis
下載的代碼在gradle編譯完成後,直接默認運行會出現這個錯誤。緣由是因爲使用了native libraries 。該native libraries 不支持當前的cpu的體系結構。
首先請檢查是否導入了必要的so文件。一共須要導入四個.so文件。
另外,請確認使用是的真機調試。由於調用了攝像頭,請使用真機調試。
後記
人臉識別是當前的熱點技術,使用範圍廣,用戶體驗良好,對硬件的依賴低,不須要昂貴的傳感器芯片。一個高清的攝像頭就能夠完成。之前的成本是人臉識別的SDK比較貴,但如今虹軟的SDK免費以後,集成的成本就大大下降了。