OpenCV相机标定与位姿估计实战:从棋盘格到三维空间定位

1. 项目概述:从图像到三维世界的钥匙

在计算机视觉和机器人领域,我们经常需要回答一个核心问题:相机“看到”的二维像素点,对应真实三维世界中的哪个位置?反过来,一个已知的三维点,又会落在图像的哪个像素上?这个二维与三维之间的映射关系,就是相机标定要解决的核心问题。而“位姿估计”,则是更进一步,在已知物体三维结构的前提下,通过单张或多张图像,反推出这个物体相对于相机的空间位置(平移)和朝向(旋转)。今天要聊的这个项目,就是利用OpenCV-Python,通过一张随处可见的棋盘格图片,一站式搞定这两件大事:先标定出相机的“身份证”(内参和畸变系数),再利用这张“身份证”和棋盘格已知的物理尺寸,实时估计出棋盘格相对于相机的精确位姿。

这听起来有点学术,但它的应用场景无处不在。比如,在工业自动化中,机械臂需要精准地抓取传送带上的工件,首先就得知道相机“眼里”的工件到底在空间的哪个位置和角度。在增强现实(AR)里,要把一个虚拟的恐龙模型“稳稳地”放在你家的地板上,程序必须知道你的手机摄像头相对于地板平面的精确位姿。甚至在你用手机扫描文档进行矫正时,背后也是类似的原理在起作用。这个项目将手把手带你走通从准备标定板、采集图像、计算参数到最终实现位姿解算的全流程,其中会穿插大量我实际调试中积累的参数选择心得和避坑指南,目标是让你不仅能跑通代码,更能理解每一步背后的数学直觉和工程考量。

2. 核心原理与工具箱拆解

2.1 相机模型与畸变:镜头如何“扭曲”世界

我们首先得建立一个数学模型来描述相机成像的过程。最常用的是针孔相机模型,它假设光线通过一个小孔(光学中心)投影到成像平面上。这里涉及几个关键参数,统称为相机内参

  • 焦距 (fx, fy):以像素为单位。fx = F * sx,其中F是物理焦距,sx是每个像素在x方向的物理尺寸。由于制造工艺,x和y方向的像素尺寸可能不同,所以通常fx和fy不相等。
  • 主点 (cx, cy):理论上图像正中心对应的像素坐标。但镜头光轴与传感器中心可能不对齐,所以主点需要标定。
  • 畸变系数 (k1, k2, p1, p2, [k3]):这是对理想针孔模型的修正。镜头由于物理构造,会引入两种主要畸变:
    • 径向畸变:由镜头形状引起,表现为图像边缘的直线变弯。k1, k2, k3用于校正这种畸变。对于大多数镜头,主要使用k1和k2就够了。
    • 切向畸变:由镜头组装时与成像平面不平行引起。p1, p2用于校正这种畸变。

标定的目标,就是求出这组内参和畸变系数。OpenCV采用张正友标定法,它要求我们从不同角度拍摄多张已知物理尺寸的平面标定板(如棋盘格)图像。通过检测图像中角点的像素坐标,并结合其已知的世界坐标(假设标定板平面为Z=0),建立方程组来求解这些参数。

2.2 位姿估计:解算空间中的位置与朝向

当我们有了相机的内参(知道相机如何看),并且知道一个物体(比如棋盘格)上若干点的三维世界坐标和它们对应的二维图像坐标时,就可以求解物体相对于相机的位姿。这是一个典型的Perspective-n-Point (PnP)问题。

OpenCV提供了多个函数来解决PnP问题,例如cv2.solvePnP。它的输入是:

  1. 物体上一组点的三维坐标(对象点)。
  2. 这些点在图像上对应的二维像素坐标(图像点)。
  3. 相机的内参矩阵和畸变系数。

输出是旋转向量 (rvec)平移向量 (tvec)

  • 旋转向量 (rvec):一个3x1的向量,其方向表示旋转轴,模长表示旋转角度(弧度)。这通常需要转换成更直观的3x3旋转矩阵(使用cv2.Rodrigues函数)或欧拉角。
  • 平移向量 (tvec):一个3x1的向量,表示从相机坐标系原点到物体坐标系原点的位移,单位通常与输入的世界坐标单位一致(例如毫米)。

