ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenCV相机标定实战:从原理到代码实现与优化

OpenCV相机标定实战:从原理到代码实现与优化

1. 项目概述与核心价值

相机标定,听起来是个挺专业的词,但说白了,就是给相机做一次“体检”和“身份认证”。我们平时用手机、单反拍照片,或者用工业相机做检测、用自动驾驶的摄像头感知环境,这些图像或多或少都存在畸变。你可能没注意,手机拍建筑时边缘的线条有时会微微弯曲,这就是镜头畸变在作祟。更关键的是,我们看到的二维图片,丢失了真实世界的深度信息。相机标定的核心任务,就是通过数学方法,计算出相机的“内参”(它自身的特性,如焦距、畸变系数)和“外参”(它在世界中的位置和姿态),从而建立起二维像素点与三维空间点之间准确的对应关系。

为什么这件事非做不可?举个例子,在视觉测量中,你需要从一张照片里算出某个零件的精确尺寸。如果不知道相机的焦距和畸变情况,你的测量结果可能差之毫厘,谬以千里。在机器人抓取中,机械臂需要根据摄像头看到的物体位置进行抓取,坐标换算不准,抓取就会失败。在增强现实(AR)里,虚拟物体需要严丝合缝地“贴”在真实场景中,这背后依赖的正是精准的相机参数。因此,无论是学术研究、工业应用还是个人项目,只要涉及从图像中获取精确的几何信息,相机标定都是无法绕开的第一步。

OpenCV作为计算机视觉领域的“瑞士军刀”,提供了一整套成熟、高效的相机标定工具。基于OpenCV实现标定,意味着我们站在了巨人的肩膀上,无需从零推导复杂的数学模型,而是聚焦于如何正确使用工具、理解流程、并解决实操中遇到的各种“坑”。本文将带你深入OpenCV相机标定的完整流程,从原理理解、棋盘格准备、代码实现到结果分析与优化,分享我多次标定实践中的经验和教训,目标是让你看完就能动手,做出可靠的结果。

2. 标定原理与OpenCV实现思路拆解

2.1 针孔相机模型与坐标变换链

理解标定,首先要理解相机如何成像。最基础的模型是针孔相机模型。想象一个暗箱,前端有个小孔,光线穿过小孔在后端的感光元件上形成倒立的像。这个过程涉及一系列坐标变换:

  1. 世界坐标系 -> 相机坐标系:物体在真实三维世界中的位置(X_w, Y_w, Z_w),通过旋转矩阵R和平移向量t,转换到以相机光心为原点的相机坐标系(X_c, Y_c, Z_c)。这个R和t,就是我们要求解的外参。
  2. 相机坐标系 -> 图像物理坐标系:根据透视投影,相机坐标系下的三维点(X_c, Y_c, Z_c)被投影到相机成像平面(Z_c = f, f为焦距)上的二维点(x, y)。关系是:x = f * X_c / Z_c, y = f * Y_c / Z_c。
  3. 图像物理坐标系 -> 像素坐标系:成像平面上的物理坐标(x, y)需要转换到以像素为单位的图像坐标(u, v)。这涉及将物理距离转换为像素个数,同时考虑图像中心(主点)可能不在感光元件正中心。公式为:u = α * x + c_x, v = β * y + c_y。其中α, β是焦距f在u, v方向上的像素尺度因子,通常认为α=β, (c_x, c_y)是主点坐标。

将以上步骤合并,并用齐次坐标和矩阵表示,就得到了经典的内参矩阵K:

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

其中,fx = α * f, fy = β * f。内参矩阵K描述了相机内部的几何和光学特性。

2.2 镜头畸变模型

真实的镜头不是理想的小孔,光线通过透镜会产生畸变,主要分为两类:

  • 径向畸变:由透镜形状引起,成像点沿径向方向发生偏移。表现为“桶形畸变”(图像边缘向内弯曲)和“枕形畸变”(图像边缘向外膨胀)。OpenCV通常用三个参数(k1, k2, k3)来建模。
  • 切向畸变:由透镜制造和安装误差引起,透镜与成像平面不平行。OpenCV用两个参数(p1, p2)来建模。

校正公式相对复杂,其作用是根据畸变参数,将观测到的带有畸变的像素坐标,修正回理想针孔模型下的坐标。

2.3 OpenCV的标定策略:张正友标定法

