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,一经查实,立即删除!

相关文章

大模型介绍

大模型通常指的是参数量超过亿级别&#xff0c;甚至千亿级别的深度学习模型。这类模型能够处理更加复杂的任务&#xff0c;并在各项基准测试中取得了优异的成绩。大模型在自然语言处理、计算机视觉、推荐系统等领域都取得了显著的成果。 大模型的主要优势在于其强大的表征能力&…

k8s的核心组件etcd功能详解【含etcd各类参数详细说明】

etcd 是 Kubernetes 中的一个关键组件&#xff0c;用于存储集群的配置信息、状态和元数据。它通常作为 Kubernetes 集群的数据存储后端&#xff0c;为其他组件提供可靠的分布式键值存储服务。下面我会详细介绍 etcd 的功能以及常见的参数&#xff0c;以及如何配置和使用 etcd。…

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;在主页中根据自己的…

基于OpenCV年龄与性别识别系统

深入解析基于OpenCV年龄与性别识别系统 在这篇博客中&#xff0c;我们将详细解析一个使用OpenCV进行年龄和性别识别的Python脚本。这个脚本展示了如何利用深度学习模型&#xff0c;从视频或图像中检测人脸并预测每个人脸的年龄和性别。 1. 导入必要的模块 import cv2 as cv …

ELK的详解

ELK是由Elasticsearch、Logstash和Kibana三个开源软件&#xff08;后来又新加了一个FileBeat&#xff09;组成的日志管理解决方案&#xff0c;这一组合在近年来得到了广泛的关注和应用。以下是对这三个组件的详细说明&#xff1a; Elasticsearch&#xff1a; Elasticsearch是…

nginx 负载均衡配置详解

基于 ${nginx_home}/conf/nginx.conf 文件配置实现&#xff0c;如下&#xff1a; http {# 定义server地址upstream server_group {server 192.168.xxx.1:8080;server 192.168.xxx.2:8080;server 192.168.xxx.3:8080;}server {listen 80;location / {root html;index …

python数据分析——时间序列

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

持续总结中!2024年面试必问 100 道 Java基础面试题(四十五)

上一篇地址&#xff1a;持续总结中&#xff01;2024年面试必问 100 道 Java基础面试题&#xff08;四十四&#xff09;-CSDN博客 八十九、在Java中&#xff0c;什么是线程局部变量&#xff08;ThreadLocal变量&#xff09;&#xff1f; 在Java中&#xff0c;ThreadLocal变量是…

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

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

Flutter 中的 CircleAvatar 小部件:全面指南

Flutter 中的 CircleAvatar 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;CircleAvatar 是一个用于显示头像的圆形控件&#xff0c;通常包含一个图标、图片或者一个简单的文本字符。它在设计上与 Material Design 指南中的头像规范相匹配&#xff0c;常用于展示用户信…

C# 常用汇总

时间处理 public static class DateTimeHelper{/// <summary>/// 获取当前时间戳&#xff08;Unix时间戳&#xff09; /// </summary>/// <returns></returns>public static long GetCurrentUnixTimestamp(){DateTimeOffset offset DateTimeOffset.…

Qt---文件系统

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

Java网络编程基础

Java网络编程基础主要涉及进程间通信、网络通信协议、IP地址和端口以及Java提供的网络应用编程接口等核心概念。 进程间通信是Java网络编程的基础。进程是运行中的程序&#xff0c;而进程间通信则是指不同进程之间进行数据交换和共享信息的过程。在Java中&#xff0c;进程间的…

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;三连支持一…

ddpm Denoising Diffusion Probabilistic Model 学习笔记

目录 Stable Diffusion 文章的贡献抽象出来就两个 潜空间上做扩散生成 ddpm(Denoising Diffusion Probabilistic Model)学习笔记 算法原理 unet预测噪声 unet推理过程 重参数化技巧 &#xff08;1&#xff09;利用前一时刻的 xt-1 得到任意时刻的噪声图片 xt&#xff…

LeetCode2215找出两数组的不同

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