最近因為需要實現了一下人臉識別,實現了這個功能之后,就想着把踩過的坑總結出來。參考過許多博客,發現主要有兩種形式,一種是基於 android SDK 實現人臉檢測,而另一種是利用openCvManager來實現。這兩種方式都實現了動態的人臉檢測,當然網上也有許多關於檢測靜態圖片中的人臉的文章。這里我就不詳細介紹了。我這里實現的功能主要是啟動Camera之后,當照相機中有人臉出現時,Camera界面動態的繪制出人臉矩形框,獲取到人臉框之后,Camera拍照,獲取到圖片,將圖片傳送到后台來驗證人臉。這里需要區分一下人臉檢測和人臉識別。人臉檢測是檢測camera中是否有人臉存在,如果存在的話會繪制出矩形框將人臉標志出來,而人臉識別則是根據兩張人臉驗證是否是一個人的概念。
先大概講一下第一種實現動態人臉檢測的方式.這是Camera基於Google自帶的FaceDetectionListener進行人臉檢測,當Camera拍攝到人臉后,會將人臉矩形繪制出來。這個具體大家可以參考這位大神的博客,http://blog.csdn.net/yanzi1225627/article/details/38098729/
他已經講的很細致了。但感覺google自帶檢測算法在測試的時候檢測人臉比較慢,而且有些機型不支持這種人臉檢測接口,不過他的文章還是很具有參考價值的。那么我就在這里就具體介紹一下使用openCv實現動態人臉檢測的具體方法。
先提供下opencv下載地址http://opencv.org/ 我下載的是最新的3.2版本,本來用的是2.4,但會發現比較卡頓。公司一直使用的eclipse,而且demo中的工程也是eclipse項目,所以直接使用eclipse來進行調試。當然網上也有許多在As中使用的教程,大家可以去多找找。
在我們下載sdk后解壓后,進入到samples目錄下會見到如下結構:

我們直接運行example-face-detection.apk后,會發現提示要安裝opencv-manager,在sdk/apk目錄下,我們會發現不同種類的opencvmanager,我們根據不同的機型選擇對應的manager安裝,我選擇的是OpenCV_3.2.0_Manager_3.20_armeabi-v7a.apk這個包,安裝之后,我們打開應用后就可以發現攝像頭進行動態的人臉檢測了。
運行完官方的apk后,我們開始自己進行調試。我們導入OpenCV-android-sdk/samples的face-detection工程,然后新建個工程作為Library,在我們的library中導入OpenCV-android-sdk/java中相關類和資源文件,

然后對face-detection進行關聯,這時候我的工程仍然報錯,發現下面的錯誤:

沒有找到ndk-build.cmd.原來是沒有配置ndk-build路徑,配置完NDKROOT即可。這里就是右鍵properties-C/C++build,如下圖:

這里的ndk我下載的是android-ndk-r9d,在下載完畢之后我們直接解壓就行,這里的NDKROOT就是我們解壓后的ndk的路徑。附帶上Ndk的下載路徑:
http://dl.google.com/android/ndk/android-ndk-r9d-windows-x86.zip
之后又碰見如下錯誤:

這是因為在jni/Android.mk中沒配置opencv的Opencv.mk的根路徑,同理和ndk配置方式一樣,我們配置完我們自己的${OPENCV_ANDROID_SDK}就行了


