VPI - 视觉编程接口

3.2 版本

重新映射

概述

重新映射将通用的几何变换应用于图像。典型用途包括

  • 镜头畸变校正。
  • 用户定义的边界扩展。
  • 不同投影之间的转换。

该算法可以处理稠密或稀疏的扭曲映射。稠密扭曲映射存储输出图像中所有像素的重新映射位置,而稀疏映射则存储其中的一个子集。处理后者比前者更有效率,但质量可能会因映射应用的畸变程度而降低。

下面的示例展示了应用于等矩形全景图像的著名“小星球”(立体)投影,其中 \(R\) 是星球半径。

输入映射结果

Kyu Shiba Rikyu Garden: on the bridge 作者 heiwa4126,根据 CC BY 2.0 许可协议授权

\begin{align*} \theta(x,y) &= \pi + \arctan\left(\frac{y}{x}\right) \\ \phi(x,y) &= \frac{\pi}{2} - 2\arctan\left(\frac{r}{2R}\right) \\ r &= \sqrt{x^2+y^2} \\ \end{align*}


此图像是 Kyu Shiba Rikyu Garden: on the bridge 作者 heiwa4126 的衍生作品,根据 CC BY 2.0 许可协议使用

实现

该算法使用用户提供的 VPIWarpMap,它将输出图像中的每个像素映射到输入图像中对应的像素。它支持稠密和稀疏映射。

映射操作

映射操作由两个步骤组成

  1. 通过使用双二次插值对输入稀疏映射进行上采样来生成稠密映射。
  2. 使用用户提供的插值器对输入图像中与输出图像中的控制点对应的像素进行采样。

当对应的源像素落在图像边界之外时,“重新映射”将使用边界扩展来决定选择哪个像素值。

扭曲网格定义

扭曲网格可以分为 3 种类型

  • 稠密:以一定的性能损失为代价提供最高的质量。
  • 均匀稀疏:在质量和速度之间提供平衡。当结果图像中细节的分布先验未知时很有用。
  • 非均匀稀疏:当结果图像中细节的分布已知时很有用。这允许通过在细节较少的区域减少采样,并在细节较多的区域更密集地采样来提高质量和性能。

在定义稀疏映射时,输出图像最多可以分成 16 个区域(水平和垂直方向各 4 个),每个区域行或列的控制点间隔(密度)不同。间隔间距必须用控制点之间像素的二次幂数来表示。网格布局还有一些其他限制。请查阅 VPIWarpGrid 以获取更多信息。

非均匀网格

要定义稠密映射,只需将扭曲网格设置为只有一个区域,并将水平和垂直间距都设置为 1。

C API 函数

有关实现该算法的限制、约束和后端列表,请查阅以下函数的参考文档

函数描述
vpiCreateRemap 重新映射 算法创建有效负载。
vpiSubmitRemap 重新映射 操作提交到流。

用法

