AI模型部署实战:利用CV-CUDA加速视觉模型部署流程

news2024/10/7 8:25:51

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

CV-CUDA简介

随着深度学习技术在计算机视觉领域的发展,越来越多的AI算法模型被用于目标检测、图像分割、图像生成等任务中,如何高效地在云端或者边缘设备上部署这些模型是工程师迫切需要解决的问题。一个完整的AI模型部署流程一般分为三个阶段:预处理、模型推理、后处理,一般情况下会把模型推理放在GPU或者专用的硬件上进行处理,预处理和后处理则是放在CPU上。对于一个计算机视觉任务来说,预处理和后处理操作往往会消耗较多的CPU资源且非常耗时,这点在嵌入式平台上尤其明显,如果可以将预处理和后处理的这些操作放到GPU上去实现将会极大地提升整个流程的执行效率。

CV-CUDA是由英伟达和字节跳动联合开发的一个开源库,该库提供了一组专门的GPU运算符用于加速图像处理和计算机视觉算法,以实现高效的预处理和后处理过程,从而显著提高视觉AI任务的整体吞吐量。CV-CUDA库的主要特性包括:

  • 一套统一、专业的高性能计算机视觉和图像处理运算符
  • 支持C/C++Python3种编程语言的API
  • 支持批处理
  • PyTorchTensorFlow提供零拷贝接口
  • 提供端到端的计算机视觉应用样例

代码仓库地址:https://github.com/CVCUDA/CV-CUDA

在线文档地址:https://cvcuda.github.io/

本文将以部署YOLOv6目标检测模型为例介绍CV-CUDA在计算机视觉任务中的应用,代码获取方式见文末

CV-CUDA的具体应用

OpenCV图像预处理

我之前写了一篇介绍如何用TensorRT部署YOLOv6的文章:如何用TensorRT部署YOLOv6。在这篇文章中,图像的预处理都是通过调用OpenCV的函数在CPU上实现的,在介绍用CV-CUDA做图像预处理之前,让我们先来回顾一下图像预处理需要做的操作。

如上图所示,一般计算机视觉任务中的图像预处理包含以下操作:

  • 色域变换:读取图片后,一般需要做色域变换,比如用OpenCV读取的图片格式为BGR,但是模型需要的格式为RGBOpenCV中做色域变换的函数为cvtColor
  • 尺寸变换:原始图像的尺寸一般都不与模型要求的输入尺寸不一致,所以需要做尺寸变换,OpenCV中做尺寸变换的函数为resize
  • 归一化:在训练模型的时候需要的是浮点型数据,并且要对图像像素值除以255进行归一化,OpenCV中可以调用函数convertTo实现。
  • 数据通道顺序变换:原始图像的数据通道为HWC,但是一般模型要求的数据通道顺序为CHW,所以要对数据通道顺序进行重排。

CV-CUDA使用方法

CV-CUDA目前最新版本为v0.3.0,官方要求在如下软件环境中运行:

  • Ubuntu >= 20.04
  • CUDA driver >= 11.7 (实测CUDA 11.6也可以)

首先从CV-CUDA的GitHub仓库中下载下面两个包

  • nvcv-dev-0.3.0_beta-cuda11-x86_64-linux.tar.xz
  • nvcv-lib-0.3.0_beta-cuda11-x86_64-linux.tar.xz

然后用下面的命令进行解压:

tar -xvf nvcv-dev-0.3.0_beta-cuda11-x86_64-linux.tar.xz
tar -xvf nvcv-lib-0.3.0_beta-cuda11-x86_64-linux.tar.xz

解压后会在opt/nvidia/cvcuda0目录下生成CV-CUDA的头文件和库文件。

CV-CUDA的使用方法可以参考GitHub仓库中samples/classification目录下的样例。在CV-CUDA中,GPU上的数据都用nvcv::Tensor来表示,图像预处理操作需要用到两个Tensor:原始输入图像Tensor和模型输入数据Tensor。这两个Tensor可以根据原始输入图像的尺寸和模型输入尺寸预先构建好:

