原文地址:
https://blog.csdn.net/qq_34902522/article/details/82464516
基于opencv實現(xiàn)人臉檢測
opencv簡述
opencv是一個開源的計算機視覺庫,它有著C++,Python,Java等接口,支持Windows,Linux,Mac OS,IOS 和 Android平臺.Opencv 是使用C/C++所寫的,可以利用多核處理.通過OpenCL啟用,它可以利用底層異構(gòu)計算平臺的硬件加速航唆。關(guān)于Opencv的詳細介紹可以去其官網(wǎng)查看.Opencv 官網(wǎng)
注意
1.如果對opencv還沒有接觸過的,可以先參考參考這篇文章,了解如何在Android項目接入opencv.android 接入opencv的3種方式 .建議結(jié)合Opencv 的Tutorials看,效果更加.
2.這邊接入使用的opencv library是利用github上面opencv和opencv-contrib庫里的源碼來編譯的適用于Android平臺的庫.這個庫的地址在opencv+opencv-contrib-lib4Android 編的這個庫是3.4.2版本,Android的各個平臺都有.還是很全的.
如果想自己編譯的話,可以參考我之前的文章編譯opencv+opencv-contrib 遇到的坑 .
當(dāng)然你也可以直接從opencv的官網(wǎng)下載人家已經(jīng)編譯好的庫.但是從其官網(wǎng)下載的庫內(nèi)容不全,比如Tracker這一塊的內(nèi)容,就沒有.(PS:后面會更一篇利用OpenCV實現(xiàn)物體追蹤功能的文章胀蛮。就是需要tracker)這是在opencv-contrib庫里的.
那什么是opencv-contrib庫?opencv-contrib庫是一個額外庫,里面包含的是一些較新,高級些的功能模塊.
目標(biāo)
利用Opencv的分類器CascadeClassifier,對從Camera讀取的yuv數(shù)據(jù)進行實時檢測,并且把結(jié)果顯示在屏幕上.
CascadeClassifier介紹#####
CascadeClassifier不僅僅可以檢測人臉,也可以檢測眼睛,身體,嘴巴等.通過加載一個想要檢測的.xml的分類器文件就可以.
開擼####
要實現(xiàn)在相機預(yù)覽畫面的實時人臉檢測,那么關(guān)于Android Camera的部分肯定要先弄起來.這邊為了方便演示,簡單封裝了一個CameraModule庫.來實現(xiàn)相機預(yù)覽,Camera 數(shù)據(jù)回調(diào).
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數(shù)據(jù)之后,我們開始處理opencv部分的邏輯.
1.首先我們需要創(chuàng)建CascadeClassifier.
在Activity的onResume回調(diào)中,先加載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);
}
}
在加載成功的回調(diào)中,創(chuàng)建需要的屬于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();
}
我們分析一下上面的代碼,首先我們創(chuàng)建了一些必須的對象,比如Mat對象.
Mat - The Basic Image Container 在opencv里,對圖像的處理,大都是先把圖像數(shù)據(jù)轉(zhuǎn)化成Mat對象,Mat對象就像是一個容器,對圖像的處理就是對Mat的處理.
然后,我們從raw文件夾里讀取了opencv訓(xùn)練好的,用于檢測人臉的分類器文件lbpcascade_frontalface.xml.xml分類器文件,可以從opencv下載的包里面找到.你會發(fā)現(xiàn),里面有兩種類型的分類器文件,一種是haar的,一種是lbp的.關(guān)于這兩種的不同.可以參考haar-vs-lbp .
這里我們選擇的是lbp的.分類器文件獲取到后,我們通過分類器文件,創(chuàng)建了我們想要的CascadeClassifier.
2.對相機預(yù)覽數(shù)據(jù)的處理
這邊設(shè)置的預(yù)覽的FPS是30,因為人臉檢測是耗時操作,為了不影響預(yù)覽的畫面流暢度.這邊采用了,子線程+隊列的處理方式.通過創(chuàng)建兩個隊列,來保證對相機數(shù)據(jù)的管理.
@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.從隊列獲取數(shù)據(jù),進行檢測
/**
* 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);
}
}
}
}
如上面的代碼所示,我們通過創(chuàng)建子線程,在里面進行face detect處理.
首先我們從隊列中獲取Camera data.接著為mSrcMat對象賦值.接著利用Opencv 的convert方法,對源數(shù)據(jù)進行灰度化處理.
Imgproc.cvtColor(mSrcMat, mDesMat, Imgproc.COLOR_YUV2GRAY_420);
這里主要看下第三個參數(shù).因為我這邊設(shè)置的image format 是NV21 ,所用了這個YUV420 to Gray的flag.我們進去可以看到:
我們發(fā)現(xiàn),只要是YUV420的無論是P還是SP都是用的同一個Flag.還挺省事,省的格式轉(zhuǎn)化了O(∩_∩)O.
我們接著看這行代碼:
mFaceCascade.detectMultiScale(mDesMat, matOfRect, 1.1, 5
, 2, mMinSize, mMaxSize);
這就是檢測的核心代碼,這里說一下各個參數(shù)所代表的含義.
Parameters
image Matrix of the type CV_8U containing an image where objects are detected.
objects Vector of rectangles where each rectangle contains the detected object, the rectangles may be partially outside the original image.
scaleFactor Parameter specifying how much the image size is reduced at each image scale.
minNeighbors Parameter specifying how many neighbors each candidate rectangle should have to retain it.
flags Parameter with the same meaning for an old cascade as in the function cvHaarDetectObjects. It is not used for a new cascade.
minSize Minimum possible object size. Objects smaller than that are ignored.
maxSize Maximum possible object size. Objects larger than that are ignored. If maxSize == minSize model is evaluated on single scale.
參數(shù)的含義,來自官網(wǎng),懶得翻譯。
這里主要說下minNeighbors,minSize,maxSize.這三個參數(shù).
通過這三個參數(shù)可以控制檢測的精確度.
minNeighbors 值越大,檢測的準(zhǔn)確度越高,不過耗時也越久.酌情調(diào)整.
minSize 可以根據(jù)Screen 尺寸的一定比例來設(shè)置,別設(shè)置太小,不然會有一些錯誤干擾結(jié)果.
maxSize 最大可檢測尺寸,酌情調(diào)整.
接著往下看,檢測結(jié)果出來后,是一個rect集合,檢測到的每一張臉是一個矩形....O__O "…
這邊做的處理選擇了一張臉最大的來顯示.
現(xiàn)在我們把代碼跑起來糯钙,看一下效果:
通過效果gif演示粪狼,我們可以很明顯的看到,成功的檢測到了人臉任岸。這里要說個缺點再榄,就是使用OpenCV的CascadeClassifier進行人臉檢測,檢測的結(jié)果是返回一個MatOfRect對象享潜,可理解為rectangle集合困鸥,意思是只記錄著檢測到臉的位置。并沒有其他的信息了剑按,并不能記住識別人臉疾就。注意人臉識別和人臉檢測的區(qū)別。關(guān)于OpenCV的人臉識別(FaceRecognizer)需要對目標(biāo)先進行數(shù)據(jù)訓(xùn)練艺蝴,訓(xùn)練好才能對目標(biāo)成功識別猬腰。關(guān)于OpenCV人臉識別的內(nèi)容,這邊先不說了猜敢,之后的文章再討論姑荷。
結(jié)語
關(guān)于使用OpenCV來進行人臉檢測,就說到這缩擂,后期想到什么要補充的會再補充鼠冕。人臉檢測的實現(xiàn),也寫了一篇利用Firebase Vision ML Kit庫來實現(xiàn)的文章撇叁,建議大家去看看供鸠,做做對比,根據(jù)需要選擇合適的技術(shù)手段來實現(xiàn)陨闹。利用Google vision來實現(xiàn)人臉檢測
演示demo地址如下楞捂。
OpencvFaceDetect