Android CameraX NDK OpenCV(二)-- 实现Dnn人脸检测

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为3869,预计阅读9分钟

前言

上一篇《Android CameraX NDK OpenCV(一)--实时灰度图预览》已经把Android下OpenCV的Ndk配置完成,并且实现了实时灰度图的显示,本篇来看看在Android下使用Dnn实时地进行人脸检测。Dnn的人脸检测在《实践|OpenCV4.2使用DNN进行人脸检测二(视频篇)》文章中已经实现过,不过那个是在Windows平台下的,检测的方式基本就是按那个来的,这次是我们把其的部分代码移植了过来。

format,png

实现效果

imgconvert.csdnimg.cn

GIF动图

视频效果

划重点

  • 从上面的效果很仔细的话可以看到,我们检测人脸到画上红色矩形框时偶尔会有延时的情况,这个在《Android JetPack组件CameraX使用及修改显示图像》中说过,我们在摄像机预览中上层加入了VIEW,在VIEW中进行绘制的,其实如果不要想这个情况,可以像灰度图显示一样,把整张已经标记好的图片都传回来,然后DrawBitmap把原来的预览图盖住也可以。

  • 还要注意的一点是加载的人脸检测的模型文件,因为要在NDK中加载并初始化,所以在程序中我们要考虑怎么拷模型文件先复制到Android设备本地,然后调用JNI的方法去加载模型文件。

代码部分

format,png

微卡智享

01

模型文件处理

Demo程序还是接上一篇已经搭建好的程序实现

format,png

format,png

首先在Res下面创建一个RAW的文件夹

format,png

然后把我们已经下载好的模型文件复制进去(Demo中已经拷进去了,可以直接在里面获取到)

复制文件到本地代码

    private lateinit var mFaceMdescFile: File
    private lateinit var mFaceMBinaryFile:File


    private fun copymFaceMdescFile() {
        try {
            // load cascade file from application resources
            val inputStream = resources.openRawResource(R.raw.opencv_face_detector)
            val faceDir = getDir("facedetector", MODE_PRIVATE)
            mFaceMdescFile = File(faceDir, "opencv_face_detector.pbtxt")
            if (mFaceMdescFile.exists()) return
            val os: FileOutputStream = FileOutputStream(mFaceMdescFile)
            val buffer = ByteArray(4096)
            var bytesRead: Int
            while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                os.write(buffer, 0, bytesRead)
            }
            inputStream.close()
            os.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }


    private fun copymFaceMBinaryFile() {
        try {
            // load cascade file from application resources
            val inputStream = resources.openRawResource(R.raw.opencv_face_detector_uint8)
            val faceDir = getDir("facedetector", MODE_PRIVATE)
            mFaceMBinaryFile = File(faceDir, "opencv_face_detector_uint8.pb")
            if (mFaceMBinaryFile.exists()) return
            val os: FileOutputStream = FileOutputStream(mFaceMBinaryFile)
            val buffer = ByteArray(4096)
            var bytesRead: Int
            while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                os.write(buffer, 0, bytesRead)
            }
            inputStream.close()
            os.close()
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }


上面两段代码是将两个模型文件复制到本地,如果文件存在就不用再重复了。

JNI加载模型文件

format,png

这次改造了一下代码,把所有JNI的调用都放入一个类中,加入了initFaceDetector的初始化人脸检测和facedetector的人脸检测。

NDK中调用

extern "C"
JNIEXPORT jboolean JNICALL
Java_lib_vaccae_opencv_OpenCVJNI_initFaceDetector(JNIEnv *env, jobject thiz, jstring model_binary,
                                                  jstring model_desc) {
    try {
        string sbinary = env->GetStringUTFChars(model_binary, 0);
        string sdesc = env->GetStringUTFChars(model_desc, 0);
        //初始化DNN
        _faceDetect = facedetect();
        jboolean res = _faceDetect.InitDnnNet(sbinary, sdesc);
        
        return res;
    } catch (Exception e) {
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    } catch (...) {
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
    }
}


extern "C"
JNIEXPORT jobject JNICALL
Java_lib_vaccae_opencv_OpenCVJNI_facedetector(JNIEnv *env, jobject thiz, jbyteArray bytes,
                                              jint width, jint height) {
    try {
        Mat src = byteArrayToMat(env, bytes, width, height);


        //获取ArrayList类引用
        jclass list_jcls = env->FindClass("java/util/ArrayList");
        if (list_jcls == nullptr) {
            LOGI("ArrayList没找到相关类!");
            return 0;
        }
        //获取ArrayList构造函数id
        jmethodID list_init = env->GetMethodID(list_jcls, "<init>", "()V");
        //创建一个ArrayList对象
        jobject list_obj = env->NewObject(list_jcls, list_init);
        //获取ArrayList对象的add()的methodID
        jmethodID list_add = env->GetMethodID(list_jcls, "add", "(Ljava/lang/Object;)Z");


        //人脸检测
        vector<vector<int>> outRects = _faceDetect.Detect(src);
        if(outRects.size()>0){
            jclass rect_jcls = env->FindClass("android/graphics/Rect");
            jmethodID  rect_init = env->GetMethodID(rect_jcls,"<init>","(IIII)V");
            for(int i=0;i<outRects.size();++i){
                vector<int> point = outRects[i];
                jobject tmprect = env->NewObject(rect_jcls,rect_init,
                                                 (int)point[0],
                                                 (int)point[1],
                                                 (int)point[2],
                                                 (int)point[3]);
                env->CallBooleanMethod(list_obj, list_add, tmprect);
            }
        }
        return list_obj;
    } catch (Exception e) {
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    } catch (...) {
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
    }
}


