本文首发于大众号【DeepDriving】,欢迎重视。
CV-CUDA简介
随着深度学习技能在计算机视觉领域的发展,越来越多的AI
算法模型被用于方针检测、图画切割、图画生成等使命中,怎么高效地在云端或许边际设备上布置这些模型是工程师迫切需求解决的问题。一个完好的AI
模型布置流程一般分为三个阶段:预处理、模型推理、后处理,一般情况下会把模型推理放在GPU
或许专用的硬件上进行处理,预处理和后处理则是放在CPU
上。对于一个计算机视觉使命来说,预处理和后处理操作往往会耗费较多的CPU
资源且十分耗时,这点在嵌入式平台上特别明显,如果能够将预处理和后处理的这些操作放到GPU
上去完成将会极大地进步整个流程的履行功率。
CV-CUDA
是由英伟达和字节跳动联合开发的一个开源库,该库供给了一组专门的GPU
运算符用于加快图画处理和计算机视觉算法,以完成高效的预处理和后处理过程,从而显著进步视觉AI
使命的全体吞吐量。CV-CUDA
库的主要特性包含:
- 一套统一、专业的高性能计算机视觉和图画处理运算符
- 支撑
C/C++
和Python
这3
种编程言语的API
- 支撑批处理
- 为
PyTorch
和TensorFlow
供给零复制接口 - 供给端到端的计算机视觉使用样例
代码库房地址:github.com/CVCUDA/CV-C…
在线文档地址:cvcuda.github.io/
本文将以布置YOLOv6
方针检测模型为例介绍CV-CUDA
在计算机视觉使命中的使用,代码获取方式见文末。
CV-CUDA的详细使用
OpenCV图画预处理
我之前写了一篇介绍怎么用TensorRT
布置YOLOv6
的文章:怎么用TensorRT布置YOLOv6。在这篇文章中,图画的预处理都是经过调用OpenCV
的函数在CPU
上完成的,在介绍用CV-CUDA
做图画预处理之前,让我们先来回忆一下图画预处理需求做的操作。
如上图所示,一般计算机视觉使命中的图画预处理包含以下操作:
- 色域改换:读取图片后,一般需求做色域改换,比如用
OpenCV
读取的图片格式为BGR
,但是模型需求的格式为RGB
,OpenCV
中做色域改换的函数为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均可布置。