Android NDK開(kāi)發(fā):Opencv實(shí)現(xiàn)戴口罩識(shí)別

目錄

效果展示

相關(guān)文章及項(xiàng)目

這里的程序是以我的這篇文章為基礎(chǔ)的:Android NDK開(kāi)發(fā):Opencv實(shí)現(xiàn)人臉識(shí)別
這里我參考了這個(gè)項(xiàng)目:https://github.com/hpc203/FaceMaskDetection-dnn良风,用的這個(gè)項(xiàng)目里面的C語(yǔ)言部分的代碼和dnn模型數(shù)據(jù),移植到了Android上

實(shí)現(xiàn)步驟

1.創(chuàng)建人臉框類(lèi)

這里創(chuàng)建一個(gè)用于存儲(chǔ)人臉框和是否佩戴口罩的類(lèi)用于繪制

public class FaceMaskBean {
    private int isMask = 0;//是否戴口罩了廷粒,0沒(méi)戴箱蟆,1戴了
    private Rect faceRect;//人臉框

    public FaceMaskBean(int isMask, Rect faceRect) {
        this.isMask = isMask;
        this.faceRect = faceRect;
    }

    public int getIsMask() {
        return isMask;
    }

    public void setIsMask(int isMask) {
        this.isMask = isMask;
    }

    public Rect getFaceRect() {
        return faceRect;
    }

    public void setFaceRect(Rect faceRect) {
        this.faceRect = faceRect;
    }
}
2.修改JNI返回類(lèi)型

Android NDK開(kāi)發(fā):Opencv實(shí)現(xiàn)人臉識(shí)別中的代碼相比床绪,修改NativeUtil中ndkCheckFace函數(shù)的返回類(lèi)型為FaceMaskBean類(lèi)型的數(shù)組

object NativeUtil {
    init {
        System.loadLibrary("NDKInterface")
        System.loadLibrary("opencv_java4")
    }

    /**
     * 加載模型
     */
    external fun ndkInit(protoTxtFilePath:String,modelFilePath:String)

    /**
     * 人臉檢測(cè)
     */
    external fun ndkCheckFace(yuvData:ByteArray,rotation:Int,width:Int,height:Int):Array<FaceMaskBean>
}
3.移植戴口罩識(shí)別代碼

這里我把文章開(kāi)頭提到的項(xiàng)目中的代碼做了如下修改
FaceMask.h

#ifndef OPENCVCHECKFACE_FACEMASK_H
#define OPENCVCHECKFACE_FACEMASK_H
#include <iostream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
#include <jni.h>
using namespace cv;
using namespace dnn;
using namespace std;

class FaceMask {
public:
    FaceMask(const float conf_thresh = 0.5, const float iou_thresh = 0.4);
    jobjectArray detect(Mat &srcimg,JNIEnv* &env);
private:
    const int feature_map_sizes[5][2] = {{33, 33}, {17, 17}, {9, 9}, {5, 5}, {3, 3}};
    const float anchor_sizes[5][2] = {{0.04, 0.056}, {0.08, 0.11}, {0.16, 0.22}, {0.32, 0.45}, {0.64, 0.72}};
    const float anchor_ratios[3] = {1, 0.62, 0.42};
    const float variances[4] = {0.1, 0.1, 0.2, 0.2};
    float conf_thresh;
    float iou_thresh;
    const Size target_shape = Size(260, 260);
    const int num_prior = 5972;
    float* prior_data;
    Net net;

    void generate_priors();
    void decode(Mat loc, Mat conf, vector<Rect>& boxes, vector<float>& confidences, vector<int>& classIds, const int srcimg_h, const int srcimg_w);
};


#endif //OPENCVCHECKFACE_FACEMASK_H

FaceMask.cpp,其中模型數(shù)據(jù)我為了方便直接手動(dòng)拷貝到了SD卡中,然后直接用路徑加載的

#include "FaceMask.h"

FaceMask::FaceMask(const float conf_thresh, const float iou_thresh)
{
    this->conf_thresh = conf_thresh;
    this->iou_thresh = iou_thresh;
    this->net = readNet("/storage/emulated/0/Android/data/com.itfitness.opencvcheckface/files/Download/face_mask_detection.caffemodel"
            , "/storage/emulated/0/Android/data/com.itfitness.opencvcheckface/files/Download/face_mask_detection.prototxt");
    this->generate_priors();
}

