• OpenCV开发笔记(七十三):红胖子8分钟带你使用opencv+dnn+yolov3识别物体


     

    前言

      级联分类器的效果并不是很好,准确度相对深度学习较低,上一章节使用了dnn中的tensorflow,本章使用yolov3模型,识别出具体的分类。

     

    Demo

      320x320,置信度0.6
    在这里插入图片描述
      608x608,置信度0.6(.cfg里面是608)
    在这里插入图片描述

     

    yolov3模型下载

    在这里插入图片描述

    OpenCV深度识别基本流程

      opencv3.4.x支持了各种模型。

    支持的模型

      opencv3.4.x支持一下深度学习的模型:
    - caffe:.caffemodel
      官网:http://caffe.berkeleyvision.org
    - tensorflow:.pb
      官网:https://www.tensorflow.org
    - torch:.t7 | .net
      官网:http://torch.ch
    - darknet:.weights
      官网:https://pjreddie.com/darknet
    - DLDT:.bin
      官网:https://software.intel.com/openvino-toolkit

    操作步骤:yolov3

      不同深度学习框架产生的模型,在操作上和数据输出上有一些区别。梳理下opencv使用tensorflow训练好的模型的使用步骤。

    步骤一:读取分类文件

      模型文件对应了不同的分类文件,分类文件是以行为标识,所在的行数(0开始),就是最终识别出的分类号的第几个分类。

    std::string classesFile = "E:/qtProject/openCVDemo/dnnData/" 
                        "yolov3/coco.names";
    // 读入分类名称,存入缓存
    std::ifstream ifs(classesFile);
    std::vector<std::string> classes;
    std::string classLine;
    while(std::getline(ifs, classLine))
    {
        classes.push_back(classLine);
    }
    

    步骤二:加载模型和配置文件,建立神经网络。

      根据不同的模型,使用cv::dnn::readNetFromXXX系列函数进行读取,opencv3.4.x系列支持的dnn模型(支持模型往上看)。
      yolov3模型如下:

    std::string modelWeights = "E:/qtProject/openCVDemo/dnnData/" 
                           "yolov3/yolov3.weights";
    std::string modelCfg = "E:/qtProject/openCVDemo/dnnData/" 
                           "yolov3/yolov3.cfg";
    // 加载yolov3模型
    cv::dnn::Net net = cv::dnn::readNetFromDarknet(modelCfg, modelWeights);
    if(net.empty())
    {
        qDebug() << __FILE__ << __LINE__ << "net is empty!!!";
        return;
    }
    

    步骤三:将要预测的图片加入到神经网络中

      加入之后,需要识别图片,那么需要把图片输入到神经网络当中去,使用yolov3模型特别注意,要先进行归一化,然后变成指定大小的图片,如下:

    // 读取图片识别
    mat = cv::imread("E:/testFile/15.jpg");
    if(!mat.data)
    {
        qDebug() << __FILE__ << __LINE__ << "Failed to read image!!!";
        return;
    }
    //    cv::dnn::blobFromImage(mat, blob);
    //  必须要设置,否则会跑飞
    cv::dnn::blobFromImage(mat,
                         blob,
                         1.0f/255,
                         cv::Size(320, 320),
                         cv::Scalar(0, 0, 0),
                         true,
                         false);
    net.setInput(blob);
    

      宽度高度增加可以提升检测的准确度,最好是根据cfg文件进行修改,本Demo是320x320,实际.cfg文件中的是608x608,并且经过测试,这个是识别效果最好的像素,大于608则会跑飞。
      在这里插入图片描述

    步骤四:分类预测,获取识别的结果

      输入之后,就进行识别,识别是向前预测(分类预测),并且拿到结果,对于yolov3模型,规定了有3个输出层,所以需要先获取3个输出层,然后预测的时候就需要指定预测这3个输出层,否则会跑飞。

    // 获取输出的层
    std::vector<cv::String> outPutNames;
    std::vector<int> outLayers = net.getUnconnectedOutLayers();
    for(int index = 0; index < outLayers.size(); index++)
    {
        outPutNames.push_back(layerNames[outLayers[index] - 1]);
        qDebug() << __FILE__ << __LINE__
        << QString(layerNames[outLayers[index] - 1].c_str());
    } 
    // 推理预测:可以输入预测的图层名称
    std::vector<cv::Mat> probs;
    net.forward(probs, outPutNames);
    

      对于预测的结果,存于std::vectorcv::Mat类型的probs,每一个元素指定为cv::Mat类型的prob,每一行代表一个检测到的分类,具体列信息如下表:
      在这里插入图片描述
      (注意:具体的使用,请参照“步骤五”)

    步骤五:对达到置信度的可以通过输出的mat进行分类和框选

      关键的输出结果步骤,不同的识别有区别,yolov3如下图:

    // 置信度预制,大于执行度的将其使用rect框出来
    for(int index = 0; index < probs.size(); index++)
    {
        for (int row = 0; row < probs[index].rows; row++)
        {
            // 获取probs中一个元素里面匹配对的所有对象中得分最高的
            cv::Mat scores = probs[index].row(row).colRange(5, probs[index].cols);
            cv::Point classIdPoint;
            double confidence;
            // Get the value and location of the maximum score
            cv::minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
            if(confidence > 0.6)
            {
                qDebug() << __FILE__ << __LINE__ << confidence << classIdPoint.x;
                int centerX = (int)(probs.at(index).at<float>(row, 0) * mat.cols);
                int centerY = (int)(probs.at(index).at<float>(row, 1) * mat.rows);
                int width   = (int)(probs.at(index).at<float>(row, 2) * mat.cols);
                int height  = (int)(probs.at(index).at<float>(row, 3) * mat.rows);
                int left = centerX - width / 2;
                int top = centerY - height / 2;
                cv::Rect objectRect(left, top, width, height);
                cv::rectangle(mat, objectRect, cv::Scalar(255, 0, 0), 2);
                cv::String label = cv::format("%s:%.4f",
                                              classes[classIdPoint.x].data(),
                                              confidence);
                cv::putText(mat,
                            label,
                            cv::Point(left, top - 10),
                            cv::FONT_HERSHEY_SIMPLEX,
                            0.4,
                            cv::Scalar(0, 0, 255));
                qDebug() << __FILE__ << __LINE__
                        << centerX << centerY << width << height;
            }
        }
    }
    
     

    函数原型

    读取yolov3模型与配置文件函数原型

    Net readNetFromDarknet(const String &cfgFile,
                          const String &darknetModel = String());
    

      从文件中读取。

    • 参数一:带有网络体系结构文本描述的.cfg文件的路径;
    • 参数二:已学习网络的.weights文件的路径;

    读取图片(需要识别的)函数原型

    void blobFromImage(InputArray image,
                      OutputArray blob,
                      double scalefactor=1.0,
                      const Size& size = Size(),
                      const Scalar& mean = Scalar(),
                      bool swapRB=false,
                      bool crop=false,
                      int ddepth=CV_32F);.
    

      从图像创建区域。可选择从中心调整和裁剪图像。

    • 参数一:图像输入图像(1、3或4通道);
    • 参数二:输出的图像空间;
    • 参数三:图像值的缩放因子乘数;
    • 参数四:大小输出图像的空间大小;
    • 参数五:从通道中减去平均值的平均标量。价值是有意的,如果image有BGR顺序,swapRB为真,则按(mean-R,mean-G,mean-B)顺序排列;
    • 参数六:swapRB标志,指示交换第一个和最后一个通道,在三通道图像是必要的;
    • 参数七:裁剪标志,指示调整大小后是否裁剪图像;
    • 参数八:输出blob的深度,选择CV_32F或CV_8U;

    设置神经网络输入函数原型

    void cv::dnn::Net::setInput(InputArray blob,
                          const String& name = "",
                          double scalefactor = 1.0,
                          const Scalar& mean = Scalar());
    

      设置网络的新输入值。

    • 参数一:一个新的blob。应具有CV_32F或CV_8U深度。
    • 参数二:输入层的名称。
    • 参数三:可选的标准化刻度。
    • 参数四:可选的平均减去值。

    返回所有层的名称(按照本身的索引循序排列)

    std::vector<String> getLayerNames() const;
    

    返回具有未连接输出的层的索引。

    std::vector<int> getUnconnectedOutLayers() const;
    

    深度检测识别(向前预测)函数原型

    void cv::dnn::Net::Mat forward(const String& outputName = String());
    

      向前预测,返回指定层的第一个输出的blob,一般是返回最后一层,可使用cv::Net::getLayarNames()获取所有的层名称。

    • 参数一:outputName需要获取输出的层的名称
     

    Demo

    void OpenCVManager::testYoloV3()
    {
        std::string classesFile = "E:/qtProject/openCVDemo/dnnData/" 
                                  "yolov3/coco.names";
        std::string modelWeights = "E:/qtProject/openCVDemo/dnnData/" 
                              "yolov3/yolov3.weights";
        std::string modelCfg = "E:/qtProject/openCVDemo/dnnData/" 
                               "yolov3/yolov3.cfg";
    
        // 读入分类名称,存入缓存
        std::ifstream ifs(classesFile);
        std::vector<std::string> classes;
        std::string classLine;
        while(std::getline(ifs, classLine))
        {
            classes.push_back(classLine);
        }
    
        // 加载yolov3模型
        cv::dnn::Net net = cv::dnn::readNetFromDarknet(modelCfg, modelWeights);
        if(net.empty())
        {
            qDebug() << __FILE__ << __LINE__ << "net is empty!!!";
            return;
        }
    
        cv::Mat mat;
        cv::Mat blob;
    
        // 获得所有层的名称和索引
        std::vector<cv::String> layerNames = net.getLayerNames();
        int lastLayerId = net.getLayerId(layerNames[layerNames.size() - 1]);
        cv::Ptr<cv::dnn::Layer> lastLayer = net.getLayer(cv::dnn::DictValue(lastLayerId));
        qDebug() << __FILE__ << __LINE__
                 << QString(lastLayer->type.c_str())
                 << QString(lastLayer->getDefaultName().c_str())
                 << QString(layerNames[layerNames.size()-1].c_str());
    
        // 获取输出的层
        std::vector<cv::String> outPutNames;
        std::vector<int> outLayers = net.getUnconnectedOutLayers();
        for(int index = 0; index < outLayers.size(); index++)
        {
            outPutNames.push_back(layerNames[outLayers[index] - 1]);
            qDebug() << __FILE__ << __LINE__ 
                     << QString(layerNames[outLayers[index] - 1].c_str());
        }
    
        while(true)
        {
            // 读取图片识别
            mat = cv::imread("E:/testFile/15.jpg");
            if(!mat.data)
            {
                qDebug() << __FILE__ << __LINE__ << "Failed to read image!!!";
                return;
            }
    
    //        cv::dnn::blobFromImage(mat, blob);
            // 必须要设置,否则会跑飞
            cv::dnn::blobFromImage(mat,
                                   blob,
                                   1.0f/255,
                                   cv::Size(320, 320),
                                   cv::Scalar(0, 0, 0),
                                   true,
                                   false);
            net.setInput(blob);
            // 推理预测:可以输入预测的图层名称
            std::vector<cv::Mat> probs;
            net.forward(probs, outPutNames);
    
            // 显示识别花费的时间
            std::vector<double> layersTimes;
            double freq = cv::getTickFrequency() / 1000;
            double t = net.getPerfProfile(layersTimes) / freq;
            std::string label = cv::format("Inference time: %.2f ms", t);
            cv::putText(mat,
                      label,
                      cv::Point(0, 15),
                      cv::FONT_HERSHEY_SIMPLEX,
                      0.5,
                      cv::Scalar(255, 0, 0));
            // 置信度预制,大于执行度的将其使用rect框出来
            for(int index = 0; index < probs.size(); index++)
            {
                for (int row = 0; row < probs[index].rows; row++)
                {
                    // 获取probs中一个元素里面匹配对的所有对象中得分最高的
                    cv::Mat scores = probs[index].row(row).colRange(5, probs[index].cols);
                    cv::Point classIdPoint;
                    double confidence;
                    // Get the value and location of the maximum score
                    cv::minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
                    if(confidence > 0.6)
                    {
                        qDebug() << __FILE__ << __LINE__ << confidence << classIdPoint.x;
                        int centerX = (int)(probs.at(index).at<float>(row, 0) * mat.cols);
                        int centerY = (int)(probs.at(index).at<float>(row, 1) * mat.rows);
                        int width   = (int)(probs.at(index).at<float>(row, 2) * mat.cols);
                        int height  = (int)(probs.at(index).at<float>(row, 3) * mat.rows);
                        int left = centerX - width / 2;
                        int top = centerY - height / 2;
                        cv::Rect objectRect(left, top, width, height);
                        cv::rectangle(mat, objectRect, cv::Scalar(255, 0, 0), 2);
                        cv::String label = cv::format("%s:%.4f",
                                                      classes[classIdPoint.x].data(),
                                                      confidence);
                        cv::putText(mat,
                                    label,
                                    cv::Point(left, top - 10),
                                    cv::FONT_HERSHEY_SIMPLEX,
                                    0.4,
                                    cv::Scalar(0, 0, 255));
                        qDebug() << __FILE__ << __LINE__
                                << centerX << centerY << width << height;
                    }
                }
            }
    
            cv::imshow(_windowTitle.toStdString(), mat);
            cv::waitKey(0);
        }
    }
    
     

    对应工程模板v1.65.0

      openCVDemo_v1.65.0_基础模板_yolov3分类检测.rar。

     

    入坑

    入坑一:加载模型时候错误

    错误
      在这里插入图片描述

    原因
      模型文件加载错误。
    解决
      检查文件是否存在,路径是否正确,模型文件是否能对应上。

    入坑二:输入blob时错误

    错误
      在这里插入图片描述

    原因
      预测的时候未输入参数,需要输入参数(注意:tensorflow未输入没有问题)。
    解决
      在这里插入图片描述

     
  • 相关阅读:
    1407251735-hd-美素数.cpp
    [Javascript] IO Functor
    [AngularJS] Test an Angular Component with $componentController
    [AngularJS] Isolate State Mutations in Angular Components
    [Jest] Track project code coverage with Jest
    [Javascript] Either Functor
    [Javascript] Maybe Functor
    [AngualrJS] ng-strict-di
    [Ramda] Simple log function for debugging Compose function / Using R.tap for logging
    [Jest] Test JavaScript with Jest
  • 原文地址:https://www.cnblogs.com/qq21497936/p/13882661.html
Copyright © 2020-2023  润新知