yolov5 OpenCV DNN c++ 部署

一、转onnx格式

yolov5-6.2版本可以用export.py导出onnx格式的模型:

python export.py  --weights yolov5n.pt yolov5n.onnx 

yolov5n.pt我是直接在这里下载的:yolov5n.pt我是直接在这里下载的:https://github.com/ultralytics/yolov5/releases/tag/v6.2

二、部署代码

yolo.hpp

// https://github.com/UNeedCryDear/yolov5-opencv-dnn-cpp
 
#pragma once
#include<iostream>
#include<opencv2/opencv.hpp>
 
#define YOLO_P6 false //是否使用P6模型
 
struct Output {
    int id;             //结果类别id
    float confidence;   //结果置信度
    cv::Rect box;       //矩形框
};
 
class Yolov5 {
public:
    Yolov5() {
    }
    ~Yolov5() {}
    bool readModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);
    bool Detect(cv::Mat& SrcImg, cv::dnn::Net& net, std::vector<Output>& output);
    void drawPred(cv::Mat& img, std::vector<Output> result, std::vector<cv::Scalar> color);
 
private:
 
    void LetterBox(const cv::Mat& image, cv::Mat& outImage,
        cv::Vec4d& params, //[ratio_x,ratio_y,dw,dh]
        const cv::Size& newShape = cv::Size(640, 640),
        bool autoShape = false,
        bool scaleFill = false,
        bool scaleUp = true,
        int stride = 32,
        const cv::Scalar& color = cv::Scalar(114, 114, 114));
 
 
 
    const int _netWidth = 640;   //ONNX图片输入宽度
    const int _netHeight = 640;  //ONNX图片输入高度
 
 
    float _classThreshold = 0.25;
    float _nmsThreshold = 0.45;
 
    std::vector<std::string> _className = { "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
        "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
        "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
        "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
        "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
        "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
        "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
        "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
        "hair drier", "toothbrush" };
};

yolo.cpp

#include "yolo.h"
using namespace std;
using namespace cv;
using namespace cv::dnn;
 
 
void Yolov5::LetterBox(const cv::Mat& image, cv::Mat& outImage, cv::Vec4d& params, const cv::Size& newShape,
    bool autoShape, bool scaleFill, bool scaleUp, int stride, const cv::Scalar& color)
{
    Size shape = image.size();
    float r = std::min((float)newShape.width / (float)shape.width,
                       (float)newShape.height / (float)shape.height); // 选出较小的缩放比,否则会超过
 
    float ratio[2]{r, r};
 
    int new_up_pad[2] ={(int)round((float)shape.width * r),
                        (int)round((float)shape.height * r)}; // 缩放后与目标长宽可能还差一点
 
    auto dw = (float)(newShape.width - new_up_pad[0]);// 算出与目标长宽差多少
    auto dh = (float)(newShape.height - new_up_pad[1]);
    dw /= 2.0f;
    dh /= 2.0f;
 
    if (shape.width != new_up_pad[0] && shape.height != new_up_pad[1])//等比例缩放
    {
        resize(image, outImage, Size(new_up_pad[0], new_up_pad[1]));
    }
    else {
        outImage = image.clone();
    }
 
    int top = int(round(dh - 0.1f)); // 四周用0来填充
    int bottom = int(round(dh + 0.1f));
    int left = int(round(dw - 0.1f));
    int right = int(round(dw + 0.1f));
    params[0] = ratio[0];
    params[1] = ratio[1];
    params[2] = left;
    params[3] = top;
    copyMakeBorder(outImage, outImage, top, bottom, left, right,BORDER_CONSTANT,color);
}
 
 
bool Yolov5::readModel(Net &net, string &netPath, bool isCuda)
{
    try {
//        net = readNet(netPath);
        net = readNetFromONNX(netPath);
 
    } catch (const std::exception&) {
        return  false;
 
    }
 
    if (isCuda)
    {
        net.setPreferableBackend(DNN_BACKEND_CUDA);
        net.setPreferableTarget(DNN_TARGET_CUDA);
    }
    else
    {
        net.setPreferableBackend(DNN_BACKEND_OPENCV);
        net.setPreferableTarget(DNN_TARGET_CPU);
    }
    return true;
}
 
