近日突然对Android上的人脸检测感兴趣,就小试了一下。中间也遇到了一些坑。下面是完整的过程。
1) 首先,关于Android OpenCV的SDK的下载,这里就不赘述了。下面使用的是Android Studio 4.1.1,下载的OpenCV SDK版本是4.5.1.。SDK解压到文件夹里面。
2)将OpenCV的SDK导入到工程
Android Studio 创建空白工程,然后,通过菜单里面的Import Module,加载SDK
选择刚才OpenCV解压路径下的SDK
点击确定后,会在Android Studio的项目目录中出现sdk,表明导入成功
然后,需要修改导入sdk的build.gradle中的配置,要与app的build.gradle的版本号一致
主要是compileSdkVersion,sdk和app的build.gradle版本一致。
接下来,要在app中引用sdk。通过项目右键菜单,打开Open Module Setting
通过下面操作顺序,引入对sdk的依赖
操作成功后,可以在app的build.gradle中看到对sdk引用的说明
此次,初步编译一次项目,运行。这里是不会出现异常。空白工程能在手机上正常运行。
3)添加代码,实现手机摄像头的实时采集显示
界面代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">
<org.opencv.android.JavaCameraView
android:id="@+id/cjv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00090808" />
</androidx.constraintlayout.widget.ConstraintLayout>
、MainActity代码
public class MainActivity extends CameraActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
private static final String TAG = "OCVSample::Activity";
private CameraBridgeViewBase mOpenCvCameraView;
private Mat mIntermediateMat;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_main);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.cjv);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
//这里的cjv也是我的项目中JavaCameraView的id,自己改一下
mOpenCvCameraView.setCvCameraViewListener(this);
}
@Override
public void onCameraViewStarted(int width, int height) {
}
@Override
public void onCameraViewStopped() {
if (mIntermediateMat != null)
mIntermediateMat.release();
mIntermediateMat = null;
}
@Override
protected List<? extends CameraBridgeViewBase> getCameraViewList() {
return Collections.singletonList(mOpenCvCameraView);
}
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
Mat rgba = inputFrame.rgba();
Size sizeRgba = rgba.size();
Mat temp = new Mat();
Mat dst = new Mat();
Imgproc.cvtColor(rgba, temp , Imgproc.COLOR_RGBA2BGR);
Imgproc.cvtColor(temp, dst, Imgproc.COLOR_BGR2GRAY);
return dst;
}
@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_0_0, this, mLoaderCallback);
} else {
Log.d(TAG, "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
}
Manifest 的设置权限,主要是对摄像头添加权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.haardemo">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<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"/>
<supports-screens android:resizeable="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HaarDemo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
运行程序,可以在手机中看到灰度化的影像。
3)添加层叠分类器和JNI的引用
首先,将层叠分类器(xml文件)复制到工程res下面的raw中来
其次,在工程目录中添加JNI(这里,我将OpenCV SDK提供的Sample中的人脸检测代码中的jni目录整个复制到工程目录中)
然后,打开app的build.gradle添加对ndk的编译选项
还有一个编译设置
此时,编译工程,会在工程目录中成功看到so文件编译出来了,程序不会报任何异常,能运行。我们距离成功不远了。
感兴趣的各位可以用winrar解压生成的apk文件,你会发现so文件已经被打包到apk里面的lib里面了。
接下来,我们需要在代码中引用so文件里面提供的jni方法。可以将sdk中sample里面的DetectionBasedTracker类的代码引入到当前工程。
但是,这个代码中下面对JNI代码的外部引用会报红色错误。
private static native long nativeCreateObject(String cascadeName, int minFaceSize); private static native void nativeDestroyObject(long thiz); private static native void nativeStart(long thiz); private static native void nativeStop(long thiz); private static native void nativeSetFaceSize(long thiz, int size); private static native void nativeDetect(long thiz, long inputImage, long faces);
这里这个坑,主要时因为我们cpp代码里面,开放出来的接口的命名空间与当前工程的不一致,需要修改。可以打开cpp文件和h文件。
下面划红线的地方要和我们当前工程的包名一致。这一点很重要,很重要,很重要。
4)MainActity添加完整的人脸检测代码,这里,注意对着Sample复制过来就好,这里笔者没有任何代码创新。本文的目的在于讲述整个操作过程,所以,最终代码与Sample一致。
5)编译,运行。效果如下图。学艺未精,只认出了一张人脸。更多的参数,还需要进一步学习。这次现分享到这里。