22、OpenVX 高效数据输入输出详解

OpenVX 高效数据输入输出详解

1. 高效数据输入输出简介

在视觉处理中,大部分效率问题都集中在数据移动上。像加法、乘法这类数学运算的执行时间和功耗,与从外部 DRAM 中获取操作数所需的时间和功耗相比,简直微不足道。视觉处理所需处理的数据集通常非常大,无法全部存储在典型嵌入式设备的片上 SRAM 中,因此我们必须谨慎管理并尽量减少从外部 DRAM 到内部 SRAM 的数据传输。

这一原则尤其适用于向 OpenVX 输入数据的初始阶段。在实时高分辨率系统中,初始输入可能是高清或更大尺寸图像的视频流。如果每个大尺寸输入图像都必须从相机输入内存区域复制到 OpenVX 系统内存中,那么在处理开始之前就已经输了。幸运的是,OpenVX API 提供了从输入源向 OpenVX 处理引擎零拷贝导入图像数据的功能。

此外,OpenVX 对象具有“不透明”的特性,即程序员不知道数据存储的底层内存位置和组织方式,只能通过该对象的 API 来操作数据对象,而不能直接访问存储对象的内存。这使得实现可以进行优化,例如将数据对象保存在专用内存(如加速器硬件上),或者在不改变图形计算最终结果的情况下,将内存用于不同的用途。不过,在某些情况下,OpenVX 应用程序需要直接访问数据对象的部分内容,可能是为了执行 OpenVX API 未提供的特殊计算,这时就需要使用映射和取消映射功能。

2. 外部源的高效数据输入输出

2.1 相关函数介绍

在视觉处理中,大部分高容量数据位于输入侧,如高分辨率视频捕获,而视觉处理的输出数据量通常要小得多,例如分类标签或电机命令。但如果应用程序所需的输出是高分辨率带注释的视频,这些函数也可用于输出侧。这里以相机输入侧为例,相关原则和函数同样适用于输出数据。

OpenVX 提供了两个函数来实现从相机等捕获设备高效输入数据,以及向显示器等输出设备输出数据,分别是 vxCreateImageFromHandle vxSwapImageHandle

  • vxCreateImageFromHandle :该函数创建一个 OpenVX vx_image 数据结构,该结构引用相机的像素数据,但不进行复制。这使得下游的 OpenVX 处理可以直接从相机放置数据的位置获取像素数据,无需将整个图像从相机内存区域复制到 OpenVX 内存区域的中间“导入”步骤。
  • vxSwapImageHandle :该函数用于在两个相机捕获缓冲区之间切换,它接受一个 vx_image 对象和一组指向新像素数据的指针,将图像对象的底层数据指针替换为给定的指针,并返回指向先前图像数据的指针。

2.2 双缓冲机制

典型的相机将图像放置在内存中可配置位置和数据组织的区域,可能会使用多个内存区域(即“缓冲区”)并循环使用它们,依次将图像放入每个缓冲区,直到所有缓冲区都被填满,然后再从第一个缓冲区开始。最简单的形式是“双缓冲”,使用两个缓冲区,如缓冲区 A 和缓冲区 B。相机先将输入图像填充到缓冲区 A,然后再填充缓冲区 B。在填充缓冲区 B 时,缓冲区 A 可以由 OpenVX 等视觉系统进行处理,这样可以在捕获“下一个”图像的同时处理“当前”图像。

2.3 vxCreateImageFromHandle 函数使用示例

以下是使用 vxCreateImageFromHandle 函数创建 OpenVX vx_image 对象的示例代码:

const int NUM_PLANES = 1;
const int NUM_BUFFERS = 2;
void* cam_ptr[NUM_BUFFERS][NUM_PLANES];
vx_image cam_image[NUM_BUFFERS];

// 填充 cam_ptr 指针,需根据图像捕获系统文档确定位置
// ...

// 构建寻址结构
vx_imagepatch_addressing_t cam_addr[NUM_PLANES];
cam_addr[0].dim_x = w_in; cam_addr[0].dim_y = h_in;
cam_addr[0].stride_x = 1; cam_addr[0].stride_y = w_in;
cam_addr[0].scale_x = VX_SCALE_UNITY;
cam_addr[0].scale_y = VX_SCALE_UNITY;
cam_addr[0].step_x = 1; cam_addr[0].step_y = 1;

