☰
镜头畸变:桶形、枕形畸变的成因与对测量的影响
2026/10/19 14:11:22 网站建设 项目流程

昨天远心那篇结尾说了,今天聊畸变。远心管的是物距变了倍率跟着变,畸变是另一回事:物距不动,倍率在一张图里也不是处处一样。

现场挺常见的一种情况:拿一个标准件放在视野中间,算出一个像素当量,多少毫米一个像素,测中间的件挺准。件一挪到视野边上,尺寸就小了一截,挪到角上更离谱。有人以为是边上光暗了、边缘没提准,换算法调了半天,数还是跟着位置走。这种跟着位置走的误差,八成是镜头畸变。只用一个比例系数换算尺寸,等于默认整张图倍率一样,这个前提在普通镜头上本来就不成立。

桶形和枕形是怎么来的

理想的镜头,拍一张方格网,出来还是方格网,只是缩小了。实际镜头里,离光轴越远,倍率会慢慢变。越往外倍率越小,四条边往里收、中间鼓出来,像个桶,叫桶形畸变;越往外倍率越大,四个角往外拉,叫枕形畸变。

成因说白了跟光阑位置有关。主光线都要穿过光阑中心,光阑不在镜片上的话,主光线打到镜片上的高度就跟视场角有关,边上的光线被多折或者少折一点,倍率就不均匀了。光阑在镜片前面,一般出桶形;在后面,一般出枕形。真实镜头是好几组镜片,设计的时候会让前后两组的畸变互相抵掉一部分,抵不干净的就是手册上那个畸变值。

经验上,短焦、广角的镜头视场角大,畸变普遍大,而且多半是桶形;长焦的通常小很多。远心镜头本来就是冲着测量去的,好的能做到 0.1% 以内。

标定里那几个系数在说什么

做过相机标定的都见过k1k_1k1​、k2k_2k2​、p1p_1p1​、p2p_2p2​这几个参数,常见的标定库基本都是这一套模型。径向畸变只跟点离畸变中心的距离rrr有关:

rd=r (1+k1r2+k2r4+k3r6) r_d = r\,(1 + k_1 r^2 + k_2 r^4 + k_3 r^6)rd​=r(1+k1​r2+k2​r4+k3​r6)

rrr是理想位置的半径,rdr_drd​是实际拍到的位置。k1k_1k1​是负的就是桶形,正的就是枕形。工业镜头一般k1k_1k1​一项就占了大头,k2k_2k2​修一下边上,k3k_3k3​很少用得上,开多了反而容易在角上过拟合。

切向畸变来自装配:镜片没对正、有点偏心,或者传感器跟镜头不完全平行,图像会往某个方向歪一点:

Δxt=2p1xy+p2(r2+2x2),Δyt=p1(r2+2y2)+2p2xy \Delta x_t = 2p_1 xy + p_2 (r^2 + 2x^2),\qquad \Delta y_t = p_1 (r^2 + 2y^2) + 2p_2 xyΔxt​=2p1​xy+p2​(r2+2x2),Δyt​=p1​(r2+2y2)+2p2​xy

工业定焦镜头装配都还行,p1p_1p1​、p2p_2p2​通常很小。我的习惯是先只开径向,看残差怎么分布,残差明显偏向某个方向,再把切向打开。还有一种情况要留心:镜头被人碰过,或者相机接口没拧紧,切向那项会突然变大,这时候该去查机械,不是加参数。

手册上的 -0.5% 怎么看

手册上的畸变百分比,定义是这样的:

D=rd−rr×100%≈k1r2×100% D = \frac{r_d - r}{r}\times 100\% \approx k_1 r^2 \times 100\%D=rrd​−r​×100%≈k1​r2×100%

负号是桶形,正号是枕形。这个值一般给的是靶面对角线最边上,也就是畸变最大的地方。只看k1k_1k1​那一项,畸变百分比跟r2r^2r2成正比,换成偏移量就是跟r3r^3r3成正比:

Δr=Dmax(rrmax)2r \Delta r = D_{\mathrm{max}} \left(\frac{r}{r_{\mathrm{max}}}\right)^2 rΔr=Dmax​(rmax​r​)2r

这个式子值得记一下。半径减半,偏移只剩八分之一,所以视野中间几乎看不出畸变,问题全堆在边上和角上。

