基於opencv實現人臉檢測
原文地址:
ofollow,noindex">https://blog.csdn.net/qq_34902522/article/details/82464516
基於opencv實現人臉檢測
opencv簡述
opencv是一個開源的計算機 視覺 庫,它有著C++,Python,Java等介面,支援Windows,Linux,Mac OS,IOS 和 Android平臺.Opencv 是使用C/C++所寫的,可以利用多核處理.通過OpenCL啟用,它可以利用底層異構計算平臺的硬體加速。關於Opencv的詳細介紹可以去其官網檢視. Opencv 官網
注意
1.如果對opencv還沒有接觸過的,可以先參考參考這篇文章,瞭解如何在Android專案接入opencv. android 接入opencv的3種方式 .建議結合Opencv 的Tutorials看,效果更加.
2.這邊接入使用的opencv library是利用github上面opencv和opencv-contrib庫裡的原始碼來編譯的適用於Android平臺的庫.這個庫的地址在 opencv+opencv-contrib-lib4Android 編的這個庫是3.4.2版本,Android的各個平臺都有.還是很全的.
如果想自己編譯的話,可以參考我之前的文章 編譯opencv+opencv-contrib 遇到的坑 .
當然你也可以直接從opencv的官網下載人家已經編譯好的庫.但是從其官網下載的庫內容不全,比如Tracker這一塊的內容,就沒有.(PS:後面會更一篇利用OpenCV實現物體追蹤功能的文章。就是需要tracker)這是在opencv-contrib庫裡的.
那什麼是opencv-contrib庫?opencv-contrib庫是一個額外庫,裡面包含的是一些較新,高階些的功能模組.
目標
利用Opencv的分類器 CascadeClassifier ,對從Camera讀取的yuv資料進行實時檢測,並且把結果顯示在螢幕上.
CascadeClassifier介紹#####
CascadeClassifier不僅僅可以檢測人臉,也可以檢測眼睛,身體,嘴巴等.通過載入一個想要檢測的.xml的分類器檔案就可以.
開擼####
要實現在相機預覽畫面的實時人臉檢測,那麼關於Android Camera的部分肯定要先弄起來.這邊為了方便演示,簡單封裝了一個CameraModule庫.來實現相機預覽,Camera 資料回撥.
CameraApi.getInstance().setCameraId(CameraApi.CAMERA_INDEX_BACK); CameraApi.getInstance().initCamera(this, this); CameraApi.getInstance().setPreviewSize(new Size(previewWidth, previewHeight)); CameraApi.getInstance().setFps(30).configCamera(); CameraApi.getInstance().startPreview(holder);
在成功獲取Camera 的preview資料之後,我們開始處理opencv部分的邏輯.
1.首先我們需要建立CascadeClassifier.
在Activity的onResume回撥中,先載入Opencv的library.
@Override protected 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_4_0, this, mLoaderCallback); } else { Log.d(TAG, "OpenCV library found inside package. Using it!"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } }
在載入成功的回撥中,建立需要的屬於opencv的物件.程式碼如下:
private LoaderCallbackInterface mLoaderCallback = new LoaderCallbackInterface() { @Override public void onManagerConnected(int status) { if (status == LoaderCallbackInterface.SUCCESS) { init(); isLoadSuccess = true; 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(); mFaceCascade = new CascadeClassifier(mCascadeFile.getAbsolutePath()); if (mFaceCascade.empty()) { Log.e(TAG, "Failed to load cascade classifier"); mFaceCascade = null; } else { Log.e(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath()); } cascadeDir.delete(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "Failed to load cascade. Exception thrown: " + e); } } } @Override public void onPackageInstall(int operation, InstallCallbackInterface callback) { } };
private void init() { mSrcMat = new Mat(previewHeight, previewWidth, CvType.CV_8UC1); mDesMat = new Mat(previewHeight, previewWidth, CvType.CV_8UC1); matOfRect = new MatOfRect(); initQueue(); }
我們分析一下上面的程式碼,首先我們建立了一些必須的物件,比如Mat物件.
Mat - The Basic Image Container 在opencv裡,對影象的處理,大都是先把影象資料轉化成Mat物件,Mat物件就像是一個容器,對影象的處理就是對Mat的處理.
然後,我們從raw資料夾裡讀取了opencv訓練好的,用於檢測人臉的分類器檔案lbpcascade_frontalface.xml.xml分類器檔案,可以從opencv下載的包裡面找到.你會發現,裡面有兩種型別的分類器檔案,一種是haar的,一種是lbp的.關於這兩種的不同.可以參考 haar-vs-lbp .
這裡我們選擇的是lbp的.分類器檔案獲取到後,我們通過分類器檔案,建立了我們想要的CascadeClassifier.
2.對相機預覽資料的處理
這邊設定的預覽的FPS是30,因為人臉檢測是 耗時操作 ,為了不影響預覽的畫面流暢度.這邊採用了,子執行緒+佇列的處理方式.通過建立兩個佇列,來保證對相機資料的管理.
@Override public void onPreviewFrameCallback(byte[] data, Camera camera) { mCamera.addCallbackBuffer(data); if (isStart) { CameraRawData rawData = mFreeQueue.poll(); if (rawData != null) { rawData.setRawData(data); rawData.setTimestamp(System.currentTimeMillis()); mFrameQueue.offer(rawData); } } }
3.從佇列獲取資料,進行檢測
/** * face detect thread */ private class DetectThread extends Thread { DetectThread(String name) { super(name); } @Override public void run() { super.run(); while (isStart && isLoadSuccess) { synchronized (mLock) { try { mCameraRawData = mFrameQueue.poll(20, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } if (mCameraRawData == null) { continue; } frameDatas = mCameraRawData.getRawData(); mSrcMat.put(0, 0, frameDatas); Imgproc.cvtColor(mSrcMat, mDesMat, Imgproc.COLOR_YUV2GRAY_420); mFaceCascade.detectMultiScale(mDesMat, matOfRect, 1.1, 5 , 2, mMinSize, mMaxSize); if (matOfRect.toArray().length != 0) { Rect rect = getBiggestFace(matOfRect.toArray()); mResultView.showFace(rect); } else { mResultView.clear(); } mFreeQueue.offer(mCameraRawData); mCamera.addCallbackBuffer(frameDatas); } } } }
如上面的程式碼所示,我們通過建立子執行緒,在裡面進行face detect處理.
首先我們從佇列中獲取Camera data.接著為mSrcMat物件賦值.接著利用Opencv 的convert方法,對源資料進行灰度化處理.
Imgproc.cvtColor(mSrcMat, mDesMat, Imgproc.COLOR_YUV2GRAY_420);
這裡主要看下第三個引數.因為我這邊設定的image format 是NV21 ,所用了這個YUV420 to Gray的flag.我們進去可以看到:

這裡寫圖片描述
我們發現,只要是YUV420的無論是P還是SP都是用的同一個Flag.還挺省事,省的格式轉化了O(∩_∩)O.
我們接著看這行程式碼:
mFaceCascade.detectMultiScale(mDesMat, matOfRect, 1.1, 5 , 2, mMinSize, mMaxSize);
這就是檢測的核心程式碼,這裡說一下各個引數所代表的含義.
Parameters
imageMatrix of the type CV_8U containing an image where objects are detected.
objectsVector of rectangles where each rectangle contains the detected object, the rectangles may be partially outside the original image.
scaleFactorParameter specifying how much the image size is reduced at each image scale.
minNeighborsParameter specifying how many neighbors each candidate rectangle should have to retain it.
flagsParameter with the same meaning for an old cascade as in the function cvHaarDetectObjects. It is not used for a new cascade.
minSizeMinimum possible object size. Objects smaller than that are ignored.
maxSizeMaximum possible object size. Objects larger than that are ignored. If maxSize == minSize model is evaluated on single scale.
引數的含義,來自官網,懶得翻譯。

這裡寫圖片描述
這裡主要說下minNeighbors,minSize,maxSize.這三個引數.
通過這三個引數可以控制檢測的精確度.
minNeighbors 值越大,檢測的準確度越高,不過耗時也越久.酌情調整.
minSize 可以根據Screen 尺寸的一定比例來設定,別設定太小,不然會有一些錯誤干擾結果.
maxSize 最大可檢測尺寸,酌情調整.
接著往下看,檢測結果出來後,是一個rect集合,檢測到的每一張臉是一個矩形....O__O "…
這邊做的處理選擇了一張 臉最大 的來顯示.

這裡寫圖片描述
現在我們把程式碼跑起來,看一下效果:

效果圖
通過效果gif演示,我們可以很明顯的看到,成功的檢測到了人臉。這裡要說個缺點,就是使用OpenCV的CascadeClassifier進行人臉檢測,檢測的結果是返回一個MatOfRect物件,可理解為rectangle集合,意思是隻記錄著檢測到臉的位置。並沒有其他的資訊了,並不能記住識別人臉。注意 人臉識別 和 人臉檢測 的區別。關於OpenCV的人臉識別(FaceRecognizer)需要對目標先進行資料訓練,訓練好才能對目標成功識別。關於OpenCV人臉識別的內容,這邊先不說了,之後的文章再討論。
結語
關於使用OpenCV來進行人臉檢測,就說到這,後期想到什麼要補充的會再補充。人臉檢測的實現,也寫了一篇利用Firebase Vision ML Kit庫來實現的文章,建議大家去看看,做做對比,根據需要選擇合適的技術手段來實現。 利用Google vision來實現人臉檢測
演示demo地址如下。
OpencvFaceDetect