本文介绍如何将dlib和opencv简单移植到Android进行人脸检测。首先准备dlib、opencv和NDK,然后通过Android Studio的C++项目进行编译,解决加载so文件时遇到的问题。接着将编译好的库导入新项目,配置CMakeLists.txt和build.gradle,编写C++代码进行人脸检测。最后展示简单的检测效果,并提到官方demo的运行速度较慢,未来会探讨优化方法。

最近实在是忙于项目,太久都没有写博客了,结果一不小心竟然过了几个月了。既然有空就多写点东西,交流交流经验,总归是没有坏处的。
之前一直想做一下人脸检测,网上一大堆SDK全是收费的,而且基本上都不是本地检测。后来总算找到了dlib来进行人脸识别,踩了不少坑之后,总算是集成了上去。但是官方的demo速度实在太慢,后面有空会去学习一些优化经验,这篇文章直接从编译开始讲起集成,并且使用官方demo以及机器学习模型来进行人脸检测。

资料准备

首先下载dlib与opencv的新版,以及Android Studio的NDK工具准备。
dlib-19.16
opencv- 3.4.4
ndk-r17c
之前我想用18的,18坑多的令人难以置信,差点直接去世了,放弃治疗了直接换回低版本。

dlib编译

现在大部分c库都有python脚本来进行编译,但是配一些环境也是麻烦,我们直接用Android Studio来进行编译即可。新建一个Android Studio的C++项目,这一步我就不多提了。直接看编译的项目结构。
我们把下载的dlib库的的源文件全部copy到cpp目录下。然后打开我们的才CMakeLists.txt。添加如下代码。

cmake_minimum_required(VERSION 3.4.1)set(DLIB_IN_PROJECT_BUILD false)set(BUILD_SHARED_LIBS true)  #编译成动态库set(DLIB_NO_GUI_SUPPORT true) #不需要gui支持set(ARM_NEON_IS_AVAILABLE true) #开启neon优化set(DLIB_PNG_SUPPORT true) #打开PNG支持
#add_definitions(-DDLIB_PNG_SUPPORT)add_subdirectory(src/main/cpp/dlib)add_library(native-lib SHARED src/main/cpp/native-lib.cpp)# Finally, you need to tell CMake that this program, assignment_learning_ex,# depends on dlib.  You do that with this statement:target_link_libraries(native-lib
        z
        log
        dlib)

然后打个build.gradle,主要就是配置一些cpu架构以及编译时候的参数。

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.ty.compile"
        minSdkVersion 23
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                abiFilters 'armeabi-v7a'
            }
        }
    }
    buildTypes {
    	//主要添加的部分
        debug {
            externalNativeBuild {
                cmake {
                    arguments '-DANDROID_PLATFORM=android-28', '-DANDROID_TOOLCHAIN=clang',
                            '-DANDROID_ARM_NEON=TRUE'
                    cFlags '-O3', '-fsigned-char', '-Wformat','-mfpu=neon', '-mfloat-abi=softfp -frtti' // full optimization, char data type is signed
                    // 编译优化,设置函式是否能被 inline 的伪指令长度
                    cppFlags '-O3', '-fexceptions', '-fsigned-char',
                            "-frtti -std=c++14", '-Wformat'

                }
            }
            minifyEnabled false
            debuggable true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }
   //....}

到目前为止其实已经可以编译出来so文件了,但是最后竟然栽在了 System.loadLibrary(“xxx”)上,加载dlib的so会出现莫名其妙的问题。找了半天,最后找到了问题,注释掉logger_kernel_1.cpp中的139行。

上面的都完成之后,我们执行assembleDebug,编译出来so文件,我们可以在生成的文件目录中找到。

由于Opencv有编译好的安卓版本,所以我们就可以省事很多了,接下来就开始集成到新的项目中。

进行人脸识别

同样新建一个Android Studio的C++项目,我们将opencv和dlib的库文件都引入到Android Studio中,然后再build.gradle中配置一些编译参数,并且将opencv和dlib的头文件包含到项目中。这些步骤基本都十分雷同,我就不赘述了,大家可以直接参考源码。我重点说一下cmake以及c++代码。

CMakeLists.txt

简单加入两个so以及添加一个宏定义

cmake_minimum_required(VERSION 3.4.1)set(PATH_TO_NATIVE ${PATH_TO_MEDIACORE}/src/main/cpp)set(PATH_TO_PRE_BUILT ${PATH_TO_MEDIACORE}/libs/${ANDROID_ABI})include_directories(BEFORE ${PATH_TO_MEDIACORE}/libs/include/)file(GLOB  FFMPEG_DECODE_SOURCE "*.cpp")#包含当前目录的cpp文件add_definitions(-DDLIB_PNG_SUPPORT)  #需要加入这个宏定义,不然会导致无法使用libpngadd_library(opencv        SHARED
        ${FFMPEG_DECODE_SOURCE}
        )target_link_libraries(opencv
                    log
                    z
                    ${PATH_TO_PRE_BUILT}/libopencv_java3.so
                    ${PATH_TO_PRE_BUILT}/libdlib.so                    )
