目标检测实战必会!4种基于YOLO目标检测(Python和C++两种版本实现)

AI算法修炼营 1周前

以下文章来源于极市平台 ,作者CV开发者都爱看的

极市平台

专注计算机视觉前沿资讯和技术干货,官网:www.cvmart.net

作者丨nihate

审稿丨邓富城

导读

本文作者使用C++编写一套基于OpenCV的YOLO目标检测,包含了经典的YOLOv3,YOLOv4,Yolo-Fastest和YOLObile这4种YOLO目标检测的实现。附代码详解。

2020年,新出了几个新版本的YOLO目标检测,在微信朋友圈里转发的最多的有YOLOv4,Yolo-Fastest,YOLObile以及百度提出的PP-YOLO。在此之前,我已经在github发布过YOLOv4,Yolo-Fastest,YOLObile这三种YOLO基于OpenCV做目标检测的程序,但是这些程序是用Python编写的。接下来,我就使用C++编写一套基于OpenCV的YOLO目标检测,这个程序里包含了经典的YOLOv3,YOLOv4,Yolo-Fastest和YOLObile这4种YOLO目标检测的实现。

1. 实现思路

用面向对象的思想定义一个类,类的构造函数会调用opencv的dnn模块读取输入的.cfg和.weights文件来初始化YOLO网络,类有一个成员函数detect对输入的图像做目标检测,主要包括前向推理forward和后处理postprocess。这样就把YOLO目标检测模型封装成了一个类。最后在主函数main里设置一个参数可以选择任意一种YOLO做目标检测,读取一幅图片,调用YOLO类里的detect函数执行目标检测,画出图片中的物体的类别和矩形框。

2. 实现步骤

定义类的构造函数和成员函数和成员变量,如下所示。其中confThreshold是类别置信度阈值,nmsThreshold是重叠率阈值,inpHeight和inpWidth使输入图片的高和宽,netname是yolo模型名称,classes是存储类别的数组,本套程序是在COCO数据集上训练出来的模型,因此它存储有80个类别。net是使用opencv的dnn模块读取配置文件和权重文件后返回的深度学习模型,postprocess是后处理函数,drawPred是在检测到图片里的目标后,画矩形框和类别名。

class YOLO
{
public:YOLO(Net_config config);void detect(Mat& frame);
private:float confThreshold;float nmsThreshold;int inpWidth;int inpHeight;char netname[20];vector<string> classes;Net net;void postprocess(Mat& frame, const vector<Mat>& outs);void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);
};

接下来,定义一个结构体和结构体数组,如下所示。结构体里包含了类别置信度阈值,重叠率阈值,模型名称,配置文件和权重文件的路径,存储所有类别信息的文档的路径,输入图片的高和宽。然后在结构体数组里,包含了四种YOLO模型的参数集合。

struct Net_config
{float confThreshold; // Confidence thresholdfloat nmsThreshold;  // Non-maximum suppression thresholdint inpWidth;  // Width of network's input imageint inpHeight; // Height of network's input imagestring classesFile;string modelConfiguration;string modelWeights;string netname;
};Net_config yolo_nets[4] = {{0.5, 0.4, 416, 416,"coco.names", "yolov3/yolov3.cfg", "yolov3/yolov3.weights", "yolov3"},{0.5, 0.4, 608, 608,"coco.names", "yolov4/yolov4.cfg", "yolov4/yolov4.weights", "yolov4"},{0.5, 0.4, 320, 320,"coco.names", "yolo-fastest/yolo-fastest-xl.cfg", "yolo-fastest/yolo-fastest-xl.weights", "yolo-fastest"},{0.5, 0.4, 320, 320,"coco.names", "yolobile/csdarknet53s-panet-spp.cfg", "yolobile/yolobile.weights", "yolobile"}
};

接下来是YOLO类的构造函数,如下所示,它会根据输入的结构体Net_config,来初始化成员变量,这其中就包括opencv读取配置文件和权重文件后返回的深度学习模型。

