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

news2024/12/24 8:14:22

本文首发于公众号【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.0

mkdir build && cd build

cmake -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格式

  // resize
  cv::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->CHW
  const 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.coloradmin.cn/o/1673201.html

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

相关文章

政安晨:【Keras机器学习示例演绎】(四十五)—— 使用变换器进行命名实体识别

目录 简介 安装 HuggingFace 的开源数据集库 将 NER 模型类构建为 keras.Model 子类 从数据集库加载 CoNLL 2003 数据集并进行处理 制作 NER 标签查找表 编译和拟合模型 指标计算 结论 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏…

PyTorch进行CIFAR-10图像分类

本节将通过一个实战案例来详细介绍如何使用PyTorch进行深度学习模型的开发。我们将使用CIFAR-10图像数据集来训练一个卷积神经网络。 神经网络训练的一般步骤如图5-3所示。 &#xff08;1&#xff09;加载数据集&#xff0c;并做预处理。 &#xff08;2&#xff09;预处理后的…

使用PageHelper分页插件,发现获取到的total总记录数量不对,无法获取到正确的total数量

目录 1.1、错误描述 1.2、解决方案 1.1、错误描述 周一在工作中&#xff0c;写了一个列表分页的接口&#xff0c;其中使用的是PageHelper分页依赖&#xff0c;原本想着挺简单的&#xff0c;也就是使用PageHelper.startPage(pageNum, pageSize);方法就可以了&#xff0c;代码…

讨论:WGS84与CGCS2000的坐标系怎么互转

前言&#xff1a; 今天我们要讨论一个问题&#xff1a;WGS84与CGCS2000的坐标系怎么互转&#xff1f; 对于有一定基础的朋友应该知道&#xff0c;WGS84和CGCS2000属于不同的椭球&#xff0c;如果进行严密的数学转换&#xff0c;是需要建立参数模型之后&#xff0c;再进行转换&…

视频素材哪里找?7个无版权视频素材网站

这篇文章为那些正在学习视频剪辑的新手提供了一份宝贵的资源清单&#xff0c;介绍了7个可以找到高质量且免费可商用的视频素材网站。每个网站都有其独特的资源库&#xff0c;可以帮助用户找到适合各种项目的视频素材&#xff0c;从生活vlog到专业旅行记录&#xff0c;都可以在这…

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

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

数据分离和混淆矩阵的学习

1.明确意义 通过训练集建立模型的意义是对新的数据进行准确的预测&#xff08;测试集的准度高才代表good fit&#xff09;&#xff1b; 2.评估流程 3.单单利用准确率accuracy进行模型评估的局限性 模型一&#xff1a;一共1000个数据&#xff08;分别为900个1和100个0&#x…

网站服务器备案及域名购买配置教程

一、阿里云服务备案准备工作 1.什么是备案? 备案是指向相关部门提交网站信息,以便监管和管理互联网信息服务,未经备案的网站可能面临罚款甚至被关闭的风险。备案主要看您的网站或App等互联网信息服务解析到的服务器是否在中国内地(大陆),如果服务器在中国内地(大陆),…

Postman基础功能-Collection集合和批量运行

一、Collection&#xff08;集合&#xff09;介绍 当我们对一个或多个系统中的很多接口用例进行维护时&#xff0c;首先想到的就是对接口用例进行分类管理&#xff0c;同时还希望对这批接口用例做回归测试。 在 Postman 中也提供了这样一个功能&#xff0c;就是 Collec…

Aim Web API 远程代码执行

摘要 漏洞类型&#xff1a;远程代码执行&#xff08;RCE&#xff09;产品&#xff1a;目标版本&#xff1a;> 3.0.0&#xff08;afaik&#xff09;受影响的端点&#xff1a; /api/runs/search/run/严重性&#xff1a;临界 描述 在aim项目中发现了一个关键的远程代码执行漏…

设计模式 六大原则之里氏替换原则

