AI模型部署实战:利用OpenCV的CUDA模块加速视觉模型部署流程

本文首发于公众号【DeepDriving】,欢迎关注。

一. 前言

我在之前的文章《AI模型部署实战:利用CV-CUDA加速视觉模型部署流程》中介绍了如何使用CV-CUDA库来加速视觉模型部署的流程,但是CV-CUDA对系统版本和CUDA版本的要求比较高,在一些低版本的系统中可能无法使用。对于像我这种不会写CUDA代码又想用CUDA来加速模型部署流程的人来说要怎么办呢,其实还有一种方式,那就是使用OpenCV的CUDA接口

本文将介绍OpenCV CUDA模块的基本使用方法(C++),以及如何使用这些接口来加速视觉模型部署。

二. 安装CUDA版本OpenCV

Ubuntu 20.04系统中使用apt install命令安装OpenCV是不会安装CUDA模块的,要想使用CUDA模块只能用源码进行编译安装。在Ubuntu系统中用源码编译安装OpenCV 4.6版本的过程如下:

  1. 安装必要的依赖

在用源码编译安装OpenCV之前,需要先执行下面一系列命令安装必要的依赖:

sudo apt update
sudo apt upgrade
sudo apt install build-essential cmake pkg-config unzip yasm git checkinstall
sudo apt install libjpeg-dev libpng-dev libtiff-dev
sudo apt install libavcodec-dev libavformat-dev libswscale-dev libavresample-dev
sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
sudo apt install libxvidcore-dev x264 libx264-dev libfaac-dev libmp3lame-dev libtheora-dev
sudo apt install libfaac-dev libmp3lame-dev libvorbis-dev
sudo apt install libopencore-amrnb-dev libopencore-amrwb-dev
sudo apt-get install libdc1394-22 libdc1394-22-dev libxine2-dev libv4l-dev v4l-utils
cd /usr/include/linux
sudo ln -s -f ../libv4l1-videodev.h videodev.h
cd -
sudo apt-get install libgtk-3-dev
sudo apt-get install libtbb-dev
sudo apt-get install libatlas-base-dev gfortran
sudo apt-get install libprotobuf-dev protobuf-compiler
sudo apt-get install libgoogle-glog-dev libgflags-dev
sudo apt-get install libgphoto2-dev libeigen3-dev libhdf5-dev doxygen
sudo apt-get install opencl-headers
sudo apt-get install ocl-icd-libopencl1
  1. GitHub网站分别下载OpenCV 4.6.0的源码包和扩展模块源码包
# 下载opencv-4.6.0源码包
https://github.com/opencv/opencv/archive/refs/tags/4.6.0.zip
#下载4.6.0对应的扩展模块源码包
https://github.com/opencv/opencv_contrib/archive/refs/tags/4.6.0.zip

下载好以后把两个包进行解压。

  1. 按照下面的步骤编译源码并进行安装:
cd opencv-4.6.0mkdir build && cd buildcmake -D CMAKE_BUILD_TYPE=RELEASE \-D CMAKE_INSTALL_PREFIX=/usr/local \-D INSTALL_PYTHON_EXAMPLES=OFF \-D INSTALL_C_EXAMPLES=OFF \-D WITH_TBB=ON \-D WITH_CUDA=ON \-D BUILD_opencv_cudacodec=OFF \-D ENABLE_FAST_MATH=1 \-D CUDA_FAST_MATH=1 \-D WITH_CUBLAS=1 \-D WITH_V4L=OFF \-D WITH_LIBV4L=ON \-D WITH_QT=OFF \-D WITH_GTK=ON \-D WITH_GTK_2_X=ON \-D WITH_OPENGL=ON \-D WITH_GSTREAMER=ON \-D OPENCV_GENERATE_PKGCONFIG=ON \-D OPENCV_PC_FILE_NAME=opencv.pc \-D OPENCV_ENABLE_NONFREE=ON \-D CUDA_nppicom_LIBRARY=stdc++ \-D OPENCV_PYTHON3_INSTALL_PATH=/usr/lib/python3/dist-packages \-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.6.0/modules \-D PYTHON_EXECUTABLE=/usr/bin/python3 \-D BUILD_EXAMPLES=OFF ..make -j8 && sudo make install

CMake的几个参数需要注意一下:

-D WITH_CUDA=ON  # 这里必须设置为ON,否则无法使用CUDA模块
-D CMAKE_INSTALL_PREFIX=/usr/local # OpenCV的安装路径,可以按照自己的需求指定
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.6.0/modules # 扩展模型源码包的路径

因为CMake过程中要下载很多依赖文件,如果速度很慢,可以加上配置选项-DOPENCV_DOWNLOAD_MIRROR_ID=gitcode,这样就可以从国内镜像下载了,速度会快很多。