注意tvec并不是目标到相机的距离!它的三个分量(x, y, z)是在相机坐标系下的坐标。通常,tz(即tvec[2])近似表示沿光轴方向的距离,但更准确的距离应该是sqrt(tx^2 + ty^2 + tz^2)

2.3 OpenCV-Python工具链准备

工欲善其事,必先利其器。我们需要一个配置好的Python环境。我强烈建议使用Anaconda来管理环境,它能极大避免包依赖冲突。

# 1. 创建并激活一个独立的虚拟环境(例如命名为 cv_calib) conda create -n cv_calib python=3.8 conda activate cv_calib # 2. 安装核心包:OpenCV, NumPy, Matplotlib # 使用清华镜像源加速 pip install opencv-python opencv-contrib-python numpy matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple
  • opencv-python:包含OpenCV主模块。
  • opencv-contrib-python:包含主模块以外的额外模块(如更丰富的特征检测器),为了功能完整建议安装。
  • numpy:OpenCV的数组操作基础。
  • matplotlib:用于结果可视化。

安装后,可以在Python中验证:

import cv2 print(cv2.__version__) # 应输出类似 4.8.1 import numpy as np

3. 实战第一步:制作标定板与采集图像

3.1 生成或获取高精度棋盘格

棋盘格是张正友标定法最常用的标定板。你需要知道两个关键信息:

  1. 内角点数量:棋盘格内部黑白格子相交的点数。例如一个9x6的棋盘格,其内角点就是8x5。
  2. 每个方格的物理尺寸:比如边长square_size = 25.0(单位:毫米)。这个尺寸的精度直接影响到标定和位姿估计的绝对精度。

你可以在网上找到很多棋盘格图片,但打印出来的物理尺寸可能不准。更可靠的方法是使用OpenCV或绘图软件生成一个高分辨率的PDF或图像,然后精确打印并贴在平整的硬质表面(如亚克力板、铝板)上

import cv2 import numpy as np # 定义棋盘格规格 (内角点数量:宽9个,高6个) pattern_size = (9, 6) # 定义每个方格的大小(单位:像素,用于生成图像) square_pixel = 100 # 计算图像尺寸 img_width = pattern_size[0] * square_pixel img_height = pattern_size[1] * square_pixel # 创建空白图像 chessboard = np.ones((img_height, img_width), dtype=np.uint8) * 255 # 绘制棋盘格 for i in range(pattern_size[1] + 1): for j in range(pattern_size[0] + 1): if (i + j) % 2 == 0: start_x = j * square_pixel start_y = i * square_pixel chessboard[start_y:start_y+square_pixel, start_x:start_x+square_pixel] = 0 # 保存 cv2.imwrite('chessboard_9x6.png', chessboard) print(f"棋盘格已生成,尺寸:{img_width}x{img_height} 像素,内角点:{pattern_size[0]-1}x{pattern_size[1]-1}")

打印时,务必用游标卡尺测量打印出来后的实际方格尺寸,并以此作为square_size的输入值。

3.2 图像采集的黄金法则

采集图像的质量直接决定标定结果的优劣。以下是几条“血泪”总结的法则:

  1. 数量要足,姿态要丰:至少准备15-20张图像。从不同距离、不同角度(俯仰、偏航、滚转)、不同位置拍摄棋盘格。确保棋盘格在有些图像中靠近边缘,以更好地约束畸变参数。
  2. 对焦清晰,光照均匀:避免模糊和过曝/欠曝。光照不均匀会导致角点检测失败。自然光或均匀的室内光为好,避免强点光源造成的反光。
  3. 棋盘格要平整:这是最重要的前提!如果标定板弯曲,平面假设不成立,标定结果会引入系统误差。务必贴在硬板上。
  4. 覆盖整个视场:让棋盘格出现在图像的各个区域,尤其是四个角落。

我通常的做法是,将棋盘格固定,然后手持相机缓慢移动,从各个方向拍摄视频,再从视频中每隔几帧抽取一帧作为图像,这样效率高且姿态连续。

4. 核心代码实现:标定与位姿估计全流程

4.1 角点检测与标定参数计算