OpenCV的calibrateCamera函数核心实现的是著名的“张正友标定法”。这个方法巧妙之处在于,它不需要昂贵的精密三维标定物,只需要一个打印出来的平面棋盘格(Checkerboard)。其核心思想是:

  1. 单应性矩阵估计:对于每一张拍摄的棋盘格图片,棋盘格平面(Z_w=0)上的角点与其在图像上检测到的角点之间存在一个单应性矩阵H。通过多组点对可以求解出这个H。
  2. 内参约束求解:利用旋转矩阵的正交性(R^T * R = I),可以从多个单应性矩阵中推导出对内参矩阵K的约束方程。通常需要至少3张不同姿态的棋盘格图像来求解所有内参(fx, fy, cx, cy)和畸变参数。
  3. 非线性优化:上述步骤得到一个初始解。OpenCV会在此基础上,将所有棋盘格角点的三维世界坐标、所有图像对应的外参(R, t)、内参K和畸变参数一起,构建一个重投影误差最小化的目标函数,采用诸如Levenberg-Marquardt算法进行非线性优化,得到最优的标定参数。

为什么选择棋盘格?因为它的角点(黑白方格相交的点)定义清晰,易于被算法高精度地自动检测。角点的世界坐标可以预先定义为(0,0,0), (1,0,0), (2,0,0)...,单位是“格子的宽度”,这简化了世界坐标的设定。

3. 实操前的核心准备与要点解析

3.1 标定板的制作与选择

标定板的质量直接决定角点检测的精度,进而影响最终标定结果。

  • 材质:首选哑光材质,如哑光相纸或亚光覆膜打印。绝对要避免反光,反光会导致角点检测失败或位置漂移。我曾试过用普通光面纸打印,在特定光照下角点像在“跳舞”,重投影误差飙升。
  • 平整度:标定板必须平整。贴在硬质平板(如亚克力板、铝板)上是理想选择。皱巴巴的纸张会引入平面外的畸变,破坏“平面棋盘格”这个基本假设。
  • 尺寸与格子数:格子数量不宜过少。OpenCV的findChessboardCorners函数通常需要至少(w, h) = (3, 2)以上的内部角点。常见选择是9x6或10x7(指内部角点数)。格子大小要合适,确保在拍摄距离下,棋盘格在图像中占据足够大的面积(建议占画面1/3到1/2),这样角点像素坐标的精度更高。
  • 备用方案:除了打印,也可以考虑购买专业的陶瓷或玻璃标定板,精度更高,但成本也高。对于大多数非计量级应用,高质量打印已足够。

3.2 图像采集的黄金法则

采集标定图像集是整个流程中最需要耐心和技巧的环节,遵循以下法则可以事半功倍:

  1. 数量要足:至少准备15-20张有效图像。太少会导致优化问题欠定,结果不稳定;太多(如超过50张)会增加计算量,但收益递减。
  2. 姿态要多样:这是最关键的一点。你需要让棋盘格在相机视野中呈现各种不同的姿态:
    • 平移:让棋盘格出现在画面的左上、右下、中心等位置。
    • 旋转:绕X、Y、Z轴分别旋转,模拟俯仰、偏航、横滚。可以倾斜棋盘格。
    • 倾斜:让棋盘格平面与相机成像平面成较大夹角(如45度)。特别注意:必须要有几张棋盘格相对相机是明显倾斜的,这对于解算焦距和畸变参数至关重要。如果所有图片里棋盘格都正对着相机(平行于成像平面),标定会失败或精度极差。
  3. 距离要变化:拍摄一些近景(棋盘格几乎充满画面)和一些远景(棋盘格只占画面一小部分)。这有助于标定在不同物距下的表现。
  4. 光照要均匀、稳定:避免强光直射产生高光或阴影,避免光线闪烁(如日光灯)。均匀的漫射光是最佳选择。采集过程中最好固定光源。
  5. 对焦要清晰:确保棋盘格线条清晰锐利,不要有运动模糊。使用三脚架固定相机是明智之举。

注意:每次拍摄时,确保棋盘格的所有角点都清晰可见,没有被遮挡。如果findChessboardCorners在某张图上失败,不要强行使用,这张图应该被剔除。

3.3 开发环境搭建与OpenCV配置

这里以Python环境为例,C++思路类似。