安装成功后,还需要设置一下环境变量:

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/opencv-4.6/lib/

三. OpenCV CUDA模块的基本使用方法

OpenCV CUDA模块的官方文档详细阐述了CUDA模块提供的函数接口以及使用方法,在写代码之前我们应该好好学习一下这些文档。

基础数据结构GpuMat

在使用CPU的时候,OpenCV是使用数据结构cv::Mat来作为数据容器的;而在GPU上,则是使用一个新的数据结构cv::gpu::GpuMat,所有在GPU上调用的接口都是使用该数据结构作为输入或输出的。GpuMatMat的使用方式非常相似,封装的接口基本上是一致的,详细内容可以参考GpuMat的文档。

CPUGPU之间的数据传输

OpenCV提供了非常简单的接口实现CPUGPU之间的数据传输,也就是cv::Matcv::gpu::GpuMat之间的转换:

  • upload: 把数据从CPU拷贝到GPU上;
  • download: 把数据从GPU拷贝到CPU上;

下面是一个简单的示例:

#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>cv::Mat img = cv::imread("test.jpg");
// 把数据从CPU拷贝到GPU上
cv::cuda::GpuMat gpu_mat;
gpu_mat.upload(img);// 在GPU上对数据做处理// 把结果从GPU拷贝到CPU上 
cv::Mat result;
gpu_mat.download(result);
使用GPU做图像预处理

在做视觉AI模型部署时,图像数据预处理的基本流程如下:

1. 把OpenCV读取的BGR格式的图片转换为RGB格式;
2. 把图片resize到模型输入尺寸;
3. 对像素值做归一化操作;
4. 把图像数据的通道顺序由HWC调整为CHW;

以部署YOLOv6模型为例,在CPU上做图像预处理的的代码如下:

bool ImagePreProcessCpu(const cv::Mat &input_image, const int resize_width,const int resize_height, const double alpha,const double beta, float *const input_blob) {if (input_image.empty()) {return false;}if (input_blob == nullptr) {return false;}// 这里默认输入图像是RGB格式// resizecv::Mat resize_image;cv::resize(input_image, resize_image,cv::Size(resize_width, resize_height));// 像素值归一化cv::Mat float_image;resize_image.convertTo(float_image, CV_32FC3, alpha, beta);// 调整通道顺序,HWC->CHWconst int size = resize_width * resize_height;std::vector<cv::Mat> input_channels;cv::split(float_image, input_channels);for (int c = 0; c < resize_image.channels(); ++c) {std::memcpy(input_blob + c * size, input_channels[c].data,size * sizeof(float));}return true;
}

调用OpenCV CUDA模块的接口做预处理的代码如下:

bool ImagePreProcessGpu(const cv::Mat &input_image,const int resize_width,const int resize_height,const double alpha, const double beta,float *const input_blob) {if (input_image.empty()) {return false;}// 注意,这里input_blob是指向GPU内存if (input_blob == nullptr) {return false;}cv::cuda::GpuMat gpu_image, resize_image,float_image;gpu_image.upload(input_image);cv::cuda::resize(gpu_image, resize_image,cv::Size(resize_width, resize_height), 0, 0,cv::INTER_LINEAR);resize_image.convertTo(float_image, CV_32FC3, alpha, beta);const int size = resize_width * resize_height;std::vector<cv::cuda::GpuMat> split_channels;for (int i = 0; i < float_image.channels(); ++i) {split_channels.emplace_back(cv::cuda::GpuMat(cv::Size(resize_width, resize_height), CV_32FC1,input_blob + i * size));}cv::cuda::split(float_image, split_channels);return true;
}

可以看到,CPUGPU版本调用的函数名是一样的,只不过GPU版本的多了一个cuda命名空间。所以使用OpenCVCUDA模块基本上是没有什么难度的,只需要查一下之前调用的CPU接口是否有对应的GPU版本就可以了。

使用CUDA

CUDA流是一系列异步操作的集合,通过在一个设备上并发地运行多个内核任务来实现任务的并发执行,这种方式使得设备的利用率更高。上面代码调用的OpenCV CUDA模块接口都是没有使用CUDA流的,不过CUDA模块为每个函数都提供了一个使用CUDA流的版本,使用起来也非常简单。

OpenCV CUDA模块的CUDA流封装在cv::cuda::Stream类中,使用之前首先创建一个类对象

cv::cuda::Stream stream;

然后在调用每个CUDA接口的时候传入该对象

gpu_image.upload(input_image,stream);

再在最后调用waitForCompletion()函数进行同步,确保该流上的所有操作都已完成。

使用CUDA流的图像预处理代码如下:

bool ImagePreProcessGpuStream(const cv::Mat &input_image,const int resize_width,const int resize_height,const double alpha, const double beta,float *const input_blob) {if (input_image.empty()) {return false;}if (input_blob == nullptr) {return false;}cv::cuda::Stream stream;cv::cuda::GpuMat gpu_image, resize_image,float_image;gpu_image.upload(input_image,stream);cv::cuda::resize(gpu_image, resize_image,cv::Size(resize_width, resize_height), 0, 0,cv::INTER_LINEA,stream);resize_image.convertTo(float_image, CV_32FC3, alpha, beta,stream);const int size = resize_width * resize_height;std::vector<cv::cuda::GpuMat> split_channels;for (int i = 0; i < float_image.channels(); ++i) {split_channels.emplace_back(cv::cuda::GpuMat(cv::Size(resize_width, resize_height), CV_32FC1,input_blob + i * size));}cv::cuda::split(float_image, split_channels,stream);stream.waitForCompletion();return true;
}

OpenCVCUDA流和原生的CUDA流可以通过结构体cv::cuda::StreamAccessor提供的两个静态函数进行转换:

// 把OpenCV的CUDA流转换为原生CUDA流
static cudaStream_t cv::cuda::StreamAccessor::getStream	(const Stream & stream	)	// 把原生CUDA流转换为OpenCV的CUDA流
static Stream cv::cuda::StreamAccessor::wrapStream	(cudaStream_t stream)	

如果对CUDA流不了解,可以参考我之前写的这篇文章。

四. 总结

本文介绍了OpenCV CUDA模块中图像处理接口的基本使用方法,用这些CUDA接口基本上可以满足视觉AI模型的部署需求,在嵌入式平台上可以有效减少CPU资源的消耗。当然,OpenCV提供的CUDA版本接口也有限,必要的时候也只能自己手搓CUDA代码了。

五. 参考资料

  1. CUDA-accelerated Computer Vision
  2. How To Run Inference Using TensorRT C++ API
  3. Using TensorRT with OpenCV CUDA
  4. Getting Started with OpenCV CUDA Module
  5. GPU-accelerated Computer Vision
  6. OpenCV CUDA samples

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/11748.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux实验 Shell编程

实验目的&#xff1a; 熟练掌握Shell程序的建立与执行&#xff1b;掌握Shell变量的两种类型&#xff08;Shell环境变量和用户自定义变量&#xff09;及其用法&#xff1b;掌握Shell中的特殊字符、算术与逻辑运算&#xff1b;掌握Shell中输入输出命令&#xff1b;掌握Shell程序…

在Windows环境下安装CPU版的PyTorch

PytTorch是基于Python开发的&#xff0c;首先需要安装Python&#xff0c;Python的安装很简单&#xff0c;这里不再赘述。而 Windows用户能直接通过conda、pip和源码编译三种方式来安装PyTorch。 打开PyTorch官网&#xff08;PyTorch&#xff09;&#xff0c;在主页中根据自己的…

python数据分析——时间序列

