yolov5 OpenCV DNN c++ 部署

一、转onnx格式

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

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

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

二、部署代码

yolo.hpp

// https://github.com/UNeedCryDear/yolov5-opencv-dnn-cpp

#pragma once
#include
#include

#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);
    void drawPred(cv::Mat& img, std::vector result, std::vector 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 _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)
{

    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 netOutputImg;
    net.forward(netOutputImg, net.getUnconnectedOutLayersNames());


    vector classIds;
    vector confidences;
    vector 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 nms_result;
    NMSBoxes(boxes,confidences,_classThreshold,_nmsThreshold,nms_result);

    for(size_t i =0; i result, vector color)
{
    for (size_t i=0; i

main.cpp


#include "yolo.h"
#include
#include

using namespace std;
using namespace cv;
using namespace cv::dnn;



#include "yolo.h"
#include 
//#include
#include

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 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 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 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!"<

三、总结:

我用摄像头直接读取处理,发现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博客

你可能感兴趣的:(#,目标检测,模型部署,YOLO,opencv,dnn)