YOLO::YOLO(Net_config config)
{cout << "Net use " << config.netname << endl;this->confThreshold = config.confThreshold;this->nmsThreshold = config.nmsThreshold;this->inpWidth = config.inpWidth;this->inpHeight = config.inpHeight;strcpy_s(this->netname, config.netname.c_str());ifstream ifs(config.classesFile.c_str());string line;while (getline(ifs, line)) this->classes.push_back(line);this->net = readNetFromDarknet(config.modelConfiguration, config.modelWeights);this->net.setPreferableBackend(DNN_BACKEND_OPENCV);this->net.setPreferableTarget(DNN_TARGET_CPU);
}

接下来的关键的detect函数,在这个函数里,首先使用blobFromImage对输入图像做预处理,然后是做forward前向推理和postprocess后处理。

void YOLO::detect(Mat& frame)
{Mat blob;blobFromImage(frame, blob, 1 / 255.0, Size(this->inpWidth, this->inpHeight), Scalar(0, 0, 0), true, false);this->net.setInput(blob);vector<Mat> outs;this->net.forward(outs, this->net.getUnconnectedOutLayersNames());this->postprocess(frame, outs);vector<double> layersTimes;double freq = getTickFrequency() / 1000;double t = net.getPerfProfile(layersTimes) / freq;string label = format("%s Inference time : %.2f ms", this->netname, t);putText(frame, label, Point(0, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255), 2);//imwrite(format("%s_out.jpg", this->netname), frame);
}

postprocess后处理函数的代码实现如下,在这个函数里,for循环遍历所有的候选框outs,计算出每个候选框的最大类别分数值,也就是真实类别分数值,如果真实类别分数值大于confThreshold,那么就对这个候选框做decode计算出矩形框左上角顶点的x, y,高和宽的值,然后把真实类别分数值,真实类别索引id和矩形框左上角顶点的x, y,高和宽的值分别添加到confidences,classIds和boxes这三个vector里。在for循环结束后,执行NMS,去掉重叠率大于nmsThreshold的候选框,剩下的检测框就调用drawPred在输入图片里画矩形框和类别名称以及分数值。


void YOLO::postprocess(Mat& frame, const vector<Mat>& outs)   // Remove the bounding boxes with low confidence using non-maxima suppression
{vector<int> classIds;vector<float> confidences;vector<Rect> boxes;for (size_t i = 0; i < outs.size(); ++i){// Scan through all the bounding boxes output from the network and keep only the// ones with high confidence scores. Assign the box's class label as the class// with the highest score for the box.float* data = (float*)outs[i].data;for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols){Mat scores = outs[i].row(j).colRange(5, outs[i].cols);Point classIdPoint;double confidence;// Get the value and location of the maximum scoreminMaxLoc(scores, 0, &confidence, 0, &classIdPoint);if (confidence > this->confThreshold){int centerX = (int)(data[0] * frame.cols);int centerY = (int)(data[1] * frame.rows);int width = (int)(data[2] * frame.cols);int height = (int)(data[3] * frame.rows);int left = centerX - width / 2;int top = centerY - height / 2;classIds.push_back(classIdPoint.x);confidences.push_back((float)confidence);boxes.push_back(Rect(left, top, width, height));}}}// Perform non maximum suppression to eliminate redundant overlapping boxes with// lower confidencesvector<int> indices;NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices);for (size_t i = 0; i < indices.size(); ++i){int idx = indices[i];Rect box = boxes[idx];this->drawPred(classIds[idx], confidences[idx], box.x, box.y,box.x + box.width, box.y + box.height, frame);}
}void YOLO::drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)   // Draw the predicted bounding box
{//Draw a rectangle displaying the bounding boxrectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255), 3);//Get the label for the class name and its confidencestring label = format("%.2f", conf);if (!this->classes.empty()){CV_Assert(classId < (int)this->classes.size());label = this->classes[classId] + ":" + label;}//Display the label at the top of the bounding boxint baseLine;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);top = max(top, labelSize.height);//rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED);putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
}