这里有两个坑。一个是有的手册标的是 TV 畸变,看的是画面上边那条直线弯了多少,跟上面这个光学畸变不是一个定义,两支镜头放一起比之前先看清楚标的是哪种。另一个是畸变跟靶面有关,同一支镜头配大靶面的相机,用到了更外圈,畸变就比配小靶面时大。

算一笔:同一个件放中间和放边上

还用 D9、D10 那套:2/3 英寸、2448×2048、像元 3.45 µm 的相机,配 25 mm 镜头,水平视野 120 mm,物方一个像素约 49 µm。图像中心到角上是 1596 个像素,对应物方约 78.2 mm。假设手册写的是 -0.5%:

Δrcorner=0.5%×1596≈8.0 px≈0.39 mm \Delta r_{\mathrm{corner}} = 0.5\% \times 1596 \approx 8.0\ \mathrm{px} \approx 0.39\ \mathrm{mm}Δrcorner​=0.5%×1596≈8.0px≈0.39mm

角上的点往里缩了 8 个像素。水平方向的视野边离中心 60 mm,那里的畸变约 -0.29%,偏移约 3.6 个像素,0.18 mm。顺带一提,这个 -0.5% 换成标定里归一化坐标下的k1k_1k1​,大约是 -0.12,自己标出来数量级差不多就对了。

放一个 50 mm 长的件。放在视野正中间,两端离中心各 25 mm,每端只往里缩 0.26 个像素,测出来短了约 0.52 个像素,也就是 26 µm。同一个件挪到右半边,从 10 mm 到 60 mm,右端缩了 3.6 个像素,左端几乎不动,测出来短了约 176 µm。同一个件换个位置,误差差了七倍左右。

假如公差是 ±0.05 mm,按测量误差最好不超过公差带十分之一的老规矩,留给畸变的也就 10 µm 上下。放中间的 26 µm 已经超了,靠边的 176 µm 比整个公差带还宽。换一支 -0.1% 的低畸变镜头,中间约 5 µm,靠边约 35 µm,中间能凑合,边上照样不行。

想自己换参数算的,这几行就够了:

importnumpyasnp W,H=2448,2048# 分辨率mm_per_px=120.0/W# 水平视野 120 mm,物方约 0.049 mm/像素r_max=np.hypot(W/2,H/2)# 中心到角,像素D_max=-0.005# 手册畸变 -0.5%,只算 k1 那一项defshift_px(r):"""半径 r(像素)处的径向偏移,像素,负数表示往里缩"""returnD_max*(r/r_max)**2*rforname,x1,x2in[("居中",-25,25),("靠边",10,60)]:s1=np.sign(x1)*shift_px(abs(x1)/mm_per_px)s2=np.sign(x2)*shift_px(abs(x2)/mm_per_px)err=(s2-s1)*mm_per_pxprint(f"{name}: 长度误差{err*1000:.1f}µm,{err/mm_per_px:.2f}像素")

校正能救多少,什么时候该换镜头

软件校正是现在的常规做法:拍十几张不同位置、不同姿态的标定板,解出内参和畸变系数,再按模型把点还原回去。标定做得认真,重投影误差压到 0.1 个像素上下是正常的,换到物方也就 5 µm 左右,上面靠边那 176 µm 基本都能救回来。

但校正不是白给的,现场踩过的坑有这么几个。

焦距和光圈一动,畸变系数就变了。标完之后对焦环、光圈环一定要锁死,最好再点个胶。现场总有人觉得图有点虚,顺手拧一下,前面的标定就白做了。

标定板要铺满视野,尤其是四个角。图省事把标定板放在中间拍几张,中间是准了,角上的系数全靠外推,偏偏畸变最大的就是角上。

整张图做 remap 要插值,边缘会软一点,桶形校正完四个角还得裁一圈或者留黑边,也吃算力。我做测量一般不校正整张图,只对提出来的边缘点做去畸变,快,也不动原始灰度。

什么时候直接上低畸变镜头?我的判断很土:先按上面的式子把视野边上的偏移算出来。校正后的残差能稳定压在公差的十分之一以内,就用普通镜头加标定;要是残差本身就卡在边上,或者现场振动大、镜头老被碰、也没人会重新标定,那就宁可多花点钱上低畸变或者远心,光学上本来就直的,比软件掰直的省心。读码、有无检测这类不测尺寸的活,畸变一两个百分点也无所谓,没必要折腾。


下篇聊衍射极限和艾里斑,光圈为什么不是越小越好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询