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

目录

一、前言:

二、关于环境安装:

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

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

四、正文开始

[4.1 图像预处理讲解](#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_);
}
相关推荐
白子寰6 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_0111 分钟前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj17 分钟前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT18 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。20 分钟前
c++多线程
java·开发语言
-Even-21 分钟前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus
小政爱学习!22 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k093338 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·