2021 OpenCV4.2+Android Studio4.0實作人臉檢測以及截取人臉區域(SDK)
- OpenCV在Android中的配置
- 整體思路
- 在MainActivity中插入以下代碼
- Activity的XML
- AndroidManifest.xml添加權限
- 將Values中的strings.xml修改
- 在menu中添加一個camera_view_menus.xml
- 最后記得添加級聯分類器
- 截取人臉區域的函式
- 上一個鏈接,里面是我上面所述的demo,
初學者記錄一下學習程序,同時分享一下結果,希望能讓大家少走彎路,
OpenCV在Android中的配置
我用的是OpenCV4.2+android studio4.0做的人臉檢測,關于如何在Android中配置OpenCV這篇文章很全面,可以參考(一定能配出來,出不來請仔細核對每一個步驟),
https://www.jianshu.com/p/6e16c0429044
這篇文章連NDK環境也一起配置了,當然我這里的內容要實作其實只要配置到NDK之前就行(因為我是SDK實作人臉檢測),當然全部配完也沒問題,
整體思路
利用JavaCameraView呈現攝像頭畫面,加載OpenCV自帶的haarcascade_frontalface_alt_tree.xml級聯分類器(測驗了多個級聯分類器,該分類器效果最好),用分類器去檢測每一幀的影像是否存在人臉,并且用框框住,
PS:最后會介紹如何將框住的人臉自動截出來,并且保存本地路徑,
實物圖(手動打碼):


在MainActivity中插入以下代碼
因為初學的原因,還試了一些影像處理的小例子,所以用了 PopupMenu來分別實作不同的功能,分為以下幾種:

public class MainActivity extends CameraActivity implements CameraBridgeViewBase.CvCameraViewListener2, View.OnClickListener {
private JavaCameraView javaCameraView;
private static int cameraIndex = 0;
private Mat frame;
int option =0;
private CascadeClassifier face_detector;
private BaseLoaderCallback baseLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
initClassifier();
javaCameraView.enableView();
}
break;
default:
super.onManagerConnected(status);
break;
}
}
};
/*
加載一個OpenCV自帶的正臉人臉級聯分類器
haarcascade_frontalface_alt_tree需要提前放入新建檔案夾raw
*/
private void initClassifier() {
try {
InputStream is = getResources().openRawResource(R.raw.haarcascade_frontalface_alt_tree);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
File cascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt_tree.xml");
FileOutputStream os = new FileOutputStream(cascadeFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();
face_detector = new CascadeClassifier(cascadeFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected List<? extends CameraBridgeViewBase> getCameraViewList() {
List<CameraBridgeViewBase> list = new ArrayList<>();
list.add(javaCameraView);
return list;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
javaCameraView = findViewById(R.id.javaCameraView);
javaCameraView.setVisibility(SurfaceView.VISIBLE);
javaCameraView.setCvCameraViewListener(this);
RadioButton back =findViewById(R.id.radioButton2);
RadioButton front = findViewById(R.id.radioButton);
back.setOnClickListener(this);
front.setOnClickListener(this);
back.setSelected(true);
Button a =findViewById(R.id.b1);
a.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String face1= saveMatData(frame,"face1.jpg");
}
});
Button button =findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(MainActivity.this, v);
popupMenu.getMenuInflater().inflate(R.menu.camera_view_menus, popupMenu.getMenu());
popupMenu.show();
// 通過上面這幾行代碼,就可以把控制元件顯示出來了
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
int id = item.getItemId();
switch (id){
case R.id.invert:
option =1;
break;
case R.id.edge:
option=2;
break;
case R.id.sobel:
option=3;
break;
case R.id.boxblur:
option=4;
break;
case R.id.face_detection:
option =5;
break;
default:
option=0;
break;
}
return true;
}
});
}
});
}
public String saveMatData(Mat mat,String path) {
File fileDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "mybook");
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String name = path;
File tempFile = new File(fileDir.getAbsoluteFile() + File.separator, name);
Mat dst = new Mat(mat.rows(), mat.cols(), CvType.CV_8UC4); //新建目標輸出影像
Imgproc.cvtColor(mat, dst, Imgproc.COLOR_RGB2BGR);
Imgcodecs.imwrite(tempFile.getAbsolutePath(), dst);
Log.e("存盤", "FielSaveMatData" + tempFile.getPath());
return tempFile.getPath();
}
@Override
public void onPause() {
super.onPause();
if (javaCameraView != null)
javaCameraView.disableView();
}
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, baseLoaderCallback);
} else {
baseLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
@Override
public void onCameraViewStarted(int width, int height) {
frame = new Mat();
}
@Override
public void onCameraViewStopped() {
frame.release();
}
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
frame = inputFrame.rgba();
//if (this.getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);{
//Core.rotate(frame,frame,Core.ROTATE_90_CLOCKWISE);
//}
process(frame);//呼叫process方法處理實時影像
return frame;
}
public void process(Mat frame){
if (option == 0){
//do nothing
}else if (option == 1){
Core.bitwise_not(frame,frame);//實作反色
}else if (option == 2){
Mat edge = new Mat();
//Canny是一種實作邊緣輸出的函式,低于100的像素點被認為不是邊緣,閾值高于200的像素點被認為是邊緣,
Imgproc.Canny(frame,edge,100,200,3,false);
//根據frame這張圖片的大小生成一個mat矩陣,
Mat result= Mat.zeros(frame.size(),frame.type());
//作用是把edge和frame重疊以后把edge中像素值為0(黑色)的點對應的frame中的點變為透明,而保留其他點,
frame.copyTo(result,edge);
//把生成的result邊緣影像復制給frame
result.copyTo(frame);
//釋放
edge.release();
result.release();
}else if (option == 3){
Mat gradx = new Mat();
Imgproc.Sobel(frame,gradx, CvType.CV_32F,1,0);
Core.convertScaleAbs(gradx,gradx);
gradx.copyTo(frame);
gradx.release();
}else if (option == 4){
Mat temp = new Mat();
Imgproc.blur(frame,temp,new Size(15,15));
temp.copyTo(frame);
temp.release();
}else if (option == 5){
//faceDetect(frame.getNativeObjAddr());
detectFace(frame);
}
else{
}
}
private void detectFace(Mat frame) {
Mat gray = new Mat();
Imgproc.cvtColor(frame, gray, Imgproc.COLOR_RGBA2GRAY);
Imgproc.equalizeHist(gray, gray);//加強對比度
//將識別到的人臉區域設定一個區域
MatOfRect faces = new MatOfRect();
//定義一個方框框住人臉,設定能識別的最小和最大人臉范圍,
face_detector.detectMultiScale(gray, faces, 1.1, 1, 0, new Size(50, 50), new Size(3000, 3000));
List<Rect> faceList = faces.toList();
//判斷是否檢測到人臉,檢測到人臉就標記出來
if(faceList.size() != 0) {
for (Rect rect : faceList) {
//設定紅框
Imgproc.rectangle(frame, rect.tl(), rect.br(), new Scalar(255, 0, 0), 2, 8, 0);
}
}
gray.release();
faces.release();
}
public void onDestroy(){
super.onDestroy();
if (javaCameraView != null){
javaCameraView.disableView();
}
}
//前后置攝像頭的選擇
@Override
public void onClick(View view) {
int id =view.getId();
if (id == R.id.radioButton){
cameraIndex=1;
}else if (id == R.id.radioButton2){
cameraIndex=0;
}
javaCameraView.setCameraIndex(cameraIndex);
if (javaCameraView != null){
javaCameraView.disableView();
}
javaCameraView.enableView();
}
}
Activity的XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button2"
android:layout_width="80dp"
android:layout_height="40dp"
android:text="模式選擇" />
<Button
android:layout_width="80dp"
android:layout_height="40dp"
android:id="@+id/b1"/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/radioButton"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="前置攝像頭" />
<RadioButton
android:id="@+id/radioButton2"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="后置攝像頭" />
</RadioGroup>
<org.opencv.android.JavaCameraView
android:id="@+id/javaCameraView"
android:layout_width="350dp"
android:layout_height="400dp"
app:camera_id="back"
app:show_fps="true" />
</LinearLayout>
AndroidManifest.xml添加權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.front"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.front.autofocus"
android:required="false" />
將Values中的strings.xml修改
因為我用了popupMenu,所以要改,不用的話可以不用修改,
<resources>
<string name="app_name">OpenCV</string>
<string name="invert">反色</string>
<string name="sobel">梯度</string>
<string name="edge">邊緣</string>
<string name="boxblur">模糊</string>
<string name="faceDetection">人臉檢測</string>
</resources>
在menu中添加一個camera_view_menus.xml
因為我用了popupMenu,所以要加,不用的話可以不用加,
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/invert"
android:title="反色">
</item>
<item
android:id="@+id/edge"
android:title="邊緣">
</item>
<item
android:id="@+id/sobel"
android:title="梯度">
</item>
<item
android:id="@+id/boxblur"
android:title="模糊">
</item>
<item
android:id="@+id/face_detection"
android:title="人臉檢測">
</item>
</menu>
最后記得添加級聯分類器
在raw中將haarcascade_frontalface_alt_tree.xml復制進去,

截取人臉區域的函式
根據畫框的坐標x和y,以及需要畫框的長寬截圖
/**
* 裁剪圖片并重新裝換大小
* @param imagePath
* @param posX
* @param posY
* @param width
* @param height
* @param outFile
*/
public static void imageCut(String imagePath,String outFile, int posX,int posY,int width,int height ){
//原始影像
Mat image = Imgcodecs.imread(imagePath);
//截取的區域:引數,坐標X,坐標Y,截圖寬度,截圖長度
Rect rect = new Rect(posX,posY,width,height);
//兩句效果一樣
Mat sub = image.submat(rect); //Mat sub = new Mat(image,rect);
Mat mat = new Mat();
Size size = new Size(300, 300);
Imgproc.resize(sub, mat, size);//將人臉進行截圖并保存
Imgcodecs.imwrite(outFile, mat);
}
上一個鏈接,里面是我上面所述的demo,
人臉實時檢測,無截圖
https://download.csdn.net/download/m0_51381592/15133180
人臉檢測,并且截取人臉區域
https://download.csdn.net/download/m0_51381592/15134399
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/258460.html
標籤:其他
