基于c++的yolov5推理之前处理详解及代码(一)

目录

一、前言:

二、关于环境安装:

三、首先记录下自己的几个问题

  问题:c++部署和python部署的区别?

四、正文开始

  4.1 图像预处理讲解

  1、BGR---->RBG

  2、等比例放缩图片(涉及到短边的填充)

  3、归一化

  4、HWC---->NCHW

  5、将图像转为一维向量格式并将整理为连续内存

  4.2图像内存开辟与传递


一、前言:

  之前实现了基于win环境中的python的加速部署过程,现在将c++实现过程进行记录,这次记录会非常详细。总的来说只要整个过程的思路捋清楚,保证思路是正确的,做起工作来就比较有方向性,开始记录。

二、关于环境安装:

  环境:wsl2+Ubuntu2004+cuda113+cudnn8.9.5.30+tensrort8.6.1.6+opencv4.2.0

  主要要安装的是cuda和cudnn和tensorrt。确定好版本,我本地cuda是11.7,cuda可以向下兼容,所以我wsl中使用11.3也是支持的。参考博客:【教程】Install NVIDIA TensorRT on WSL 在WSL上安装英伟达TensorRT_wsl tensorrt-CSDN博客win11 WSL ubuntu安装CUDA、CUDNN、TensorRT最有效的方式-CSDN博客,这两个博客互补一下。

  tips:1、安装位置最好新建一个文件夹,放在一起,在设定环境变量的时候会方便些。

   2、opencv4版本会和libtorch1.10冲突,如果没有装libtorch之前opencv是好的,装了libtorch发现这两个库都用不了,大概率是版本冲突了(一踩坑,心态崩了一天时间,sad)但是不用libtorch也完全不影响推理。

三、首先记录下自己的几个问题

  问题:c++部署和python部署的区别?

    我最开始个人理解可能c++会更多设计自己手动开辟回收内存,在我实现c++部署之后,会过头看python实现过程,发现python也是手动开辟回收内存的,所以他们最重要的区别到底在哪里呢?

    回答:最重要的东西确实还是内存管理上,然后自然而然的会影响性能,还有系统集成中。      在内存管理方面:

        1、C++版本需要显式地管理所有资源的生命周期,包括内存分配和释放。

        2、Python版本虽然也进行了一些手动内存管理,但仍然依赖于Python的垃圾回收机制来处理大部分对象的清理。

        3、C++版本提供了更细粒度的控制,可以精确地控制何时分配和释放资源。

        4、Python版本在语法上更简洁,并且某些资源管理被抽象化,使得代码更容易编写和维护。

      性能方面:我理解在一些计算开销没有非常限制的情况下,或者执行次数不多的情况下两者区别不会拉太卡,但是涉及到性能的极致使用的情况下,肯定是c++性能要高出python。

        1、C++:通常能提供更高的性能,尤其是在低延迟场景中。直接编译为机器码,没有解释器开销。

        2、Python:有解释器开销,但在许多情况下,性能差异可能不明显,特别是当大部分计算都在GPU上进行时。

      生态集成方面:

        python更加依赖于python开发环境,而c++可以编译为动态库,限制更少一些。总的来说在工业应用中肯定吃c++更加方便些。

四、正文开始

  前处理主要分为两部分,第一部分是对image的格式的转换,即cv::imread读取上来的单张图片要转为符合深度学习torch中dataload中读取上来的nchw格式的数据。第二部分就是将这样格式的数据从cpu传输到GPU中。第二部分相对操作固定一些。

  Opencv读取图片格式转换为深度学习图片输入格式

  一般有两方面需要转换,第一方面是格式,第二方面是尺寸。一般来说应该是先进行格式转换,然后在进行尺寸变化,因为尺寸变化要是涉及到放缩的话,uint8的数据空间可能会一点,可能会有一些精度的损失。

  使用opencv中cv::imread读取上来的image一般保存在cv::Mat中,OpenCV 中的 cv::Mat 格式如下:

  1. 数据类型cv::Mat 默认使用 8 位无符号整数(CV_8U)存储图像数据。

  2. 通道顺序cv::Mat 默认使用 BGR 顺序存储图像通道。

  3. 形状cv::Mat 通常具有二维或三维矩阵结构,对应于 (height, width, channels)

  而深度学习一般需要的输入数据格式如下:

  1. 数据类型:深度学习模型通常使用 32 位浮点数(float32)存储图像数据。

  2. 通道顺序:深度学习模型通常使用 RGB 顺序存储图像通道。

  3. 形状:深度学习模型的输入形状通常为 (batch_size, channels, height, width),即 NCHW 格式。

  以上是格式方面的区别,往往还有尺寸方面的区别。yolov5一般要求输入尺寸大小为640*640大小。我们还需要将图片进行缩放。注意:是等比例缩放。

  前处理流程和代码

  4.1 图像预处理讲解

  首先创建处理图像的函数:

cv::Mat preImage(cv::Mat& rawBGRImage,int inputH, int inputW)

  我们创建一个preImage的函数,返回类型是cv::Mat类型,参数输入是一个cv::Mat类型的rawBGRImage的引用,同时输入两个整数,代表深度学习输入的大小,这里为640*640。

  第一步:

  1、BGR---->RBG

// BGR --> RGB

cv::cvtColor(oriImage,image,cv::COLOR_BGR2RGB);

  2、等比例放缩图片(涉及到短边的填充)

首先获得原始图像的h和w;假设1280*1920
    int ori_h = oriImage.rows;
    int ori_w = oriImage.cols;
    std::cout<<"原始图片的size是"<<oriImage.size<<std::endl;
两边不一样长的图片要保证长边缩小到640,短边进行填充操作。
    // 取最小的因子,以保证长的一边被正确缩放,短边过度缩放可在后面使用padding填充
    // 这两个结果肯定小于1,使用整除会将结果截断为0
    double r = std::min(static_cast<double>(inputH) / ori_h, static_cast<double>(inputW) / ori_w);  
    std::cout<<"缩放因子 r 是"<< r <<std::endl;
    int new_h = std::round(ori_h * r);//四舍五入
    int new_w = std::round(ori_w * r);
    std::cout<<"新图片的size是"<<new_h <<","<<new_w<<std::endl;
计算填充大小,640减去新图像的边长
    //计算padding的大小,
    int dh = inputH - new_h;
    int dw = inputW - new_w;
    std::cout<<"需要填充的size是"<<dh <<","<<dw<<std::endl;
    // 使用双线性插值将图像进行等比例缩放
    cv::resize(image,image,cv::Size(new_h,new_w),0, 0, cv::INTER_LINEAR);
此时得到了一幅(427,640)的图像,需要对h边进行填充 
    //判断是否需要填充   
    if(dh > 0 || dw > 0){
        cv::resize(image,image,cv::Size(new_h,new_w),0, 0, cv::INTER_LINEAR);
计算填充的像素
        // Pad the short side with (128,128,128)
        int top = dh / 2;
        int bottom = dh - top;
        int left = dw / 2;
        int right = dw - left;
        std::cout<<"方框是"<<top <<","<<bottom<<","<<left<<","<<right<<std::endl;
进行填充,其中top,bottom,left,right等代表填充到图像上下左右的像素行数
        //扩充边界
        cv::copyMakeBorder(image, image, left, right, top, bottom, cv::BORDER_CONSTANT, cv::Scalar(128, 128, 128));
        std::cout<<"扩充边界之后的图片的size是"<<image.size<<std::endl;
    }
得到一幅填充到640*640大小的图像

  3、归一化

image.convertTo(image, CV_32F); //自动转,如果原图是32f,只拷贝,不做转换。
    image /= 255.0; // 8u转32f之后,原始像素值还是不变的,只是可表示范围变大了。

  4、HWC---->NCHW

// HWC to CHW format:并处理成cv中的dnn多维格式数据,类似于tensor?
    cv::Mat imageCHW;
    cv::dnn::blobFromImage(image, imageCHW, 1.0, cv::Size(), cv::Scalar(), false, false);//这种缩放会影响图像精度
    //该函数默认bs维度=1。cv::dnn::blobFromImages函数可以自由设定bs维度的值
    std::cout << "NCHW shape: " << imageCHW.size << std::endl;

    函数介绍:

    cv::dnn::blobFromImage 函数是 OpenCV 中的一个实用工具,用于将图像转换为深度学习模型所需的多维格式(通常是 NCHW,即 batch size、channels、height、width)。这是深度学习中常用的数据格式。

