VPI - 视觉编程接口

3.2 版本

镜头畸变校正

概述

VPI 提供了一些函数,这些函数与 重映射 算法一起,执行图像矫正。输入图像可能具有由相机镜头引起的一定程度的畸变。最终结果是一个无畸变的图像,可以选择性地重新投影到第二个相机中,例如,允许输入相机的光轴重新对齐。这使其成为某些计算机立体视觉应用中的重要阶段,例如深度估计,其中两个相机必须使其光轴水平和平行。

以下类型的畸变模型包含在内

  • 多项式畸变 - 包含广泛的常见镜头畸变,例如桶形畸变、枕形畸变以及这些畸变的混合等。
  • 鱼眼畸变 - 常见于鱼眼镜头,可以看作是桶形畸变的夸张形式。

对于其他畸变模型,用户始终可以创建自己的输出到输入映射,如 此处 所示。

输入参数输出

版权所有 © 2012 Michel Thoby,经作者许可。
投影: 鱼眼等距
焦距: 7.5mm APS-C
k1 -0.126
k2 0.004
k3 0
k4 0

C API 函数

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

函数描述
vpiWarpMapGenerateFromFisheyeLensDistortionModel 生成一个映射,用于校正由鱼眼镜头引起的图像畸变。
vpiWarpMapGenerateFromPolynomialLensDistortionModel 生成一个映射,使用多项式镜头畸变模型校正图像。

实现

镜头畸变校正算法通过将畸变的输入图像扭曲成已矫正的、无畸变的输出图像来实现。它通过执行逆变换来实现;即,对于目标图像中的每个像素 \((u,v)\),计算输入图像中对应的坐标 \((\check{u},\check{v})\)。

  1. 对于目标图像中的每个像素 \((u,v)\),使用其内参矩阵 \(\mathsf{K_{out}}\) 计算其在输出相机空间中对应的 3D 点 \(\mathsf{P_{out}}\)。

    \[ \mathsf{P_{out}} = \mathsf{K_{out}}^{-1} \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} \]

  2. 使用 \([\mathsf{R}|\mathsf{t}]^{-1}\) 矩阵将 3D 点 \(\mathsf{P_{out}}\) 从输出相机空间变换到输入相机空间。

    \[ \mathsf{P_{in}} = \mathsf{R}^{-1}(\mathsf{P_{out}}-\mathsf{t}) \]

  3. 将镜头畸变模型 \(L\) 应用于焦距单位的理想(无畸变)投影点 \((\tilde{x},\tilde{y})\),得到畸变点 \((x_d,y_d)\)。\(s\) 只是一个比例因子。

    \begin{align*} s \begin{bmatrix} \tilde{x} \\ \tilde{y} \\ 1 \end{bmatrix} &= \begin{bmatrix} x \\ y \\ z \end{bmatrix} = \mathsf{P_{in}} \\ (x_d,y_d) &= L(\tilde{x}, \tilde{y}) \end{align*}

  4. 使用其内参矩阵 \(\mathsf{K_{in}}\) 将畸变点 \((\tilde{x},\tilde{y})\) 投影到输入图像空间,得到坐标 \((\check{u},\check{v})\)。同样,\(s\) 只是另一个比例因子。

    \[ s \begin{bmatrix} \check{u} \\ \check{v} \\ 1 \end{bmatrix} = \mathsf{K_{in}} \begin{bmatrix} x_d \\ y_d \\ 1 \end{bmatrix} \]

  5. 使用用户提供的插值器,对输入图像进行采样,并将结果分配给相应的输出像素。

    \[ (u,v) \leftarrow S_{\mathsf{interp}}(\check{u},\check{v}) \]

支持以下插值器

以上公式假设使用 针孔相机模型。在链接中显示的 图表 中,假设输入相机与世界坐标系对齐,原点位于 \(O = (0,0,0)\),光轴与世界的 \(Z_w\) 轴共线。输出相机的原点位于 \(F_c\),光轴沿 \(Z_c\)。总而言之,这使得矩阵 \([R|t]\) 将点从输入相机空间变换到输出相机空间。

镜头畸变模型

以上公式假设投影是线性运算。但实际上,情况并非如此。镜头畸变使得现实世界中的直线在捕获的图像中投影为弯曲的。为了考虑到这一点,畸变模型应用于输入相机空间中与要渲染的输出图像像素坐标相对应的理想的、无畸变的坐标。结果坐标是在输出图像中渲染的像素在输入图像上的实际投影位置。

VPI 提供了处理多项式和鱼眼畸变模型的函数。这些模型的特点是畸变系数,对于鱼眼镜头,还有映射类型。系数对于每个镜头都是唯一的,可以由制造商提供,也可以通过镜头校准过程估算出来。

多项式畸变模型

多项式畸变模型,也称为 Brown-Conrady 模型,允许表示广泛的镜头畸变,例如桶形畸变、枕形畸变、胡子状畸变等。

VPI 使用结构体 VPIPolynomialLensDistortionModel 存储畸变参数,最终由 vpiWarpMapGenerateFromPolynomialLensDistortionModel 使用,以创建 VPIWarpMap,从而对输入图像进行去畸变。