MainActivity.kt

很简单的功能,就是通过native层返回一个数组,生成bitmap并且显示。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val progress=ProgressDialog(this)
        iv_origin.setImageURI(Uri.parse("/sdcard/dlibfolder/header.png"));
        btn_detect.setOnClickListener {
            progress.show()
            thread {
                val byte = detect()
                val b=Bitmap.createBitmap(byte,384,250,Bitmap.Config.ARGB_8888)
                runOnUiThread {
                    iv_detect.setImageBitmap(b)
                    progress.dismiss()
                }
            }
        }

    }

    companion object {
        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("opencv")
        }
    }

    private external fun detect():IntArray}
native-lib.cpp

简单返回一个int数组,数组大小就是图片的宽高,这里我都是用的是我使用的图片的宽高。然后用改方法返回的数据

extern "C" JNIEXPORT jintArray JNICALLJava_com_ty_opencvtest_MainActivity_detect(JNIEnv *env, jobject) {
    jintArray intArray = env->NewIntArray(384*250);
    const  jint * buf= reinterpret_cast<const jint *>(test_faced());
    env->SetIntArrayRegion(intArray, 0,384*250, buf);  
    return intArray;}
face_landmark_detection_ex.cpp

关键的检测代码,我把demo稍微修改了一下,几乎是最简单的模式。注释我也写了,大概总结一下步骤。

  1. 首先获取人脸检测的模型

  2. 读取图片

  3. 检测人脸,每张脸保存68个点

  4. 使用Opencv画点然后返回数据

#include <dlib/image_processing/frontal_face_detector.h>#include <dlib/image_processing/render_face_detections.h>#include <dlib/image_processing.h>#include <dlib/gui_widgets.h>#include <dlib/image_io.h>#include <iostream>#include <opencv2/opencv.hpp>using namespace dlib;using namespace std;#include <android/log.h>#define LOG_TAG "FaceDetection/DetectionBasedTracker"#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))// ----------------------------------------------------------------------------------------uchar * test_faced() {
    try {
		
		//人脸检测核心模型
        frontal_face_detector detector = get_frontal_face_detector();
        //机器学习的模型,从sd卡中导入
        shape_predictor sp;
        deserialize("/sdcard/dlibfolder/shape_predictor_68_face_landmarks.dat") >> sp;

		//导入实现的png图片,dlib
        array2d<rgb_pixel> img;
        load_image(img, "/sdcard/dlibfolder/header.png");
        const rectangle &rectOri = get_rect(img);
        //为了检测更小的人脸,放大图片
        pyramid_up(img);
        //获取图片比例
        const rectangle &rectUp = get_rect(img);
        float scalY = rectUp.bottom() / rectOri.bottom();
        float scalX = rectUp.right() / rectOri.right();
        LOGD("%f == %f",scalY, scalX);

		//opencv导入图片
        cv::Mat temp;
        temp=cv::imread("/sdcard/dlibfolder/header.png",cv::IMREAD_UNCHANGED);
        // Now tell the face detector to give us a list of bounding boxes
        // around all the faces in the image.
        //检测人脸数量 dets.siez()
        std::vector<rectangle> dets = detector(img);
        //每一张脸去检测68个点
        std::vector<full_object_detection> shapes;
        for (unsigned long j = 0; j < dets.size(); ++j) {
            full_object_detection shape = sp(img, dets[j]);
            shapes.push_back(shape);
        }
		//使用Opencv画点,注意坐标的还原,最后将数据返回
        if (!shapes.empty()) {
            for (int i = 0; i < 68; i++) {
                LOGD("%d == %d",shapes[0].part(i).x(), shapes[0].part(i).y());
                //bgr
                circle(temp, cvPoint(static_cast<int>(shapes[0].part(i).x() / scalX), static_cast<int>(shapes[0].part(i).y() / scalY)), 1, cv::Scalar(0, 0, 255, 255), 1, cv::LINE_AA, 0);
            }
        }


        return temp.data;
    }
    catch (exception &e) {
        LOGD("\nexception thrown!");
        LOGD("%s", e.what());
    }}

最终效果

demo比较简单,唯一的麻烦是编译以及集成的时候踩了不少坑。官方的demo是真滴慢,当然读取文件和各种模型的时间也花了不少,以后在看看优化速度。源码中我放入了编译项目的压缩文件以及dlib所需的资源。

源码地址


评论(0条)

请登录后评论
ziyuan

ziyuan Rank: 16

0

0

0

( 此人很懒并没有留下什么~~ )

首页

栏目

搜索

会员