当前位置: 代码网 > 服务器>服务器>Linux > YoloV8部署:使用opencv的dnn模块部署在Linux平台上

YoloV8部署:使用opencv的dnn模块部署在Linux平台上

2024年08月01日 Linux 我要评论
前段时间做了Yolov5的部署,就想训练yolov8并且部署在Linux平台上,中间也踩了很多坑,花了两天时间终于部署完成了,在这里浅浅记录一下。

       前段时间做了yolov5的部署,就想训练yolov8并且部署在linux平台上,中间也踩了很多坑,花了两天时间终于部署完成了,在这里浅浅记录一下。

一、yolov8训练

        yolov8是yolov5的作者写的,源码:https://github.com/ultralytics/ultralytics,但是新版的改动较yolov5较大,因为集成化程度较高,所以无法通过yolov5那样直接修改参数实现,一般都通过终端执行yolo命令来部署训练。至于环境配置,如果你之前搭建过yolov5的环境,那么yolov8环境搭建起来就很简单啦,只需要卸载torch,然后更新到支持gpu推理的版本即可,如果你的cuda版本太低的话,需要卸载原有的cuda版本,对于环境安装,这里不做过多讲述。

        这里着重需要讲的是,对于opencv的dnn模块来说,目前还不支持动态的batch_size,所以在训练好了自己的网络模型后,在转onnx的时候需要注意以下几点:

1)关闭参数dynamic

        这个参数代表了需不需要或者输出图像的batch可变,模型的输入输出有时是可变,因此可通过该参数设置。但是opencv目前是不支持动态推理的,所以dynamic需要设置为false。如果不设置为false,在读取模型的时候会报错。

2)关闭参数do_constant folding

        因为你要训练yolov8,那么就需要高版本的torch,如果torch版本高于2.0以上的话,不关闭此参数,也存在opencv读取不通过的情况,具体设置为:在yolov8的ultralytics文件夹下,找到engine文件夹,点击exporter.py,然后ctrl+f搜索变量do_constant folding,然后将其值改为false。如下:

3)设置opset =12

          将onnx的算子集版本设置为12,默认支持11,最好设置为12

讲完这几点之后,那么只需要在pytorch环境下的终端中输入如下代码:

yolo export model=path/to/model.pt format=onnx dynamic=false  opset=12

其中model就是你训练好的pth文件的路径,然后在终端执行即可,至此onnx的转换就完成啦。转换完成就可以在netron中查看一下自己的输出对不对,如下所示:

如果输入是1x3x640x640,输出是[1,84,8400](针对coco数据集)那就是对的, 因为yolov8的输出变了,将yolov5之前的置信度参数整合到了其他地方,因此这里无类别概率一项。至此转换为onnx就完成了,下一步就是基于opencv的推理了。

二、推理onnx模型

(1)部署opencv

        又到了老生常谈的问题,是的,opencv4.7.0以下版本不支持yolov8推理,重要的事情说三遍:

opencv4.7.0以下版本不支持yolov8推理

opencv4.7.0以下版本不支持yolov8推理

opencv4.7.0以下版本不支持yolov8推理

但是,我的ubuntu上4.7.0版本也读取模型失败,因此我换了4.8.1之后,问题解决,为了大家省事,直接上4.8.1版本:安装和之前是一样的,如果还未安装,请移至本人博客: 中有详细介绍opencv的安装,也可以在网盘里自行下载提取码:wert

  (2)yolov8.h文件
#ifndef yolov8_h
#define yolov8_h
#pragma once
#include<iostream>
#include<opencv2/opencv.hpp>
#include "yolov8_utils.h"
using namespace cv;
using namespace cv::dnn;

class yolov8 {
public:
    yolov8() {
    }
    ~yolov8() {}

    bool readmodel(cv::dnn::net& net, std::string& netpath, bool iscuda);
    bool detect(cv::mat& srcimg, cv::dnn::net& net, std::vector<outputparams>& output);
    void drawpred(cv::mat& img,
        std::vector<outputparams> result,
        std::vector<std::string> classnames,
        std::vector<cv::scalar> color,
        bool isvideo = false
    );

    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));
    int _netwidth = 640;   //onnx图片输入宽度
    int _netheight = 640;  //onnx图片输入高度


    //类别名,自己的模型需要修改此项
    std::vector<std::string> _classname = { "ok","ng"};
private:
    float _classthreshold = 0.25;
    float _nmsthreshold = 0.45;
};





#endif // yolov8_h

代码中的类别名根据自己的网络来修改,其他均不变,类似于yolov5

(3)yolov8.cpp
#include"yolov8.h"

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

