4. 相机成像模型:针孔相机模型、透镜畸变、内参矩阵

聊到手眼标定,绕不开一个基础问题——相机到底是怎么“看”世界的?

你想想看,一个三维空间里的点,怎么就能映射到一张二维图片上?这背后就是相机成像模型在起作用。我个人习惯把相机模型拆成三块来理解:针孔模型(理想情况)、透镜畸变(现实误差)、内参矩阵(数学表达)。

嗯,咱们一个一个说。

4.1 针孔相机模型:理想化的“小孔成像”

针孔模型是最经典的成像模型。说白了,就是初中物理课上的小孔成像实验——光线穿过一个小孔,在后面的平面上形成倒立的像。

但在机器视觉里,我们通常把成像平面“虚拟”地放到相机前方,这样图像就是正立的,计算起来也方便。这个虚拟平面,就是归一化成像平面。

从三维世界到二维图像,经历了四次坐标变换:

  1. 世界坐标系 → 相机坐标系(刚体变换,旋转+平移)
  2. 相机坐标系 → 归一化成像平面(透视投影)
  3. 归一化成像平面 → 像素坐标系(内参缩放+平移)

用数学公式表达就是:

λ * [u, v, 1]^T = K * [R | t] * [Xw, Yw, Zw, 1]^T

其中:

  • [u, v] 是像素坐标
  • [Xw, Yw, Zw] 是世界坐标
  • K 是内参矩阵
  • [R | t] 是外参矩阵
  • λ 是尺度因子

关键理解:针孔模型是线性的,但现实中的镜头不是针孔,所以我们需要引入畸变模型来修正。

4.2 透镜畸变:现实世界的“不完美”

我在项目中遇到过最头疼的问题,就是标定板拍出来边缘是弯的。你想想看,一个正方形的标定板,在图像边缘变成了“枕形”或“桶形”,这还怎么标定?

畸变主要分两类:

畸变类型 产生原因 视觉效果 数学模型
径向畸变 透镜形状不完美(球面误差) 直线变弯曲,越靠近边缘越明显 δr = k1*r^3 + k2*r^5 + k3*r^7
切向畸变 透镜与成像平面不平行(装配误差) 图像倾斜、梯形失真 δx = 2*p1*x*y + p2*(r^2 + 2*x^2)

径向畸变又分两种:

  • 桶形畸变:图像中心向外膨胀(广角镜头常见)
  • 枕形畸变:图像边缘向内收缩(长焦镜头常见)

切向畸变嘛,说白了就是镜头装歪了。我见过一个案例,某品牌的工业相机,出厂时镜头座就有0.1mm的偏斜,导致整个视场的切向畸变都偏大。嗯,这种问题只能通过标定来补偿。

避坑指南:我曾经在标定一个120度广角镜头时,只用了2个径向畸变参数(k1, k2),结果标定残差一直下不去。后来加了k3参数,残差立刻降了一个数量级。所以,对于大畸变镜头,一定要用3个径向畸变参数。

4.3 内参矩阵:相机的“身份证”

内参矩阵 K 描述了相机内部的几何特性。它把归一化成像平面上的点,映射到像素坐标系上。

标准形式:

K = [fx,  0, cx;
      0, fy, cy;
      0,  0,  1]

参数含义:

  • fx, fy:焦距在x、y方向上的像素当量(单位:像素)
  • cx, cy:主点坐标(光轴与成像平面的交点,单位:像素)

你可能会问:为什么焦距会有两个值?

因为像素不一定是正方形的。工业相机里,有些传感器的像元尺寸是5.3μm × 5.3μm,那就是正方形;但有些是5.3μm × 5.0μm,那就不是正方形。所以 fx 和 fy 会略有差异。

个人经验:我习惯在标定完成后,先看一眼 cx 和 cy 是否接近图像中心。如果偏差超过10个像素,我会检查标定板是否拍偏了,或者镜头是否松动。这个“直觉检查”帮我发现过好几次标定数据异常。

4.4 完整成像模型:从世界坐标到像素坐标

把上面三块拼起来,就是完整的成像模型:

  1. 世界坐标 Pw 通过外参 [R|t] 转到相机坐标 Pc
  2. 相机坐标 Pc 通过透视投影转到归一化平面 Pn
  3. 归一化坐标 Pn 加上畸变修正,得到 Pd
  4. 畸变后的坐标 Pd 通过内参 K 转到像素坐标 Puv

这个流程,我画了一张图帮你理解:

相机成像模型完整流程 世界坐标 Pw 相机坐标 Pc 归一化坐标 Pn 畸变坐标 Pd 像素坐标 Puv 外参 [R|t]:刚体变换 透视投影:除以 Zc 畸变模型:径向 + 切向 内参矩阵 K:缩放 + 平移

4.5 HALCON中的相机模型与标定

在HALCON里,相机模型用 cam_par 这个数据结构来表示。你可以通过 create_calib_data 创建标定数据,然后调用 calibrate_cameras 来求解内参和畸变参数。

常用的标定算子:

算子 功能 输出
create_calib_data 创建标定数据对象 CalibDataID
set_calib_data_cam_param 设置相机初始参数 —
find_calib_object 在图像中查找标定板 标定点坐标
calibrate_cameras 执行标定计算 内参、外参
get_calib_data 获取标定结果 相机参数

一个典型的标定流程:

// 创建标定数据
create_calib_data ('calibration_object', 'calibration_plate', [], [], CalibDataID)

// 设置相机初始参数(先给个大概值)
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.008, 0, 0.008, 0, 640, 480, 640, 480])

// 对每张标定图像查找标定板
for i := 1 to 15 by 1
    read_image (Image, 'calib_' + i$'02d')
    find_calib_object (Image, CalibDataID, 0, 0, i, [], [])
endfor

// 执行标定
calibrate_cameras (CalibDataID, Errors)

// 获取标定结果
get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)

小技巧:标定完成后,我习惯用 disp_caltab 把标定板重投影到图像上,看看重投影误差。如果误差超过0.3像素,我会检查标定板是否弯曲,或者图像是否模糊。这个习惯帮我避免过很多次“标定成功但定位不准”的尴尬。

4.6 本章小结

相机成像模型是手眼标定的数学基础。你只有理解了:

  • 针孔模型如何把三维点投影到二维平面
  • 畸变模型如何修正镜头的物理误差
  • 内参矩阵如何描述相机的内部几何

才能在后续的手眼标定中,准确地把相机坐标系和机器人坐标系关联起来。