至此,我們運行官方的demo,可直接就運行出來,但我們發現總需要安裝opencvmanager,很明顯,有時候這是不合適的。所以接下來我介紹下不需要安裝opencvmanager的方法,很簡單,分下面4步:
1.修改Android.mk前幾行 為下列形式,
include $(CLEAR_VARS) OPENCV_CAMERA_MODULES:=on OPENCV_INSTALL_MODULES:=on OPENCV_LIB_TYPE:=SHARED
- 1
- 2
- 3
- 4
2.打開FdActivity.java文件,在其中添加一個靜態初始化塊代碼,
static {
Log.i(TAG, "OpenCV library load!"); if (!OpenCVLoader.initDebug()) { Log.i(TAG, "OpenCV load not successfully"); } else { System.loadLibrary("detection_based_tracker");// load other libraries } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
它是用來加載OpenCV_java庫的,如果沒有加回報類似於native method not found的錯誤
3.注釋掉onResume中的initAsync那句,讓程序不去訪問OpenCV Manager。
@Override public void onResume() { super.onResume(); if (!OpenCVLoader.initDebug()) { Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization"); // OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback); } else { Log.d(TAG, "OpenCV library found inside package. Using it!"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4.修改FdActivity.java的OnCreate()方法,從上面的private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this)代碼塊中拷貝try-catch塊放到OnCreate的setContentView()之后。
try { // load cascade file from application resources InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface); File cascadeDir = getDir("cascade", Context.MODE_PRIVATE); mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml"); FileOutputStream os = new FileOutputStream(mCascadeFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } is.close(); os.close(); mJavaDetector = new CascadeClassifier(mCascadeFile.getAbsolutePath()); if (mJavaDetector.empty()) { Log.e(TAG, "Failed to load cascade classifier"); mJavaDetector = null; } else Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath()); mNativeDetector = new DetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0); cascadeDir.delete(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "Failed to load cascade. Exception thrown: " + e); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
之后運行,即可發現不用在依賴OpenCvManager,程序也可以直接運行了。
這里上述進行的主要是camera動態監測出人臉,其實這基本上已經算完成了。現在我們集成到我們自己的工程中,通過利用百度的人臉識別,來判斷人臉是否一致,從而實現人臉識別。
首先,我們可以在百度AI開放平台上 http://ai.baidu.com/ 創建我們自己的人臉識別應用

創建完后 我們的應用會有一個APPiD和ApI key我們利用這兩個屬性值可生成access_token具體的調用方式我們可參考
https://cloud.baidu.com/doc/FACE/Face-API.html生成access_token后,當發送post請求時,附帶上這個token即可,我們可以進行人臉識別了。我這里調用的是人臉注冊和人臉認證接口,通過認證返回的分數值,我們可判斷是否是同一個人。具體請求參數和返回參數,大家可以參考百度人臉識別的API。
好了,現在開始創建我們的應用。在創建完工程后,我們把face-detection中的FdActivity和DetectionBasedTracker導入到我們自己的工程中,這之中的FdActivity相當於相機界面,這里注意不要改動包名,否則可能會出現找不到本地方法的錯誤。同時,我們要將jni和Lib armead目錄復制到我們的目錄里,之后我們要利用Eclipse自動編譯NDK/JNI。下面簡單介紹一下自動編譯的方法:
1.將Android項目轉換為C/C++項目,如下圖,New -> Other -> C/C++ -> Convert to a C/C++ Project.

2. 配置NDK編譯路徑,Project->Properties,如下圖,其中Build-Command中ANDROID_NDK為環境變量中配置的Android-NDK路徑;Build-Directory為當前工程目錄


3.Project->Properties,CNU C和GNU C++中配置OpenCV的鏈接庫

配置完指后,我們需要稍微修改一下我們自己創建的opencvLibrary的內容。因為camera中出現人臉框后需要拍照功能,我們需要簡單的修改OpenCvLibrary中的JavaCameraView,在這個類里面我們可以調用camera的takepicture方法。同時通過PictureCallback將我們拍的照片保存到我們指定的路徑。
mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback); ShutterCallback mShutterCallback = new ShutterCallback() { public void onShutter() { } }; PictureCallback mJpegPictureCallback = new PictureCallback() { public void onPictureTaken(byte[] data, Camera camera) { Log.d("hr", "拍照回調"); Bitmap b = null; if (null != data) { b = BitmapFactory.decodeByteArray(data, 0, data.length); mCamera.stopPreview(); } if (null != b) { Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 00.0f); boolean bitmap = FileUtil.saveBitmap(rotaBitmap,idTag); if (bitmap) { if (handler!=null) { handler.sendEmptyMessage(2000); } } } mCamera.startPreview(); }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
在FdActivity的 onCameraFramed中 facesArray 得長度 即是人臉顯示的數目
Rect[] facesArray = faces.toArray(); for (int i = 0; i < facesArray.length; i++) Imgproc.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
- 1
- 2
- 3
當相機捕捉到人臉后,我們可以通過handle自動拍照也可以手動拍照,這就看具體的需求了。保存圖片之后,我們調用百度的接口,根據返回值來判斷人體的相似度。
url = new URL(urlPath); Bitmap bmp = FileUtil.getValidateBitmap(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, 100, baos); try { baos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } byte[] buffer = baos.toByteArray(); // 將圖片的字節流數據加密成base64字符輸出 String photo = Base64.encodeToString(buffer, 0, buffer.length,Base64.DEFAULT); HashMap<String, String> map=new HashMap<String, String>(); map.put("uid", uid); map.put("group_id", groupid); map.put("image", photo); map.put("ext_fields", "faceliveness"); String str=HttpClientUtil.doPost(urlPath, map);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
獲取驗證和注冊的方式代碼基本上一樣,就是發送普通的網絡請求,我們根據返回值可以判斷出人臉是否一致。至此整個人臉驗證流程就算結束了。我這里就光展示一下動態檢測人臉的效果
當然還有認證的流程,因為感覺難點主要在動態識別人臉這塊,所以調用百度的接口,實現人臉識別的過程就不再詳細介紹了。當然 ,還有一些小問題,當豎屏時,檢測不准確或者Camera界面沒有全屏, 或者是前置攝像頭的開啟。大家有興趣的話可以去研究一下。