这是最核心的一步。我们将读取所有采集的图像,自动检测角点,并计算相机参数。

import cv2 import numpy as np import glob import os # 配置参数 chessboard_size = (9, 6) # 棋盘格内角点数量 (宽,高) square_size_mm = 25.0 # 每个方格的实际物理尺寸(毫米) # 存储对象点和图像点的数组 obj_points = [] # 真实世界中的3D点 img_points = [] # 图像中的2D点 # 1. 准备真实世界坐标 (对象点) # 假设棋盘格在Z=0平面上,生成如 (0,0,0), (25,0,0), (50,0,0) ... 的坐标 objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) objp *= square_size_mm # 乘以物理尺寸 # 2. 读取图像并查找角点 images = glob.glob('./calib_images/*.jpg') # 假设图像放在此文件夹 if not images: print("错误:未找到图像文件!请检查路径。") exit() print(f"找到 {len(images)} 张图像。开始角点检测...") criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 角点优化迭代条件 for i, fname in enumerate(images): img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找角点 ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None) if ret: # 如果找到,对角点位置进行亚像素级精确化 corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) img_points.append(corners_refined) obj_points.append(objp) # 每张成功的图像都对应同一组对象点 # 可视化角点(可选) cv2.drawChessboardCorners(img, chessboard_size, corners_refined, ret) cv2.putText(img, f'Found: {i+1}', (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('Corners Detection', img) cv2.waitKey(500) # 显示500ms else: print(f"警告:在图像 {os.path.basename(fname)} 中未找到棋盘格角点。") cv2.destroyAllWindows() # 3. 相机标定 print(f"\n成功用于标定的图像数量:{len(img_points)}") if len(img_points) < 10: print("警告:用于标定的图像较少,结果可能不准确。建议增加有效图像至15张以上。") ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) print("\n=== 相机标定结果 ===") print(f"重投影误差 (RMS): {ret:.6f} 像素") print("内参矩阵 K:") print(camera_matrix) print("\n畸变系数 (k1, k2, p1, p2, k3):") print(dist_coeffs.ravel()) # 4. 保存标定结果(供后续使用) np.savez('camera_calib_params.npz', camera_matrix=camera_matrix, dist_coeffs=dist_coeffs, rvecs=rvecs, tvecs=tvecs) print("\n标定参数已保存至 'camera_calib_params.npz'")

关键点解析与心得:

  • cv2.findChessboardCorners:这个函数有时对光照和对比度很敏感。如果检测失败,可以尝试对图像进行直方图均衡化 (cv2.equalizeHist) 或调整对比度亮度。
  • cv2.cornerSubPix至关重要的一步。它将整数像素级的角点位置优化到亚像素精度,能显著提高标定精度。
  • 重投影误差 (RMS):这是标定质量的核心指标。它表示将标定出的3D点用求得的参数重新投影到图像上,与检测到的角点之间的平均像素误差。一般来说,RMS误差小于0.5像素就算很不错了,1像素以内可以接受。如果误差太大,需要检查角点检测是否准确、标定板是否平整、图像是否模糊。
  • dist_coeffs:通常是一个5x1或更多元素的向量。我们最关心前5个[k1, k2, p1, p2, k3]。如果镜头畸变很小,后几项可能接近于0。

4.2 单张图像位姿估计

标定完成后,我们就可以对任意一张包含完整棋盘格的图像,估计其位姿了。