cv::Mat blobFromImage(
    const cv::Mat& image, //输入图像
    double scalefactor = 1.0, //缩放因子,用于缩放图像的每个像素值。默认值是 1.0
    const cv::Size& size = cv::Size(),//目标图像的大小。cv::Size(width, height) 格式。如果为 Size(),则保持原始图像大小。
    const cv::Scalar& mean = cv::Scalar(),//用于减去的均值向量,cv::Scalar(mean_r, mean_g, mean_b) 格式。用于均值归一化。
    bool swapRB = false,//是否交换R和B通道
    bool crop = false,//是否在缩放后裁剪图像。如果设置为 true,则图像会被裁剪以适应目标大小
    int ddepth = CV_32F//默认32位
);

  5、将图像转为一维向量格式并将整理为连续内存

    //此处还待确认,是否一定需要转为一维向量。实际测试下来发现转或不转都可以识别。

    cv::Mat imageNCHW = imageCHW.reshape(1, 1);
    std::cout << "zuihou d  NCHW shape: " << imageNCHW.size << std::endl;

    // Convert the image to row-major order, also known as "C order":
    // 复制矩阵 转换位行主序(C order)存储。意味着数据在内存中是按行连续存储的
    // 某些深度学习框架或推理引擎可能要求输入数据是连续存储的
    cv::Mat imageRowMajor;
    imageNCHW.copyTo(imageRowMajor);

  4.2图像内存开辟与传递

    首先设定图像大小并计算数据缓存区,然后将数据从cpu复制到gpu。

// 图像前处理函数实现
void YoLov5TRT::preprocessImage(const cv::Mat& image, float* gpu_input) {
     cv::Mat Image = image;

    // preImage函数中对图片进行详细前处理
    cv::Mat risezed_image = preImage(Image, H, W);

    size_t image_size = risezed_image.total() * risezed_image.channels(); // 计算输入大小,用来分配缓存区
    std::vector<float> input_data(image_size); // 分配输入数据缓冲区

    // 5. 使用 memcpy 复制数据
    std::memcpy(input_data.data(), risezed_image.data, image_size * sizeof(float));

    // 6. 异步复制数据到 GPU
    cudaMemcpyAsync(gpu_input, input_data.data(), image_size * sizeof(float), cudaMemcpyHostToDevice, stream_);
}

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在C++中进行Yolov5推理,您需要下载Yolov5的预训练模型和相应的C++推理库。以下是一个简单的步骤: 1. 下载Yolov5预训练模型 您可以从Yolov5官方GitHub存储库(https://github.com/ultralytics/yolov5)下载预训练模型。选择您需要的模型并将其下载到本地计算机。 2. 安装C++推理库 有许多C++推理库可供选择,例如OpenCV、TensorRT、ONNX等。在这里,我们将使用OpenCV深度学习模块,因为它易于安装和使用。 要使用OpenCV中的深度学习模块,您需要安装OpenCV并启用DNN模块。可以在以下链接中找到有关如何安装OpenCV的信息:https://opencv.org/releases/。 3. 加载模型 使用OpenCV的dnn模块,您可以轻松加载Yolov5模型。以下是示例代码: ```cpp cv::dnn::Net net = cv::dnn::readNet("path/to/your/yolov5/model", "path/to/your/yolov5/config"); ``` 4. 进行推理 一旦模型被加载,您可以使用它来进行推理。以下是一个示例代码,演示如何使用OpenCV进行Yolov5推理: ```cpp cv::Mat image = cv::imread("path/to/your/image"); cv::Mat blob = cv::dnn::blobFromImage(image, 1/255.0, cv::Size(640, 640), cv::Scalar(0, 0, 0), true, false); net.setInput(blob); std::vector<cv::Mat> outs; net.forward(outs, net.getUnconnectedOutLayersNames()); for (cv::Mat& out : outs) { // 处理输出结果 } ``` 在上面的代码中,我们首先读取要进行推理的图像,然后使用OpenCV的blobFromImage函数将其转换为网络输入。接下来,我们将输入设置为网络的输入,然后调用前向方法来获得输出。最后,我们可以通过处理输出来获得检测结果。 请注意,在处理输出时,您需要将输出转换为可读的格式。具体来说,您需要将它们转换为边界框,并且通过应用非最大抑制算法来过滤掉重叠的检测结果。 这是一个简单的步骤来使用C++进行Yolov5推理,但要进行更深入的推理,您需要深入了解底层库和算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值