概述
动态重映射将通用的几何变换应用于图像。它通过允许以下功能扩展了现有的 重映射 功能:
- 变换映射作为图像
- 映射作为算法的输入参数
- 映射的不同插值类型
- 使用相对映射表示映射值
- 使用归一化映射表示映射值
- 在算法序列中异步更改映射
- 源(输入)、目标(输出)和映射图像的不同尺寸
下面的示例展示了如何使用动态重映射和简单的映射来扭曲鱼眼图像。
源图像 | 映射 | 目标图像 |

版权所有 © 2012 Michel Thoby,经作者许可。 | 源图像插值: 线性
映射插值: 双三次
标志: VPI_RELATIVE_MAP | VPI_NORMALIZED_MAP
映射内容
(+0.15, +0.15) | (-0.15, +0.15) |
(+0.15, -0.15) | (-0.15, -0.15) |
| |
实现
该算法在目标图像中写入从源图像中读取的像素,该像素的位置由映射图像确定。 对于每个目标像素,算法使用映射插值类型读取其对应位置的映射值。 映射值可以是绝对位置 \( (x, y) \),反归一化(当在算法标志中使用零时)或归一化(当在标志中设置 VPI_NORMALIZED_MAP 时),或者是从归一化目标坐标到源图像中归一化位置的相对偏移量(当同时设置 VPI_RELATIVE_MAP 时)\( (dx, dy) \)(参见下图)。 从源图像中的该位置获取像素并存储在目标位置。 映射值和源像素的采样由映射和源图像插值类型以及算法标志控制。 通过在标志中使用 VPI_SAMPLING_ALIGNED_TO_CORNER,采样发生在像素的左上角而不是像素中心。
上图说明了动态重映射算法的工作原理。 所有三个图像(源图像、映射和目标图像)可以具有不同的大小。 目标图像控制映射中的采样,即目标图像中的每个像素通过归一化目标坐标并使用映射插值类型来采样映射中的一个像素。
映射将控制点存储为两个浮点坐标(参见 VPI_IMAGE_FORMAT_2F32 和 VPIKeypointF32),可以是 \( (x, y) \) 或 \( (dx, dy) \)。 绝对坐标 \( (x, y) \) 是源图像中的直接像素位置。 相对坐标 \( (dx, dy) \) 是增量差值,将添加到映射的当前位置以产生要从中获取像素的目标源位置。 当使用相对映射时,也需要使用归一化映射。
上图显示了相对和归一化映射的示例,使用了双三次(参见 VPI_INTERP_CATMULL_ROM)映射插值类型。 使用这些参数,一个简单的 \( 2x2 \) 映射就能够将源图像重映射到目标图像中。 此外,映射和源图像都可以在算法的后续调用中进行修改,例如在视频帧或全景拼接期间。
C API 函数
有关实现该算法的限制、约束和后端的列表,请查阅以下函数的参考文档
用法
语言
- 导入 VPI 模块
- 将输出大小定义为输入大小的 80%。
dst_size = (int(input.size[0] * .8), int(input.size[1] * .8))
- 将映射定义为 2x2 图像,图像格式为 2F32。
map = vpi.Image((2, 2), vpi.Format._2F32)
with map.rwlock_cpu() as data
data[0, 0] = [.15, .15]
data[0, 1] = [-.15, .15]
data[1, 0] = [.15, -.15]
data[1, 1] = [-.15, -.15]
- 执行算法,它从输入和映射生成输出图像。
output = vpi.dynamic_remap(input, map, size=dst_size, map_interp=vpi.Interp.CATMULL_ROM,
relative_map=True, backend=vpi.Backend.CUDA)
- 初始化阶段
- 包含定义动态重映射函数的头文件。
- 定义源(或输入)图像对象。
struct VPIImageImpl * VPIImage
图像的句柄。
- 创建目标(或输出)图像。 在这种特定情况下,目标图像的大小是源图像的 80%。
int32_t srcSize[2];
int32_t dstSize[2] = {(int32_t)(srcSize[0] * .8), (int32_t)(srcSize[1] * .8)};
VPIStatus vpiImageGetFormat(VPIImage img, VPIImageFormat *format)
获取图像格式。
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用指定的标志创建空的图像实例。
VPIStatus vpiImageGetSize(VPIImage img, int32_t *width, int32_t *height)
获取图像尺寸(以像素为单位)。
- 创建将提交算法以供执行的流。
struct VPIStreamImpl * VPIStream
流的句柄。
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
创建流实例。
- 生成自定义映射,以将鱼眼图像扭曲为更规则的图像。 映射图像存储控制点,这些控制点对应于归一化的相对偏移量。
int32_t mapSize[2] = {2, 2};
VPIImageBuffer buffer
存储图像内容。
VPIImagePlanePitchLinear planes[VPI_MAX_PLANE_COUNT]
间距线性布局中所有图像平面的数据。
VPIImageBufferPitchLinear pitch
以间距线性布局存储的图像。
int32_t pitchBytes
一行开头和前一行开头之间的字节差。
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
获取图像对象的锁并返回图像内容。
VPIStatus vpiImageUnlock(VPIImage img)
释放图像对象的锁。
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
主机可访问,平面采用间距线性内存布局。
@ VPI_LOCK_WRITE
仅锁定内存以进行写入。
存储 float32 关键点坐标。坐标相对于图像的左上角。
- 创建算法负载。 负载在 CUDA 后端创建。
VPIStatus vpiCreateDynamicRemap(uint64_t backends, VPIPayload *payload)
为动态重映射算法创建负载。
struct VPIPayloadImpl * VPIPayload
算法负载的句柄。
@ VPI_BACKEND_CUDA
CUDA 后端。
- 处理阶段
- 将算法连同所有参数一起提交到流。 该算法将由创建负载的后端执行,即 CUDA。
#define VPI_NORMALIZED_MAP
通知算法考虑归一化坐标。
#define VPI_RELATIVE_MAP
通知算法考虑相对坐标。
VPIStatus vpiSubmitDynamicRemap(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage input, VPIImage output, VPIImage map, VPIInterpolationType inputInterpolation, VPIInterpolationType mapInterpolation, VPIBorderExtension border, uint64_t flags)
将动态重映射操作提交到流。
@ VPI_BORDER_REFLECT
edcba|abcde|edcba
@ VPI_INTERP_CATMULL_ROM
Catmull-Rom 三次插值。
- (可选)等待直到处理完成。
VPIStatus vpiStreamSync(VPIStream stream)
阻止调用线程,直到此流队列中的所有已提交命令完成(队列为空)...
- 清理阶段
- 释放流、负载、源图像、目标图像和映射图像所持有的资源。
void vpiImageDestroy(VPIImage img)
销毁图像实例。
void vpiPayloadDestroy(VPIPayload payload)
释放负载对象和所有相关资源。
void vpiStreamDestroy(VPIStream stream)
销毁流实例并释放所有硬件资源。
性能
有关如何使用下面的性能表的信息,请参阅算法性能表。
在比较测量结果之前,请查阅比较算法运行时间。
有关如何对性能进行基准测试的更多信息,请参阅性能基准。
-
有关更多信息,请参阅《VPI - 视觉编程接口》的“C API 参考”部分中的动态重映射。