bool yolov8::readmodel(cv::dnn::net& net, std::string& netpath, bool iscuda = false) {
    try {

        net = cv::dnn::readnetfromonnx(netpath);
#if cv_version_major==4 &&cv_version_minor==7&&cv_version_revision==0
        net.enablewinograd(false);  //bug of opencv4.7.x in avx only platform ,https://github.com/opencv/opencv/pull/23112 and https://github.com/opencv/opencv/issues/23080
        //net.enablewinograd(true);		//if your cpu supports avx2, you can set it true to speed up
#endif
    }
    catch (const std::exception&) {
        return false;
    }

    if (iscuda) {
        //cuda
        net.setpreferablebackend(cv::dnn::dnn_backend_cuda);
        net.setpreferabletarget(cv::dnn::dnn_target_cuda); //or dnn_target_cuda_fp16
    }
    else {
        //cpu
        std::cout << "inference device: cpu" << std::endl;
        net.setpreferablebackend(cv::dnn::dnn_backend_default);
        net.setpreferabletarget(cv::dnn::dnn_target_cpu);
    }
    return true;
}

void yolov8::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)
{
    if (false) {
        int maxlen = max(image.rows, image.cols);
        outimage = cv::mat::zeros(cv::size(maxlen, maxlen), cv_8uc3);
        image.copyto(outimage(cv::rect(0, 0, image.cols, image.rows)));
        params[0] = 1;
        params[1] = 1;
        params[3] = 0;
        params[2] = 0;
    }

    cv::size shape = image.size();
    float r = std::min((float)newshape.height / (float)shape.height,
        (float)newshape.width / (float)shape.width);
    if (!scaleup)
        r = std::min(r, 1.0f);

    float ratio[2]{ r, r };
    int new_un_pad[2] = { (int)std::round((float)shape.width * r),(int)std::round((float)shape.height * r) };

    auto dw = (float)(newshape.width - new_un_pad[0]);
    auto dh = (float)(newshape.height - new_un_pad[1]);

    if (autoshape)
    {
        dw = (float)((int)dw % stride);
        dh = (float)((int)dh % stride);
    }
    else if (scalefill)
    {
        dw = 0.0f;
        dh = 0.0f;
        new_un_pad[0] = newshape.width;
        new_un_pad[1] = newshape.height;
        ratio[0] = (float)newshape.width / (float)shape.width;
        ratio[1] = (float)newshape.height / (float)shape.height;
    }

    dw /= 2.0f;
    dh /= 2.0f;

    if (shape.width != new_un_pad[0] && shape.height != new_un_pad[1])
    {
        cv::resize(image, outimage, cv::size(new_un_pad[0], new_un_pad[1]));
    }
    else {
        outimage = image.clone();
    }

    int top = int(std::round(dh - 0.1f));
    int bottom = int(std::round(dh + 0.1f));
    int left = int(std::round(dw - 0.1f));
    int right = int(std::round(dw + 0.1f));
    params[0] = ratio[0];
    params[1] = ratio[1];
    params[2] = left;
    params[3] = top;
    cv::copymakeborder(outimage, outimage, top, bottom, left, right, cv::border_constant, color);
}



bool yolov8::detect(cv::mat& srcimg, cv::dnn::net& net, std::vector<outputparams>& output) {
    cv::mat blob;
    output.clear();
    int col = srcimg.cols;
    int row = srcimg.rows;
    cv::mat netinputimg;
    cv::vec4d params;
    letterbox(srcimg, netinputimg, params, cv::size(_netwidth, _netheight));
    cv::dnn::blobfromimage(netinputimg, blob, 1 / 255.0, cv::size(_netwidth, _netheight), cv::scalar(0, 0, 0), true, false);
    //**************************************************************************************************************************************************/
    //如果在其他设置没有问题的情况下但是结果偏差很大,可以尝试下用下面两句语句
    // if there is no problem with other settings, but results are a lot different from  python-onnx , you can try to use the following two sentences
    //
    //$ cv::dnn::blobfromimage(netinputimg, blob, 1 / 255.0, cv::size(_netwidth, _netheight), cv::scalar(104, 117, 123), true, false);
    //$ cv::dnn::blobfromimage(netinputimg, blob, 1 / 255.0, cv::size(_netwidth, _netheight), cv::scalar(114, 114,114), true, false);
    //****************************************************************************************************************************************************/
    net.setinput(blob);
    std::vector<cv::mat> net_output_img;
    net.enablewinograd(false);
    net.forward(net_output_img, net.getunconnectedoutlayersnames()); //get outputs

    //net.forward(net_output_img, "output0"); //获取output的输出
    std::vector<int> class_ids;// res-class_id
    std::vector<float> confidences;// res-conf
    std::vector<cv::rect> boxes;// res-box
    cv::mat output0=cv::mat( cv::size(net_output_img[0].size[2], net_output_img[0].size[1]), cv_32f, (float*)net_output_img[0].data).t();  //[bs,116,8400]=>[bs,8400,116]
    int net_width = output0.cols;
    int rows = output0.rows;
    int socre_array_length = net_width - 4;
    float* pdata = (float*)output0.data;
    for (int r = 0; r < rows; ++r) {
            cv::mat scores(1, socre_array_length, cv_32fc1, pdata + 4);
            cv::point classidpoint;
            double max_class_socre;
            minmaxloc(scores, 0, &max_class_socre, 0, &classidpoint);
            max_class_socre = (float)max_class_socre;
            if (max_class_socre >= _classthreshold) {
                //rect [x,y,w,h]
                float x = (pdata[0] - params[2]) / params[0];
                float y = (pdata[1] - params[3]) / params[1];
                float w = pdata[2] / params[0];
                float h = pdata[3] / params[1];
                int left = max(int(x - 0.5 * w + 0.5), 0);
                int top = max(int(y - 0.5 * h + 0.5), 0);
                class_ids.push_back(classidpoint.x);
                confidences.push_back(max_class_socre);
                boxes.push_back(cv::rect(left, top, int(w + 0.5), int(h + 0.5)));
            }
            pdata += net_width;//next line
    }
    //nms
    std::vector<int> nms_result;
    cv::dnn::nmsboxes(boxes, confidences, _classthreshold, _nmsthreshold, nms_result);
    std::vector<std::vector<float>> temp_mask_proposals;
    cv::rect holeimgrect(0, 0, srcimg.cols, srcimg.rows);
    for (int i = 0; i < nms_result.size(); ++i) {
        int idx = nms_result[i];
        outputparams result;
        result.id = class_ids[idx];
        result.confidence = confidences[idx];
        result.box = boxes[idx] & holeimgrect;
        output.push_back(result);
    }
    if (output.size())
        return true;
    else
        return false;
}


