专栏名称: 极市平台
极市平台是由深圳极视角推出的专业的视觉算法开发与分发平台,为视觉开发者提供多领域实景训练数据库等开发工具和规模化销售渠道。本公众号将会分享视觉相关的技术资讯,行业动态,在线分享信息,线下活动等。 网站: http://cvmart.net/
目录
相关文章推荐
数据派THU  ·  多智能体协作机制:大语言模型综述 ·  昨天  
江西工信  ·  赣州市南康区:企业数字化转型按下加速键 ·  15 小时前  
江西工信  ·  赣州市南康区:企业数字化转型按下加速键 ·  15 小时前  
保险一哥  ·  135家非上市险企偿付能力:7家得AAA,至 ... ·  3 天前  
天池大数据科研平台  ·  一文漫谈DeepSeek及其背后的核心技术 ·  昨天  
大数据文摘  ·  为什么中国只有一个 DeepSeek? ·  4 天前  
51好读  ›  专栏  ›  极市平台

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

极市平台  · 公众号  ·  · 2024-05-18 22:00

正文

↑ 点击 蓝字 关注极市平台
作者丨 一天到晚潜水的鱼
来源丨DeepDriving
编辑丨极市平台

极市导读

本文介绍了OpenCV CUDA模块中图像处理接口的基本使用方法,用这些CUDA接口基本上可以满足视觉AI模型的部署需求,在嵌入式平台上可以有效减少CPU资源的消耗。 >> 加入极市CV技术交流群,走在计算机视觉的最前沿

一. 前言

我在之前的文章《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 上调用的接口都是使用该数据结构作为输入或输出的。 GpuMat Mat 的使用方式非常相似,封装的接口基本上是一致的,详细内容可以参考GpuMat的文档。

CPU GPU 之间的数据传输

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

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

下面是一个简单的示例:


#include 
#include 
 
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<: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<: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;
}

可以看到, CPU GPU 版本调用的函数名是一样的,只不过 GPU 版本的多了一个 cuda 命名空间。所以使用 OpenCV CUDA 模块基本上是没有什么难度的,只需要查一下之前调用的 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<: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;
}

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







请到「今天看啥」查看全文