怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能
怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
1 概述
完成 Android 相机预览功能以后,在此基础上我使用 dlib 与 opencv 库做了一个关于人脸检测的 demo。该 demo 在相机预览过程中对人脸进行实时检测,并将检测到的人脸用矩形框描绘出来。具体实现原理如下:
采用双层 View,底层的 TextureView 用于预览,程序从 TextureView 中获取预览帧数据,然后调用 dlib 库对帧数据进行处理,最后将检测结果绘制在顶层的 SurfaceView 中。
2 项目配置
由于项目中用到了 dlib 与 opencv 库,因此需要对其进行配置。主要涉及到以下几个方面:
2.1 C++支持
在项目创建过程中依次选择 Include C++ Support、C++11、Exceptions Support ( -fexceptions )以及 Runtime Type Information Support ( -frtti ) 。最后生成的 build.gradle 文件如下:
defaultConfig{ applicationId"com.example.lightweh.facedetection" minSdkVersion23 targetSdkVersion28 versionCode1 versionName"1.0" testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner" externalNativeBuild{ cmake{ arguments"-DCMAKE_BUILD_TYPE=Release" cppFlags"-std=c++11-frtti-fexceptions" } } }
其中,arguments 参数是后添加上去的,主要用于指定 CMake 的编译模式为 Release,因为在 Debug 模式下 dlib 库中相关算法的运行速度非常慢。前期如果需要调试 C++ 代码,可先将 arguments 参数注释。
2.2 dlib 与 opencv 下载
•到dlib官网下载最新版本的源码,解压后将文件夹中的dlib目录复制到 Android Studio 工程的 cpp 目录下。
•到sourceforge 下载最新的 opencv-android 库,解压后将文件夹中的 native 目录同样复制到 Android Studio 工程的 cpp 目录下,并改名为 opencv。
2.3 CMakeLists 配置
在 CMakeLists 文件中,我们首先包含 dlib 的 cmake 文件,接下来添加 opencv 的 include 文件夹并引入 opencv 的 so 库,同时将 jni_common 目录中的文件及人脸检测相关文件添加至 native-lib 库中,最后进行链接。
#设置native目录 set(NATIVE_DIR${CMAKE_SOURCE_DIR}/src/main/cpp) #设置dlib include(${NATIVE_DIR}/dlib/cmake) #设置opencvinclude文件夹 include_directories(${NATIVE_DIR}/opencv/jni/include) #设置opencv的so库 add_library( libopencv_java3 SHARED IMPORTED) set_target_properties( libopencv_java3 PROPERTIES IMPORTED_LOCATION ${NATIVE_DIR}/opencv/libs/${ANDROID_ABI}/libopencv_java3.so) #将jni_common目录中所有文件名,存至SRC_LIST中 AUX_SOURCE_DIRECTORY(${NATIVE_DIR}/jni_commonSRC_LIST) add_library(#Setsthenameofthelibrary. native-lib #Setsthelibraryasasharedlibrary. SHARED #Providesarelativepathtoyoursourcefile(s). ${SRC_LIST} src/main/cpp/face_detector.h src/main/cpp/face_detector.cpp src/main/cpp/native-lib.cpp) find_library(#Setsthenameofthepathvariable. log-lib #SpecifiesthenameoftheNDKlibrarythat #youwantCMaketolocate. log) target_link_libraries(#Specifiesthetargetlibrary. native-lib dlib libopencv_java3 jnigraphics #Linksthetargetlibrarytotheloglibrary #includedintheNDK. ${log-lib}) #指定release编译选项 set(CMAKE_C_FLAGS_RELEASE"${CMAKE_C_FLAGS_RELEASE}-s-O3-Wall") set(CMAKE_CXX_FLAGS_RELEASE"${CMAKE_CXX_FLAGS_RELEASE}-s-O3-Wall")
由于 C++ 代码中用到了头文件 "android/bitmap.h",所以链接时需要添加 jnigraphics 库。
3 JNI相关 Java 类定义
3.1 VisionDetRet 类
VisionDetRet 类的相关对象主要负责 C++ 与 Java 之间的数据传递。
publicfinalclassVisionDetRet{ privateintmLeft; privateintmTop; privateintmRight; privateintmBottom; VisionDetRet(){} publicVisionDetRet(intl,intt,intr,intb){ mLeft=l; mTop=t; mRight=r; mBottom=b; } publicintgetLeft(){ returnmLeft; } publicintgetTop(){ returnmTop; } publicintgetRight(){ returnmRight; } publicintgetBottom(){ returnmBottom; } }
3.2 FaceDet 类
FaceDet 类为 JNI 函数调用类,主要定义了一些需要 C++ 实现的 native 方法。
publicclassFaceDet{ privatestaticfinalStringTAG="FaceDet"; //accessedbynativemethods @SuppressWarnings("unused") privatelongmNativeFaceDetContext; static{ try{ //预加载native方法库 System.loadLibrary("native-lib"); jniNativeClassInit(); Log.d(TAG,"jniNativeClassInitsuccess"); }catch(UnsatisfiedLinkErrore){ Log.e(TAG,"librarynotfound"); } } publicFaceDet(){ jniInit(); } @Nullable @WorkerThread publicList<VisionDetRet>detect(@NonNullBitmapbitmap){ VisionDetRet[]detRets=jniBitmapDet(bitmap); returnArrays.asList(detRets); } @Override protectedvoidfinalize()throwsThrowable{ super.finalize(); release(); } publicvoidrelease(){ jniDeInit(); } @Keep privatenativestaticvoidjniNativeClassInit(); @Keep privatesynchronizednativeintjniInit(); @Keep privatesynchronizednativeintjniDeInit(); @Keep privatesynchronizednativeVisionDetRet[]jniBitmapDet(Bitmapbitmap); }
4 Native 方法实现
4.1 定义 VisionDetRet 类对应的 C++ 类
#include<jni.h> #defineCLASSNAME_VISION_DET_RET"com/lightweh/dlib/VisionDetRet" #defineCONSTSIG_VISION_DET_RET"()V" #defineCLASSNAME_FACE_DET"com/lightweh/dlib/FaceDet" classJNI_VisionDetRet{ public: JNI_VisionDetRet(JNIEnv*env){ //查找VisionDetRet类信息 jclassdetRetClass=env->FindClass(CLASSNAME_VISION_DET_RET); //获取VisionDetRet类成员变量 jID_left=env->GetFieldID(detRetClass,"mLeft","I"); jID_top=env->GetFieldID(detRetClass,"mTop","I"); jID_right=env->GetFieldID(detRetClass,"mRight","I"); jID_bottom=env->GetFieldID(detRetClass,"mBottom","I"); } voidsetRect(JNIEnv*env,jobject&jDetRet,constint&left,constint&top, constint&right,constint&bottom){ //设置VisionDetRet类对象jDetRet的成员变量值 env->SetIntField(jDetRet,jID_left,left); env->SetIntField(jDetRet,jID_top,top); env->SetIntField(jDetRet,jID_right,right); env->SetIntField(jDetRet,jID_bottom,bottom); } //创建VisionDetRet类实例 staticjobjectcreateJObject(JNIEnv*env){ jclassdetRetClass=env->FindClass(CLASSNAME_VISION_DET_RET); jmethodIDmid= env->GetMethodID(detRetClass,"<init>",CONSTSIG_VISION_DET_RET); returnenv->NewObject(detRetClass,mid); } //创建VisionDetRet类对象数组 staticjobjectArraycreateJObjectArray(JNIEnv*env,constint&size){ jclassdetRetClass=env->FindClass(CLASSNAME_VISION_DET_RET); return(jobjectArray)env->NewObjectArray(size,detRetClass,NULL); } private: jfieldIDjID_left; jfieldIDjID_top; jfieldIDjID_right; jfieldIDjID_bottom; };
4.2 定义人脸检测类
人脸检测算法需要用大小位置不同的窗口在图像中进行滑动,然后判断窗口中是否存在人脸。本文采用的是 dlib 中的是HOG(histogram of oriented gradient)方法对人脸进行检测,其检测效果要好于 opencv。dlib 中同样提供了 CNN 方法来进行人脸检测,效果好于 HOG,不过需要使用 GPU 加速,不然程序运行会非常慢。
classFaceDetector{ private: dlib::frontal_face_detectorface_detector; std::vector<dlib::rectangle>det_rects; public: FaceDetector(); //实现人脸检测算法 intDetect(constcv::Mat&image); //返回检测结果 std::vector<dlib::rectangle>getDetResultRects(); }; FaceDetector::FaceDetector(){ //定义人脸检测器 face_detector=dlib::get_frontal_face_detector(); } intFaceDetector::Detect(constcv::Mat&image){ if(image.empty()) return0; if(image.channels()==1){ cv::cvtColor(image,image,CV_GRAY2BGR); } dlib::cv_image<dlib::bgr_pixel>dlib_image(image); det_rects.clear(); //返回检测到的人脸矩形特征框 det_rects=face_detector(dlib_image); returndet_rects.size(); } std::vector<dlib::rectangle>FaceDetector::getDetResultRects(){ returndet_rects; }
4.3 native 方法实现
JNI_VisionDetRet*g_pJNI_VisionDetRet; JavaVM*g_javaVM=NULL; //该函数在加载本地库时被调用 JNIEXPORTjintJNI_OnLoad(JavaVM*vm,void*reserved){ g_javaVM=vm; JNIEnv*env; vm->GetEnv((void**)&env,JNI_VERSION_1_6); //初始化g_pJNI_VisionDetRet g_pJNI_VisionDetRet=newJNI_VisionDetRet(env); returnJNI_VERSION_1_6; } //该函数用于执行清理操作 voidJNI_OnUnload(JavaVM*vm,void*reserved){ g_javaVM=NULL; deleteg_pJNI_VisionDetRet; } namespace{ #defineJAVA_NULL0 usingDetPtr=FaceDetector*; //用于存放人脸检测类对象的指针,关联Jave层对象与C++底层对象(相互对应) classJNI_FaceDet{ public: JNI_FaceDet(JNIEnv*env){ jclassclazz=env->FindClass(CLASSNAME_FACE_DET); mNativeContext=env->GetFieldID(clazz,"mNativeFaceDetContext","J"); env->DeleteLocalRef(clazz); } DetPtrgetDetectorPtrFromJava(JNIEnv*env,jobjectthiz){ DetPtrconstp=(DetPtr)env->GetLongField(thiz,mNativeContext); returnp; } voidsetDetectorPtrToJava(JNIEnv*env,jobjectthiz,jlongptr){ env->SetLongField(thiz,mNativeContext,ptr); } jfieldIDmNativeContext; }; //Protectgetting/settingandcreating/deletingpointerbetweenjava/native std::mutexgLock; std::shared_ptr<JNI_FaceDet>getJNI_FaceDet(JNIEnv*env){ staticstd::once_flagsOnceInitflag; staticstd::shared_ptr<JNI_FaceDet>sJNI_FaceDet; std::call_once(sOnceInitflag,[env](){ sJNI_FaceDet=std::make_shared<JNI_FaceDet>(env); }); returnsJNI_FaceDet; } //从java对象获取它持有的c++对象指针 DetPtrconstgetDetPtr(JNIEnv*env,jobjectthiz){ std::lock_guard<std::mutex>lock(gLock); returngetJNI_FaceDet(env)->getDetectorPtrFromJava(env,thiz); } //ThefunctiontosetapointertojavaanddeleteitifnewPtrisempty //C++对象new以后,将指针转成long型返回给java对象持有 voidsetDetPtr(JNIEnv*env,jobjectthiz,DetPtrnewPtr){ std::lock_guard<std::mutex>lock(gLock); DetPtroldPtr=getJNI_FaceDet(env)->getDetectorPtrFromJava(env,thiz); if(oldPtr!=JAVA_NULL){ deleteoldPtr; } getJNI_FaceDet(env)->setDetectorPtrToJava(env,thiz,(jlong)newPtr); } }//endunnamespace #ifdef__cplusplus extern"C"{ #endif #defineDLIB_FACE_JNI_METHOD(METHOD_NAME)Java_com_lightweh_dlib_FaceDet_##METHOD_NAME voidJNIEXPORT DLIB_FACE_JNI_METHOD(jniNativeClassInit)(JNIEnv*env,jclass_this){} //生成需要返回的结果数组 jobjectArraygetRecResult(JNIEnv*env,DetPtrfaceDetector,constint&size){ //根据检测到的人脸数创建相应大小的jobjectArray jobjectArrayjDetRetArray=JNI_VisionDetRet::createJObjectArray(env,size); for(inti=0;i<size;i++){ //对检测到的每一个人脸创建对应的实例对象,然后插入数组 jobjectjDetRet=JNI_VisionDetRet::createJObject(env); env->SetObjectArrayElement(jDetRetArray,i,jDetRet); dlib::rectanglerect=faceDetector->getDetResultRects()[i]; //将人脸矩形框的值赋给对应的jobject实例对象 g_pJNI_VisionDetRet->setRect(env,jDetRet,rect.left(),rect.top(), rect.right(),rect.bottom()); } returnjDetRetArray; } JNIEXPORTjobjectArrayJNICALL DLIB_FACE_JNI_METHOD(jniBitmapDet)(JNIEnv*env,jobjectthiz,jobjectbitmap){ cv::MatrgbaMat; cv::MatbgrMat; jniutils::ConvertBitmapToRGBAMat(env,bitmap,rgbaMat,true); cv::cvtColor(rgbaMat,bgrMat,cv::COLOR_RGBA2BGR); //获取人脸检测类指针 DetPtrmDetPtr=getDetPtr(env,thiz); //调用人脸检测算法,返回检测到的人脸数 jintsize=mDetPtr->Detect(bgrMat); //返回检测结果 returngetRecResult(env,mDetPtr,size); } jintJNIEXPORTJNICALL DLIB_FACE_JNI_METHOD(jniInit)(JNIEnv*env,jobjectthiz){ DetPtrmDetPtr=newFaceDetector(); //设置人脸检测类指针 setDetPtr(env,thiz,mDetPtr); returnJNI_OK; } jintJNIEXPORTJNICALL DLIB_FACE_JNI_METHOD(jniDeInit)(JNIEnv*env,jobjectthiz){ //指针置0 setDetPtr(env,thiz,JAVA_NULL); returnJNI_OK; } #ifdef__cplusplus } #endif
5 Java端调用人脸检测算法
在开启人脸检测之前,需要在相机 AutoFitTextureView 上覆盖一层自定义 BoundingBoxView 用于绘制检测到的人脸矩形框,该 View 的具体实现如下:
publicclassBoundingBoxViewextendsSurfaceViewimplementsSurfaceHolder.Callback{ protectedSurfaceHoldermSurfaceHolder; privatePaintmPaint; privatebooleanmIsCreated; publicBoundingBoxView(Contextcontext,AttributeSetattrs){ super(context,attrs); mSurfaceHolder=getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT); setZOrderOnTop(true); mPaint=newPaint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5f); mPaint.setStyle(Paint.Style.STROKE); } @Override publicvoidsurfaceChanged(SurfaceHoldersurfaceHolder,intformat,intwidth,intheight){ } @Override publicvoidsurfaceCreated(SurfaceHoldersurfaceHolder){ mIsCreated=true; } @Override publicvoidsurfaceDestroyed(SurfaceHoldersurfaceHolder){ mIsCreated=false; } publicvoidsetResults(List<VisionDetRet>detRets) { if(!mIsCreated){ return; } Canvascanvas=mSurfaceHolder.lockCanvas(); //清除掉上一次的画框。 canvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR); canvas.drawColor(Color.TRANSPARENT); for(VisionDetRetdetRet:detRets){ Rectrect=newRect(detRet.getLeft(),detRet.getTop(),detRet.getRight(),detRet.getBottom()); canvas.drawRect(rect,mPaint); } mSurfaceHolder.unlockCanvasAndPost(canvas); } }
同时,需要在布局文件中添加对应的 BoundingBoxView 层,保证与 AutoFitTextureView 完全重合:
<?xmlversion="1.0"encoding="utf-8"?> <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CameraFragment"> <com.lightweh.facedetection.AutoFitTextureView android:id="@+id/textureView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"/> <com.lightweh.facedetection.BoundingBoxView android:id="@+id/boundingBoxView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textureView" android:layout_alignTop="@+id/textureView" android:layout_alignRight="@+id/textureView" android:layout_alignBottom="@+id/textureView"/> </RelativeLayout>
BoundingBoxView 添加完成以后,即可在 CameraFragment 中添加对应的人脸检测代码:
privateclassdetectAsyncextendsAsyncTask<Bitmap,Void,List<VisionDetRet>>{ @Override protectedvoidonPreExecute(){ mIsDetecting=true; super.onPreExecute(); } protectedList<VisionDetRet>doInBackground(Bitmap...bp){ List<VisionDetRet>results; //返回检测结果 results=mFaceDet.detect(bp[0]); returnresults; } protectedvoidonPostExecute(List<VisionDetRet>results){ //绘制检测到的人脸矩形框 mBoundingBoxView.setResults(results); mIsDetecting=false; } }
然后,分别在 onResume 与 onPause 函数中完成人脸检测类对象的初始化和释放:
@Override publicvoidonResume(){ super.onResume(); startBackgroundThread(); mFaceDet=newFaceDet(); if(mTextureView.isAvailable()){ openCamera(mTextureView.getWidth(),mTextureView.getHeight()); }else{ mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); } } @Override publicvoidonPause(){ closeCamera(); stopBackgroundThread(); if(mFaceDet!=null){ mFaceDet.release(); } super.onPause(); }
最后,在 TextureView 的回调函数 onSurfaceTextureUpdated 完成调用:
@Override publicvoidonSurfaceTextureUpdated(SurfaceTexturetexture){ if(!mIsDetecting){ Bitmapbp=mTextureView.getBitmap(); //保证图片方向与预览方向一致 bp=Bitmap.createBitmap(bp,0,0,bp.getWidth(),bp.getHeight(),mTextureView.getTransform(null),true); newdetectAsync().execute(bp); } }
关于怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注恰卡编程网行业资讯频道了解更多相关知识。
推荐阅读
-
android(如何快速开发框架 小米note开发版MIUI,安卓6.0,怎么安装Xposed框架)
稳定版,你必须先根除。你上网搜索安卓可以叫别人s框架,对方可以把框架做成jar包,把这个jar包加载到项目目录的libs文件中使...
-
android(studio 虚拟机启动不了 android studio可以当模拟器用吗)
androidstudio可以当模拟器用吗?AmdCUP引导模拟器有点复杂。雷电模拟器上的抖音怎么登录不上?不是,闪电模拟调用...
-
从实践中学习手机抓包与数据分析(android 手机抓包app)
android手机抓包app?netcapture抓包精灵app(手机抓包工具)又名sslcapture,是什么专业的安卓手机抓...
-
android(studio全局搜索 android studio怎么看app界面)
androidstudio怎么看app界面?在设备桌面点击运用直接进入到App界面,就也可以参与其他你的操作了。android-...
-
怎么把android框架源代码拉到本地(android studio如何运行别人的源代码)
androidstudio如何运行别人的源代码?androidstudio点击刚建在列表中你选择导入module,导入即可在用...
-
android(studio2022年使用教程 怎么安装Android studio详细教程)
怎么安装Androidstudio详细教程?androidstudio中haxm直接安装的方法追加:1、简单的方法打开Andr...
-
怎么使用Android基准配置文件Baseline Profile方案提升启动速度
-
HTML5如何实现禁止android视频另存为
-
学java好还是学php好?
-
Android如何实现多点触控功能