yolov5 opencv dnn部署 github代码
- 源码地址
- 实现推理源码中作者的yolov5s.onnx
- 推理条件
- python部署(因为python比较简单就直接介绍了)
- c++部署
- 参考链接
源码地址
- yolov5官网还提供的dnn、tensorrt推理链接
- 本人使用的opencv c++ github代码,代码作者非本人,也是上面作者推荐的链接之一
实现推理源码中作者的yolov5s.onnx
推理条件
实现推理code中作者的yolov5s.onnx
windows 10
Visual Studio 2019
Nvidia GeForce GTX 1070
opencv 4.5.5、opencv4.7.0 (注意 4.7.0代码不适用,如果要使用opencv4.7.0来进行推理,可能会出现下面的问题图1 problem中的问题)(但是,如果添加了之后,4.7.0的推理速度会比4.5.5的速度慢了不少)
图 1 p r o b l e m 图1 problem 图1problem
解决方法 图 1 s o l v t i o n 图1 solvtion 图1solvtion
python部署(因为python比较简单就直接介绍了)
一、直接用VScode打开代码
二、然后在终端输入命令
python python/yolo.py
三、结果如下图所示
c++部署
当然不管是使用opencv dnn的cpu还是gpu都得创建相应的环境,这里先不做介绍,以后有时间再介绍。
一、使用VS2019创建一个新的项目,这里不做过多赘述
二、该项目搭建公共的opencv属性,或者使用已搭建好的公共的opencv455属性(以opencv455为例,添加其它的也是这样的,例如opencv455_cuda等)
这里如果使用公共的opencv455属性,只需要在新建的c++空项目中使用以下步骤即可添加
1、 “属性管理器”——>“鼠标右键点击Release|x64”——>“添加现有属性表(E)”
2、 选在对应的已创建好的属性表,然后点击“打开”
3、添加后的结果
三、将code中的下列文件复制到新建的项目中的repos/Project4/Project4
中,如下图所示
四、将code中的cpp/yolo.cpp
添加到新建项目的源文件
中,添加过程和结果如下图所示
结果
五、使用x64进行Release,结果如下图所示
1、opencv4.5.5推理结果
2、opencv4.7.0推理结果
六、yolo.cpp的代码在这(本人应该没做改动吧,忘记了)
#include <fstream>#include <opencv2/opencv.hpp>/*下文所有注释全是自我理解*//*加载classes.txt*/
std::vector<std::string> load_class_list()
{std::vector<std::string> class_list; // 该行代码可以理解为申明一个可变容量的字符串数组class_liststd::ifstream ifs("config_files/classes.txt");std::string line;while (getline(ifs, line)){class_list.push_back(line);}return class_list;
}void load_net(cv::dnn::Net& net, bool is_cuda)
{auto result = cv::dnn::readNet("config_files/yolov5s.onnx");if (is_cuda){std::cout << "Attempty to use CUDA\n";result.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);// result.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);result.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);// 经过验证,这里必须去掉FP16,才能使用cuda加速,就是yolov5导出的是half onnx也不行,opencv只能读取16,但是还是按照32运行的}else{std::cout << "Running on CPU\n";result.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);result.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);}net = result;
}const std::vector<cv::Scalar> colors = { cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 255, 255), cv::Scalar(255, 0, 0) };const float INPUT_WIDTH = 640.0;
const float INPUT_HEIGHT = 640.0;
const float SCORE_THRESHOLD = 0.2; // NMS中的score阈值(一般来说,这个应该是conf * class的阈值)
const float NMS_THRESHOLD = 0.4; // NMS中的IoU阈值
const float CONFIDENCE_THRESHOLD = 0.4; // conf阈值 (class, conf, x, y, w, h)struct Detection
{int class_id;float confidence;cv::Rect box;
};/*这个作用是将需要预测的图片都变为正方形图片,以左上角对齐,将短的边都填充0*/
cv::Mat format_yolov5(const cv::Mat& source) {int col = source.cols;int row = source.rows;int _max = MAX(col, row);cv::Mat result = cv::Mat::zeros(_max, _max, CV_8UC3);source.copyTo(result(cv::Rect(0, 0, col, row)));return result;
}void detect(cv::Mat& image, cv::dnn::Net& net, std::vector<Detection>& output, const std::vector<std::string>& className) {cv::Mat blob;auto input_image = format_yolov5(image);cv::dnn::blobFromImage(input_image, blob, 1. / 255., cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::Scalar(), true, false); // 将预测图片resize到640,并将所有的像素都归一化net.setInput(blob);std::vector<cv::Mat> outputs; // 预测框作为一个矩阵保存在Mat,这个可变数组永远只有一个Mat,因为一个Mat即可保存所有的预测框,类似于图片[1, H, W],所以下文中outputs[0].data是所有预测框的地址net.forward(outputs, net.getUnconnectedOutLayersNames());float x_factor = input_image.cols / INPUT_WIDTH; // 缩放因子float y_factor = input_image.rows / INPUT_HEIGHT;float* data = (float*)outputs[0].data;const int dimensions = 85; // 其实就是COCO的class + conf + xywhconst int rows = 25200; // pre_box的数量小于25200std::vector<int> class_ids;std::vector<float> confidences;std::vector<cv::Rect> boxes;// 原始的NMS使用的非极大值抑制,并不是yolov5中的多分类非极大值抑制for (int i = 0; i < rows; ++i) {float confidence = data[4];if (confidence >= CONFIDENCE_THRESHOLD) {float* classes_scores = data + 5;cv::Mat scores(1, className.size(), CV_32FC1, classes_scores); // 将classes_scores转化为一个Mat格式的数据cv::Point class_id;double max_class_score;minMaxLoc(scores, 0, &max_class_score, 0, &class_id); // 将scores中的最大的值以及其id分别赋给max_class_score, class_idif (max_class_score > SCORE_THRESHOLD) {confidences.push_back(confidence);class_ids.push_back(class_id.x);float x = data[0];float y = data[1];float w = data[2];float h = data[3];int left = int((x - 0.5 * w) * x_factor);int top = int((y - 0.5 * h) * y_factor);int width = int(w * x_factor);int height = int(h * y_factor);boxes.push_back(cv::Rect(left, top, width, height));}}data += 85; // 这个是cv::Mat中的每一个[x, y, w, h, conf, class]的首地址,一个预测框的首地址都加85[xywh+conf+class]即[5+80]后就是下一个预测框的首地址}std::vector<int> nms_result;cv::dnn::NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, nms_result); // 将NMS筛选之后的索引返回给nms_resultfor (int i = 0; i < nms_result.size(); i++) {int idx = nms_result[i];Detection result;result.class_id = class_ids[idx];result.confidence = confidences[idx];result.box = boxes[idx];output.push_back(result);}
}int main(int argc, char** argv)
{std::vector<std::string> class_list = load_class_list();cv::Mat frame;cv::VideoCapture capture("sample.mp4");if (!capture.isOpened()){std::cerr << "Error opening video file\n";return -1;}//bool is_cuda = argc > 1 && strcmp(argv[1], "cuda") == 0;bool is_cuda = true;cv::dnn::Net net;load_net(net, is_cuda);auto start = std::chrono::high_resolution_clock::now();int frame_count = 0;float fps = -1;int total_frames = 0;while (true){capture.read(frame);if (frame.empty()){std::cout << "End of stream\n";break;}std::vector<Detection> output;detect(frame, net, output, class_list);frame_count++;total_frames++;int detections = output.size();for (int i = 0; i < detections; ++i){auto detection = output[i];auto box = detection.box;auto classId = detection.class_id;const auto color = colors[classId % colors.size()];cv::rectangle(frame, box, color, 3);cv::rectangle(frame, cv::Point(box.x, box.y - 20), cv::Point(box.x + box.width, box.y), color, cv::FILLED);cv::putText(frame, class_list[classId].c_str(), cv::Point(box.x, box.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));}if (frame_count >= 30){auto end = std::chrono::high_resolution_clock::now();fps = frame_count * 1000.0 / std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();frame_count = 0;start = std::chrono::high_resolution_clock::now();}if (fps > 0){std::ostringstream fps_label;fps_label << std::fixed << std::setprecision(2);fps_label << "FPS: " << fps;std::string fps_label_str = fps_label.str();cv::putText(frame, fps_label_str.c_str(), cv::Point(10, 25), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);}cv::imshow("output", frame);if (cv::waitKey(1) != -1){capture.release();std::cout << "finished by user\n";break;}}std::cout << "Total frames: " << total_frames << "\n";return 0;
}
参考链接
- 代码参考链接
- https://github.com/doleron/yolov5-opencv-cpp-python
- https://github.com/Hexmagic/ONNX-yolov5/tree/master
- https://github.com/yzy12-max/yolov5_deploy(这个是理论参考链接2中对应的仓库)
- 理论参考链接
- https://github.com/ultralytics/yolov5/issues/251
- https://blog.csdn.net/weixin_41311686/article/details/128421801(这个的是另外的代码推理解析部分,值得一看)