专栏名称: OpenCV学堂
一个致力于计算机视觉OpenCV原创技术传播的公众号!OpenCV计算机视觉与tensorflow深度学习相关算法原创文章分享、函数使用技巧、源码分析与讨论、,计算机视觉前沿技术介绍,技术专家经验分享,人才交流,学习交流。
目录
相关文章推荐
国泰君安证券研究  ·  国君计算机|DeepSeek开源技术提高硬件利用率 ·  昨天  
中国证券报  ·  实探 | 小米SU7 ... ·  2 天前  
中国证券报  ·  深圳发布!支持前海金融高质量发展 ·  3 天前  
51好读  ›  专栏  ›  OpenCV学堂

图像编辑器 Monica 之使用 OpenCV DNN 实现人脸、年龄、性别检测

OpenCV学堂  · 公众号  ·  · 2024-08-07 16:44

正文

一. 图像编辑器 Monica

Monica 是一款跨平台的桌面图像编辑软件,使用 Kotlin Compose Desktop 作为 UI 框架。由于应用层是由 Kotlin 编写的,Monica 基于 mvvm 架构,使用 koin 作为依赖注入框架。大部分算法用 Kotlin 编写,少部分图像处理算法使用 OpenCV C++ 或调用深度学习的模型。

Monica 目前还处于开发阶段,当前版本的可以参见 github 地址:https://github.com/fengzhizi715/Monica

二. OpenCV DNN 推理

OpenCV DNN(Deep Neural Network)是 OpenCV 库中的一个模块,专门用于深度学习模型的加载、构建和推理。它支持多种神经网络架构和训练算法,包括卷积神经网络(CNN)、循环神经网络(RNN)等。OpenCV DNN 模块为开发者提供了一个强大且易于使用的工具,用于在各种应用中利用深度学习技术。

先封装一个人脸检测的类,对外暴露的两个方法:初始化模型和推理图片。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace cv;
using namespace cv::dnn;
using namespace std;

class FaceDetect {

public:
    void init(string faceProto,string faceModel,string ageProto,string ageModel,string genderProto,string genderModel);

    void inferImage(Mat& src, Mat& dst);

private:
    Net ageNet;
    Net genderNet;
    Net faceNet;

    vector<string> ageList;
    vector<string> genderList;
    Scalar MODEL_MEAN_VALUES;

    tuplevector<vector<int>>> getFaceBox(Net net, Mat &frame, double conf_threshold);
};

然后实现该类

#include "FaceDetect.h"

void FaceDetect::init(string faceProto,string faceModel,string ageProto,string ageModel,string genderProto,string genderModel) {

    // Load Network
    ageNet = readNet(ageModel, ageProto);
    genderNet = readNet(genderModel, genderProto);
    faceNet = readNet(faceModel, faceProto);

    cout <"Using CPU device"     ageNet.setPreferableBackend(DNN_TARGET_CPU);
    genderNet.setPreferableBackend(DNN_TARGET_CPU);
    faceNet.setPreferableBackend(DNN_TARGET_CPU);

    MODEL_MEAN_VALUES = Scalar(78.426337760387.7689143744114.895847746);
    ageList = {"(0-2)""(4-6)""(8-12)""(15-20)""(25-32)",
                              "(38-43)""(48-53)""(60-100)"};

    genderList = {"Male""Female"};
}