人脸检测类

format,png

//
// Created by 36574 on 2020-12-04.
//


#include "facedetect.h"


//初始化Dnn
bool facedetect::InitDnnNet(string modelbinary, string modeldesc) {
    _modelbinary = modelbinary;
    _modeldesc = modeldesc;


    //初始化置信阈值
    confidenceThreshold = 0.6;
    inScaleFactor = 0.5;
    inWidth = 300;
    inHeight = 300;
    meanVal = Scalar(104.0, 177.0, 123.0);


    _net = dnn::readNetFromTensorflow(_modelbinary, _modeldesc);
    _net.setPreferableBackend(dnn::DNN_BACKEND_OPENCV);
    _net.setPreferableTarget(dnn::DNN_TARGET_CPU);


    return !_net.empty();
}


//人脸检测
vector<Rect> facedetect::DetectToRect(Mat frame) {
    Mat tmpsrc = frame;
    vector<Rect> dsts = vector<Rect>();
    // 修改通道数
    if (tmpsrc.channels() == 4)
        cvtColor(tmpsrc, tmpsrc, COLOR_BGRA2BGR);
    // 输入数据调整
    Mat inputBlob = dnn::blobFromImage(tmpsrc, inScaleFactor,
                                       Size(inWidth, inHeight), meanVal, false, false);
    _net.setInput(inputBlob, "data");


    //人脸检测
    Mat detection = _net.forward("detection_out");


    Mat detectionMat(detection.size[2], detection.size[3],
                     CV_32F, detection.ptr<float>());


    //检测出的结果进行绘制和存放到dsts中
    for (int i = 0; i < detectionMat.rows; i++) {
        //置值度获取
        float confidence = detectionMat.at<float>(i, 2);
        //如果大于阈值说明检测到人脸
        if (confidence > confidenceThreshold) {
            //计算矩形
            int xLeftBottom = static_cast<int>(detectionMat.at<float>(i, 3) * tmpsrc.cols);
            int yLeftBottom = static_cast<int>(detectionMat.at<float>(i, 4) * tmpsrc.rows);
            int xRightTop = static_cast<int>(detectionMat.at<float>(i, 5) * tmpsrc.cols);
            int yRightTop = static_cast<int>(detectionMat.at<float>(i, 6) * tmpsrc.rows);
            //生成矩形
            Rect rect((int)xLeftBottom, (int)yLeftBottom,
                      (int)(xRightTop - xLeftBottom),
                      (int)(yRightTop - yLeftBottom));


            //截出图矩形存放到dsts数组中
            dsts.push_back(rect);


            //在原图上用红框画出矩形
            rectangle(frame, rect, Scalar(0, 0, 255));
        }
    }


    return dsts;
}


//人脸检测返回点
vector<vector<int>> facedetect::Detect(Mat frame) {
    Mat tmpsrc = frame;
    vector<vector<int>> points = vector<vector<int>>();
    // 修改通道数
    if (tmpsrc.channels() == 4)
        cvtColor(tmpsrc, tmpsrc, COLOR_BGRA2BGR);
    // 输入数据调整
    Mat inputBlob = dnn::blobFromImage(tmpsrc, inScaleFactor,
                                       Size(inWidth, inHeight), meanVal, false, false);
    _net.setInput(inputBlob, "data");


    //人脸检测
    Mat detection = _net.forward("detection_out");


    Mat detectionMat(detection.size[2], detection.size[3],
                     CV_32F, detection.ptr<float>());


    //检测出的结果进行绘制和存放到dsts中
    for (int i = 0; i < detectionMat.rows; i++) {
        //置值度获取
        float confidence = detectionMat.at<float>(i, 2);
        //如果大于阈值说明检测到人脸
        if (confidence > confidenceThreshold) {
            vector<int> item;
            //获取左上和右下两个点的XY坐标
            //左上X
            int xLeftTop = static_cast<int>(detectionMat.at<float>(i, 3) * tmpsrc.cols);
            item.push_back(xLeftTop);
            //左上Y
            int yLeftTop = static_cast<int>(detectionMat.at<float>(i, 4) * tmpsrc.rows);
            item.push_back(yLeftTop);
            //右下X
            int xRightBottom = static_cast<int>(detectionMat.at<float>(i, 5) * tmpsrc.cols);
            item.push_back(xRightBottom);
            //右下Y
            int yRightBottom = static_cast<int>(detectionMat.at<float>(i, 6) * tmpsrc.rows);
            item.push_back(yRightBottom);




            //截出图矩形存放到dsts数组中
            points.push_back(item);
        }
    }
    return points;
}








