openVX加速-结合AI推理引擎代码示例
假设我们使用 OpenVX 进行图像的 Resize 和饱和度矫正,然后将处理后的图像直接传递给 GPU 上的 AI 推理引擎。
以下是两段 C++ 示例代码,展示如何在 GPU 上实现这些操作,我给了前后两个代码示例,分别是在CPU和GPU进行IO的和全部都在GPU操作的。注意代码是伪代码,大家最好不要直接照搬照抄,目的是为了理解内在逻辑。
这个代码假设已经有一个 OpenVX 环境和一个 AI 推理引擎(如 OpenCV DNN 模块,或其他 GPU 加速的推理引擎):
1. 安装依赖
确保系统安装了 OpenVX 库和 OpenCV 库。
2. C++ 示例代码1-存在数据拷贝
#include <VX/vx.h>
#include <VX/vxu.h>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>// 自定义饱和度校正核
vx_node vxColorSaturationNode(vx_graph graph, vx_image src, vx_image dst, float saturationScale) {vx_context context = vxGetContext((vx_reference)graph);vx_float32 scale = saturationScale;vx_scalar s_scale = vxCreateScalar(context, VX_TYPE_FLOAT32, &scale);vx_node node = vxCreateGenericNode(graph, vxGetKernelByEnum(context, VX_KERNEL_COLOR_CONVERT));vxSetParameterByIndex(node, 0, (vx_reference)src);vxSetParameterByIndex(node, 1, (vx_reference)dst);vxSetParameterByIndex(node, 2, (vx_reference)s_scale);return node;
}int main() {// 初始化 OpenVX contextvx_context context = vxCreateContext();// 创建 OpenCV Mat 并加载图像cv::Mat inputImage = cv::imread("input.jpg");// 定义图像尺寸和参数vx_uint32 width = inputImage.cols;vx_uint32 height = inputImage.rows;vx_uint32 newWidth = 224; // 目标尺寸vx_uint32 newHeight = 224;// 创建 OpenVX 图像vx_image vxInputImage = vxCreateImage(context, width, height, VX_DF_IMAGE_RGB);vx_image vxResizedImage = vxCreateImage(context, newWidth, newHeight, VX_DF_IMAGE_RGB);vx_image vxOutputImage = vxCreateImage(context, newWidth, newHeight, VX_DF_IMAGE_RGB);// 将 OpenCV Mat 数据复制到 OpenVX 图像vx_rectangle_t rect = {0, 0, width, height};vx_imagepatch_addressing_t addr = {width * 3, 3, width, height};vx_uint8 *base = inputImage.data;vxCopyImagePatch(vxInputImage, &rect, 0, &addr, base, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);// 创建 OpenVX 图形vx_graph graph = vxCreateGraph(context);// 添加图像 resize 节点vx_node resizeNode = vxResizeImageNode(graph, vxInputImage, vxResizedImage, VX_INTERPOLATION_BILINEAR);// 添加饱和度矫正节点vx_node saturationNode = vxColorSaturationNode(graph, vxResizedImage, vxOutputImage, 1.5f);// 验证图形vx_status status = vxVerifyGraph(graph);if (status == VX_SUCCESS) {// 处理图形vxProcessGraph(graph);// 从 OpenVX 图像复制到 OpenCV Matvx_rectangle_t rectOutput = {0, 0, newWidth, newHeight};vx_imagepatch_addressing_t addrOutput = {newWidth * 3, 3, newWidth, newHeight};vx_uint8 *outputBase = new vx_uint8[newWidth * newHeight * 3];vxCopyImagePatch(vxOutputImage, &rectOutput, 0, &addrOutput, outputBase, VX_READ_ONLY, VX_MEMORY_TYPE_HOST);cv::Mat outputImage(newHeight, newWidth, CV_8UC3, outputBase);// 使用 OpenCV DNN 或其他推理引擎进行 AI 推理cv::dnn::Net net = cv::dnn::readNet("model.onnx");cv::Mat blob = cv::dnn::blobFromImage(outputImage, 1.0, cv::Size(224, 224), cv::Scalar(104, 117, 123));net.setInput(blob);cv::Mat output = net.forward();// 处理推理结果// ... (处理 AI 推理结果的代码)delete[] outputBase;} else {std::cerr << "Graph verification failed!" << std::endl;}// 释放资源vxReleaseImage(&vxInputImage);vxReleaseImage(&vxResizedImage);vxReleaseImage(&vxOutputImage);vxReleaseGraph(&graph);vxReleaseContext(&context);return 0;
}
3. 上面2的代码说明
-
初始化 OpenVX:首先创建一个 OpenVX context,然后加载输入图像并创建对应的 OpenVX 图像对象。
-
图像预处理:
- Resize:使用
vxResizeImageNode
节点将图像缩放到指定尺寸。 - 饱和度矫正:自定义一个节点
vxColorSaturationNode
,调整图像的饱和度。
- Resize:使用
-
AI 推理:
- 使用 OpenCV 的 DNN 模块读取预训练模型并进行推理。
-
数据处理关键分析:
这段代码片段涉及到从 OpenVX 图像 (vx_image
) 转换为 OpenCV 的 cv::Mat
,这个过程是将图像数据从 OpenVX 的 GPU 内存空间复制到 CPU 的内存空间。
vx_uint8 *outputBase = new vx_uint8[newWidth * newHeight * 3];
vxCopyImagePatch(vxOutputImage, &rectOutput, 0, &addrOutput, outputBase, VX_READ_ONLY, VX_MEMORY_TYPE_HOST);
cv::Mat outputImage(newHeight, newWidth, CV_8UC3, outputBase);
vx_uint8:
-
vx_uint8 *outputBase = new vx_uint8[newWidth * newHeight * 3];
这句话创建的outputBase
指针指向的是在 CPU 内存中分配的空间。 -
new
关键字:- 在 C++ 中,
new
关键字用于动态分配内存,默认情况下,分配的内存是在 CPU 的主内存中,也就是主机内存(Host Memory)。
- 在 C++ 中,
-
outputBase
指向的内存:outputBase
指针指向的是通过new
在 CPU 内存中分配的字节数组。这个数组的大小是newWidth * newHeight * 3
字节,表示分配了一个足够存储图像数据的空间。
vxCopyImagePatch:
- 这个函数的作用是将 OpenVX 图像(可能在 GPU 内存中)的数据复制到
outputBase
指向的 CPU 内存中。VX_MEMORY_TYPE_HOST
指定了内存类型为主机(即 CPU)内存。 - 因此,这个操作实际上将图像数据从 GPU 内存(如果
vxOutputImage
存储在 GPU 上)复制到 CPU 内存。这是一个 I/O 操作,涉及 CPU 和 GPU 之间的数据传输。
cv::Mat:
outputBase
是一个指向 CPU 内存的指针,cv::Mat
的构造函数使用这个指针创建一个 OpenCV 的矩阵(图像)。outputImage
是一个指向 CPU 内存中图像数据的 OpenCV 对象。
在这个过程中,图像数据从 GPU 内存迁移到 CPU 内存。这是因为 OpenVX 的 vxCopyImagePatch
函数在复制时明确指定了目标内存类型为 VX_MEMORY_TYPE_HOST
,这意味着要将数据从 GPU 拷贝到 CPU。所以,这个过程不完全在 GPU 内部,而是涉及到数据从 GPU 到 CPU 的迁移。
4. 如何避免数据迁移:
如果想避免这种数据迁移,可以尝试在 GPU 内部完成所有处理。具体做法包括:
- 直接在 GPU 上完成所有前处理和后处理。
- 在推理引擎也支持 GPU 的情况下,确保推理引擎能够直接使用 GPU 内存中的数据,而不需要将数据复制到 CPU 内存。
优化方案:
如果推理引擎也在 GPU 上运行,并且可以接受 OpenVX 输出的 GPU 内存中的数据,可以避免调用 vxCopyImagePatch
,直接传递 vxOutputImage
到推理引擎,这样数据就不会从 GPU 迁移到 CPU,避免了 I/O 开销。
这能极大提升处理效率,尤其在实时视频处理场景中。
GPU 内存与 CPU 内存:
如果我们希望在 GPU 上分配内存,通常需要使用特定的 API 来分配 GPU 内存,比如 CUDA 中的 cudaMalloc
,或 OpenCL 中的 clCreateBuffer
。而 new
分配的内存默认是在 CPU 上。
5. C++ 示例代码-避免数据迁移
#include <VX/vx.h>
#include <VX/vxu.h>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>// 自定义饱和度校正核(在 GPU 上操作)
vx_node vxColorSaturationNode(vx_graph graph, vx_image src, vx_image dst, float saturationScale) {vx_context context = vxGetContext((vx_reference)graph);vx_float32 scale = saturationScale;vx_scalar s_scale = vxCreateScalar(context, VX_TYPE_FLOAT32, &scale);vx_node node = vxCreateGenericNode(graph, vxGetKernelByEnum(context, VX_KERNEL_COLOR_CONVERT));vxSetParameterByIndex(node, 0, (vx_reference)src);vxSetParameterByIndex(node, 1, (vx_reference)dst);vxSetParameterByIndex(node, 2, (vx_reference)s_scale);return node;
}int main() {// 初始化 OpenVX contextvx_context context = vxCreateContext();// 加载图像数据到 OpenCV Matcv::Mat inputImage = cv::imread("input.jpg");vx_uint32 width = inputImage.cols;vx_uint32 height = inputImage.rows;vx_uint32 newWidth = 224; // 目标尺寸vx_uint32 newHeight = 224;// 创建 OpenVX 图像,使用 GPU 内存(VX_MEMORY_TYPE_OPENCL 可选,根据实现库)vx_image vxInputImage = vxCreateImage(context, width, height, VX_DF_IMAGE_RGB);vx_image vxResizedImage = vxCreateImage(context, newWidth, newHeight, VX_DF_IMAGE_RGB);vx_image vxOutputImage = vxCreateImage(context, newWidth, newHeight, VX_DF_IMAGE_RGB);// 将 OpenCV Mat 数据复制到 OpenVX 图像(假设是 CPU -> GPU 拷贝)vx_rectangle_t rect = {0, 0, width, height};vx_imagepatch_addressing_t addr = {width * 3, 3, width, height};vx_uint8 *base = inputImage.data;vxCopyImagePatch(vxInputImage, &rect, 0, &addr, base, VX_WRITE_ONLY, VX_MEMORY_TYPE_HOST);// 创建 OpenVX 图形vx_graph graph = vxCreateGraph(context);// 添加图像 resize 节点vx_node resizeNode = vxResizeImageNode(graph, vxInputImage, vxResizedImage, VX_INTERPOLATION_BILINEAR);// 添加饱和度矫正节点vx_node saturationNode = vxColorSaturationNode(graph, vxResizedImage, vxOutputImage, 1.5f);// 验证图形vx_status status = vxVerifyGraph(graph);if (status == VX_SUCCESS) {// 处理图形(在 GPU 上处理)vxProcessGraph(graph);// 创建 OpenCV DNN 模型并设置为 GPU 模式cv::dnn::Net net = cv::dnn::readNet("model.onnx");net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); // 选择 CUDA 作为后端net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); // 在 GPU 上运行// 将 OpenVX 结果直接转换为 OpenCV GPU Mat(不回到 CPU)cv::cuda::GpuMat gpuOutputImage(newHeight, newWidth, CV_8UC3, vxAccessImagePatch(vxOutputImage, &rect, 0, &addr, (void**)&base, VX_READ_ONLY));// 进行 AI 推理cv::Mat blob = cv::dnn::blobFromImage(gpuOutputImage, 1.0, cv::Size(224, 224), cv::Scalar(104, 117, 123), false, false, CV_32F);net.setInput(blob);cv::Mat output = net.forward();// 处理推理结果// ... (处理 AI 推理结果的代码)// 释放 OpenVX 结果图像vxCommitImagePatch(vxOutputImage, &rect, 0, &addr, base);} else {std::cerr << "Graph verification failed!" << std::endl;}// 释放资源vxReleaseImage(&vxInputImage);vxReleaseImage(&vxResizedImage);vxReleaseImage(&vxOutputImage);vxReleaseGraph(&graph);vxReleaseContext(&context);return 0;
}
6. 上面5的代码说明
-
图像处理:
- 使用 OpenVX 在 GPU 上进行图像处理,包括 Resize 和饱和度矫正。
- 数据在 GPU 上被处理,并且不会传回 CPU。
-
数据传递:
- 使用
cv::cuda::GpuMat
直接从 OpenVX 结果图像读取数据(在 GPU 上),并将其传递给 OpenCV 的 DNN 模块进行推理。
- 使用
-
推理:
- 在 GPU 上进行推理(使用 CUDA),所有操作在 GPU 内部完成,避免了不必要的 CPU 和 GPU 之间的数据传输。
-
优化:
- 整个数据流从图像加载、预处理、推理到最终结果,完全在 GPU 内部完成,最大限度地减少了 I/O 开销,提高了处理效率。