void yolov8::drawpred(cv::mat& img, std::vector<outputparams> result, std::vector<std::string> classnames, std::vector<cv::scalar> color, bool isvideo) {
	cv::mat mask = img.clone();
	for (int i = 0; i < result.size(); i++) {
		int left=0, top=0;

		int color_num = i;
		if (result[i].box.area() > 0) {
			rectangle(img, result[i].box, color[result[i].id], 2, 8);
			left = result[i].box.x;
			top = result[i].box.y;
		}
		if (result[i].rotatedbox.size.width * result[i].rotatedbox.size.height > 0) {
			drawrotatedbox(img, result[i].rotatedbox, color[result[i].id], 2);
			left = result[i].rotatedbox.center.x;
			top = result[i].rotatedbox.center.y;
		}
		if (result[i].boxmask.rows && result[i].boxmask.cols > 0)
			mask(result[i].box).setto(color[result[i].id], result[i].boxmask);
		std::string label = classnames[result[i].id] + ":" + std::to_string(result[i].confidence);
		int baseline;
		cv::size labelsize = cv::gettextsize(label, cv::font_hershey_simplex, 0.5, 1, &baseline);
		top = max(top, labelsize.height);
		//rectangle(frame, cv::point(left, top - int(1.5 * labelsize.height)), cv::point(left + int(1.5 * labelsize.width), top + baseline), cv::scalar(0, 255, 0), filled);
		puttext(img, label, cv::point(left, top), cv::font_hershey_simplex, 1, color[result[i].id], 2);
	}
	cv::addweighted(img, 0.5, mask, 0.5, 0, img); //add mask to src
	cv::imshow("1", img);
	if (!isvideo)
		cv::waitkey();
	//destroyallwindows();

}

如果模型加载成功的话,那么运行代码是完全没有问题的,如果报错,那就是你的模型问题,没有按照第一节的设置来弄。

(4)main函数

main函数中就很简单了,只需要先实例化一个yolov8的对象,然后先调用detect函数,在调用drawpred函数,最后就可以看到预测结果了,当然作者是基于qt开发的,不局限于平台,都可以的。

int main()
{
    yolov8 yolov8;
    net net;
    std::string model_path = "./models/yolov8n.onnx";   //onnx的路径,填绝对路径
	if (yolov8.readmodel(net, model_path, false)) {
		std::cout << "read net ok!" << std::endl;
	}
	else {
		return -1;
	}
	//生成随机颜色
	std::vector<cv::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(cv::scalar(b, g, r));
	}
	std::vector<outputparams> result;

	if (yolov8.detect(img, net, result)) {

		drawpred(img, result, yolov8._classname, color);
		
	}
	else {
		std::cout << "detect failed!" << std::endl;
	}
	system("pause");
	return 0;
}

部署到这里就完成啦,其实大家如果之前部署过yolov5的话,对于yolov8变化不大,部署起来也很简单,希望能帮到大家,写的比较仓促,错误之处希望大家及时批评指正!!! 

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com