语言
  1. 导入 VPI 模块
    import vpi
  2. 创建一个恒等扭曲映射,即所有控制点的位置将与其在输出图像中的位置匹配。这里扭曲映射是稠密的,尺寸与输入图像相同。
    warp = vpi.WarpMap(vpi.WarpGrid(input.size))
  3. 将扭曲映射坐标解释为 numpy 数组。不进行复制。numpy 数组的形状为 (H,W,2),并且是扭曲映射的视图。转置形状为 (2,W,H) 只是为了使 xy 坐标分别位于数组 wxwy 中。这使得坐标的操作更简单、更有效。
    wx,wy = np.asarray(warp).transpose(2,1,0)
  4. 生成自定义映射,以渲染具有“小星球”效果的等矩形全景图。当执行镜头畸变校正时,对于畸变模型与 VPI 提供的模型不匹配的镜头,也采用类似的方法。有关更多详细信息,请参阅 镜头畸变校正
    x = wx-input.width/2
    y = wy-input.height/2
    R = input.height/8 # 星球半径
    r = np.sqrt(x*x + y*y)
    theta = np.pi + np.arctan2(y,x)
    phi = np.pi/2 - 2*np.arctan2(r, 2*R)
    wx[:] = np.fmod((theta + np.pi)/(2*np.pi)*(input.width-1), input.width-1)
    wy[:] = (phi + np.pi/2) / np.pi*(input.height-1)
  5. 执行算法,它将使用创建的扭曲映射变换 VPI 图像输入,并将结果写入输出 VPI 图像。
    with vpi.Backend.CUDA
    output = input.remap(warp)
  1. 初始化阶段
    1. 包含定义“重新映射”函数和 VPIWarpMap 定义的头文件。
      #include <vpi/WarpMap.h>
      #include <vpi/algo/Remap.h>
      声明实现“重新映射”算法的函数。
      声明实现 WarpMap 结构和相关函数的函数。
    2. 定义输入图像对象。
      VPIImage input = /*...*/;
      struct VPIImageImpl * VPIImage
      图像的句柄。
      Definition: Types.h:256
    3. 创建输出图像。在本例中,输入和输出具有相同的尺寸,但这不是必需的。但是,格式必须匹配。
      int32_t w, h;
      vpiImageGetSize(input, &w, &h);
      vpiImageGetFormat(input, &type);
      VPIImage output;
      vpiImageCreate(w, h, type, 0, &output);
      uint64_t VPIImageFormat
      预定义的图像格式。
      Definition: ImageFormat.h:94
      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)
      获取图像尺寸(以像素为单位)。
    4. 创建将在其中提交算法以供执行的流。
      VPIStream stream;
      vpiStreamCreate(0, &stream);
      struct VPIStreamImpl * VPIStream
      流的句柄。
      Definition: Types.h:250
      VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
      创建流实例。
    5. 创建稠密扭曲映射。
      memset(&map, 0, sizeof(map));
      map.grid.regionWidth[0] = w;
      map.grid.regionHeight[0] = h;
      map.grid.horizInterval[0] = 1;
      map.grid.vertInterval[0] = 1;
      int8_t numHorizRegions
      水平区域的数量。
      Definition: WarpGrid.h:159
      VPIWarpGrid grid
      扭曲网格控制点结构定义。
      Definition: WarpMap.h:91
      int16_t horizInterval[VPI_WARPGRID_MAX_HORIZ_REGIONS_COUNT]
      给定区域内控制点之间的水平间距。
      Definition: WarpGrid.h:174
      int8_t numVertRegions
      垂直区域的数量。
      Definition: WarpGrid.h:162
      int16_t vertInterval[VPI_WARPGRID_MAX_VERT_REGIONS_COUNT]
      给定区域内控制点之间的垂直间距。
      Definition: WarpGrid.h:180
      int16_t regionWidth[VPI_WARPGRID_MAX_HORIZ_REGIONS_COUNT]
      每个区域的宽度。
      Definition: WarpGrid.h:165
      int16_t regionHeight[VPI_WARPGRID_MAX_VERT_REGIONS_COUNT]
      每个区域的高度。
      Definition: WarpGrid.h:168
      VPIStatus vpiWarpMapAllocData(VPIWarpMap *warpMap)
      为给定的扭曲网格分配扭曲映射的控制点数组。
      定义输入和输出图像像素之间的映射。
      Definition: WarpMap.h:88
    6. 生成自定义映射,以将等矩形全景图渲染为小星球。vpiWarpMapGenerateIdentity 将使用恒等映射填充扭曲映射,即所有控制点的位置将与其在输出图像中的位置匹配。完成此操作后,代码将循环遍历所有控制点,并使用输出坐标,计算输入图像中对应的坐标。当执行镜头畸变校正时,对于畸变模型与 VPI 提供的模型不匹配的镜头,也采用这种方法。有关更多详细信息,请参阅 镜头畸变校正
      int i;
      for (i = 0; i < map.numVertPoints; ++i)
      {
      VPIKeypointF32 *row = (VPIKeypointF32 *)((uint8_t *)map.keypoints + map.pitchBytes * i);
      int j;
      for (j = 0; j < map.numHorizPoints; ++j)
      {
      float x = row[j].x - w / 2.0f;
      float y = row[j].y - h / 2.0f;
      const float R = h / 8.0f; /* 星球半径 */
      const float r = sqrtf(x * x + y * y);
      float theta = M_PI + atan2f(y, x);
      float phi = M_PI / 2 - 2 * atan2f(r, 2 * R);
      row[j].x = fmod((theta + M_PI) / (2 * M_PI) * (w - 1), w - 1);
      row[j].y = (phi + M_PI / 2) / M_PI * (h - 1);
      }
      }
      float x
      关键点的 x 坐标。
      Definition: Types.h:335
      float y
      关键点的 y 坐标。
      Definition: Types.h:336
      存储 float32 关键点坐标。坐标相对于图像的左上角。
      Definition: Types.h:334
      int32_t pitchBytes
      一个控制点与其正下方控制点之间的字节数。
      Definition: WarpMap.h:103
      VPIKeypointF32 * keypoints
      指向数组的指针,该数组包含输入图像中控制点的位置,这些位置与输出图像中的控制点相对应...
      Definition: WarpMap.h:109
      int16_t numHorizPoints
      水平点的数量。
      Definition: WarpMap.h:95
      int16_t numVertPoints
      垂直点的数量。
      Definition: WarpMap.h:99
      VPIStatus vpiWarpMapGenerateIdentity(VPIWarpMap *warpMap)
      使用恒等映射填充给定的扭曲映射。
    7. 使用创建的映射创建算法有效负载。有效负载在 CUDA 后端中创建。
      VPIPayload warp;
      struct VPIPayloadImpl * VPIPayload
      算法有效负载的句柄。
      Definition: Types.h:268
      VPIStatus vpiCreateRemap(uint64_t backends, const VPIWarpMap *warpMap, VPIPayload *payload)
      为“重新映射”算法创建有效负载。
      @ VPI_BACKEND_CUDA
      CUDA 后端。
      Definition: Types.h:93
  2. 处理阶段
    1. 将算法连同所有参数一起提交到流。该算法将由创建有效负载的后端(CUDA)执行。
      VPIStatus vpiSubmitRemap(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage input, VPIImage output, VPIInterpolationType interp, VPIBorderExtension border, uint64_t flags)
      将“重新映射”操作提交到流。
      @ VPI_BORDER_ZERO
      图像外部的所有像素都被视为零。
      Definition: Types.h:278
      @ VPI_INTERP_LINEAR
      线性插值。
      Definition: Interpolation.h:93
    2. (可选)等待直到处理完成。
      vpiStreamSync(stream);
      VPIStatus vpiStreamSync(VPIStream stream)
      阻塞调用线程,直到此流队列中的所有提交命令都完成(队列为空)。。
  3. 清理阶段
    1. 释放流、有效负载、扭曲映射以及输入和输出图像所持有的资源。
      vpiImageDestroy(output);
      void vpiImageDestroy(VPIImage img)
      销毁图像实例。
      void vpiPayloadDestroy(VPIPayload payload)
      释放有效负载对象和所有关联的资源。
      void vpiStreamDestroy(VPIStream stream)
      销毁流实例并释放所有硬件资源。
      void vpiWarpMapFreeData(VPIWarpMap *warpMap)
      释放由 vpiWarpMapAllocData 分配的扭曲映射控制点。

有关更多信息,请参阅 VPI - 视觉编程接口 的“C API 参考”部分中的 重新映射

性能

有关如何使用下表中的性能信息,请参阅 算法性能表
在比较测量结果之前,请查阅 比较算法运行时间
有关性能基准测试方式的更多信息,请参阅 性能基准

 -