// 创建 vx_image 对象
for (i = 0; i < NUM_BUFFERS; i++) {
    cam_image[i] = vxCreateImageFromHandle(context, VX_DF_IMAGE_U8,
        cam_addr, cam_ptr[i], VX_MEMORY_TYPE_HOST);
}

2.4 简单处理图示例

下面是一个简单的处理图示例,该图捕获相机数据,对其进行处理并显示。图中有两个节点,一个用于缩放图像,另一个用于运行 Canny 边缘检测器。输入来自相机,输出显示在显示器上。

const vx_uint32 w_in = 1080, h_in = 1920;
const int scale = 4;
const int w = w_in/scale; // 缩放后图像宽度
const int h = h_in/scale; // 缩放后图像高度
vx_pixel_value_t hi_thresh, lo_thresh;
hi_thresh.U8 = (vx_uint8)120;
lo_thresh.U8 = (vx_uint8)15;
vx_context context = vxCreateContext();
vx_graph graph = vxCreateGraph(context);

// 创建图像
vx_image images[] = {
    // 0. 输入:
    vxCreateImageFromHandle(context, VX_DF_IMAGE_U8, cam_addr,
        cam_ptr[0], VX_MEMORY_TYPE_HOST),
    // 1. 缩放后的输入(中间结果):
    vxCreateVirtualImage(graph, w, h, VX_DF_IMAGE_U8),
    // 2. Canny 边缘图像(输出):
    vxCreateImage(context, w, h, VX_DF_IMAGE_U8),
};

// 创建阈值对象
vx_threshold thresh =
    vxCreateThresholdForImage(context, VX_THRESHOLD_TYPE_RANGE,
        VX_DF_IMAGE_U8, VX_DF_IMAGE_U8);
// 设置阈值
vxCopyThresholdRange(thresh, &lo_thresh, &hi_thresh, VX_WRITE_ONLY,
    VX_MEMORY_TYPE_HOST);

// 创建图节点
vx_node nodes[] = {
    vxScaleImageNode(graph, images[0], images[1], VX_INTERPOLATION_AREA),
    vxCannyEdgeDetectorNode(graph, images[1], thresh, 3, VX_NORM_L1,
        images[2]),
};

// 验证图
if (vxVerifyGraph(graph) != VX_SUCCESS) goto exit;

2.5 视频处理循环

int nextbuf = 1;
// 读取第一张输入图像到相机缓冲区 0
vx_bool newframe = myCaptureImage(0);

// 开始处理循环
while (newframe == vx_true_e) {
    // 启动图处理(可与下面的捕获并行进行)
    if (vxScheduleGraph(graph) != VX_SUCCESS) goto exit;
    // 在图处理运行时,捕获下一张输入图像到另一个相机缓冲区
    newframe = myCaptureImage(nextbuf);
    // 等待图处理完成
    if (vxWaitGraph(graph) != VX_SUCCESS) goto exit;
    // 显示输出结果
    myDisplayImage(images[2]);
    // 为下一帧做准备
    vxSwapImageHandle(images[0], new_ptrs, prev_ptrs, NUM_PLANES);
    // 保存返回的指针,用于下一次迭代
    for (i = 0; i < NUM_PLANES; i++) new_ptrs[i] = prev_ptrs[i];
    nextbuf = (nextbuf == 0) ? 1 : 0; // 切换下一个缓冲区
}

2.6 流程图

graph TD;
    A[开始] --> B[读取第一张图像到缓冲区 0];
    B --> C{是否成功读取图像};
    C -- 是 --> D[启动图处理];
    D --> E[捕获下一张图像到另一个缓冲区];
    E --> F[等待图处理完成];
    F --> G[显示输出结果];
    G --> H[切换图像缓冲区指针];
    H --> I[保存指针用于下次迭代];
    I --> J[切换下一个缓冲区];
    J --> E;
    C -- 否 --> K[结束];

3. 通过映射和取消映射访问数据对象

3.1 映射和取消映射的作用