# 使用pip安装OpenCV,推荐安装contrib版本,它包含更多功能(虽然基础标定不需要)。 pip install opencv-contrib-python

验证安装:

import cv2 print(cv2.__version__)

对于C++项目,你需要从源码编译OpenCV,并在CMakeLists.txt中正确链接opencv_calib3d等库。

一个常见大坑(ModuleNotFoundError):如果你在Python中遇到ModuleNotFoundError: No module named 'cv2',通常有几个原因:1) 虚拟环境未激活或安装位置不对;2) 有多个Python解释器,pip安装到了另一个解释器下;3) 在IDE(如VSCode)中未选择正确的Python解释器。解决方法是检查终端当前环境,使用which pythonwhich pip确认路径一致,或在VSCode中按Ctrl+Shift+P选择正确的解释器。

4. 一步步实现相机标定

4.1 代码结构总览

我们将整个过程封装成一个类或一系列函数,逻辑清晰:

  1. 定义棋盘格规格:内部角点的宽度和高度。
  2. 准备标定数据:遍历图像文件夹,读取每张图,查找角点,存储成功的角点。
  3. 执行标定计算:调用cv2.calibrateCamera
  4. 评估标定结果:计算重投影误差,可视化校正效果。
  5. 保存与加载参数:将标定结果保存为文件(如YAML、XML、JSON)以备后用。

4.2 详细代码实现与解析