时间序列 前言一、Datetime 模块常用函数和数据结构的详细解释datetime模块示例一示例二 二、时间运算示例一示例二示例三 三、时间序列分析自回归(Autoregressive model/AR)模型示例 滑动平均(moving average model/MA)模型示例 自回归滑动平均(Autoregressive moving average…

企业微信hook接口协议,ipad协议http,发送链接的方式邀请成员进群

发送链接的方式邀请成员进群 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid":"3240fde0-45e2-48c0-90e8-cb098d0ebe43","roomid":10696052955013729, "vids":[788130334…

Qt---文件系统

一、基本文件操作 1. QFile对文件进行读和写 QFile file( path 文件路径) 读&#xff1a; file.open(打开方式) QlODevice::readOnly 全部读取->file.readAll()&#xff0c;按行读->file.readLine()&#xff0c;atend()->判断是否读到文件尾 …

STM32存储左右互搏 USB接口FATS文件读写U盘

STM32存储左右互搏 USB接口FATS文件读写U盘 STM32的USB接口可以例化为Host主机从而对U盘进行操作。SD卡/MicroSD/TF卡也可以通过读卡器转换成U盘使用。这里介绍STM32CUBEIDE开发平台HAL库实现U盘FATS文件访问的例程。 USB接口介绍 常见的USB接口电路部分相似而有不同的连接器…

K8S -----二进制搭建 Kubernetes v1.20

目录 一、准备环境 1.1 修改主机名 1.2 关闭防火墙&#xff08;三台一起&#xff0c;这里只展示master01&#xff09; 1.3 在master添加hosts&#xff08;依旧是三台一起&#xff09; 1.4 调整内核参数并开启网桥模式 二、部署docker引擎 三、部署 etcd 集群 1.在mast…

15.JUC原子类

文章目录 JUC原子类1.JUC中的Atomic原子操作包1.1. 基本原子类&#xff08;Basic Atomic Classes&#xff09;1.2. 数组原子类&#xff08;Array Atomic Classes&#xff09;1.3. 引用原子类&#xff08;Reference Atomic Classes&#xff09;4. 字段更新原子类&#xff08;Fie…

StackQueue+泛型简单理解

&#x1f341; 个人主页&#xff1a;爱编程的Tom&#x1f4ab; 本篇博文收录专栏&#xff1a;Java专栏&#x1f449; 目前其它专栏&#xff1a;c系列小游戏 c语言系列--万物的开始_ &#x1f389; 欢迎 &#x1f44d;点赞✍评论⭐收藏&#x1f496;三连支持一…

LeetCode2215找出两数组的不同

题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;请你返回一个长度为 2 的列表 answer &#xff0c;其中&#xff1a;answer[0] 是 nums1 中所有 不 存在于 nums2 中的 不同 整数组成的列表。answer[1] 是 nums2 中所有 不 存在于 nums1 中的 不同 整数组…

Autosar架构

蓝框那种叫component&#xff0c;绿框的叫function cluster。 接口 有三种接口&#xff0c;RTE跟SWC之间链接的叫Autosar Interface&#xff0c;RTE跟BSW的Components链接是Standardized Interface&#xff0c;RTE跟BSW的services链接的是Standardized Autosar Interface。 St…

网络协议的分类

1.概要 网络协议可以分为三类&#xff1a; 封装协议路由协议功能类协议 2.分类说明 OSPF报文直接调用_ IP协议__协议进行封装&#xff0c;以目的地址_244.0.0.5 __发送到所有的OSPF路由器? 244.0.0.1 所有主机&#xff1b;244.0.0.2 所有路由器&#xff1b;244.0.0.6 指定…

Vue+springboot的批量删除功能

vue前台 <div style"margin-bottom: 10px"><el-button type"primary" plain click"handleAdd">新增</el-button><el-button click"delBatch" type"danger" plain style"margin-left: 5px"…

C语言例题41、八进制转换为十进制

#include<stdio.h>void main() {int x;printf("请输入一个8进制整数&#xff1a;");scanf("%o", &x);printf("转换成十进制后的整数为%d\n", x); }运行结果&#xff1a; 本章C语言经典例题合集&#xff1a;http://t.csdnimg.cn/FK0Qg…

Java入门基础学习笔记21——Scanner

在程序中接收用户通过键盘输入的数据&#xff1a; 需求&#xff1a; 请在程序中&#xff0c;提示用户通过键盘输入自己的姓名、年龄、并能在程序中收到这些信息&#xff0c;怎么解决&#xff1f; Java已经写好了实现程序&#xff0c;我们调用即可。 API&#xff1a;Applicat…

2024 年中国大学生程序设计竞赛全国邀请赛(郑州)暨第六届CCPC河南省大学生程序设计竞赛 problem K. 树上问题

//先找一个美丽的树&#xff0c;然后遍历树找节点,分析是否符合条件。 //画几个图&#xff0c;思考下。 #include<bits/stdc.h> using namespace std; #define int long long const int n1e611; int a,b,c[n],d,l,r,k,w,an; vector<int>t[n]; void dfs(int x,int…

什么是页分裂、页合并?

数据组织方式 在InnoDB存储引擎中&#xff0c;表数据都是根据主键顺序组织存放的&#xff0c;这种存储方式的表称为索引组织表(index organized table IOT)。 行数据&#xff0c;都是存储在聚集索引的叶子节点上的。而我们之前也讲解过InnoDB的逻辑结构图&#xff1a; 在I…

61、内蒙古工业大学、内蒙科学技术研究院:CBAM-CNN用于SSVEP - BCI的分类方法[脑机二区还是好发的]

前言&#xff1a; 之前写过一篇对CBAM模型改进的博客&#xff0c;在CBAM中引入了ECANet结构&#xff0c;对CBAM中的CAM、SAM模块逐一改进&#xff0c;并提出ECA-CBAM单链双链结构&#xff0c;我的这个小的想法已经被一些同学实现了&#xff0c;并进行了有效的验证&#xff0c;…

快速对比 找出2个名单不同之处

import pandas as pd# 读取两个Excel文件 df1 pd.read_excel(1.xlsx) df2 pd.read_excel(2.xlsx)# 检查两个DataFrame的列是否相同 if list(df1.columns) ! list(df2.columns):print("两个Excel文件的列不一致。")print("文件1的列&#xff1a;", df1.co…

AI智能体|手把手教你申请一个Kimi(Moonshot)的API KEY

大家好&#xff0c;我是无界生长。 今天分享一下如何申请一个Kimi(Moonshot)的API KEY&#xff0c;为后面Kimi(Moonshot)接入微信机器人做铺垫。学会了的话&#xff0c;欢迎分享转发&#xff01; 前提 拥有一个Kimi(Moonshot)账号 使用手机号注册即可&#xff0c;新用户可免费…