最后是主函数main,代码实现如下。在主函数里的第一行代码,输入参数yolo_nets[2]表示选择了四种YOLO模型里的第三个yolo-fastest,使用者可以自由设置这个参数,从而能自由选择YOLO模型。接下来是定义输入图片的路径,opencv读取图片,传入到yolo_model的detect函数里做目标检测,最后在窗口显示检测结果。

int main()
{YOLO yolo_model(yolo_nets[2]);string imgpath = "person.jpg";Mat srcimg = imread(imgpath);yolo_model.detect(srcimg);static const string kWinName = "Deep learning object detection in OpenCV";namedWindow(kWinName, WINDOW_NORMAL);imshow(kWinName, srcimg);waitKey(0);destroyAllWindows();
}

在编写并调试完程序后,我多次运行程序来比较这4种YOLO目标检测网络在一幅图片上的运行耗时。运行程序的环境是win10-cpu,VS2019+opencv4.4.0,这4种YOLO目标检测网络在同一幅图片上的运行耗时的结果如下:

可以看到Yolo-Fastest运行速度最快,YOLObile号称是实时的,但是从结果看并不如此。并且查看它们的模型文件,可以看到Yolo-Fastest的是最小的。如果在ubuntu-gpu环境里运行,它还会更快。

整个程序的运行不依赖任何深度学习框架,只需要依赖OpenCV4这个库就可以运行整个程序,做到了YOLO目标检测的极简主义,这个在硬件平台部署时是很有意义的。建议在ubuntu系统里运行这套程序,上面展示的是在win10-cpu机器上的运行结果,而在ubuntu系统里运行,一张图片的前向推理耗时只有win10-cpu机器上的十分之一。

我把这套程序发布在github上,这套程序包含了C++和Python两种版本的实现,地址是 https://github.com/hpc203/yolov34-cpp-opencv-dnn

此外,我也编写了使用opencv实现yolov5目标检测,程序依然是包含了C++和Python两种版本的实现,地址是

https://github.com/hpc203/yolov5-dnn-cpp-python 和 https://github.com/hpc203/yolov5-dnn-cpp-python-v2

考虑到yolov5的模型文件是在pytorch框架里从.pt文件转换生成的.onnx文件,而之前的yolov3,v4都是在darknet框架里生成的.cfg和.weights文件,还有yolov5的后处理计算与之前的yolov3,v4有所不同,因此我没有把yolov5添加到上面的4种YOLO目标检测程序里。

原文地址:https://mp.weixin.qq.com/s/mU6s3edDk9Oi3lOC4_OLNg