void FaceMask::generate_priors()
{
    this->prior_data = new float[this->num_prior *4];
    float* pdata = prior_data;
    int i = 0, j = 0, h = 0, w = 0;
    float height = 0, width = 0, ratio = 0;
    for (i = 0; i < 5; i++)
    {
        const int feature_map_height = this->feature_map_sizes[i][0];
        const int feature_map_width = this->feature_map_sizes[i][1];
        for (h = 0; h < feature_map_height; h++)
        {
            for (w = 0; w < feature_map_width; w++)
            {
                ratio = sqrt(this->anchor_ratios[0]);
                for(j=0;j<2;j++)
                {
                    width = this->anchor_sizes[i][j] * ratio;
                    height = this->anchor_sizes[i][j] / ratio;
//                    pdata[0] = (w + 0.5) / feature_map_width - 0.5 * width;       ///xmin
//                    pdata[1] = (h + 0.5) / feature_map_height - 0.5 * height;      ////ymin
//                    pdata[2] = (w + 0.5) / feature_map_width + 0.5 * width;       ///xmax
//                    pdata[3] = (h + 0.5) / feature_map_height + 0.5 * height;      ////ymax
                    pdata[0] = (w + 0.5) / feature_map_width;       ///center_x
                    pdata[1] = (h + 0.5) / feature_map_height;      ////center_y
                    pdata[2] = width;       ///width
                    pdata[3] = height;      ////height
                    pdata += 4;
                }

                for(j=0;j<2;j++)
                {
                    ratio = sqrt(this->anchor_ratios[j+1]);
                    width = this->anchor_sizes[i][0] * ratio;
                    height = this->anchor_sizes[i][0] / ratio;
//                    pdata[0] = (w + 0.5) / feature_map_width - 0.5 * width;       ///xmin
//                    pdata[1] = (h + 0.5) / feature_map_height - 0.5 * height;      ////ymin
//                    pdata[2] = (w + 0.5) / feature_map_width + 0.5 * width;       ///xmax
//                    pdata[3] = (h + 0.5) / feature_map_height + 0.5 * height;      ////ymax
                    pdata[0] = (w + 0.5) / feature_map_width;       ///center_x
                    pdata[1] = (h + 0.5) / feature_map_height;      ////center_y
                    pdata[2] = width;       ///width
                    pdata[3] = height;      ////height
                    pdata += 4;
                }
            }
        }
    }
}

void FaceMask::decode(Mat loc, Mat conf, vector<Rect>& boxes, vector<float>& confidences, vector<int>& classIds, const int srcimg_h, const int srcimg_w)
{
    if(loc.dims==3)
    {
        loc = loc.reshape(0, this->num_prior);
    }
    if(conf.dims==3)
    {
        conf = conf.reshape(0, this->num_prior);
    }
    float predict_xmin = 0, predict_ymin = 0, predict_w = 0, predict_h = 0;
    int srcimg_xmin = 0, srcimg_ymin = 0;
    int i = 0;
    for(i=0;i<this->num_prior;i++)
    {
        Mat scores = conf.row(i).colRange(0, 2);
        Point classIdPoint;
        double score;
        // Get the value and location of the maximum score
        minMaxLoc(scores, 0, &score, 0, &classIdPoint);
        if (score>this->conf_thresh)
        {
            const int row_ind = i * 4;
            const float* pbox = (float*)loc.data + row_ind;
            predict_w = exp(pbox[2] * this->variances[2]) * this->prior_data[row_ind + 2];
            predict_h = exp(pbox[3] * this->variances[3]) * this->prior_data[row_ind + 3];
            predict_xmin = pbox[0] * this->variances[0] * this->prior_data[row_ind + 2] + this->prior_data[row_ind] - 0.5 * predict_w;
            predict_ymin = pbox[1] * this->variances[1] * this->prior_data[row_ind + 3] + this->prior_data[row_ind + 1] - 0.5 * predict_h;
            classIds.push_back(classIdPoint.x);
            confidences.push_back(score);
            srcimg_xmin = (int)max(predict_xmin * srcimg_w, 0.f);
            srcimg_ymin = (int)max(predict_ymin * srcimg_h, 0.f);
            boxes.push_back(Rect(srcimg_xmin, srcimg_ymin, (int)(predict_w * srcimg_w), (int)(predict_h * srcimg_h)));
        }
    }
}