此畸变模型由径向和切向畸变分量组成

\begin{align*} L(\tilde{x},\tilde{y}) &= L_r(\tilde{x},\tilde{y}) + L_t(\tilde{x},\tilde{y}) \end{align*}

径向畸变由参数 \(k_1,k_2,k_3,k_4,k_5\) 和 \(k_6\) 定义

\begin{align*} L_r(\tilde{x},\tilde{y}) &= \frac{1+k_1r^2+k_2r^4+k_3r^6}{1+k_4r^2+k_5r^4+k_6r^6} \begin{bmatrix} \tilde{x} \\ \tilde{y} \end{bmatrix}\\ r^2 &= \tilde{x}^2 + \tilde{y}^2 \end{align*}

切向畸变由参数 \(p_1\) 和 \(p_2\) 定义,是由于镜头组件的不完美居中和其他制造缺陷造成的。

\begin{align*} L_t(\tilde{x},\tilde{y}) &= \begin{bmatrix} 2p_1\tilde{x}\tilde{y} + p_2(r^2+2\tilde{x}^2) \\ p_1(r^2+2\tilde{y}^2) + 2p_2\tilde{x}\tilde{y} \end{bmatrix} \\ r^2 &= \tilde{x}^2+\tilde{y}^2 \end{align*}

鱼眼畸变模型

鱼眼镜头是一种超广角镜头,会产生强烈的桶形畸变。其用途之一是创建宽幅全景图。

VPI 使用结构体 VPIFisheyeLensDistortionModel 存储畸变参数,最终由 vpiWarpMapGenerateFromFisheyeLensDistortionModel 使用,以创建 VPIWarpMap,从而对输入图像进行去畸变。

畸变模型由映射函数 \(M_f(\theta)\) 定义,该函数取决于鱼眼镜头类型,以及系数 \(k_1,k_2,k_3\) 和 \(k_4\),如下所示

\begin{align*} L(\tilde{x},\tilde{y}) &= \frac{r_d}{r} \begin{bmatrix} \tilde{x} \\ \tilde{y} \end{bmatrix} \\ r_d &= M_1(\theta_d) \\ \theta_d &= \theta(1+ k_1\theta^2 + k_2\theta^4 + k_3\theta^6 + k_4\theta^8) \\ \theta &= \arctan(r) \\ r &= \sqrt{\tilde{x}^2 + \tilde{y}^2} \end{align*}

其中

  • \(\theta\) 是入射光相对于相机光轴的入射角。
  • \(\theta_d\) 是畸变的入射光角度,通常是由于镜头制造缺陷造成的。
  • \(r_d\) 是入射光记录在图像上的位置到主点的距离。

鱼眼镜头可以根据入射光角度与其在图像上的记录位置之间的关系进行分类,这种关系由映射函数 \(M_f(\theta)\) 确定。

注意
在这些公式中,\(f=1\),因为这是与投影的 \((\tilde{x},\tilde{y})\) 坐标相关的焦距。

VPI 支持以下映射函数,每种函数都具有一些理想的特性

用法

