前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android CameraX NDK OpenCV(二)-- 实现Dnn人脸检测

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

作者头像
Vaccae
发布2020-12-18 15:54:50
1.7K0
发布2020-12-18 15:54:50
举报
文章被收录于专栏:微卡智享

前言

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

实现效果

GIF动图

视频效果

划重点

  • 从上面的效果很仔细的话可以看到,我们检测人脸到画上红色矩形框时偶尔会有延时的情况,这个在《Android JetPack组件CameraX使用及修改显示图像》中说过,我们在摄像机预览中上层加入了VIEW,在VIEW中进行绘制的,其实如果不要想这个情况,可以像灰度图显示一样,把整张已经标记好的图片都传回来,然后DrawBitmap把原来的预览图盖住也可以。
  • 还要注意的一点是加载的人脸检测的模型文件,因为要在NDK中加载并初始化,所以在程序中我们要考虑怎么拷模型文件先复制到Android设备本地,然后调用JNI的方法去加载模型文件。

代码部分

微卡智享

01

模型文件处理

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

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

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

复制文件到本地代码

代码语言:javascript
复制
    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加载模型文件

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

NDK中调用

代码语言:javascript
复制
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}");
    }
}

人脸检测类

代码语言:javascript
复制
//
// 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

检测到的人脸标记

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

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

03

调用相关代码

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

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

Demo地址

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 复制文件到本地代码
  • JNI加载模型文件
  • Demo地址
相关产品与服务
人脸识别
腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档