消费级相机和镜头既便宜又普遍。不幸的是,与它们的工业同行不同,它们的设计目的并不是作为计算机视觉应用中精确测量的工具。
在各种类型的失真中,影响低档相机和镜头的最明显的失真是径向畸变。
径向畸变是场景中对象的视角与图像中该对象出现的像素之间的非线性。在光学中心附近,这种影响很难察觉,但当我们径向远离光学中心时,失真变得更加明显。
通常,远离光学中心的像素看起来比应该的更接近中心。图像的角似乎被拉向中心。这种现象被称为桶形失真,因为相机垂直看到的矩形物体将显示为圆形的“桶”(见下图)。
失真补偿
这个文章的目的是基于一个简单的模型来描述相机镜头对的径向畸变。一旦我们知道了失真参数,我们就能够补偿对象的像素位置,并获得一个无失真的像素位置。
你可以克隆此存储库中的代码和示例图像。
https://github.com/sebastiengilbert73/tutorial_distortion_calibration
棋盘图像将为我们提供共线特征点。在没有径向畸变的情况下,场景中共线点的像素位置应该是共线的。
由于它们明显不共线,我们将构建一个参数可调的模型,它将扭曲的像素点映射到未扭曲的点上。
特征点
第一步是提取图1中的特征点。
# Find the checkerboard intersections, which will be our feature points that belong to a plane
checkerboard_intersections = checkerboard.CheckerboardIntersections(
adaptive_threshold_block_side=adaptive_threshold_block_side,
adaptive_threshold_bias=adaptive_threshold_bias,
correlation_threshold=correlation_threshold,
debug_directory=output_directory
)
intersections_list = checkerboard_intersections.FindIntersections(checkerboard_img)
CheckerboardIntersections类型的对象将彩色图像转换为灰度图像,然后应用自适应阈值。结果是一个二进制图像,其中的正方形交叉点非常清晰。
阈值图像与旨在强调两种交叉点的合成模式图像相关联。
对两个相关图像进行阈值处理以获得最高峰值。
检测到阈值相关图像中的斑点,并计算每个斑点的质心,生成交点列表。
径向畸变模型
我们将考虑一个基本的径向畸变模型——尽可能简单:作为距光学中心距离的函数的二次校正因子。未失真半径将是失真半径和校正系数的乘积。
这种失真模型只有三个参数:
光学中心(cx,cy)。它不一定与图像中心重合(w/2,h/2)。
二次系数α。当α>0时,我们有桶形畸变。当α<0时,我们有枕形失真(图像角向外拉伸)。当α=0时,没有径向畸变。
模型优化
我们面临着一个非线性优化问题:找到最佳参数(cx,cy)和α,它们将以形成直线的方式投影我们找到的交点。为此,我们将创建一个存储三个失真参数的PyTorch模型。
class DistortionParametersOptimizer(torch.nn.Module):
def __init__(self, center, alpha, image_sizeHW, zero_threshold=1e-12):
super(DistortionParametersOptimizer, self).__init__()
self.center = torch.nn.Parameter(torch.tensor([center[0]/image_sizeHW[1], center[1]/image_sizeHW[0]]).float())
self.alpha = torch.nn.Parameter(torch.tensor(alpha).float())
self.image_sizeHW = image_sizeHW
self.zero_threshold = zero_threshold
这个类还需要知道图像大小,因为为了数值稳定性,像素坐标将在(-1,1)中归一化。
DistortionParametersOptimizer.forward方法返回一批均方误差,每个均方误差对应于投影在相应最佳拟合线上的一行特征点的残差。
在理想情况下,如果径向畸变得到了完美补偿,forward方法将返回一批零。
我们直接与失真补偿类RadialDistortion交互。当我们调用它的Optimize方法时,它将在内部实例化DistortionParametersOptimizer类型的对象。
# Create a RadialDistortion object, that will optimize its parameters
radial_distortion = radial_dist.RadialDistortion((checkerboard_img.shape[0], checkerboard_img.shape[1]))
# Start the optimization
epoch_loss_center_alpha_list = radial_distortion.Optimize(intersections_list, grid_shapeHW)
Plot([epoch for epoch, _, _, _ in epoch_loss_center_alpha_list],
[[loss for _, loss, _, _ in epoch_loss_center_alpha_list]], ["loss"])
在100个epoch内,均方误差下降100倍:
哇!这很容易¹!
找到的参数为(cx,cy)=(334.5, 187.2)(即图像中心的东北偏北(320, 240))和α=0.119,与预期的桶形失真校正系数相对应。
去失真
既然我们已经表征了径向畸变,我们可以将校正因子应用于我们的特征点。
图8显示了补偿径向畸变后的蓝色特征点。中心点基本不变,而外围点被推离中心更远。我们可以观察到,属于棋盘上直线的场景点看起来在图像中对齐得更好。
虽然我们可能不想在实时应用程序中这样做,但我们可以通过将原始图像中的每个像素投影到其相应的无失真位置来消除整个图像的失真。
由于我们逐渐将像素径向推离光学中心,我们经常会遇到投影空间中的像素,这些像素没有被原始图像中的像素映射,从而导致图9中令人讨厌的黑色痕迹。我们可以通过用附近的中间颜色替换黑色像素来消除这些像素。
结论
我们考虑了影响大多数消费级相机和镜头的径向畸变问题。我们假设了一个简单的失真模型,根据二次定律径向推拉像素。我们通过梯度下降,使用棋盘的特征点优化了一组参数。这些参数允许我们补偿径向畸变。
重要的是要认识到畸变参数是相机镜头对固有的。一旦知道它们,我们就可以补偿任何图像的径向畸变,只要相机镜头对是固定的。
☆ END ☆
如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。
↓扫描二维码添加小编↓