void FaceDetect::inferImage(Mat& src, Mat& dst) {

    int padding = 20;
    vector<vector<int>> bboxes;

    FaceDetect faceDetect = FaceDetect();
    tie(dst, bboxes) = faceDetect.getFaceBox(faceNet, src, 0.7);

    if(bboxes.size() == 0) {
        cout <"No face detected..."         dst = src;
        return;
    }

    for (auto it = begin(bboxes); it != end(bboxes); ++it) {
        Rect rec(it->at(0) - padding, it->at(1) - padding, it->at(2) - it->at(0) + 2*padding, it->at(3) - it->at(1) + 2*padding);
        Mat face = src(rec); // take the ROI of box on the frame

        Mat blob;
        blob = blobFromImage(face, 1, Size(227227), MODEL_MEAN_VALUES, false);
        genderNet.setInput(blob);
        // string gender_preds;
        vector<float> genderPreds = genderNet.forward();
        // printing gender here
        // find max element index
        // distance function does the argmax() work in C++
        int max_index_gender = std::distance(genderPreds.begin(), max_element(genderPreds.begin(), genderPreds.end()));
        string gender = genderList[max_index_gender];
        cout <"Gender: " 
        /* // Uncomment if you want to iterate through the gender_preds vector
        for(auto it=begin(gender_preds); it != end(gender_preds); ++it) {
          cout <        }
        */


        ageNet.setInput(blob);
        vector<float> agePreds = ageNet.forward();
        /* // uncomment below code if you want to iterate through the age_preds
         * vector
        cout <        for(auto it = age_preds.begin(); it != age_preds.end(); ++it) {
          cout <        }
        */


        // finding maximum indicd in the age_preds vector
        int max_indice_age = std::distance(agePreds.begin(), max_element(agePreds.begin(), agePreds.end()));
        string age = ageList[max_indice_age];
        cout <"Age: "         string label = gender + ", " + age; // label
        cv::putText(dst, label, Point(it->at(0), it->at(1-15), cv::FONT_HERSHEY_SIMPLEX, 1, Scalar(02550), 4, cv::LINE_AA);
    }
}

tuplevector<vector<int>>> FaceDetect::getFaceBox(Net net, Mat &frame, double conf_threshold) {
    Mat frameOpenCVDNN = frame.clone();
    int frameHeight = frameOpenCVDNN.rows;
    int frameWidth = frameOpenCVDNN.cols;
    double inScaleFactor = 1.0;
    Size size = Size(300300);
     // std::vector meanVal = {104, 117, 123};
    Scalar meanVal = Scalar(104117123);

    cv::Mat inputBlob;
    inputBlob = cv::dnn::blobFromImage(frameOpenCVDNN, inScaleFactor, size, meanVal, truefalse);

    net.setInput(inputBlob, "data");
    cv::Mat detection = net.forward("detection_out");

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

    vector<vector<int>> bboxes;

    for(int i = 0; i     {
        float confidence = detectionMat.at<float>(i, 2);
        if(confidence > conf_threshold)
        {
            int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frameWidth);
            int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frameHeight);
            int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frameWidth);
            int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frameHeight);
            vector<int> box = {x1, y1, x2, y2};
            bboxes.push_back(box);
            rectangle(frameOpenCVDNN, Point(x1, y1), Point(x2, y2), Scalar(00255), 28);
        }
    }

    return make_tuple(frameOpenCVDNN, bboxes);
}

三. 应用层调用

编写好底层的推理实现后,接下来就可以给应用层使用了。其实上面还省略了  jni 层的代码,感兴趣的可以直接看项目的源码。

对于应用层,先编写好 Kotlin 调用 jni 层的代码:

object ImageProcess {

    init { // 对于不同的平台加载的库是不同的,mac 是 dylib 库,windows 是 dll 库,linux 是 so 库
        if (isMac) {
            if (arch == "aarch64") { // 即使是 mac 系统,针对不同的芯片 也需要加载不同的 dylib 库
                System.load("${FileUtil.loadPath}libMonicaImageProcess_aarch64.dylib")
            } else {
                System.load("${FileUtil.loadPath}libMonicaImageProcess.dylib")
            }
        } else if (isWindows) {
            System.load("${FileUtil.loadPath}MonicaImageProcess.dll")
        }
    }

    ......

    /**
     * 初始化人脸检测模块
     */

    external fun initFaceDetect(faceProto:String,faceModel:String,
                                ageProto:String, ageModel:String,
                                genderProto:String,genderModel:String)


    /**
     * 人脸检测
     */

    external fun faceDetect(src: ByteArray):IntArray
}

在 Monica 启动时,需要先加载人脸、年龄、性别的模型

        FileUtil.copyFaceDetectModels()

        val faceProto = "${FileUtil.loadPath}opencv_face_detector.pbtxt"
        val faceModel = "${FileUtil.loadPath}opencv_face_detector_uint8.pb"
        val ageProto = "${FileUtil.loadPath}age_deploy.prototxt"
        val ageModel = "${FileUtil.loadPath}age_net.caffemodel"
        val genderProto = "${FileUtil.loadPath}gender_deploy.prototxt"
        val genderModel = "${FileUtil.loadPath}gender_net.caffemodel"

        ImageProcess.initFaceDetect(faceProto,faceModel, ageProto,ageModel, genderProto,genderModel)

最后,就可以在应用层调用了

        val (width,height,byteArray) = state.currentImage!!.getImageInfo()

        try {
             val outPixels = ImageProcess.faceDetect(byteArray)
             state.addQueue(state.currentImage!!)
             state.currentImage = BufferedImages.toBufferedImage(outPixels,width,height)
        } catch (e:Exception) {
             logger.error("faceDetect is failed", e)
        }

我们跑几张图片看看效果:

效果1.png
效果2.png
效果3.png

可以看到控制台的推理结果:







请到「今天看啥」查看全文