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

若该文为原创文章,转载请注明原文出处

本文章博客地址:https://blog.csdn.net/qq21497936/article/details/109201809

各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究

红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…(点击传送门)

OpenCV开发专栏(点击传送门)

上一篇:《OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体》

下一篇:持续补充中…

前言

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

Demo

320x320,置信度0.6

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

608x608,置信度0.6(.cfg里面是608)

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

yolov3模型下载


coco.names:模型具体的分类信息。

https://github.com/pjreddie/darknet/blob/master/data/coco.names

yolov3.weights:权重文件

https://pjreddie.com/media/files/yolov3.weights

yolov3.cfg:配置文件

https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg

以上若是下载不下来,提供其他下载地址,因为github非常慢。

CSDN:https://download.csdn.net/download/qq21497936/12995972

QQ群:1047134658(点击“文件”搜索“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 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则会跑飞。

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

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

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

// 获取输出的层

std::vector outPutNames;

std::vector 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 probs;

net.forward(probs, outPutNames);

对于预测的结果,存于std::vectorcv::Mat类型的probs,每一个元素指定为cv::Mat类型的prob,每一行代表一个检测到的分类,具体列信息如下表:


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

(注意:具体的使用,请参照“步骤五”)

步骤五:对达到置信度的可以通过输出的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(row, 0) * mat.cols);

            int centerY = (int)(probs.at(index).at(row, 1) * mat.rows);

            int width  = (int)(probs.at(index).at(row, 2) * mat.cols);

            int height  = (int)(probs.at(index).at(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 getLayerNames() const;

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

std::vector 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 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 layerNames = net.getLayerNames();

    int lastLayerId = net.getLayerId(layerNames[layerNames.size() - 1]);

    cv::Ptr 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 outPutNames;

    std::vector 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 probs;

        net.forward(probs, outPutNames);

        // 显示识别花费的时间

        std::vector 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(row, 0) * mat.cols);

                    int centerY = (int)(probs.at(index).at(row, 1) * mat.rows);

                    int width  = (int)(probs.at(index).at(row, 2) * mat.cols);

                    int height  = (int)(probs.at(index).at(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时错误

错误

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

预测的时候未输入参数,需要输入参数(注意:tensorflow未输入没有问题)。

解决

上一篇:《OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体》

下一篇:持续补充中…

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