def estimate_pose(image_path, camera_matrix, dist_coeffs, chessboard_size, square_size_mm): """ 估计单张图像中棋盘格的位姿。 """ # 1. 读取并预处理图像 img = cv2.imread(image_path) if img is None: print(f"无法读取图像:{image_path}") return None gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 准备对象点 (与世界坐标系中棋盘格角点对应) objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) objp *= square_size_mm # 3. 检测角点 ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None) if not ret: print(f"在 {image_path} 中未检测到棋盘格。") return None # 亚像素优化 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 4. 求解PnP问题 # 使用迭代法 (SOLVEPNP_ITERATIVE),它需要至少4个点,对平面目标(棋盘格)效果很好。 ret, rvec, tvec = cv2.solvePnP(objp, corners_refined, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) if not ret: print("PnP求解失败。") return None # 5. 将旋转向量转换为旋转矩阵和欧拉角(更直观) rotation_matrix, _ = cv2.Rodrigues(rvec) # 3x3旋转矩阵 # 将旋转矩阵转换为欧拉角(绕ZYX轴旋转,即偏航Yaw、俯仰Pitch、滚转Roll) # 注意:欧拉角存在万向节死锁和顺序定义问题,这里仅作示意。 sy = np.sqrt(rotation_matrix[0,0] * rotation_matrix[0,0] + rotation_matrix[1,0] * rotation_matrix[1,0]) singular = sy < 1e-6 if not singular: yaw = np.arctan2(rotation_matrix[1,0], rotation_matrix[0,0]) pitch = np.arctan2(-rotation_matrix[2,0], sy) roll = np.arctan2(rotation_matrix[2,1], rotation_matrix[2,2]) else: yaw = np.arctan2(-rotation_matrix[1,2], rotation_matrix[1,1]) pitch = np.arctan2(-rotation_matrix[2,0], sy) roll = 0 # 转换为角度 yaw_deg = np.degrees(yaw) pitch_deg = np.degrees(pitch) roll_deg = np.degrees(roll) # 6. 计算距离(从相机原点到棋盘格坐标系原点的欧氏距离) distance_mm = np.linalg.norm(tvec) # 7. 在图像上可视化位姿(绘制3D坐标系) axis_points = np.float32([[3*square_size_mm,0,0], [0,3*square_size_mm,0], [0,0,-3*square_size_mm], [0,0,0]]).reshape(-1,3) img_points_axis, _ = cv2.projectPoints(axis_points, rvec, tvec, camera_matrix, dist_coeffs) img_points_axis = np.int32(img_points_axis).reshape(-1,2) origin = tuple(img_points_axis[3]) img = cv2.line(img, origin, tuple(img_points_axis[0]), (0,0,255), 5) # X轴 - 红色 img = cv2.line(img, origin, tuple(img_points_axis[1]), (0,255,0), 5) # Y轴 - 绿色 img = cv2.line(img, origin, tuple(img_points_axis[2]), (255,0,0), 5) # Z轴 - 蓝色 (指向相机外) # 添加文本信息 cv2.putText(img, f'Yaw: {yaw_deg:.1f}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2) cv2.putText(img, f'Pitch: {pitch_deg:.1f}', (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2) cv2.putText(img, f'Roll: {roll_deg:.1f}', (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2) cv2.putText(img, f'Dist: {distance_mm:.1f} mm', (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2) cv2.putText(img, f'tvec: [{tvec[0][0]:.1f}, {tvec[1][0]:.1f}, {tvec[2][0]:.1f}]', (10, 150), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (200,200,0), 2) return { 'rvec': rvec, 'tvec': tvec, 'rotation_matrix': rotation_matrix, 'euler_angles_deg': (yaw_deg, pitch_deg, roll_deg), 'distance_mm': distance_mm, 'annotated_image': img } # 使用示例:加载标定参数并估计新图像的位姿 calib_data = np.load('camera_calib_params.npz') camera_matrix = calib_data['camera_matrix'] dist_coeffs = calib_data['dist_coeffs'] # 估计某张测试图像的位姿 result = estimate_pose('./test_image.jpg', camera_matrix, dist_coeffs, chessboard_size=(9,6), square_size_mm=25.0) if result: print("\n=== 位姿估计结果 ===") print(f"平移向量 tvec (mm): {result['tvec'].ravel()}") print(f"欧拉角 (Yaw, Pitch, Roll): {result['euler_angles_deg']}") print(f"到相机的距离: {result['distance_mm']:.2f} mm") # 显示带标注的图像 cv2.imshow('Pose Estimation', result['annotated_image']) cv2.waitKey(0) cv2.destroyAllWindows()