目标检测实战必会!4种基于YOLO目标检测(Python和C++两种版本实现)相关推荐

  1. 运行python程序的两种方式交互式和文件式_执行Python程序的两种方式

    交互式(了解) 交互式环境下,敲完一条命令按下enter键马上能看到结果,调试程序方便.程序无法永久保存,关掉cmd窗口数据就消失了. 命令行式(了解) 打开文本编辑器,在文本编辑器中写入一串字符. ...

  2. c语言检测正弦波波峰波谷,一种基于波峰波谷检测的计步算法的制作方法

    本发明涉及计步器算法领域,具体是一种基于波峰波谷检测的计步算法. 背景技术: 当今社会,健康越来越受到人们的重视,步行作为人类活动中最基础.最常见.最重要的运动形式,使得深入研究计步算法有着重要的意义 ...

  3. matlab能量谱分析检验,一种基于短时能量检测和频谱特征分析的地震波预警方法...

    一种基于短时能量检测和频谱特征分析的地震波预警方法 [技术领域] [0001] 本发明涉及一种地震波预警技术,尤其涉及一种可以降低虚警概率的地震波预警 技术,更具体地涉及一种基于短时能量检测和频谱特征 ...

  4. 多视图CAD检测系统乳腺X线摄影基于案例的检测性能优化

    多视图CAD检测系统乳腺X线摄影基于案例的检测性能优化 介绍 通常乳腺肿块的检测基于双视图乳腺摄影,医生在阅片是会将所有可用视图的信息组合在一起,他们比较MLO和CC视图,寻找不对称性并评估相对于先前 ...

  5. flume系列之:flume基于kafka.topics和kafka.topics.regex两种方式匹配Kafka Topic

    flume系列之:flume基于kafka.topics和kafka.topics.regex两种方式匹配Kafka Topic 一.flume基于kafka.topics匹配Kafka Topic ...

  6. 基于python的selenium两种文件上传方式

    方法一.input标签上传     如果是input标签,可以直接输入路径,那么可以直接调用send_keys输入路径. 方法二.非input标签上传 这种上传方式需要借助第三方工具,主要有以下三种情 ...

  7. python有两个运行程序分别是什么_运行python程序的两种方式

    [单选题]I wonder why ________ are so interested in action movies. [单选题]项目经理的职责不包括以下哪项内容 ? [简答题]结合项目的特点和 ...

  8. 运行python程序的两种方式交互式和文件式_Python基础知识2

    运行Python程序的两种方式 小白学习,如有错误欢迎指点 一.每位小白写的第一个Python程序 1.运行Python程序的两种方式 1.1 交互式模式(即时对话) 打开cmd,打开Python解释 ...

  9. 周一02.3运行python程序的两种方式

    一.运行python程序的两种方式 方法一:交互式:                      优点:输入一行代码立刻返回结果                       缺点:无法永久保存代码 方法 ...

最新文章

  1. MySQL的一些概念笔记
  2. I love exam HDU - 6968
  3. html dom 替换节点,替换 从javascript dom文本节点
  4. java基础知识复习
  5. 9008刷机模式写入超时刷机帮_刷机时没有成功,然后变成黑砖,usb接口直接变成未知设备~希望大神救助!...
  6. MATLAB 读取文件数据(txt)
  7. splay详解(二)
  8. Java程序设计基础(第五版)期末总复习
  9. 服务器pe 装linux,微PE工具箱增加安装Linux/Ubuntu/Centos/deepin系统菜单
  10. 带你快速了解ISO27001信息安全管理体系认证
  11. 克隆巴赫系数 Cronbach‘s alpha 及 R, Python 实现
  12. ConstraintLayout约束布局
  13. Intel VT学习笔记(一)—— 基础知识支持检测
  14. Stimulsoft Reports.JAVA 2022.4.3 Crack
  15. Maven多模块开发Action:Consider defining a bean of type '*.*.*' in your configuration解决
  16. DPABI详细使用教材——数据准备、预处理流程、数据分析流程
  17. 两个实打实干活的同事离职了,老板连谈都没谈,一句挽留都没有,你怎么看?
  18. 不能用来修饰interface修饰的方法
  19. Android Strongbox( Android Ready SE)
  20. YOLOF训练自己的数据集(目标检测,cvpods版本)

热门文章

  1. VBox ntcreatefile failed vboxstubdrv
  2. html 设置textarea字体颜色,textarea标签设置值
  3. AutoLisp从入门到放弃(十五)
  4. 面试测试管理岗经常遇到的问题
  5. 山东理工大学计算机研究生宿舍,安德的游戏1
  6. 配置描述文件web clip-iOS技巧:不越狱在主屏创建快速拨打图标
  7. 【转】DELL戴尔N4050笔记本拆机(图文)
  8. python webbrowser javascript_13.8. webbrowser — 显示网页
  9. DataGridView合并单元格(横向合并)
  10. RAC 管理(crs_stat、crsctl、srvctl各种命令详解)