美文网首页
CV04-01:OpenCV的dnn模块

CV04-01:OpenCV的dnn模块

作者: 杨强AT南京 | 来源:发表于2019-12-02 09:11 被阅读0次

      OpenCV的DNN模块,提供了对其他框架训练模型的加载,目前支持框架有:
      1. *Caffe, http://caffe.berkeleyvision.org/
      2. *TensorFlow, https://www.tensorflow.org/
      3. *Torch, http://torch.ch/
      4. *Darknet, https://pjreddie.com/darknet/
      5. *DLDT, https://software.intel.com/openvino-toolkit
      6. *ONNX, https://onnx.ai/


      本主题按照OpenCV官方的例子撸了两个模型加载的Demo:Darknet与Caffe. 其中使用的预训练模型可以从下面两个URL获取下载地址:
      - Caffe模型:
    https://docs.opencv.org/4.1.2/d5/de7/tutorial_dnn_googlenet.html
      - Darknet模型:
    https://pjreddie.com/darknet/yolo/


    Caffe模型加载

    #include <opencv2/opencv.hpp>
    #include <iostream>
    #include <fstream>
    
    
    int main(int argc, char const *argv[]){
        /*
            加载别人训练好的模型,用来识别图片的类别;
                - bvlc_googlenet.caffemodel             :  训练好的模型
                - bvlc_googlenet.prototxt               :  训练使用的深度网络结构
                - classification_classes_ILSVRC2012.txt :类别与类别名称
         */
    
        /*
            框架名:
                *Caffe, http://caffe.berkeleyvision.org/
                *TensorFlow, https://www.tensorflow.org/
                *Torch, http://torch.ch/
                *Darknet, https://pjreddie.com/darknet/
                *DLDT, https://software.intel.com/openvino-toolkit
                *ONNX, https://onnx.ai/
            框架的模型扩展名:
                *.caffemodel (Caffe
                *.pb (TensorFlow)
                *.t7 | *.net (Torch)
                *.weights (Darknet)
                *.bin (DLDT)
                *.onnx (ONNX)
            框架的结构配置扩展名:
                *.prototxt (Caffe)
                *.pbtxt (TensorFlow)
                *.cfg (Darknet)
                *.xml (DLDT)
         */
        // 加载模型
        cv::String model        = "bvlc_googlenet.caffemodel";
        cv::String config       = "bvlc_googlenet.prototxt";
        cv::String framework    = "Caffe";               // 显式指定框架名(可以使用"",会根据扩展名自动确定)
        cv::dnn::Net net = cv::dnn::readNet(model, config, framework);
        // 设置使用的计算推理库
        /*
             : 
            cv::dnn::DNN_BACKEND_DEFAULT,   = DNN_BACKEND_INFERENCE_ENGINE
            cv::dnn::DNN_BACKEND_HALIDE,    = DNN_BACKEND_OPENCV
            cv::dnn::DNN_BACKEND_INFERENCE_ENGINE, 
            cv::dnn::DNN_BACKEND_OPENCV, 
            cv::dnn::DNN_BACKEND_VKCOM      = DNN_BACKEND_OPENCV
    }
         */
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT); // 采用默认值
        // 设置运算的目标设备
        /*
            cv::dnn::DNN_TARGET_CPU, 
            cv::dnn::DNN_TARGET_OPENCL, 
            cv::dnn::DNN_TARGET_OPENCL_FP16, 
            cv::dnn::DNN_TARGET_MYRIAD, 
            cv::dnn::DNN_TARGET_VULKAN, 
            cv::dnn::DNN_TARGET_FPGA 
         */
        net.setPreferableBackend(cv::dnn::DNN_TARGET_CPU); // 采用默认值
        // net.setPreferableBackend(cv::dnn::DNN_BACKEND_HALIDE); // 采用OpenGL的管道图像处理
        // 加载图片(或者直接从视频抓取图片识别)
        cv::String imgfile = "space_shuttle.jpg";
        cv::Mat img = cv::imread(imgfile);
    
        // 图像的预处理(去均值,缩放),并返回服务训练模型的图像格式:NCHW矩阵
        cv::Mat std_img;
        // 注意:bvlc_googlenet.prototxt中的深度网络模型需要的图像必须是224 * 224大小的。
        cv::dnn::blobFromImage(img, std_img, 1.0, cv::Size(224, 224), cv::Scalar(), false, false, CV_32F);
        // 预测
        net.setInput(std_img);             // 输入数据
        // 返回的结果是一个数组(一维矩阵),包含是每类可能的概率,结果当然取概率最高的。
        // 类别的对应清单,从classification_classes_ILSVRC2012.txt获取
        cv::Mat prob = net.forward();       // 返回预测结果
    
        // 取最大的概率数据与位置
        double  max_prob;
        cv::Point max_pos;
        cv::minMaxLoc(prob, 0, &max_prob, 0, &max_pos);  // 最小值不取,就直接使用空指针(0)。
        std::cout << "识别类别是:" << max_pos.x << "概率是:" << max_prob << std::endl;  // 0行,所以取x列数
        // 从classification_classes_ILSVRC2012.txt文件获取max_pos.x位置的类别名称:space shuttle
        // 注意:矩阵下标从0开始,文件的行数也要按照从0开始计。
        std::ifstream ifs("classification_classes_ILSVRC2012.txt");
        int idx = 0;
        char buf[256] = {0};
        while(ifs.good()){
            bzero(buf, sizeof(buf));
            ifs.getline(buf, sizeof(buf)-1);
            if(idx == max_pos.x)
                break;
            idx++;
        }
        std::cout << "类别名:"<< buf << std::endl;
        return 0;
    }
    
    

    Darknet模型加载

    #include <fstream>
    #include <iostream>
    #include <opencv2/opencv.hpp>
    
    // 调用预先训练好的yolo模型,用来侦测识别目标。
    /**
     * 深度网络模型 :yolov3.cfg
     * 预先训练好的权重:yolov3.weights
     * 分类的类别文件:coco.names
     * 用来测试的侦测识别图像:dog.jpg
     */
    int main(int argc, char** argv){
        // 加载深度网络模型
        cv::String model = "../yolov3.weights";     // 使用darknet框架训练出来的模型
        cv::String config = "../yolov3.cfg";
        cv::dnn::Net net = cv::dnn::readNet(model, config, "Darknet");   // 如果不知道使用的什么框架训练,最后参数使用空字符串,函数自动识别;
        // 设置运算方式
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);          // cv::dnn::DNN_BACKEND_INFERENCE_ENGINE
        net.setPreferableBackend(cv::dnn::DNN_TARGET_CPU);              // 使用CPU计算
        // 获取输出层名字
        std::vector<cv::String> outNames = net.getUnconnectedOutLayersNames();
        // 打印输出层名字
        // for(cv::String name : outNames){
        //     std::cout<< name << std::endl;
        // }
        // 打开需要测试的图片(可以使用VideoCapture打开视频,来动态抓取图像)
        cv::Mat img = cv::imread("../road.jpg");
    
        // 对图像进行预处理
        cv::Mat blob;
        cv::dnn::blobFromImage(
            img,        // 输入
            blob,       // 输出
            1.0,        // 图像的像素的控制系数
            // cv::Size(img.rows, img.cols),    // 测试过,图片太大,会产生错误,1008测试也会出错,768宽可以
            cv::Size(416, 416), 
            cv::Scalar(),       // 均值处理(如果不指定,策使用图像的均值)
            true,       // 第1通道与第3通道交换(就是RGB与BGR的区别)
            false,      // 图像resize后是否裁剪
            CV_8U);     // 输出图像的深度
    
        net.setInput(
            blob,       // 输入的数据
            "",         // 输入层的名字,采用默认
            0.00392,        // 对输入的控制系数
            cv::Scalar());  // 对输入的均值处理(上面已经处理,这个函数只需要第一个参数即可)
        // 处理Faster-RCNN与R-FCN的情况
        if (net.getLayer(0)->outputNameToIndex("im_info") != -1){
            cv::Mat imInfo = (cv::Mat_<float>(1, 3) << img.rows, img.cols, 1.6f);
            net.setInput(imInfo, "im_info");
        }
        // 输出预测
        std::vector<cv::Mat> outs;          // 输出层可能多个
        net.forward(outs, outNames);
        // 对预测的结果处理:类别,概率,图像区域
        // 得到输出层数量
        std::vector<int> outLayers = net.getUnconnectedOutLayers();
        // 得到第一个输出层类型
        std::string outLayerType = net.getLayer(outLayers[2])->type;
    
        // 定义解析的侦测类别结果
        std::vector<int> classIds;      // 类别id
        std::vector<float> confidences; // 类别的概率(置信度)
        std::vector<cv::Rect> boxes;        // 侦测对象的图像区域
    
        // 输出层类型两类:DetectionOutput或者Region,表示两种不通的输出格式
        if(outLayerType == "Region"){
            // 格式:N * C,N表示侦测的对象数,C表示总得类别数 + 4,其中最前面4个参数是识别的对象区域[center-x, center-y, w, h]
            //循环解析数据
            for(cv::Mat an_out: outs){
                // 每行表示一个侦测到的对象
                for(int row = 0; row < an_out.rows; row++){
                    // 取坐标
                    cv::Mat rect = an_out.row(row).colRange(0,4);
                    // 取侦测的概率
                    cv::Mat prob = an_out.row(row).colRange(5, an_out.cols);
                    // 取概率最大值
                    cv::Point classIdPoint;
                    double confidence;
                    cv::minMaxLoc(prob, 0, &confidence, 0, &classIdPoint);
                    if(confidence > 0.5){
                        // 类别
                        classIds.push_back(classIdPoint.x);
                        // 概率
                        confidences.push_back((float)confidence);
                        // 区域
                        int centerX = (int)(rect.at<float>(0,0) * img.cols);
                        int centerY = (int)(rect.at<float>(0,1) * img.rows);
                        int width = (int)(rect.at<float>(0,2) * img.cols);
                        int height = (int)(rect.at<float>(0,3) * img.rows);
                        int left = centerX - width / 2;
                        int top = centerY - height / 2;
                        boxes.push_back(cv::Rect(left, top, width, height));
                    }
                }
            }
        }
        else if(outLayerType == "DetectionOutput"){
            //
            std::cout << "DetectionOutput" << std::endl;
        }
        std::vector<int> indices;
        cv::dnn::NMSBoxes(boxes, confidences, 0.5, 0.4, indices);
        for(cv::Rect b : boxes){
            cv::rectangle(
                img, 
                cv::Point(b.x, b.y), 
                cv::Point(b.x + b.width, b.y + b.height), 
                cv::Scalar(0, 0, 255),
                4);
        }
        cv::imwrite("../road_out.png", img);
    
        return 0;
    }
    
    • 图片效果


      目标侦测

    附录:

    • 有OpenCV的dnn模型加载,Python训练的模型可以很轻易的使用到任何设备与平台。

    相关文章

      网友评论

          本文标题:CV04-01:OpenCV的dnn模块

          本文链接:https://www.haomeiwen.com/subject/aflxgctx.html