OpenVX 对象的“不透明”特性虽然有助于实现优化,但在某些情况下,应用程序需要直接访问数据对象的部分内容。映射和取消映射功能允许应用程序将 OpenVX 对象的底层数据放置在一个固定的、CPU 可访问的位置,并且具有可由应用程序读写的定义好的组织方式。

3.2 使用注意事项

不过,使用映射和取消映射功能需要谨慎。根据具体实现和底层硬件的不同,使用映射和取消映射可能会导致昂贵的数据复制操作。例如,如果数据对象是 vx_image ,而底层硬件包含一个与 CPU 内存分离的 GPU 显存,那么 OpenVX 可能会将图像存储在 GPU 内存中以实现高效访问。映射该图像可能需要将其数据从 GPU 内存复制到 CPU 内存,这在时间和功耗方面可能代价高昂。而在另一些平台上,GPU 或其他加速器与 CPU 共享公共内存,映射和取消映射可能只需要一些消息和指针传递,而无需复制底层数据。因此,如果大量使用映射和取消映射功能,可能会导致相同的 OpenVX 程序在不同硬件平台上的性能表现差异很大,这违背了 OpenVX 性能可移植性的目标。

3.3 支持映射/取消映射的对象

OpenVX 为以下数据对象提供了映射和取消映射功能:
- vx_array
- vx_distribution
- vx_image
- vx_lut
- vx_remap
- vx_tensor

3.4 映射/取消映射的理解

可以将映射/取消映射功能看作是将数据的所有权从 OpenVX 实现转移到应用程序。在映射操作之后,应用程序可以直接访问和操作数据,而在取消映射操作之后,数据的控制权又交回给 OpenVX 实现。

3.5 总结

在使用 OpenVX 进行视觉处理时,合理利用高效数据输入输出功能以及映射和取消映射功能,可以在保证性能的同时,满足应用程序对数据的特殊处理需求。但在使用过程中,需要充分考虑硬件平台的差异,谨慎使用可能导致数据复制的操作,以确保程序的性能可移植性。

4. 映射操作的具体类型及应用

4.1 映射图像补丁

在处理图像时,有时我们只需要访问图像的一部分,即图像补丁。通过映射图像补丁,我们可以直接操作特定区域的像素数据。以下是映射图像补丁的一般步骤:
1. 获取图像对象 :首先需要有一个 vx_image 对象,这个对象可以通过前面提到的 vxCreateImageFromHandle 等函数创建。
2. 设置映射参数 :确定要映射的图像补丁的位置、大小等信息。
3. 执行映射操作 :使用相应的映射函数将图像补丁映射到 CPU 可访问的内存区域。

4.2 更多关于像素寻址

像素寻址是理解和操作图像数据的关键。在 OpenVX 中, vx_imagepatch_addressing_t 结构体用于描述像素数据的组织方式。该结构体的字段如下表所示:
| 字段 | 描述 |
| ---- | ---- |
| dim_x | 捕获图像的宽度(像素) |
| dim_y | 捕获图像的高度(像素) |
| stride_x | 水平方向相邻像素之间的字节距离 |
| stride_y | 垂直方向相邻像素之间的字节距离 |
| scale_x | 用于多平面图像的水平缩放参数 |
| scale_y | 用于多平面图像的垂直缩放参数 |
| step_x | 多平面图像的水平步长参数 |
| step_y | 多平面图像的垂直步长参数 |
| stride_x_bits | 仅用于二进制( VX_DF_IMAGE_U1 )图像 |

对于单平面 U8 图像, stride_x 通常为 1, stride_y 通常等于图像的宽度。

4.3 映射数组

映射数组允许应用程序直接访问 vx_array 对象中的数据。步骤如下:
1. 创建数组对象 :使用 vxCreateArray 函数创建 vx_array 对象。
2. 执行映射操作 :调用相应的映射函数将数组数据映射到 CPU 可访问的内存。
3. 访问和操作数据 :在映射的内存区域中对数组数据进行读写操作。
4. 取消映射 :操作完成后,使用取消映射函数将数据控制权交回给 OpenVX。

4.4 映射分布

