2、畸变数学模型:枕形畸变、桶形畸变、非线性映射的数学表达

好,咱们接着聊。上一章我们看了振镜扫描的物理原理,知道了理想情况下激光点应该乖乖地落在网格点上。但现实嘛,总是不那么完美。

这一章,我们来啃硬骨头——畸变的数学模型。说白了,就是搞清楚激光点到底是怎么“跑偏”的,以及我们怎么用数学语言描述这种跑偏。

2.1 两种最常见的“跑偏”姿势

我在现场调机的时候,见过最多的畸变就两种:枕形畸变和桶形畸变。你想想看,一个正方形的网格,扫描出来变成了枕头形状,或者变成了木桶形状。

核心区别:

  • 枕形畸变(Pincushion Distortion):图像边缘的四个角向外拉伸,像枕头鼓起来。常见于远心镜头或大角度扫描。
  • 桶形畸变(Barrel Distortion):图像边缘向内收缩,像木桶的弧度。常见于广角镜头或小角度扫描。

为什么会这样?嗯,这里要注意。根本原因在于振镜的偏转角度和实际光斑位置不是简单的线性关系。你给振镜一个角度增量,光斑在远处的移动距离并不是恒定的。

我记得有一次,客户拿来的工件边缘总是切不齐。我一看,典型的枕形畸变。边缘的激光能量都偏到外面去了,切割线自然就歪了。

2.2 从几何角度看畸变

咱们先抛开复杂的公式,用几何直觉来理解。

假设理想情况下,激光点位置是 (x, y)。实际位置是 (x', y')。畸变就是这两个位置之间的映射关系。

对于桶形畸变,实际位置会向中心收缩。数学上可以描述为:

x' = x * (1 + k * r²)
y' = y * (1 + k * r²)

其中 r² = x² + y²,k 是畸变系数。当 k < 0 时,就是桶形畸变;当 k > 0 时,就是枕形畸变。

这个公式看着简单,但实际项目中,k 往往不是一个常数。它跟扫描角度、场镜焦距都有关系。我建议你把它当成一个初始模型,后面还要加高阶项。

2.3 非线性映射:更通用的数学表达

实际畸变比上面那个公式复杂得多。你想想看,振镜是两个镜片在转,每个镜片都有自己的非线性特性。再加上场镜的像差,整个系统就是一个复杂的非线性映射。

通用的数学表达是这样的:

x' = a0 + a1*x + a2*y + a3*x² + a4*x*y + a5*y² + ...
y' = b0 + b1*x + b2*y + b3*x² + b4*x*y + b5*y² + ...

说白了,就是用多项式去拟合实际位置和理想位置之间的关系。阶数越高,拟合越准,但计算量也越大。

我的经验:

在实际项目中,我一般用3阶到5阶的多项式。阶数太低,校正不干净;阶数太高,容易过拟合,反而在边缘出现奇怪的抖动。我曾经试过7阶多项式,结果在角落出现了波浪纹,后来老老实实降到了4阶。

2.4 径向畸变与切向畸变

除了上面说的径向畸变(枕形和桶形都属于径向畸变),还有一种叫切向畸变。这个在振镜扫描中不太常见,但一旦出现就很头疼。

切向畸变,说白了就是图像发生了“扭曲”,像拧毛巾一样。这通常是因为振镜的安装角度有偏差,或者镜片没有完全对准光轴。

数学上,切向畸变的模型是:

x' = x + [2*p1*x*y + p2*(r² + 2*x²)]
y' = y + [p1*(r² + 2*y²) + 2*p2*x*y]

其中 p1、p2 是切向畸变系数。这个模型在相机标定中很常见,在振镜校正中也可以借用。

避坑指南:

我曾经遇到一个案例,怎么校正都校不准。后来发现是振镜的安装底座松了,导致镜片有微小的倾斜。这不是数学模型能解决的问题,得先修硬件。所以,遇到校正不准,先检查机械结构,别急着调参数。

2.5 知识体系总览

为了让你更直观地理解这一章的内容,我画了一张图。它把畸变的分类、数学模型和校正思路串在了一起。

畸变数学模型知识体系 畸变分类 径向畸变 枕形畸变 桶形畸变 切向畸变 安装偏差/镜片倾斜 数学模型 多项式拟合 x' = x * (1 + k*r²) x' = x + [2*p1*x*y + p2*(r²+2x²)] 校正思路:标定 → 拟合系数 → 反向映射

2.6 实际项目中的系数求解

有了数学模型,下一步就是求解系数。说白了,就是通过标定得到一组已知的理想点和实际点,然后用最小二乘法去拟合多项式系数。

我习惯用 Python 的 numpy 来做这件事。代码很简单:

import numpy as np

# 假设我们有N个标定点
# ideal_points: 理想坐标 (N x 2)
# actual_points: 实际坐标 (N x 2)

# 构造多项式特征矩阵
def build_poly_matrix(points, degree=3):
    N = points.shape[0]
    cols = []
    for d in range(degree + 1):
        for i in range(d + 1):
            j = d - i
            cols.append((points[:, 0] ** i) * (points[:, 1] ** j))
    return np.column_stack(cols)

# 拟合x方向和y方向的系数
A = build_poly_matrix(actual_points, degree=3)
coeff_x, _, _, _ = np.linalg.lstsq(A, ideal_points[:, 0], rcond=None)
coeff_y, _, _, _ = np.linalg.lstsq(A, ideal_points[:, 1], rcond=None)

这段代码看着简单,但实际用的时候有几个坑。我曾经因为标定点分布不均匀,导致拟合出来的系数在边缘区域误差很大。后来我改用均匀网格标定,效果就好多了。

小技巧:

标定点的数量建议至少是多项式项数的3倍。比如3阶多项式有10个系数,那至少要有30个标定点。我一般用11x11的网格,121个点,足够用了。

好了,这一章我们搞清楚了畸变的数学模型。从枕形、桶形这些直观的畸变,到多项式拟合这种通用的数学工具。下一章,我们会把这些模型变成真正的校正算法。


公众号:蓝海资料掘金营,微信deep3321