jobjectArray FaceMask::detect(Mat &srcimg,JNIEnv* &env)
{
    int height = srcimg.rows;
    int width = srcimg.cols;
    Mat blob = blobFromImage(srcimg, 1/255.0, this->target_shape);
    this->net.setInput(blob);
    vector<Mat> outs;
    this->net.forward(outs, this->net.getUnconnectedOutLayersNames());
    ////post process
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
    this->decode(outs[0], outs[1], boxes, confidences, classIds, height, width);
    vector<int> indices;
    NMSBoxes(boxes, confidences, this->conf_thresh, this->iou_thresh, indices);


    jclass faceMaskBeanCls = env->FindClass("com/itfitness/opencvcheckface/FaceMaskBean");
    jmethodID faceMaskBean_construct = env->GetMethodID(faceMaskBeanCls, "<init>","(ILandroid/graphics/Rect;)V"); //Rect的構(gòu)造函數(shù)

    jclass rectCls = env->FindClass("android/graphics/Rect");
    jmethodID rect_construct = env->GetMethodID(rectCls, "<init>", "(IIII)V"); //Rect的構(gòu)造函數(shù)
    jobjectArray faceRectArray = env->NewObjectArray(indices.size(),faceMaskBeanCls,nullptr);

    for (size_t i = 0; i < indices.size(); ++i)
    {
        int idx = indices[i];
        Rect box = boxes[idx];
        if(classIds[idx]==1)
        {
//            rectangle(srcimg, Point(box.x, box.y), Point(box.x + box.width, box.y + box.height), Scalar(0, 0, 255), 2);
//            putText(srcimg, "No mask", Point(box.x, box.y -10), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 1);
            jobject rect = env->NewObject(rectCls,rect_construct,box.x,box.y,box.x + box.width,box.y + box.height);
            jobject faceMaskBean = env->NewObject(faceMaskBeanCls,faceMaskBean_construct,0,rect);
            env->SetObjectArrayElement(faceRectArray,i,faceMaskBean);
        }
        else
        {
//            rectangle(srcimg, Point(box.x, box.y), Point(box.x + box.width, box.y + box.height), Scalar(0, 255, 0), 2);
//            putText(srcimg, "wear mask", Point(box.x, box.y -10), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
            jobject rect = env->NewObject(rectCls,rect_construct,box.x,box.y,box.x + box.width,box.y + box.height);
            jobject faceMaskBean = env->NewObject(faceMaskBeanCls,faceMaskBean_construct,1,rect);
            env->SetObjectArrayElement(faceRectArray,i,faceMaskBean);
        }
    }
    return faceRectArray;
}

NDK的ndkCheckFace函數(shù)也進(jìn)行了修改,如下所示:

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_itfitness_opencvcheckface_NativeUtil_ndkCheckFace(JNIEnv *env, jobject thiz,
                                                           jbyteArray yuv_data, jint rotation,jint width,jint height) {
    jbyte *yuvBuffer = (jbyte *) env->GetByteArrayElements(yuv_data, JNI_FALSE);

    Mat imageSrc(height + height / 2, width, CV_8UC1, (unsigned char *) yuvBuffer);



    Mat bgrCVFrame;
    cvtColor(imageSrc, bgrCVFrame, cv::COLOR_YUV2BGR_NV21);

    rotateMat(bgrCVFrame,rotation);

    return modelMask.detect(bgrCVFrame,env);
}

案例源碼

https://gitee.com/itfitness/opencv-face-mask

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市认烁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌介汹,老刑警劉巖却嗡,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘹承,居然都是意外死亡稽穆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)赶撰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舌镶,“玉大人,你說(shuō)我怎么就攤上這事豪娜〔驼停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵瘤载,是天一觀的道長(zhǎng)否灾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鸣奔,這世上最難降的妖魔是什么墨技? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任惩阶,我火速辦了婚禮,結(jié)果婚禮上扣汪,老公的妹妹穿的比我還像新娘断楷。我一直安慰自己,他們只是感情好崭别,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布冬筒。 她就那樣靜靜地躺著,像睡著了一般茅主。 火紅的嫁衣襯著肌膚如雪舞痰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天诀姚,我揣著相機(jī)與錄音响牛,去河邊找鬼。 笑死赫段,一個(gè)胖子當(dāng)著我的面吹牛呀打,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瑞佩,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼聚磺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坯台!你這毒婦竟也來(lái)了炬丸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜒蕾,失蹤者是張志新(化名)和其女友劉穎稠炬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咪啡,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡首启,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撤摸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毅桃。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖准夷,靈堂內(nèi)的尸體忽然破棺而出钥飞,到底是詐尸還是另有隱情,我是刑警寧澤衫嵌,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布读宙,位于F島的核電站,受9級(jí)特大地震影響楔绞,放射性物質(zhì)發(fā)生泄漏结闸。R本人自食惡果不足惜唇兑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桦锄。 院中可真熱鬧扎附,春花似錦、人聲如沸察纯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饼记。三九已至香伴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間具则,已是汗流浹背即纲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留博肋,地道東北人低斋。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匪凡,于是被迫代替她去往敵國(guó)和親膊畴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容