vx_distribution 对象用于表示数据的分布情况。映射分布对象的步骤与映射数组类似:
1. 创建分布对象 :使用 vxCreateDistribution 函数创建 vx_distribution 对象。
2. 映射操作 :将分布对象的数据映射到 CPU 可访问的内存。
3. 数据操作 :对映射后的分布数据进行分析和处理。
4. 取消映射 :完成操作后取消映射。

4.5 映射查找表(LUT)

查找表(LUT)在图像处理中常用于颜色转换等操作。映射 LUT 对象的步骤如下:
1. 创建 LUT 对象 :使用 vxCreateLUT 函数创建 vx_lut 对象。
2. 映射操作 :将 LUT 数据映射到 CPU 可访问的内存。
3. 修改 LUT 数据 :根据需要修改映射后的 LUT 数据。
4. 取消映射 :操作完成后取消映射。

4.6 映射重映射对象

vx_remap 对象用于图像的重映射操作,如旋转、缩放等。映射重映射对象的步骤如下:
1. 创建重映射对象 :使用 vxCreateRemap 函数创建 vx_remap 对象。
2. 映射操作 :将重映射对象的数据映射到 CPU 可访问的内存。
3. 修改重映射数据 :根据需要修改映射后的重映射数据。
4. 取消映射 :操作完成后取消映射。

4.7 映射张量

张量是一种多维数组,在深度学习等领域有广泛应用。映射张量对象的步骤如下:
1. 创建张量对象 :使用 vxCreateTensor 函数创建 vx_tensor 对象。
2. 映射操作 :将张量数据映射到 CPU 可访问的内存。
3. 访问和操作张量数据 :在映射的内存区域中对张量数据进行读写操作。
4. 取消映射 :操作完成后取消映射。

4.8 映射操作流程图

graph TD;
    A[创建数据对象] --> B[执行映射操作];
    B --> C[访问和操作数据];
    C --> D[取消映射操作];
    D --> E[数据控制权交回 OpenVX];

5. 总结与建议

5.1 高效数据输入输出总结

通过使用 OpenVX 提供的 vxCreateImageFromHandle vxSwapImageHandle 函数,我们可以实现从外部源高效地输入和输出数据,避免不必要的数据复制,提高处理效率。双缓冲机制可以在捕获新图像的同时处理当前图像,进一步提升性能。

5.2 映射和取消映射总结

映射和取消映射功能为应用程序提供了直接访问 OpenVX 对象数据的能力,但需要谨慎使用,因为不同硬件平台可能会导致不同的性能表现。在使用时,应尽量减少不必要的映射和取消映射操作,以确保程序的性能可移植性。

5.3 建议

  • 在设计视觉处理系统时,优先考虑使用零拷贝技术,如 vxCreateImageFromHandle 函数,减少数据移动带来的开销。
  • 合理使用双缓冲或多缓冲机制,提高图像捕获和处理的并行性。
  • 仅在必要时使用映射和取消映射功能,避免频繁的数据复制操作。
  • 在不同硬件平台上进行性能测试,优化程序以确保在各种平台上都能获得良好的性能。

通过合理利用 OpenVX 的高效数据输入输出功能以及映射和取消映射功能,我们可以开发出高性能、可移植的视觉处理应用程序。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制方法。通过结合数据驱动技术与Koopman算子理论,将非线性系统动态近似为高维线性系统,进而利用递归神经网络(RNN)建模并实现系统行为的精确预测。文中详细阐述了模型构建流程、线性化策略及在预测控制中的集成应用,并提供了完整的Matlab代码实现,便于科研人员复现实验、优化算法并拓展至其他精密控制系统。该方法有效提升了纳米级定位系统的控制精度与动态响应性能。; 适合人群:具备自动控制、机器学习或信号处理背景,熟悉Matlab编程,从事精密仪器控制、智能制造或先进控制算法研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①实现非线性动态系统的数据驱动线性化建模;②提升纳米定位平台的轨迹跟踪与预测控制性能;③为高精度控制系统提供可复现的Koopman-RNN融合解决方案; 阅读建议:建议结合Matlab代码逐段理解算法实现细节,重点关注Koopman观测矩阵构造、RNN训练流程与模型预测控制器(MPC)的集成方式,鼓励在实际硬件平台上验证并调整参数以适应具体应用场景。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值