bool Yolov5::Detect(Mat &SrcImg, Net &net, vector<Output> &output)
{
 
    Mat blob;
    int col = SrcImg.cols;
    int row = SrcImg.rows;
    int maxLen = MAX(col, row);
    Mat netinputImg = SrcImg.clone();
    Vec4d params;
    LetterBox(SrcImg, netinputImg, params, Size(_netWidth, _netHeight));
 
    blobFromImage(netinputImg, blob, 1/255.0, Size(_netWidth, _netHeight),Scalar(0,0,0), true, false);
    //如果在其他设置没有问题的情况下但是结果偏差很大,可以尝试下用下面两句语句
    //blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(104, 117, 123), true, false);
    //blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(114, 114,114), true, false);
 
    net.setInput(blob);
    vector<Mat> netOutputImg;
    net.forward(netOutputImg, net.getUnconnectedOutLayersNames());
 
 
    vector<int> classIds;
    vector<float> confidences;
    vector<Rect> boxes;
 
    float ratio_h = (float)netinputImg.rows / _netWidth; // 此时为1
    float ratio_w = (float)netinputImg.cols / _netWidth;
    int net_width = _className.size() + 5;
    int net_out_width = netOutputImg[0].size[2];
    CV_Assert(net_out_width == net_width);
 
    float* pdata = (float*)netOutputImg[0].data;
    int net_height = netOutputImg[0].size[1];
    for (int r = 0; r < net_height; ++r) // 下一个框)
    {
        float box_score = pdata[4];
        if (box_score >= _classThreshold)
        {
            Mat scores(1, _className.size(), CV_32FC1, pdata+5);
            Point classIdPoint;
            double max_class_score;
 
            minMaxLoc(scores, 0, &max_class_score,0, &classIdPoint);
            max_class_score = max_class_score * box_score;
            if(max_class_score > _classThreshold)
            {
                float x = (pdata[0] - params[2]) / params[0]; //  缩放、padding后,-》原图
                float y = (pdata[1] - params[3]) / params[1]; //  params: out // in
                float w = pdata[2] / params[0];
                float h = pdata[3] / params[1];
                int left = MAX(round(x - 0.5 * w), 0);
                int top = MAX(round(y - 0.5*h), 0);
 
                classIds.push_back(classIdPoint.x);
                confidences.push_back(max_class_score);
                boxes.push_back(Rect(left, top, round( w * ratio_w), round(h * ratio_h)));// ??
            }
        }
        pdata += net_width;
 
    }
 
 
    // 执行非最大抑制以消除具有较低置信度的冗余重叠框(NMS)
    vector<int> nms_result;
    NMSBoxes(boxes,confidences,_classThreshold,_nmsThreshold,nms_result);
 
    for(size_t i =0; i<nms_result.size(); i++)
    {
        int idx = nms_result[i];
        Output result;
        result.id = classIds[idx];
        result.confidence = confidences[idx];
        result.box = boxes[idx];
        output.push_back(result);
    }
    if (output.size())
        return true;
    else
        return false;
 
 
}
 
void Yolov5::drawPred(Mat &img, vector<Output> result, vector<Scalar> color)
{
    for (size_t i=0; i<result.size(); i++)
    {
        int left, top;
        left = result[i].box.x;
        top = result[i].box.y;
        int color_num = i;
        rectangle(img, result[i].box, color[result[i].id],2,8);
 
        string label = _className[result[i].id] + ":" + to_string(result[i].confidence);
 
        int baseLine;
        Size labeSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
        top = top - labeSize.height;
        putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);
    }
 
 
 
 
//    imshow("1", img);
 
 
//    waitKey();
 
}

main.cpp

 
#include "yolo.h"
#include<math.h>
#include<iostream>
 
using namespace std;
using namespace cv;
using namespace cv::dnn;
 
 
 
#include "yolo.h"
#include <iostream>
//#include<opencv2//opencv.hpp>
#include<math.h>
 
using namespace std;
using namespace cv;
using namespace dnn;
 
int main()
{
    string img_path = "/home/jason/work/01-img/dog2.png";
    string model_path = "/home/jason/PycharmProjects/pytorch_learn/yolov5-6.2/yolov5n.onnx";
    //int num_devices = cv::cuda::getCudaEnabledDeviceCount();
    //if (num_devices <= 0) {
        //cerr << "There is no cuda." << endl;
        //return -1;
    //}
    //else {
        //cout << num_devices << endl;
    //}
 
    Yolov5 test;
    Net net;
    if (test.readModel(net, model_path, false)) {
        cout << "read net ok!" << endl;
    }
    else {
        return -1;
    }
 
    //生成随机颜色
    vector<Scalar> color;
    srand(time(0));
    for (int i = 0; i < 80; i++) {
        int b = rand() % 256;
        int g = rand() % 256;
        int r = rand() % 256;
        color.push_back(Scalar(b, g, r));
    }
    vector<Output> result;
//    Mat img = imread(img_path);
 
    VideoCapture capture(2);
    Mat img;
 
    while (1)
    {
        capture >>img;
        test.Detect(img, net, result);
//        if (test.Detect(img, net, result))
        {
            test.drawPred(img, result, color);
            result.erase(result.begin(), result.end());
 
            vector<double> layersTimes;
            double freq = getTickFrequency() / 1000;  // https://blog.csdn.net/chaipp0607/article/details/71056580
            double t = net.getPerfProfile(layersTimes) / freq;
            string label = format("%s Inference time : %.2f ms", "yolov5n", t);
            putText(img, label, Point(0, 30), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0,0,255),2);
 
//        }
//        else
//        {
//            cout << "Detect Failed!"<<endl;
//        }
        imshow("1", img);
 
        if (waitKey(1) == 27) break;
 
    }
 
 
 
//    system("pause");
    return 0;
}
三、总结:

我用摄像头直接读取处理,发现yolov5n 运行起来有点卡,AMD R5300H CPU 的耗时是150ms左右,而 同样设备Pytorch Python 推理 只有几十ms耗时!!

欢迎留言、欢迎交流!!

参考:

GitHub - UNeedCryDear/yolov5-opencv-dnn-cpp: 使用opencv模块部署yolov5-6.0版本

2021.11.01 c++下 opencv部署yolov5-6.0版本 (四)_怎么查看yolov5版本_爱晚乏客游的博客-CSDN博客

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值