// Allocating memory for input image batch
nvcv::TensorDataStridedCuda::Buffer inBuf;
const int input_channels = input_image.channels();
const int input_width = input_image.cols;
const int input_height = input_image.rows;
inBuf.strides[3] = sizeof(uint8_t);
inBuf.strides[2] = input_channels * inBuf.strides[3];
inBuf.strides[1] = input_width * inBuf.strides[2];
inBuf.strides[0] = input_height * inBuf.strides[1];
cudaMalloc(&inBuf.basePtr, 1 * inBuf.strides[0]);
nvcv::Tensor::Requirements inReqs = nvcv::Tensor::CalcRequirements(
    1, {input_width, input_height}, nvcv::FMT_BGR8);

nvcv::TensorDataStridedCuda inData(
    nvcv::TensorShape{inReqs.shape, inReqs.rank, inReqs.layout},
    nvcv::DataType{inReqs.dtype}, inBuf);
nvcv::TensorWrapData input_image_tensor(inData);

// Allocate input layer buffer based on input layer dimensions and batch size
// Calculates the resource requirements needed to create a tensor with given
// shape
nvcv::Tensor::Requirements reqsInputLayer = nvcv::Tensor::CalcRequirements(
    1, {model_width_, model_height_}, nvcv::FMT_RGBf32p);
// Calculates the total buffer size needed based on the requirements
int64_t inputLayerSize = nvcv::CalcTotalSizeBytes(
    nvcv::Requirements{reqsInputLayer.mem}.cudaMem());
nvcv::TensorDataStridedCuda::Buffer bufInputLayer;
std::copy(reqsInputLayer.strides,
        reqsInputLayer.strides + NVCV_TENSOR_MAX_RANK,
        bufInputLayer.strides);
// Allocate buffer size needed for the tensor
cudaMalloc(&bufInputLayer.basePtr, inputLayerSize);
// Wrap the tensor as a CVCUDA tensor
nvcv::TensorDataStridedCuda inputLayerTensorData(
    nvcv::TensorShape{reqsInputLayer.shape, reqsInputLayer.rank,
                    reqsInputLayer.layout},
    nvcv::DataType{reqsInputLayer.dtype}, bufInputLayer);
nvcv::TensorWrapData model_input_tensor(inputLayerTensorData);

构建好原始输入图像的Tensor后,先把图像数据拷贝到Tensor中,

// copy image data to tensor
  auto input_image_data =
      input_image_tensor.exportData<nvcv::TensorDataStridedCuda>();
  cudaMemcpy(input_image_data->basePtr(), input_image.data,
             input_image_data->stride(0), cudaMemcpyHostToDevice);

然后就可以调用CV-CUDA中的算子对数据进行处理了。

下面以尺寸变换为例介绍CV-CUDA中算子的使用方法。CV-CUDA中尺寸变换对应的算子类为cvcuda::Resize,在调用算子之前需要为其构建一个Tensor保存算子输出的数据:

nvcv::Tensor resizedTensor(batch_size, {width, height}, nvcv::FMT_BGR8);

算子调用的方式非常简单,只需要两行代码:

cvcuda::Resize resizeOp;
resizeOp(stream_, input_image_tensor, resizedTensor,NVCV_INTERP_LINEAR);

可以看到,上面两个的代码只做了两件事:创建cvcuda::Resize对象resizeOp、调用()操作符。具体怎么实现的呢?有兴趣的话看看源码分析一下,我这里就不贴代码了。主要思想就是上层cvcuda::Resize类在构造函数中创建底层CUDA算子对象,然后在()操作符重载函数中调用CUDA算子的执行函数去执行算子的具体操作,其它算子都是这样的设计方式,所以用CV-CUDA做图像预处理其实非常简单,需要用到的算子如下:

  • 色域变换:cvcuda::CvtColor
  • 尺寸变换:cvcuda::Resize
  • 归一化:cvcuda::ConvertTo
  • 数据通道顺序变换:cvcuda::Reformat

整个预处理流程的代码如下:

const int batch_size = 1;

// Resize to the dimensions of input layer of network
nvcv::Tensor resizedTensor(batch_size, {width, height}, nvcv::FMT_BGR8);
cvcuda::Resize resizeOp;
resizeOp(stream, input_image_tensor), resizedTensor,
        NVCV_INTERP_LINEAR);

