社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

目标检测实战:4种YOLO目标检测的C++和Python两种版本实现

极市平台 • 5 年前 • 353 次点击  
↑ 点击蓝字 关注极市平台

作者丨nihate
审稿丨邓富城
编辑丨极市平台

极市导读

 

本文作者使用C++编写一套基于OpenCV的YOLO目标检测,包含了经典的YOLOv3,YOLOv4,Yolo-Fastest和YOLObile这4种YOLO目标检测的实现。附代码详解。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

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& outs);    void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);};

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

struct Net_config{  float confThreshold; // Confidence threshold  float nmsThreshold;  // Non-maximum suppression threshold  int inpWidth;  // Width of network's input image  int inpHeight; // Height of network's input image  string 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 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& outs)   // Remove the bounding boxes with low confidence using non-maxima suppression{  vector<int> classIds;  vector<float> confidences;  vector 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 score minMaxLoc(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 confidences vector<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 box rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255), 3);
//Get the label for the class name and its confidence string 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 box int 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-pythonhttps://github.com/hpc203/yolov5-dnn-cpp-python-v2

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

如果觉得有用,就请分享到朋友圈吧!

△点击卡片关注极市平台,获取最新CV干货

公众号后台回复“广东CVPR”获取CSIG-广东省CVPR 2021论文学术报告会回放


极市干货

YOLO教程:YOLO算法最全综述:从YOLOv1到YOLOv5YOLO系列(从V1到V5)模型解读!
实操教程:PyTorch自定义CUDA算子教程与运行时间分析详解PyTorch中的ModuleList和Sequential详细记录solov2的ncnn实现和优化
算法技巧(trick):深度神经网络模型训练中的 tricks(原理与代码汇总)神经网络训练trick总结深度学习调参tricks总结
最新CV竞赛: 2021 高通人工智能应用创新大赛CVPR 2021 | Short-video Face Parsing Challenge3D人体目标检测与行为分析竞赛开赛,奖池7万+,数据集达16671张!


极市原创作者激励计划 #


极市平台深耕CV开发者领域近5年,拥有一大批优质CV开发者受众,覆盖微信、知乎、B站、微博等多个渠道。通过极市平台,您的文章的观点和看法能分享至更多CV开发者,既能体现文章的价值,又能让文章在视觉圈内得到更大程度上的推广。

对于优质内容开发者,极市可推荐至国内优秀出版社合作出书,同时为开发者引荐行业大牛,组织个人分享交流会,推荐名企就业机会,打造个人品牌 IP。

投稿须知:
1.作者保证投稿作品为自己的原创作品。
2.极市平台尊重原作者署名权,并支付相应稿费。文章发布后,版权仍属于原作者。
3.原作者可以将文章发在其他平台的个人账号,但需要在文章顶部标明首发于极市平台

投稿方式:
添加小编微信Fengcall(微信号:fengcall19),备注:姓名-投稿
△长按添加极市平台小编


觉得有用麻烦给个在看啦~  
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/114461