关键点解析与心得:

  • cv2.solvePnPflags参数:对于平面目标(棋盘格),cv2.SOLVEPNP_ITERATIVE是默认且稳健的选择。如果点对很多且可能有噪声,可以尝试cv2.SOLVEPNP_EPNPcv2.SOLVEPNP_IPPE专门用于平面姿态估计,速度很快。
  • 欧拉角转换:代码中给出的转换公式(旋转矩阵到欧拉角)只是一种约定(可能是ZYX顺序)。欧拉角存在顺序(如XYZ, ZYX等)和万向节死锁问题,不同领域定义不同。在实际项目中,明确你需要的旋转表示法(旋转矩阵、四元数还是特定顺序的欧拉角),并保持一致。这里可视化仅用于直观理解。
  • 坐标系:OpenCV的相机坐标系通常是:X轴向右,Y轴向下,Z轴指向相机前方。棋盘格坐标系通常定义在棋盘格平面上。绘制时,Z轴负方向(代码中[0,0,-3*square_size_mm])通常指向相机,这样绘制的坐标系更符合“物体指向相机”的直觉。
  • 距离计算np.linalg.norm(tvec)计算的是从相机光心到棋盘格坐标系原点的直线距离。这个距离的精度依赖于标定板物理尺寸square_size_mm的测量精度。

5. 精度评估、优化与常见问题排查

5.1 如何评估你的标定结果?

仅仅跑通代码不够,我们必须量化评估结果的可靠性。

  1. 重投影误差分析:这是最直接的指标。标定函数cv2.calibrateCamera返回的RMS误差是整体平均值。我们还可以计算每张图像的重投影误差,找出误差特别大的“坏图”。

    # 接续标定代码之后 mean_error_per_image = [] for i in range(len(obj_points)): imgpoints2, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], camera_matrix, dist_coeffs) error = cv2.norm(img_points[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) mean_error_per_image.append(error) print(f"图像 {i:2d} 的平均重投影误差: {error:.3f} 像素") print(f"\n所有图像的平均重投影误差: {np.mean(mean_error_per_image):.3f} 像素")

    如果某几张图的误差明显高于其他图(例如高出2倍以上),考虑将它们从标定数据集中移除,重新标定。

  2. 参数合理性检查

    • 焦距fx, fy:它们应该接近,比值fx/fy通常在0.9-1.1之间,如果相差太大,可能是角点检测有系统误差。它们的绝对值应与图像的像素尺寸和镜头的物理焦距有粗略对应关系(例如,一个1/2.8英寸传感器、4mm镜头的相机,fx大概在500-800像素左右)。
    • 主点(cx, cy):理论上应在图像中心(width/2, height/2)附近。如果偏离太远(例如超过图像尺寸的10%),需要警惕。
    • 畸变系数k1, k2通常为负值(桶形畸变)或正值(枕形畸变),绝对值大小反映畸变程度。对于普通镜头,|k1|1e-41e-7量级。p1, p2通常很小(1e-5量级或更小)。

5.2 提升精度的实战技巧

  1. 更多、更高质量的图像:这是提升精度最有效的方法。确保图像清晰、棋盘格平整、覆盖整个视野和所有姿态。
  2. 精确的物理尺寸:用高精度游标卡尺多次测量棋盘格方格尺寸,取平均值。这是所有计算的“尺子”,尺子不准,结果必然有系统误差。
  3. 使用更复杂的畸变模型:OpenCV默认使用(k1, k2, p1, p2, k3)5个参数。对于鱼眼镜头或畸变非常大的镜头,可以使用cv2.fisheye模块的标定方法,或者启用更多参数(如k4, k5, k6),但需要更多数据来约束,否则容易过拟合。
  4. 手动验证角点:对于自动检测失败的图像,或者怀疑角点不准的图像,可以编写一个简单的GUI程序,手动点击或微调角点位置。虽然繁琐,但对于关键应用是值得的。
  5. 多阶段标定:先使用所有图像进行标定,剔除重投影误差大的图像,再用剩下的“好图”重新标定一次。

5.3 常见问题与解决方案速查表