语言
  1. 导入 VPI 模块
    import vpi
  2. 创建一个密集的变形映射图,用于将畸变图像变形为校正后的输出。
    grid = vpi.WarpGrid(input.size)
  3. 定义相机内参和外参。输入图像由 APS-C 传感器记录,镜头焦距为 7.5mm。主点正好在图像中心。最后,由于这是一个单目设置,外参是单位矩阵,这意味着输入和输出相机处于相同的位置,光轴对齐。
    sensorWidth = 22.2 # APS-C 传感器
    focalLength = 7.5
    f = focalLength * input.width / sensorWidth
    K = [[f, 0, input.width/2 ],
    [0, f, input.height/2 ]]
    X = np.eye(3,4)
  4. 从相机参数和鱼眼镜头畸变模型创建去畸变变形映射图。
    warp = vpi.WarpMap.fisheye_correction(grid, K=K, X=X,
    mapping=vpi.FisheyeMapping.EQUIDISTANT,
    coeffs=[-0.126, 0.004])
  5. 对输入图像执行重映射操作以进行去畸变。我们使用立方插值器以获得最大质量,并且将落在源图像边界之外的映射像素视为黑色。
    with vpi.Backend.CUDA
    output = input.remap(warp, interp=vpi.Interp.CATMULL_ROM, border=vpi.Border.ZERO)
  1. 初始化阶段
    1. 包含定义镜头畸变模型和 重映射 算法的头文件。
      #include <vpi/algo/Remap.h>
      声明了基于常用镜头畸变模型生成变形映射图的函数。
      声明了实现重映射算法的函数。
    2. 定义输入图像对象。
      VPIImage input = /*...*/;
      struct VPIImageImpl * VPIImage
      图像的句柄。
      定义: Types.h:256
    3. 创建输出图像,在本例中,输出图像具有与输入图像相同的尺寸和格式。
      int32_t width, height;
      vpiImageGetSize(input, &width, &height);
      vpiImageGetFormat(input, &type);
      VPIImage output;
      vpiImageCreate(width, height, type, 0, &output);
      uint64_t VPIImageFormat
      预定义的图像格式。
      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. 创建一个密集的变形映射图,用于将畸变图像变形为校正后的输出。
      memset(&map, 0, sizeof(map));
      map.grid.regionWidth[0] = width;
      map.grid.regionHeight[0] = height;
      map.grid.horizInterval[0] = 1;
      map.grid.vertInterval[0] = 1;
      int8_t numHorizRegions
      水平方向的区域数量。
      定义: WarpGrid.h:159
      VPIWarpGrid grid
      变形网格控制点结构定义。
      定义: WarpMap.h:91
      int16_t horizInterval[VPI_WARPGRID_MAX_HORIZ_REGIONS_COUNT]
      给定区域内控制点之间的水平间距。
      定义: WarpGrid.h:174
      int8_t numVertRegions
      垂直方向的区域数量。
      定义: WarpGrid.h:162
      int16_t vertInterval[VPI_WARPGRID_MAX_VERT_REGIONS_COUNT]
      给定区域内控制点之间的垂直间距。
      定义: WarpGrid.h:180
      int16_t regionWidth[VPI_WARPGRID_MAX_HORIZ_REGIONS_COUNT]
      每个区域的宽度。
      定义: WarpGrid.h:165
      int16_t regionHeight[VPI_WARPGRID_MAX_VERT_REGIONS_COUNT]
      每个区域的高度。
      定义: WarpGrid.h:168
      VPIStatus vpiWarpMapAllocData(VPIWarpMap *warpMap)
      为给定的变形网格分配变形映射图的控制点数组。
      定义输入和输出图像像素之间的映射。
      定义: WarpMap.h:88
    5. 使用映射类型和畸变系数定义鱼眼镜头畸变模型。后者来自镜头校准过程。
      memset(&fisheye, 0, sizeof(fisheye));
      fisheye.k1 = -0.126;
      fisheye.k2 = 0.004;
      fisheye.k3 = 0;
      fisheye.k4 = 0;
      VPIFisheyeMapping mapping
      像素角度与像素到图像中心的距离之间的映射。
      @ VPI_FISHEYE_EQUIDISTANT
      指定等距鱼眼映射。
      保存鱼眼镜头畸变模型的系数。
    6. 定义相机内参和外参。输入图像由 APS-C 传感器记录,镜头焦距为 7.5mm。主点正好在图像中心。最后,由于这是一个单目设置,外参是单位矩阵,这意味着输入和输出相机处于相同的位置,光轴对齐。
      float sensorWidth = 22.2; /* APS-C 传感器 */
      float focalLength = 7.5;
      float f = focalLength*width/sensorWidth;
      {
      { f, 0, width/2.0 },
      { 0, f, height/2.0 }
      };
      {
      { 1, 0, 0, 0 },
      { 0, 1, 0, 0 },
      { 0, 0, 1, 0 }
      };
      float VPICameraExtrinsic[3][4]
      相机外参矩阵。
      定义: Types.h:668
      float VPICameraIntrinsic[2][3]
      相机内参矩阵。
      定义: Types.h:655
    7. 将镜头畸变模型隐含的校正烘焙到变形映射图中,如上定义。
      VPIStatus vpiWarpMapGenerateFromFisheyeLensDistortionModel(const VPICameraIntrinsic Kin, const VPICameraExtrinsic X, const VPICameraIntrinsic Kout, const VPIFisheyeLensDistortionModel *distModel, VPIWarpMap *warpMap)
      生成一个映射,用于校正由鱼眼镜头引起的图像畸变。
    8. 为重映射算法创建负载,该负载将执行校正。负载在 CUDA 后端上创建,最终将在该后端上执行算法。
      VPIPayload warp;
      struct VPIPayloadImpl * VPIPayload
      算法负载的句柄。
      定义: Types.h:268
      VPIStatus vpiCreateRemap(uint64_t backends, const VPIWarpMap *warpMap, VPIPayload *payload)
      为重映射算法创建负载。
      @ VPI_BACKEND_CUDA
      CUDA 后端。
      定义: Types.h:93
    9. 创建将在其中提交算法以供执行的流。
      VPIStream stream;
      vpiStreamCreate(0, &stream);
      struct VPIStreamImpl * VPIStream
      流的句柄。
      定义: Types.h:250
      VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
      创建流实例。
  2. 处理阶段
    1. 将算法连同所有参数一起提交到流。我们使用立方插值器以获得最大质量,并且将落在源图像边界之外的映射像素视为黑色。
      VPIStatus vpiSubmitRemap(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage input, VPIImage output, VPIInterpolationType interp, VPIBorderExtension border, uint64_t flags)
      将重映射操作提交到流。
      @ VPI_BORDER_ZERO
      图像外部的所有像素均被视为零。
      定义: Types.h:278
      @ VPI_INTERP_CATMULL_ROM
      Catmull-Rom 立方插值。
    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 参考” 部分中的 镜头畸变校正

性能

镜头畸变校正的主循环使用重映射,因此性能受其影响。请参阅重映射的 性能表