// convert BGR to RGB
nvcv::Tensor rgbTensor(batch_size, {width, height}, nvcv::FMT_RGB8);
cvcuda::CvtColor cvtColorOp;
cvtColorOp(stream, resizedTensor, rgbTensor, NVCV_COLOR_BGR2RGB);

// Convert to data format expected by network (F32). Apply scale 1/255.
nvcv::Tensor floatTensor(batch_size, {width, height}, nvcv::FMT_RGBf32);
cvcuda::ConvertTo convertOp;
convertOp(stream, rgbTensor, floatTensor, 1.0 / 255.0, 0.0);

// Convert the data layout from HWC to CHW
cvcuda::Reformat reformatOp;
reformatOp(stream, floatTensor, model_input_tensor);

以上就是用CV-CUDA做图像预处理的全部代码,是不是非常简单?

总结

本文以YOLOv6目标检测中的图像预处理为例介绍了CV-CUDA在计算机视觉任务中的应用,还有很多算子本文没有做介绍,感兴趣的读者可以直接查看CV-CUDA的文档和代码学习使用。目前CV-CUDA只提供x86版本的库,如果能提供arm版本的就更好了,毕竟在嵌入式平台上才是刚需(在嵌入式平台上用源码进行编译我还没试过,有兴趣的读者可以试一下)。

关注微信公众号【DeepDriving】,后台回复关键字【YOLOv6】可获取本文代码,YOLOv5/YOLOv6/YOLOv7均可部署

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

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

相关文章

Android 13(T) - 智能指针

Android有一套自己的智能指针管理办法&#xff0c;并且将其运用在源码的各个角落&#xff0c;所以学习Media框架之前&#xff0c;我们有必要先了解下Android智能指针。 本节代码源自于Android 13(T)&#xff0c;参考 (aospxref.com) 1 概述 与智能指针相关的总共有5个类&#…

某小厂面试加答案(6.15)

看 Java 面试题就去 www.javacn.site 磊哥新推出《企业面经和答案》栏目&#xff0c;最近会持续更新&#xff0c;欢迎大家订阅此账号查看&#xff0c;或访问 www.javacn.site 查看。 面经来源于牛客&#xff0c;如下图所示&#xff1a; https://www.nowcoder.com/feed/main/det…

OpenAI的创始人World Coin项目介绍

&#x1f3af; 在一个崇高的目标支持下&#xff0c;不停地工作&#xff0c;即使慢&#xff0c;也一定会获得成功。—— 爱因斯坦 如果你对项目感兴趣请联系v&#xff1a;weixin605405145 一、项目速览 项目背景 Worldcoin由OpenAI的创始人Sam Altman于2019年创立&#xff0c;就…

【C++】的继承

继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构…

010、体系架构之TiFlash