02

检测到的人脸标记

format,png

在ViewOverLay的类中加入一个DrawRect的方法,因为在JNI返回的是人脸检测到的矩形,所以这里加入一个画矩形的函数,后面两个参数的宽度和高度在上一篇灰度显示中提到过,我们传入的图片的大小和预览的图片大小不一致,预览时自动就缩放至设备屏幕的宽高了,所以这里传入的参数为实际处理图片的宽和高,用于计算宽和度偏移的比例

format,png

然后在OnDraw的函数中针对矩形的四个点进行比例的偏移。

03

调用相关代码

format,png

程序启动时加入复制文件及初始化DNN模型文件的调用

format,png

AnalysisCvDetector的analyze事件中加入人脸检测的调用,这样基本就完成了。

format,png

Demo地址

https://github.com/Vaccae/AndroidCameraXNDKOpenCV.git

Demo如果有新的就会直接提交更新到主分支了

format,png

扫描二维码

获取更多精彩

微卡智享

format,png

「 往期文章 」

Android CameraX NDK OpenCV(一)--实时灰度图预览

Android JetPack组件CameraX使用及修改显示图像

OpenCV图片动态特效显示(四)-- 中间扩张和栅格显示效果

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: android-ndk-r12b-windows-x86.zip 是一个 Android NDK 的压缩文件。Android NDK (Native Development Kit) 是一个让开发者能够使用 C 或 C++ 程序语言编写 Android 应用的工具集合。它主要用于开发需要高性能或使用底层库的应用程序。 android-ndk-r12b-windows-x86.zip 针对的是 Windows 系统的 32 位操作系统。该文件包含了 Android NDK 的相关文件和工具,可供开发者使用。开发者可以通过下载该文件,解压缩并配置环境变量,以便在 Windows 上编写和构建 C/C++ 代码的 Android 应用程序。 该压缩文件中包含了编译器、调试器、标准系统库、头文件以及其他构建和调试所需的文件。通过使用该工具集合,开发者可以充分利用 C/C++ 语言的速度和功能,编写高性能、复杂的 Android 应用程序。 同时需要注意的是,该压缩文件适用于 Windows 系统的 32 位操作系统。如果你的设备或操作系统是其他类型的,可能需要下载适用于该设备或操作系统的相应版本。 ### 回答2: android-ndk-r12b-windows-x86.zip 是一个Android NDK(Native Development Kit)的压缩文件。 NDK是一个用于开发Android平台上原生代码的工具集。它允许开发者在Java平台上编写Android应用程序的同时,通过使用C或C++等编程语言编写高性能和复杂度较高的代码模块。NDK通过将应用程序的代码编译为与硬件平台相关的机器码实现高效执行。 android-ndk-r12b-windows-x86.zip 这个文件是适用于Windows操作系统的NDK的压缩文件,特定于x86架构。这意味着该NDK版本适用于基于x86架构的32位执行环境,例如32位的Windows操作系统。 该压缩文件包含了开发者在Windows系统上使用NDK所需的所有文件和工具,如编译器、调试器、库文件等。开发者可以使用这个NDK版本来编写和构建在x86架构上运行的Android应用程序。 通过使用NDK,开发者可以利用C或C++编写更高效和功能强大的代码,也可以重用已有的C/C++代码库。这对于需要执行高计算密集型任务、需要访问底层硬件功能或需要与跨平台C/C++库集成的应用程序非常有用。 总之,android-ndk-r12b-windows-x86.zip 是一个适用于Windows操作系统的Android NDK的压缩文件,特定于x86架构。使用这个NDK版本,开发者可以编写高性能、复杂度较高的代码以实现更强大的Android应用程序。 ### 回答3: android-ndk-r12b-windows-x86.zip是一个用于Windows操作系统的Android NDK(Native Development Kit)软件包。Android NDK是一个开发工具集,可以帮助开发人员使用C和C++语言编写Android应用程序。它提供了一组库和工具,允许开发者直接使用本地代码进行开发。 android-ndk-r12b-windows-x86.zip包含了NDK的安装程序和相关的文件。用户可以通过解压缩这个压缩包来安装NDK。一旦安装完成,开发者就可以在Windows操作系统上使用NDK来开发Android应用程序。这个特定的压缩包适用于x86架构的计算机,这是一种常见的Windows计算机架构。 使用android-ndk-r12b-windows-x86.zip,开发者可以利用NDK的功能来更好地优化他们的Android应用程序。这包括直接使用本机代码编写应用程序的能力,以及访问底层系统功能的能力。通过使用NDK,开发者可以在性能关键的部分使用底层代码,提高应用程序的速度和效率。 android-ndk-r12b-windows-x86.zip是一个方便开发者使用Android NDK的软件包。它使开发者能够充分利用底层功能,并开发出更高效的Android应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值