重點(diǎn)是人臉檢測(cè),檢測(cè),檢測(cè)。
就是把人臉檢測(cè)出來(lái),不是識(shí)別,不是識(shí)別,不是識(shí)別。識(shí)別的意思,就是檢測(cè)到人臉,并且通過(guò)數(shù)據(jù)比對(duì),算法分析后得出人臉相識(shí)度的過(guò)程。而檢測(cè),僅僅是檢測(cè)出來(lái)。
針對(duì)全網(wǎng)關(guān)于安卓OpenCV識(shí)別XXX之類的標(biāo)題,而實(shí)際只做了檢測(cè)的相關(guān)文章,在此表示呵呵
回到正題
如何預(yù)覽視頻并進(jìn)行人臉檢測(cè)?
(一)預(yù)覽視頻
可直接使用OpenCV庫(kù)中的JavaCameraView控件,進(jìn)行視頻的預(yù)覽。
1、布局中聲明該對(duì)象:
<?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">
<org.opencv.android.JavaCameraView
android:id="@+id/activity_main_camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
2、在布局中,直接調(diào)用該對(duì)象的enableView()方法,即可預(yù)覽。
備注:權(quán)限申請(qǐng)之類的老生常談,這里不再啰嗦。
(二)構(gòu)建檢測(cè)分類器
OpenCV中,CascadeClassifier是用于分類器進(jìn)行數(shù)據(jù)處理的。首先,構(gòu)建一個(gè)分類器,需要數(shù)據(jù)源,而人臉檢測(cè)的分類器數(shù)據(jù)源,OpenCV官方已經(jīng)有一個(gè)可以直接用了,這里可以直接拿過(guò)來(lái)用。而文件的路徑,就在下載的資源文件中的OpenCV-android-sdk\sdk\etc\lbpcascades目錄下,關(guān)于構(gòu)建項(xiàng)目的流程,不懂可看我上一篇文章
OpenCV導(dǎo)入
把分類器數(shù)據(jù)復(fù)制到主項(xiàng)目的res-raw目錄下,沒(méi)有該目錄就新建,復(fù)制后如下圖:

然后,在應(yīng)用檢測(cè)前,進(jìn)行分類器數(shù)據(jù)復(fù)制到本地,并初始化分類器,代碼如下:
try {
InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
File 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();
cascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
} catch (Exception e) {
Log.e("OpenCVActivity", "Error loading cascade", e);
}
這樣就完成了分類器的構(gòu)建了。
(三)監(jiān)聽視頻數(shù)據(jù)
對(duì)JavaCameraView設(shè)置CameraBridgeViewBase.CvCameraViewListener即可對(duì)視頻數(shù)據(jù)進(jìn)行監(jiān)聽
(四)人臉檢測(cè),基于第三部,在監(jiān)聽回調(diào)方法onCameraFragment()中,對(duì)回調(diào)的視頻數(shù)據(jù)進(jìn)行監(jiān)聽,實(shí)現(xiàn)代碼如下:
@Override
public Mat onCameraFrame(Mat aInputFrame) {
Imgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);
MatOfRect faces = new MatOfRect();
if (cascadeClassifier != null) {
cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 3, 2,
new Size(absoluteFaceSize, absoluteFaceSize), new Size());
}
Rect[] facesArray = faces.toArray();
for (int i = 0; i <facesArray.length; i++){
Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
}
return aInputFrame;
}
首先,這里對(duì)傳入的圖片幀,進(jìn)行了一個(gè)色值轉(zhuǎn)換的才做,然后再通過(guò)分類器的detectMultiScale方法,進(jìn)行人臉檢測(cè),最后通過(guò)Imgproc.rectangle()方法,進(jìn)行繪制人臉。
對(duì)于detectMultiScale方法的傳入?yún)?shù),析意如下:
1.image表示的是要檢測(cè)的輸入圖像
2.objects表示檢測(cè)到的人臉目標(biāo)序列
3.scaleFactor表示每次圖像尺寸減小的比例
4.minNeighbors表示每一個(gè)目標(biāo)至少要被檢測(cè)到3次才算是真的目標(biāo)(因?yàn)橹車南袼睾筒煌拇翱诖笮《伎梢詸z測(cè)到人臉)
5.minSize為目標(biāo)的最小尺寸
6.minSize為目標(biāo)的最大尺寸
最后附上完整代碼:
package com.north.light.libopencv;
import android.Manifest;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCameraView;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener{
private CameraBridgeViewBase openCvCameraView;
private CascadeClassifier cascadeClassifier;
private Mat grayscaleImage;
private int absoluteFaceSize;
private void initializeOpenCVDependencies() {
try {
InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
File 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();
cascadeClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
} catch (Exception e) {
Log.e("OpenCVActivity", "Error loading cascade", e);
}
openCvCameraView.enableView();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_main); // 為該活動(dòng)設(shè)置布局
openCvCameraView = findViewById(R.id.activity_main_camera_view);
openCvCameraView.setCvCameraViewListener(this);
}
@Override
public void onCameraViewStarted(int width, int height) {
grayscaleImage = new Mat(height, width, CvType.CV_8UC4);
absoluteFaceSize = (int) (height * 0.2);
}
@Override
public void onCameraViewStopped() {
}
@Override
public Mat onCameraFrame(Mat aInputFrame) {
Imgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);
MatOfRect faces = new MatOfRect();
if (cascadeClassifier != null) {
cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 3, 2,
new Size(absoluteFaceSize, absoluteFaceSize), new Size());
}
Rect[] facesArray = faces.toArray();
for (int i = 0; i <facesArray.length; i++){
Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
}
return aInputFrame;
}
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
}
initializeOpenCVDependencies();
final String[] permissions = {
Manifest.permission.CAMERA
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions,0);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
Log.d("返回鍵","back back back");
}
}
至此,人臉檢測(cè)已經(jīng)完成,不過(guò)目前檢測(cè)人臉的預(yù)覽顯示,還是橫屏了,官方的demo也是橫屏顯示的,而對(duì)于豎屏顯示,請(qǐng)看我下一篇文章。
that's all---------------------------------------------------------------------------------