TiFlash TiFlash 功能架构异步复制一致性读取场景选择是选择TiKV还是TiFLash TiFlash 功能 异步复制一致性读取(写虽然是异步&#xff0c;但读可以做到一致性)引擎智能选择计算加速 架构 TiFLASH 也是通过raft 算法进行同步&#xff0c;但它不怎么消耗资源&#xff0c;因为它…

ProGuard 进阶系列(二)配置解析

书接上文&#xff0c;从开源库中把代码下载到本地后&#xff0c;就可以在 IDE 中进行运行了。从 main 方法入手&#xff0c;可以看到 ProGuard 执行的第一步就是去解析参数。本文的内容主要分析源码中我们配置的规则解析的实现。 在上一篇文章末尾&#xff0c;在 IDE 中&#x…

Vue Router4

后端路由 客户端请求不同的URL服务器匹配URL并给一个Controller处理Controller处理完返回渲染好的HTML页面或数据给前端 优点&#xff1a; 不需要单独加载js和css&#xff0c;直角交给浏览器展示&#xff0c;有利于SEO优化 缺点&#xff1a; 页面有后端人员编写或由前端人员…

告别里程焦虑:深蓝S7超级增程打造超长续航

提起新能源汽车&#xff0c;估计许多人第一时间都会想要查看它的续航里程。 虽然如今的新能源汽车在续航里程上较过去已经有了很大改进&#xff0c;但是稀缺的充电桩和漫长的充电时间&#xff0c;仍然无法让需要长途出行的用户摆脱里程焦虑。 那么问题就来了&#xff1a;有没有…

基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码+文档

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码文档 视频演示 https://www.bilibili.com/video/BV1HW4y197Fe/ 系统介绍 摘 要 …

dubbo源码之-ExtensionInjector

dubbo源码之-ExtensionInjector 概述源码入口Extension 是如何获取到&#xff1f;SpiExtensionInjector 概述 其实ExtensionInjector 非常简单&#xff0c; 我们知道dubbo有ioc注入的功能&#xff0c; 是靠的set方法注入&#xff0c;对应的底层源码主要是ExtensionInjector 如…

MySQL数据库语言一、DDL

&#x1f618;作者简介&#xff1a;正在努力的99年打工人。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;动…

华为OD机试真题B卷 JavaScript 实现【分班】,附详细解题思路

一、题目描述 幼儿园两个班的小朋友在排队时混在了一起&#xff0c;每位小朋友都知道自己是否与前面一位小朋友是否同班&#xff0c;请你帮忙把同班的小朋友找出来。 小朋友的编号为整数&#xff0c;与前一位小朋友同班用Y表示&#xff0c;不同班用N表示。 二、输入描述 输…

uniapp/手机APP使用支付宝支付(服务端)

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

chatgpt赋能python:Python接口应用于SEO的指南

Python接口应用于SEO的指南 Python成为了web开发中最流行的语言之一&#xff0c;而且尤其在SEO领域中被广泛应用。一些Python库和框架可帮助SEO团队实现其目标&#xff0c;如排名跟踪&#xff0c;爬取数据&#xff0c;进行网站分析&#xff0c;等等。在本文中&#xff0c;我们…

基于Hexo和Butterfly创建个人技术博客,(9) 优化butterfly主题配置文章版本

Butterfly官方网站&#xff0c;请 点击进入 本章目标&#xff1a; 掌握butterfly主题对文章的配置&#xff0c;熟悉并可按需配置到个人的博客站点中&#xff0c;本章内容是一个必会章节&#xff0c;不仅包括文章的UI美化、SEO相关配置还包括其它增加的功能&#xff0c;内容不多…

英语不好能不能学编程?

入门教程、案例源码、学习资料、读者群 请访问&#xff1a; python666.cn 大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 常有人问我&#xff1a;我英语不好&#xff0c;想学编程行不行&#xff1f; 这个问题需要分情况讨论。 1. 可以学 如果你因为担心自己英语不…

chatgpt赋能python:Python怎么用?Python编程的入门指南

Python怎么用&#xff1f;Python编程的入门指南 Python是一种流行的高级编程语言&#xff0c;它被广泛应用于数据分析、机器学习、Web开发、自动化测试等领域。Python语言非常容易学习和使用&#xff0c;因此非常适合初学者和有经验的开发人员。在这篇文章中&#xff0c;我们将…

手把手教你在CentOS7.9上使用docker 安装MySQL5.7

前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文主要讲解如何用docker在centos7.9系统上安装MySQL5.7&#xff0c;以及如何设置MySQL的远程登录。 文章收录到【容器管理】和【数据库入门到精通专栏】&#xff0c;此专栏是沐风晓月对linux云计算架构…

chatgpt赋能python:Python怎么清除动点轨迹?

Python怎么清除动点轨迹&#xff1f; 引言 在数据科学和可视化的领域中&#xff0c;动点轨迹是很有用的工具。动点轨迹可以轻松地显示数据点的时间序列&#xff0c;这可以帮助分析者发现有关数据集的有用信息。然而&#xff0c;当轨迹过于密集和复杂时&#xff0c;这种可视化…

Spring Cloud Alibaba - Sentinel源码分析(一)

目录 一、Sentinel核心源码分析 1、Sentinel核心概念 1.1、Node之间的关系 2、Sentinel源码入口 2.0、Sentinel源码启动 2.1、SlotChain解析 2.2、NodeSelectorSlot解析 2.3、ClusterBuilderSlot解析 2.4、StatisticSlot解析 2.5、FlowSlot解析 2.6、DegradeSlot解析…