怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能

怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

1 概述

怎么在Android 中使用 dlib+opencv 实现一个动态人脸检测功能

完成 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 实现一个动态人脸检测功能问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注恰卡编程网行业资讯频道了解更多相关知识。

发布于 2021-03-26 01:50:26
收藏
分享
海报
0 条评论
164
上一篇:如何在Django中使用Celery 下一篇:WMIC命令怎么在Windows中使用
目录

    推荐阅读

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码