文章目录 概念替换逻辑行为不变 拆解小结 概念 子类对象能够替换程序中父类对象出现的任何地方&#xff0c;并且保证原来程序的逻辑行为不变及正确性不被破坏。 替换 替换的前提是面向对象语言所支持的多态特性&#xff0c;同一个行为具有多个不同表现形式或形态的能力。 逻…

js基础-数组-事件对象-日期-本地存储

一、大纲 一、获取元素位置 在JavaScript中&#xff0c;获取一个元素在页面上的位置可以通过多种方法实现。以下是一些常见的方法&#xff1a; getBoundingClientRect() getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。它提供了元素的left、top、right和bo…

Tkinter组件:Text-显示和处理多行文本

Tkinter组件&#xff1a;Text Text&#xff08;文本&#xff09;组件用于显示和处理多行文本。在 Tkinter 的所有组件中&#xff0c;Text 组件显得异常强大和灵活&#xff0c;适用于多种任务。虽然该组件的主要目的是显示多行文本&#xff0c;但它常常也被用于作为简单的文本编…

【单片机调试】mcu调试bug记录

【单片机调试】mcu调试bug记录 2023.5-2023.11待输入 2023.12-2023.22024.3-至今1.spi通信问题 2023.5-2023.11 待输入 2023.12-2023.2 辞职阶段&#xff1a;【STM32调试】寄存器调试不良问题记录持续版 2024.3-至今 1.spi通信问题 现象说明&#xff1a; mcu与afe芯片为spi通…

为什么使用AI 在游戏中不犯法

使用AI在游戏中本身并不违法&#xff0c;甚至在很多情况下&#xff0c;游戏公司自己也会在游戏中集成AI来提高游戏体验&#xff0c;例如通过AI驱动的非玩家角色&#xff08;NPC&#xff09;来增加游戏的互动性和挑战性。然而&#xff0c;使用AI是否违法取决于AI的使用方式和目的…

设计一个游戏的基本博弈框架

设计一个游戏的基本博弈框架&#xff0c;玩家通过操作改变某个数值&#xff0c;这个数值的变动会引发一系列实时变化&#xff0c;并且当这些数值累计到特定阈值时&#xff0c;会导致游戏中出现其他变化&#xff0c;可以分为以下几个步骤&#xff1a; 1. 确定游戏类型和主题 首…

邮件地址采集软件有哪些-邮箱地址采集软件

邮件地址采集软件是帮助用户收集、管理和使用邮件地址的工具&#xff0c;它们在商业营销、市场调研、网络爬虫等领域有着广泛的应用。以下是一些常见的邮件地址采集软件&#xff1a; 易邮件地址搜索大师&#xff1a;易邮件地址搜索大师是一款搜索邮件地址和手机号码的软件&…

一篇文章拿下Redis 通用命令

文章目录 Redis数据结构介绍Redis 通用命令命令演示KEYSDELEXISTSEXPIRE RedisTemplate 中的通用命令 本篇文章介绍 Redis 的通用命令, 通用命令在 Redis 的所有数据类型下都使用, 学好通用命令可以让我们更好的使用 Redis. Redis数据结构介绍 Redis 是一个key-value的数据库&…

如何进行免杀

0x03 免杀思路总结 环境准备&#xff1a; 火绒&#xff08;静态&#xff09;、360、windowsdef&#xff08;动态&#xff09; 免杀的最基本思路就是去除其特征&#xff0c;这个特征有可能是特征码&#xff0c;也有可能是行为特征&#xff0c;只要在不修改其 原有功能的情况下…

基于C#开发web网页管理系统模板流程-登录界面

前言&#xff0c;首先介绍一下本项目将要实现的功能 &#xff08;一&#xff09;登录界面 实现一个不算特别美观的登录窗口&#xff0c;当然这一步跟开发者本身的设计美学相关&#xff0c;像蒟蒻博主就没啥艺术细胞&#xff0c;勉强能用能看就行…… &#xff08;二&#xff09…