第四章:光学成像原理——成像模型、分辨率与视场、畸变校正、景深计算

各位工程师朋友,大家好。这一章我们聊聊光学成像。说实话,在视觉引导焊接系统里,光学成像就是系统的「眼睛」。眼睛不好使,后面算法再牛也白搭。我见过太多项目,最后卡在成像质量上,不得不返工换镜头、调光源。所以,这一章的内容,我建议你认真看。

4.1 成像模型:从三维世界到二维图像

我们先从最基础的开始。相机是怎么把三维世界变成二维图像的?

说白了,就是小孔成像原理的升级版。一个理想的光学系统,可以用针孔模型来描述。这个模型假设所有光线都通过一个点——光心,然后投射到成像平面上。

数学上,它表达为:

u = f * (X / Z) + u0
v = f * (Y / Z) + v0

其中 (X, Y, Z) 是空间点坐标,(u, v) 是图像像素坐标,f 是焦距, (u0, v0) 是主点偏移。

嗯,这里要注意:实际镜头不是完美的针孔。真实镜头有透镜,会引入畸变。但针孔模型是基础,所有标定算法都从它出发。

我在项目中遇到过一件事:有个同事直接用针孔模型算焊接轨迹,结果偏差了2毫米。为什么?因为镜头畸变没校正。所以,理解模型只是第一步,后面还得处理现实问题。

核心要点:针孔模型是理想情况,实际系统必须考虑畸变。标定就是求解从「理想模型」到「实际成像」的映射关系。

4.2 分辨率与视场:选镜头的两个硬指标

选镜头时,我最先看两个参数:分辨率和视场。这两个参数直接决定了你能看清多小的焊缝,以及能覆盖多大的焊接区域。

分辨率,说白了就是相机能分辨的最小细节。它由传感器像素尺寸和镜头光学质量共同决定。公式很简单:

分辨率(mm/pixel)= 视场宽度(mm) / 图像宽度(pixel)

举个例子:如果你的视场是100mm宽,相机是2000像素宽,那每个像素对应0.05mm。理论上你能分辨0.05mm的细节。但实际中,受镜头MTF(调制传递函数)影响,可能只能到0.1mm。

视场则取决于焦距和传感器尺寸。焦距越短,视场越大。但短焦距镜头畸变更明显,这是个 trade-off。

焦距 (mm) 视场角 (水平) 典型应用场景
8 约56° 大范围焊缝定位
12 约39° 中等尺寸工件
16 约30° 精细焊缝跟踪
25 约20° 高精度小区域

我个人习惯:先确定需要的视场,再反推焦距。比如焊接一个500mm长的工件,我至少需要600mm的视场(留点余量)。然后根据传感器尺寸算焦距。你想想看,如果视场选小了,焊缝跑到画面外,那整个系统就废了。

我的经验:视场最好比工件大20%-30%。这样即使工件摆放有偏差,也能完整成像。我曾经吃过亏,视场刚好卡着工件边缘,结果一次定位偏差就导致图像截断,焊接轨迹全错。

4.3 畸变校正:让直线变回直线

畸变是镜头的「原罪」。尤其是广角镜头,画面边缘的直线会变成曲线。焊接引导系统对畸变非常敏感——你想想看,如果焊缝在画面边缘弯曲了,机器人按这个轨迹走,焊出来肯定歪。

畸变主要分两种:

  • 径向畸变:光线通过透镜边缘时弯曲更厉害。表现为「桶形畸变」(画面向外凸)或「枕形畸变」(画面向内凹)。
  • 切向畸变:镜头和传感器不平行导致的。表现为画面倾斜或拉伸。

校正方法,我一般用张正友标定法。流程是这样的:

  1. 打印一张棋盘格标定板
  2. 从不同角度拍10-20张照片
  3. 用OpenCV的 calibrateCamera() 函数计算内参和畸变系数
  4. undistort()initUndistortRectifyMap() 生成校正后的图像

代码示例(Python + OpenCV):

import cv2
import numpy as np

# 准备标定板角点
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*9, 3), np.float32)
objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)

objpoints = []  # 世界坐标系中的点
imgpoints = []  # 图像坐标系中的点

# 遍历标定图片
for fname in glob.glob('calib_images/*.jpg'):
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)
    if ret:
        objpoints.append(objp)
        corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners2)

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

# 校正单张图像
img = cv2.imread('test.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

避坑指南:我曾经用一张标定板照片就做校正,结果畸变参数完全不对。后来才知道,至少需要10张不同角度的照片,而且标定板要占画面1/3以上。另外,标定板必须平整,我见过有人用打印纸贴在弯曲的纸箱上,校正后图像反而更扭曲。

4.4 景深计算:焊接时别让焊缝跑出焦平面

景深,就是图像清晰的范围。焊接时,工件表面不是完全平的,焊缝可能有起伏。如果景深太小,焊缝稍微一偏就模糊了。

景深由三个因素决定:

  • 光圈:光圈越小(F值越大),景深越大。但光圈太小会引入衍射,图像反而变模糊。
  • 焦距:焦距越短,景深越大。但短焦距镜头畸变大。
  • 拍摄距离:距离越远,景深越大。但分辨率会下降。

计算公式(近似):

景深 = 2 * (F值 * 弥散圆直径 * 拍摄距离^2) / (焦距^2)

弥散圆直径一般取0.01-0.03mm,取决于传感器像素大小。

举个例子:

参数
焦距 12mm
光圈 F8
拍摄距离 500mm
弥散圆 0.02mm
景深 约±15mm

也就是说,在±15mm范围内,图像都是清晰的。对于大多数焊接场景,这个景深够用了。但如果焊缝起伏超过30mm,你就得考虑缩小光圈或换短焦距镜头。

我的建议:焊接系统里,我一般把光圈设在F8-F11之间。F2.8虽然进光多,但景深太浅,焊缝稍微一偏就糊了。F16以上虽然景深大,但衍射效应会让图像变「肉」。F8是个不错的平衡点。

知识体系总览

下面这张图,是我自己总结的本章知识结构。你可以把它当作一个快速索引:

光学成像原理知识体系 成像模型 分辨率与视场 畸变校正 景深计算 核心内容 • 针孔模型原理 • 坐标系转换 • 内参/外参矩阵 • 投影方程 核心内容 • 分辨率计算 • 视场与焦距关系 • 传感器尺寸影响 • 选型权衡策略 核心内容 • 径向畸变 • 切向畸变 • 张正友标定法 • OpenCV实现 核心内容 • 景深公式 • 光圈/焦距影响 • 弥散圆概念 • 焊接场景应用 核心目标:为视觉引导焊接系统提供清晰、无畸变的图像 标定 → 校正 → 选型 → 调参,四步缺一不可 四个模块相互关联:成像模型是基础,分辨率与视场决定硬件选型 畸变校正保证几何精度,景深计算确保焊接过程图像稳定

好了,这一章的内容就到这里。光学成像原理是视觉系统的基石,你把这些概念吃透了,后面设计系统时会少走很多弯路。记住:成像质量决定了整个系统的上限,算法再好也救不了一张模糊的照片。


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