import cv2 import numpy as np import glob import os class CameraCalibrator: def __init__(self, chessboard_size=(9, 6), square_size=0.025): """ 初始化标定器 :param chessboard_size: 棋盘格内部角点尺寸 (width, height) :param square_size: 每个棋盘格方格的物理尺寸(单位:米) """ self.chessboard_size = chessboard_size # (w, h) self.square_size = square_size # 终止亚像素角点精化的条件 self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 准备物体点 (0,0,0), (1,0,0), (2,0,0) ....,(w-1, h-1, 0) self.objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32) self.objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) self.objp *= square_size # 乘上物理尺寸 # 存储所有图像的物体点和图像点 self.objpoints = [] # 真实世界3D点 self.imgpoints = [] # 图像中对应的2D点 self.calibration_success = False self.mtx = None # 相机内参矩阵 self.dist = None # 畸变系数 [k1, k2, p1, p2, k3] self.rvecs = None # 旋转向量(每张图) self.tvecs = None # 平移向量(每张图) def find_corners(self, image_path_pattern): """ 从一系列图像中查找并精化棋盘格角点 :param image_path_pattern: 图像路径模式,如 './calib_images/*.jpg' """ images = glob.glob(image_path_pattern) if not images: print(f"未找到匹配的图像: {image_path_pattern}") return False for i, fname in enumerate(images): img = cv2.imread(fname) if img is None: print(f"无法读取图像: {fname}") continue gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找棋盘格角点 ret, corners = cv2.findChessboardCorners(gray, self.chessboard_size, None) if ret: print(f"图像 {os.path.basename(fname)}: 找到角点,正在精化...") # 亚像素级角点精化 corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), self.criteria) self.objpoints.append(self.objp) self.imgpoints.append(corners_refined) # 可选:可视化角点 cv2.drawChessboardCorners(img, self.chessboard_size, corners_refined, ret) cv2.imshow('Corners Found', img) cv2.waitKey(500) # 显示500毫秒 else: print(f"图像 {os.path.basename(fname)}: 未找到角点,已跳过。") cv2.destroyAllWindows() print(f"角点查找完成。成功图像数: {len(self.objpoints)} / {len(images)}") return len(self.objpoints) > 3 # 至少需要3张有效图像 def calibrate(self, image_size): """ 执行相机标定 :param image_size: 图像尺寸 (width, height) :return: 标定是否成功 """ if len(self.objpoints) < 3: print("错误:有效标定图像不足,至少需要3张。") return False print(f"开始标定,使用 {len(self.objpoints)} 张图像,图像尺寸: {image_size}") # 核心标定函数 ret, self.mtx, self.dist, self.rvecs, self.tvecs = cv2.calibrateCamera( self.objpoints, self.imgpoints, image_size, None, None ) self.calibration_success = ret if ret: print("标定成功!") self._print_calibration_results() self._evaluate_reprojection_error() else: print("标定失败。") return ret def _print_calibration_results(self): """打印标定结果""" print("\n=== 相机内参矩阵 ===") print(self.mtx) print("\n=== 畸变系数 ===") print(self.dist.ravel()) # 通常为5个参数:[k1, k2, p1, p2, k3] # 计算并打印等效焦距(像素单位)和主点 fx, fy = self.mtx[0, 0], self.mtx[1, 1] cx, cy = self.mtx[0, 2], self.mtx[1, 2] print(f"\n焦距 (fx, fy): ({fx:.2f}, {fy:.2f}) pixels") print(f"主点 (cx, cy): ({cx:.2f}, {cy:.2f}) pixels") def _evaluate_reprojection_error(self): """计算并打印平均重投影误差,这是标定精度的关键指标""" mean_error = 0 for i in range(len(self.objpoints)): imgpoints2, _ = cv2.projectPoints(self.objpoints[i], self.rvecs[i], self.tvecs[i], self.mtx, self.dist) error = cv2.norm(self.imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) mean_error += error mean_error /= len(self.objpoints) print(f"\n=== 标定精度评估 ===") print(f"平均重投影误差: {mean_error:.6f} 像素") print("说明:误差值小于0.5像素通常认为标定质量很好;0.5-1.0像素可以接受;大于1.0像素则需要检查图像质量和标定过程。") def undistort_image(self, img): """使用标定结果校正单张图像""" if not self.calibration_success: raise ValueError("请先成功执行标定!") h, w = img.shape[:2] # 获取最优的新相机矩阵和ROI(可选,用于裁剪黑边) newcameramtx, roi = cv2.getOptimalNewCameraMatrix(self.mtx, self.dist, (w, h), 1, (w, h)) # 校正图像 dst = cv2.undistort(img, self.mtx, self.dist, None, newcameramtx) # 裁剪ROI x, y, w_roi, h_roi = roi dst_cropped = dst[y:y+h_roi, x:x+w_roi] return dst_cropped, newcameramtx def save_calibration(self, filename='camera_calibration.yaml'): """保存标定参数到YAML文件""" if not self.calibration_success: raise ValueError("无有效的标定结果可保存。") fs = cv2.FileStorage(filename, cv2.FILE_STORAGE_WRITE) fs.write('camera_matrix', self.mtx) fs.write('distortion_coefficients', self.dist) fs.write('image_width', self.mtx.shape[1]) # 假设 fs.write('image_height', self.mtx.shape[0]) fs.release() print(f"标定参数已保存至: {filename}") def load_calibration(self, filename='camera_calibration.yaml'): """从YAML文件加载标定参数""" fs = cv2.FileStorage(filename, cv2.FILE_STORAGE_READ) if not fs.isOpened(): raise FileNotFoundError(f"无法打开文件: {filename}") self.mtx = fs.getNode('camera_matrix').mat() self.dist = fs.getNode('distortion_coefficients').mat() fs.release() self.calibration_success = self.mtx is not None and self.dist is not None if self.calibration_success: print(f"标定参数已从 {filename} 加载。") self._print_calibration_results() return self.calibration_success # ===== 使用示例 ===== if __name__ == '__main__': # 1. 初始化标定器 (假设棋盘格为9x6内部角点,每个格子2.5厘米) calibrator = CameraCalibrator(chessboard_size=(9, 6), square_size=0.025) # 2. 查找角点 (假设标定图片在 './calib_imgs/' 目录下) success_find = calibrator.find_corners('./calib_imgs/*.jpg') if not success_find: print("角点查找失败,请检查图像路径和棋盘格参数。") exit() # 3. 执行标定 (需要输入图像尺寸,这里假设所有图像尺寸一致) # 通常从第一张成功图像获取尺寸 sample_img = cv2.imread(glob.glob('./calib_imgs/*.jpg')[0]) img_size = (sample_img.shape[1], sample_img.shape[0]) # (width, height) success_calib = calibrator.calibrate(img_size) if success_calib: # 4. 保存参数 calibrator.save_calibration() # 5. 测试校正效果 test_img = cv2.imread('./test_image.jpg') if test_img is not None: undistorted_img, _ = calibrator.undistort_image(test_img) # 并排显示对比 combined = np.hstack((test_img, undistorted_img)) cv2.imshow('Original vs Undistorted', combined) cv2.waitKey(0) cv2.destroyAllWindows()

