前幾篇文章中有提到對openCV環境配置,這里再重新梳理導入和使用openCV進行簡單的人臉檢測(包括使用級聯分類器)
一 首先導入openCVLibrary320
二 設置gradle的sdk版本號與當前項目一致
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
}
三 新建 jniLibs 目錄
在 app/src/main 目錄下 與 java 目錄同級,新建 jniLibs 目錄
把 OpenCV-android-sdk-3.2/sdk/native/libs 目錄下的所有文件夾內容拷貝到 jniLibs 目錄
四 加載so包
private static String strLibraryName = "opencv_java3"; // 不需要添加前綴 libopencv_java3 static { try { Log.e("loadLibrary", strLibraryName); System.loadLibrary(strLibraryName); //System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // couldn't find "libopencv_java320.so" } catch (UnsatisfiedLinkError e) { Log.e("loadLibrary", "Native code library failed to load.\n" + e); } catch (Exception e) { Log.e("loadLibrary", "Exception: " + e); } }
五 添加xml級聯分類器
在導入的openCVLibrary320模塊中,找到 src/main/res 目錄,新建raw文件夾。
在 OpenCV-android-sdk-3.2/sdk/etc 目錄下,存放的則是訓練好的分類器。把 haarcascades 目錄和 lbpcascades 目錄內的所有xml文件拷貝到 raw目錄中。
六 配置權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FLASHLIGHT" />
完成以上步驟,就可以使用opencv了。
使用openCV
首先創建檢測器
mCascadeClassifier = createDetector(context, R.raw.haarcascade_frontalface_alt); /** * 創建檢測器 * * @param context 上下文 * @param id 級聯分類器ID * @return 檢測器 */ private CascadeClassifier createDetector(Context context, int id) { CascadeClassifier javaDetector; InputStream is = null; FileOutputStream os = null; try { is = context.getResources().openRawResource(id); File cascadeDir = context.getDir("cascade", Context.MODE_PRIVATE); File cascadeFile = new File(cascadeDir, id + ".xml"); os = new FileOutputStream(cascadeFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } javaDetector = new CascadeClassifier(cascadeFile.getAbsolutePath()); if (javaDetector.empty()) { javaDetector = null; } boolean delete = cascadeDir.delete(); return javaDetector; } catch (IOException e) { e.printStackTrace(); return null; } finally { try { if (null != is) { is.close(); } if (null != os) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } }
檢測人臉方法:
/** * 目標檢測 圖片 * * @param gray 灰度圖像 * @param object 識別結果的容器 * @return */ public Rect[] detectObjectImage(Mat gray, MatOfRect object) { mCascadeClassifier.detectMultiScale(gray,object); return object.toArray(); }
detectFace 整個函數:
private void detectFace() { try { // bitmapToMat Mat toMat = new Mat(); Utils.bitmapToMat(bitmap, toMat); Mat copyMat = new Mat(); toMat.copyTo(copyMat); // 復制 // togray Mat gray = new Mat(); Imgproc.cvtColor(toMat, gray, Imgproc.COLOR_RGBA2GRAY); MatOfRect mRect = new MatOfRect(); Rect[] object = mFaceDetector.detectObjectImage(gray, mRect); Log.e("objectLength", object.length + ""); int maxRectArea = 0 * 0; Rect maxRect = null; int facenum = 0; // Draw a bounding box around each face. for (Rect rect : object) { Imgproc.rectangle( toMat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(255, 0, 0), 3); ++facenum; // 找出最大的面積 int tmp = rect.width * rect.height; if (tmp >= maxRectArea) { maxRectArea = tmp; maxRect = rect; } } rectBitmap = null; if (facenum != 0) { // 剪切最大的頭像 Log.e("剪切的長寬", String.format("高:%s,寬:%s", maxRect.width, maxRect.height)); Rect rect = new Rect(maxRect.x, maxRect.y, maxRect.width, maxRect.height); Mat rectMat = new Mat(copyMat, rect); // 從原始圖像拿 rectBitmap = Bitmap.createBitmap(rectMat.cols(), rectMat.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(rectMat, rectBitmap); } textView.setText(String.format("檢測到%1$d個人臉", facenum)); Utils.matToBitmap(toMat, bitmap); } catch (Exception e) { e.printStackTrace(); } }
附所有代碼:
package com.myproject.facedetection.ui; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.myproject.facedetection.R; import com.myproject.facedetection.entity.CustomImageButton; import com.opencvlib.ObjectDetector; import org.opencv.android.Utils; import org.opencv.core.Mat; import org.opencv.core.MatOfRect; import org.opencv.core.Point; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.imgproc.Imgproc; import java.io.File; public class FaceDetectionOpenCVActivity extends AppCompatActivity { private ObjectDetector mFaceDetector; private static String CAMERAIMAGENAME = "image.jpg"; private CustomImageButton imageButton; private CustomImageButton imageButton2; private TextView textView; private Bitmap bitmap; private Bitmap rectBitmap; private Bitmap resizeBitmap; private Toast toast; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_face_detection_opencv); textView = (TextView) findViewById(R.id.tv_face); imageButton = (CustomImageButton) findViewById(R.id.iv_face); imageButton.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); imageButton2 = (CustomImageButton) findViewById(R.id.iv_face2); imageButton2.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); mFaceDetector = new ObjectDetector(this, R.raw.haarcascade_frontalface_alt, 6, 0.2F, 0.2F, new Scalar(255, 0, 0, 255)); } /** * 點擊添加照片事件 * * @param v */ public void onClick(View v) { int bt_id = v.getId(); switch (bt_id) { case R.id.addPic: // 添加照片 // 打開本地相冊 Intent intent1 = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent1, 101); break; case R.id.takePhoto: // 拍照 // 打開本地相機 Intent intent2 = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); Uri imageUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), CAMERAIMAGENAME)); intent2.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent2, 102); break; case R.id.back: this.finish(); break; default: break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // 加判斷 不選擇照片或者不拍照時不閃退 //Log.e("data", String.valueOf(data)); //if (data == null) //return; bitmap = null; switch (requestCode) { // 選擇圖片庫的圖片 case 101: if (resultCode == RESULT_OK) { try { Uri uri = data.getData(); bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri); } catch (Exception e) { e.printStackTrace(); } } break; // 表示調用本地照相機拍照 case 102: if (resultCode == RESULT_OK) { //Bundle bundle = data.getExtras(); //bm = (Bitmap) bundle.get("data"); bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/" + CAMERAIMAGENAME); } break; default: break; } Log.e("bitmap", String.valueOf(bitmap)); if (bitmap == null) { toast = Toast.makeText(FaceDetectionOpenCVActivity.this, "未選擇圖像", Toast.LENGTH_SHORT); toast.show(); return; } // 識別圖片 並畫框 detectFace(); // 將照片剪裁 bitmap將被釋放重新賦值 int ibWidth = imageButton.getWidth(); int ibHeight = imageButton.getHeight(); resizeBitmap = imageButton.resizeBitmap(bitmap, ibWidth, ibHeight); imageButton.setBitmap(resizeBitmap); imageButton2.setBitmap(rectBitmap); } private void detectFace() { try { // bitmapToMat Mat toMat = new Mat(); Utils.bitmapToMat(bitmap, toMat); Mat copyMat = new Mat(); toMat.copyTo(copyMat); // 復制 // togray Mat gray = new Mat(); Imgproc.cvtColor(toMat, gray, Imgproc.COLOR_RGBA2GRAY); MatOfRect mRect = new MatOfRect(); Rect[] object = mFaceDetector.detectObjectImage(gray, mRect); Log.e("objectLength", object.length + ""); int maxRectArea = 0 * 0; Rect maxRect = null; int facenum = 0; // Draw a bounding box around each face. for (Rect rect : object) { Imgproc.rectangle( toMat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(255, 0, 0), 3); ++facenum; // 找出最大的面積 int tmp = rect.width * rect.height; if (tmp >= maxRectArea) { maxRectArea = tmp; maxRect = rect; } } rectBitmap = null; if (facenum != 0) { // 剪切最大的頭像 Log.e("剪切的長寬", String.format("高:%s,寬:%s", maxRect.width, maxRect.height)); Rect rect = new Rect(maxRect.x, maxRect.y, maxRect.width, maxRect.height); Mat rectMat = new Mat(copyMat, rect); // 從原始圖像拿 rectBitmap = Bitmap.createBitmap(rectMat.cols(), rectMat.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(rectMat, rectBitmap); } textView.setText(String.format("檢測到%1$d個人臉", facenum)); Utils.matToBitmap(toMat, bitmap); } catch (Exception e) { e.printStackTrace(); } } }
package com.opencvlib; import android.content.Context; 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.objdetect.CascadeClassifier; import org.opencv.objdetect.Objdetect; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; /** * Created by think-hxr on 17-10-12. */ public class ObjectDetector { private CascadeClassifier mCascadeClassifier; private int mMinNeighbors; private float mRelativeObjectWidth; private float mRelativeObjectHeight; private Scalar mRectColor; /** * 構造方法 * * @param context 上下文 * @param id 級聯分類器ID * @param minNeighbors 連續幾幀確認目標 * @param relativeObjectWidth 最小寬度屏占比 * @param relativeObjectHeight 最小高度屏占比 * @param rectColor 畫筆顏色 */ public ObjectDetector(Context context, int id, int minNeighbors, float relativeObjectWidth, float relativeObjectHeight, Scalar rectColor) { context = context.getApplicationContext(); mCascadeClassifier = createDetector(context, id); mMinNeighbors = minNeighbors; mRelativeObjectWidth = relativeObjectWidth; mRelativeObjectHeight = relativeObjectHeight; mRectColor = rectColor; } /** * 創建檢測器 * * @param context 上下文 * @param id 級聯分類器ID * @return 檢測器 */ private CascadeClassifier createDetector(Context context, int id) { CascadeClassifier javaDetector; InputStream is = null; FileOutputStream os = null; try { is = context.getResources().openRawResource(id); File cascadeDir = context.getDir("cascade", Context.MODE_PRIVATE); File cascadeFile = new File(cascadeDir, id + ".xml"); os = new FileOutputStream(cascadeFile); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } javaDetector = new CascadeClassifier(cascadeFile.getAbsolutePath()); if (javaDetector.empty()) { javaDetector = null; } boolean delete = cascadeDir.delete(); return javaDetector; } catch (IOException e) { e.printStackTrace(); return null; } finally { try { if (null != is) { is.close(); } if (null != os) { os.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 目標檢測 視頻 * * @param gray 灰度圖像 * @param object 識別結果的容器 * @return 檢測到的目標位置集合 */ public Rect[] detectObject(Mat gray, MatOfRect object) { // 使用Java人臉檢測 mCascadeClassifier.detectMultiScale( gray, // 要檢查的灰度圖像 object, // 檢測到的人臉 1.1, // 表示在前后兩次相繼的掃描中,搜索窗口的比例系數。默認為1.1即每次搜索窗口依次擴大10%; mMinNeighbors, // 默認是3 控制誤檢測,表示默認幾次重疊檢測到人臉,才認為人臉存在 Objdetect.CASCADE_SCALE_IMAGE, getSize(gray, mRelativeObjectWidth, mRelativeObjectHeight), // 目標最小可能的大小 gray.size()); // 目標最大可能的大小 return object.toArray(); } /** * 目標檢測 圖片 * * @param gray 灰度圖像 * @param object 識別結果的容器 * @return */ public Rect[] detectObjectImage(Mat gray, MatOfRect object) { mCascadeClassifier.detectMultiScale(gray,object); return object.toArray(); } /** * 根據屏占比獲取大小 * * @param gray gray * @param relativeObjectWidth 最小寬度屏占比 * @param relativeObjectHeight 最小高度屏占比 * @return 大小 */ private Size getSize(Mat gray, float relativeObjectWidth, float relativeObjectHeight) { Size size = gray.size(); int cameraWidth = gray.cols(); int cameraHeight = gray.rows(); int width = Math.round(cameraWidth * relativeObjectWidth); int height = Math.round(cameraHeight * relativeObjectHeight); size.width = 0 >= width ? 0 : (cameraWidth < width ? cameraWidth : width); // width [0, cameraWidth] size.height = 0 >= height ? 0 : (cameraHeight < height ? cameraHeight : height); // height [0, cameraHeight] return size; } /** * 獲取畫筆顏色 * * @return 顏色 */ public Scalar getRectColor() { return mRectColor; } }
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.myproject.facedetection.ui.FaceDetectionOpenCVActivity"> <com.myproject.facedetection.entity.CustomImageButton android:id="@+id/iv_face" android:layout_width="0dp" android:layout_height="460dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" tools:layout_editor_absoluteY="10dp" /> <com.myproject.facedetection.entity.CustomImageButton android:id="@+id/iv_face2" android:layout_width="100dp" android:layout_height="100dp" android:layout_marginRight="10dp" app:layout_constraintRight_toRightOf="parent" tools:layout_editor_absoluteY="10dp" /> <TextView android:id="@+id/tv_face" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="未檢測到人臉" android:textColor="@color/colorAccent" app:layout_constraintBottom_toTopOf="@+id/ll1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <LinearLayout android:id="@+id/ll1" android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <Button android:id="@+id/takePhoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_weight="1" android:onClick="onClick" android:text="拍照(CV)" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <Button android:id="@+id/addPic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_weight="1" android:onClick="onClick" android:text="選擇圖片(CV)" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <Button android:id="@+id/back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="0dp" android:layout_weight="1" android:onClick="onClick" android:text="返回(CV)" android:textSize="16sp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> </LinearLayout> </android.support.constraint.ConstraintLayout>