问题现象可能原因排查与解决方案
findChessboardCorners检测失败或部分失败1. 图像模糊、过曝或欠曝。
2. 棋盘格对比度低。
3. 棋盘格部分被遮挡或不在视野内。
4. 传入的patternSize参数错误。
1. 检查图像质量,重新拍摄。
2. 尝试图像预处理:cv2.equalizeHist或调整对比度。
3. 确保棋盘格完整可见。
4. 确认patternSize内角点数量。
重投影误差非常大 (>2像素)1. 角点检测不准确(尤其是边缘角点)。
2. 标定板物理尺寸square_size输入错误。
3. 标定板不平整。
4. 图像数量不足或姿态变化不够。
1. 使用cornerSubPix,并检查角点可视化图。
2. 用卡尺精确测量并核对代码。
3. 确保标定板贴在硬质平面上。
4. 增加图像数量(>15),并涵盖更多角度。
标定后图像矫正仍然扭曲1. 畸变系数求解不准确(可能过拟合或欠拟合)。
2. 使用的畸变模型不适合你的镜头(如鱼眼镜头用了普通模型)。
1. 检查畸变系数值是否合理。尝试减少畸变参数数量(如只用k1,k2,p1,p2)。
2. 鱼眼镜头请使用cv2.fisheye.calibrate
位姿估计结果抖动或不稳定1. 单张图像的角点检测有噪声。
2. PnP求解方法不适合或初值不好。
3. 相机内参/畸变标定不准。
1. 对同一场景连续多帧估计,取平均或中值滤波。
2. 尝试不同的solvePnPflags,如SOLVEPNP_EPNP
3. 重新评估和优化相机标定结果。
欧拉角跳变(如从179度跳到-179度)这是欧拉角表示法的固有缺陷(万向节死锁和角度周期性)。避免直接使用欧拉角进行连续滤波或控制。使用旋转矩阵或四元数作为内部表示,只在需要显示时转换为欧拉角。
距离 (tz) 为负值或很小1. 棋盘格坐标系定义与相机坐标系关系理解有误。
2. PnP求解得到了镜像解。
1. 理解tvec物体原点在相机坐标系下的坐标。如果物体在相机前方,tz应为正。检查绘制的3D轴方向是否正确。
2. 对于平面物体,PnP可能存在两解。使用solvePnPuseExtrinsicGuess参数提供一个合理的初始估计。

6. 项目扩展与高级应用思路

掌握了基础的单目棋盘格标定与位姿估计后,你可以将这个项目扩展到更多有趣和实用的方向:

  1. 实时位姿估计与AR叠加:结合摄像头视频流,实时检测棋盘格并计算位姿。然后利用这个位姿,将3D模型(如一个虚拟的茶壶)准确地“放置”在棋盘格上,实现最简单的增强现实效果。这需要用到OpenGL或Open3D等库进行渲染。

  2. 多相机系统标定:如果你有多个相机(比如双目立体视觉系统),需要对它们进行立体标定,求出两个相机之间的相对位置和姿态(旋转矩阵R和平移向量T)。OpenCV提供了cv2.stereoCalibrate函数。标定好后,就可以进行三维重建,计算像素点的深度信息。

  3. 手眼标定:在机器人领域,相机安装在机械臂末端(Eye-in-Hand)或固定位置(Eye-to-Hand)。需要通过一系列机械臂运动和对应的相机观测,标定出相机坐标系与机械臂末端坐标系(或基座坐标系)之间的变换关系。这用到了cv2.calibrateHandEye函数。

  4. 使用ArUco或Charuco标记:棋盘格需要全部在视野内才能检测。而ArUco标记是二进制编码的方形标记,单个即可提供四个角点,对遮挡和部分可见更鲁棒。Charuco板结合了棋盘格的角点精度和ArUco标记的鲁棒性,是更先进的标定板。OpenCV的aruco模块提供了完整支持。

  5. 集成到实际应用:将标定和位姿估计模块封装成类或函数,作为视觉SLAM(同步定位与建图)、物体抓取、尺寸测量等更大项目的前端部分。这时需要考虑算法的实时性、鲁棒性,并可能需要加入滤波(如卡尔曼滤波)来平滑位姿输出。

这个项目是进入计算机视觉几何领域的绝佳起点。它涉及了从图像处理、几何模型到优化求解的完整链条。我建议你在跑通基础代码后,不要停留在表面,而是尝试去修改参数、故意引入错误(比如输入错误的方格尺寸)、观察结果如何变化,并深入阅读OpenCV官方文档和相关的论文(如张正友的原始论文)。这个过程积累的直觉和经验,远比单纯复制代码有价值得多。在实际项目中,你遇到的挑战会复杂得多,但解决问题的基本思路和工具链,已经在这里了。