4.3 关键代码段解析与注意事项

  1. findChessboardCornerscornerSubPix

    • findChessboardCorners是粗检测,返回的角点坐标是整数像素级别。
    • cornerSubPix是精化,利用周围像素的梯度信息,将角点定位到亚像素精度(如0.1像素),这对提高标定精度至关重要。(11,11)是搜索窗口大小,(-1,-1)是死区,criteria是迭代终止条件。
  2. calibrateCamera函数参数

    • objpoints,imgpoints: 我们收集到的三维-二维点对集合。
    • image_size: 图像的宽和高,用于初始化内参矩阵。
    • 后两个None参数可以用于指定内参/畸变的初始猜测或固定某些参数,对于初学者保持默认即可。
  3. 重投影误差:这是评估标定质量最直接的指标。它计算的是:用标定得到的参数,将三维点重新投影回二维图像,得到的投影点与你实际检测到的图像角点之间的平均像素距离。误差越小,标定越准。经验值:对于普通USB相机或手机,误差控制在0.3像素以内算优秀,0.5以内良好,1.0以内可用。如果误差大于1.5,就需要回头检查图像质量了。

  4. getOptimalNewCameraMatrixundistort

    • 校正畸变后,图像边缘可能会产生黑色区域(无效像素)。getOptimalNewCameraMatrix可以计算一个新的内参矩阵,通过缩放有效区域来尽可能保留更多像素,其返回的roi就是有效区域的边界框。
    • undistort函数利用内参和畸变系数进行几何变换,校正图像。

5. 标定结果深度分析与优化策略

5.1 理解你的标定结果

拿到标定结果(内参矩阵mtx和畸变系数dist)后,需要会看、会分析:

  • 内参矩阵mtx:

    [[fx, 0, cx], [ 0, fy, cy], [ 0, 0, 1]]
    • fx,fy:以像素为单位的焦距。如果相机传感器是正方形像素且制造完美,fxfy应该非常接近。如果两者相差较大(比如超过5%),可能意味着图像在某个方向上有拉伸(非1:1宽高比),或者标定过程有问题。
    • cx,cy:主点坐标,理论上应该在图像中心附近([width/2, height/2])。如果偏离中心很远,需要警惕。
  • 畸变系数dist: 通常是一个5维向量[k1, k2, p1, p2, k3]

    • k1, k2, k3:径向畸变参数。对于常见的镜头,k1通常为负值(校正桶形畸变),k2k3数值较小。它们的绝对值大小反映了畸变的严重程度。
    • p1, p2:切向畸变参数。对于装配良好的镜头,这两个值通常非常小(接近0)。如果它们很大,可能意味着镜头传感器平面存在较大的不平行度。

5.2 标定质量评估与问题诊断

除了重投影误差,还可以通过以下方式直观评估:

  1. 校正效果可视化:找一张包含大量直线的图像(如建筑、门框),分别显示原图和校正后的图。观察原图中弯曲的线条在校正后是否变得笔直。这是最直接的检验。
  2. 角点重投影可视化:在标定使用的图像上,用绿色圆圈画出检测到的真实角点,用红色十字画出根据标定参数重投影得到的角点。观察两者是否基本重合。如果某些点上红绿分离很远,说明那张图像的角点检测可能不准,或者该姿态下标定模型拟合不好。
  3. 参数合理性判断:检查fx/fy是否合理。例如,一个1/2.3英寸传感器、焦距4mm的镜头,在像素尺寸为1.4μm的相机上,fx大约为4mm / 0.0014mm ≈ 2857像素。如果你的计算结果与此数量级相差甚远,可能square_size(棋盘格物理尺寸)设置错了。

5.3 常见问题排查与解决技巧

以下是我在多次标定中踩过的坑和解决方案:

问题现象可能原因排查与解决思路
findChessboardCorners总是失败1. 棋盘格被遮挡或光照不均。
2. 棋盘格内部角点数(w,h)设置错误。
3. 图像模糊或棋盘格太小。
1. 确保棋盘格完整、光照均匀。用cv2.imshow显示并手动确认。
2.仔细数!是内部角点,比如9x6的棋盘格,指的是内部黑白格子相交的点有9列6行。
3. 重新拍摄更清晰、棋盘格占画面比例更大的图片。
重投影误差很大 (>1.5像素)1. 角点检测不精确(亚像素精化失败)。
2. 图像姿态不够多样,特别是缺少倾斜姿态。
3. 棋盘格不平整或移动时模糊。
4. 标定图像中存在大量无效图片(角点检测勉强成功)。
1. 检查cornerSubPix的窗口大小和迭代条件。尝试增大窗口(如从(11,11)到(15,15))。
2.重新采集图像,确保包含大量不同角度、不同距离、不同位置的棋盘格,特别是大倾斜角的。
3. 使用更平整的标定板和三脚架。
4. 严格筛选图像,只保留角点清晰、检测质量高的图片。可以写个脚本,计算每张图初始角点检测的置信度或对比度。
校正后的图像有严重黑边或扭曲1. 畸变参数过于极端,特别是k3过大。
2. 使用了undistort默认参数,未用getOptimalNewCameraMatrix优化。
1. 检查dist系数。如果k3绝对值很大(例如 > 0.5),可能是标定过程过拟合。尝试只用k1, k2, p1, p2四个参数标定(设置flags=cv2.CALIB_FIX_K3)。
2. 务必使用getOptimalNewCameraMatrix获取新的相机矩阵和ROI,并用其进行校正和裁剪。
标定结果不稳定(每次运行参数差异大)1. 有效图像数量太少。
2. 图像质量或姿态重复性差。
3. 角点检测的随机性。
1. 增加有效图像数量至20-30张。
2. 提高采集标准,确保每张图都清晰、姿态独特。
3. 可以多次运行角点检测和标定,取参数的平均值。或者,使用cv2.calibrateCameraflags参数,如cv2.CALIB_USE_INTRINSIC_GUESS结合一个稳定的初始值。
焦距 (fx, fy) 值异常1.square_size设置错误(单位不对)。
2. 相机分辨率设置错误。
1.这是最常见错误!确认square_size的单位(米)和实际测量值。如果是25mm的格子,应输入0.025
2. 确认输入的image_size(width, height),且与实际图像尺寸一致。

一个高级技巧:标定参数验证集。不要用所有图像来标定。可以保留3-5张质量最好的图像作为“验证集”。用剩余图像标定得到参数后,在验证集上计算重投影误差。如果验证集误差与训练集误差接近,说明标定结果泛化性好;如果验证集误差显著更大,说明可能过拟合了,需要检查标定图像集的多样性和质量。

6. 标定参数的应用与后续工作

成功获取相机参数后,它们就成为你视觉系统的“眼睛的身份证”,可以在多种场景下应用:

  1. 图像去畸变:这是最直接的应用,确保后续所有的图像处理(如边缘检测、特征匹配)都在无畸变的图像上进行,提高几何精度。
  2. 三维重建与测量:结合双目视觉或多视角几何,利用标定参数可以将二维图像点反投影到三维射线,通过三角测量计算深度,实现三维坐标测量。
  3. 机器人手眼标定:将相机坐标系下的物体位置,转换到机器人基座坐标系,引导机械臂进行精准抓取。
  4. 增强现实(AR):将虚拟物体准确地渲染在真实场景的特定位置,需要精确的相机参数来计算透视投影。
  5. 视觉SLAM/VIO:视觉惯性里程计中,精确的相机内参和畸变模型是保证定位精度的基础。

保存与加载:务必将标定参数(mtx,dist)保存到文件(YAML/XML/JSON)。在后续的所有应用代码中,首先加载这些参数。这样避免了每次运行都要重新标定的麻烦。

关于温度与焦距:需要提醒的是,对于某些镜头,尤其是带有自动对焦或变焦功能的,焦距可能会变化。固定焦距的工业镜头通常比较稳定。如果你的应用环境温度变化剧烈,或者相机有自动对焦,可能需要考虑定期重新标定,或者使用焦距固定的镜头并关闭自动对焦。

整个标定过程,从原理理解、精心准备、代码实现到结果分析,是一个系统工程。它没有太多黑魔法,更多的是耐心、细致和对细节的把控。我第一次做标定时,因为square_size单位弄错(把米当成毫米),导致所有测量结果放大了一千倍,闹了大笑话。所以,每一步都值得你反复确认。当你看到扭曲的图像被校正得横平竖直,当你的视觉测量结果与游标卡尺读数吻合时,那种成就感就是对这项工作最好的回报。

返回列表