本文首发于大众号【DeepDriving】,欢迎重视。

CV-CUDA简介

随着深度学习技能在计算机视觉领域的发展,越来越多的AI算法模型被用于方针检测、图画切割、图画生成等使命中,怎么高效地在云端或许边际设备上布置这些模型是工程师迫切需求解决的问题。一个完好的AI模型布置流程一般分为三个阶段:预处理、模型推理、后处理,一般情况下会把模型推理放在GPU或许专用的硬件上进行处理,预处理和后处理则是放在CPU上。对于一个计算机视觉使命来说,预处理和后处理操作往往会耗费较多的CPU资源且十分耗时,这点在嵌入式平台上特别明显,如果能够将预处理和后处理的这些操作放到GPU上去完成将会极大地进步整个流程的履行功率。

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

CV-CUDA是由英伟达和字节跳动联合开发的一个开源库,该库供给了一组专门的GPU运算符用于加快图画处理和计算机视觉算法,以完成高效的预处理和后处理过程,从而显著进步视觉AI使命的全体吞吐量。CV-CUDA库的主要特性包含:

  • 一套统一、专业的高性能计算机视觉和图画处理运算符
  • 支撑C/C++Python3种编程言语的API
  • 支撑批处理
  • PyTorchTensorFlow供给零复制接口
  • 供给端到端的计算机视觉使用样例

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

代码库房地址:github.com/CVCUDA/CV-C…

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

本文将以布置YOLOv6方针检测模型为例介绍CV-CUDA在计算机视觉使命中的使用,代码获取方式见文末

CV-CUDA的详细使用

OpenCV图画预处理

我之前写了一篇介绍怎么用TensorRT布置YOLOv6的文章:怎么用TensorRT布置YOLOv6。在这篇文章中,图画的预处理都是经过调用OpenCV的函数在CPU上完成的,在介绍用CV-CUDA做图画预处理之前,让我们先来回忆一下图画预处理需求做的操作。

AI模型部署实战:利用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-CUDAGitHub库房中下载下面两个包

  • 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均可布置