OpenCV相机标定与位姿估计实战:从原理到代码实现

📅 2026/8/2 14:22:28
OpenCV相机标定与位姿估计实战:从原理到代码实现
1. 项目缘起从一张“歪”照片到三维世界重建不知道你有没有遇到过这种情况用手机拍一张建筑的照片明明楼是笔直的但照片里靠近边缘的线条却有点“往外弯”或者用广角镜头拍集体照两边的人脸被拉得有点变形。这背后其实是相机的镜头在“作怪”——它并非完美的针孔光线穿过透镜时会发生畸变。在计算机视觉领域尤其是做三维重建、机器人导航、AR/VR这些硬核应用时这种“作怪”是绝对不能容忍的。我们需要精确地知道相机是如何“看”世界的这个过程就叫相机标定。而今天要聊的就是用OpenCV-Python这个几乎人手一套的工具箱来完成一次完整的相机标定并且更进一步利用最常见的棋盘格标定板不仅算出相机的“内在性格”内参和畸变系数还能实时估算出这个棋盘格相对于相机的“姿势”位姿。这听起来很学术但想象一下你做一个AR应用手机摄像头识别到桌面上的一个图案就能让一个虚拟的恐龙稳稳地“站”在图案上这个让虚拟物体和真实世界对齐的关键技术就是位姿估计。整个过程我们不需要昂贵的专业设备一张打印的棋盘格、一台普通的摄像头甚至手机摄像头和Python环境就能搞定。我最初接触这个是因为一个机器人抓取项目需要让机械臂的“眼睛”相机准确知道目标物体的位置和朝向。市面上成熟的视觉库固然好用但黑盒操作遇到奇怪问题时你根本不知道问题出在图像预处理、特征点检测还是模型本身上。自己动手实现一遍标定和位姿估计的完整流程就像给相机做了一次全面的“体检”之后无论用它做什么心里都有底。这篇文章我就把我从环境搭建、原理理解、代码实现到实际踩坑的完整经验分享出来目标是让你看完后能独立完成一套可运行、可调试、可应用于自己项目的视觉感知基础模块。2. 核心原理拆解相机如何“看见”与世界如何“被定位”在动手写代码之前我们必须把几个核心概念掰扯清楚。很多人调包跑通Demo后依然一头雾水就是因为跳过了原理部分。我会尽量用比喻和图示在脑海里来解释。2.1 相机模型从三维世界到二维像素的数学旅行相机成像本质上是把三维空间中的点[X, Y, Z]映射到二维图像平面[u, v]的过程。最常用的模型是针孔相机模型它假设光线通过一个无限小的孔成像。1. 内参矩阵相机的“身份证”内参矩阵描述了相机自身的属性与它的位置和朝向无关。主要包括焦距(fx, fy)可以理解为相机镜头“汇聚光线”的能力。fx f / dx,fy f / dy其中f是物理焦距dx, dy是每个像素在感光元件上的物理尺寸。由于制造工艺fx和fy通常接近但不完全相等。主点(cx, cy)理论上图像正中心的像素坐标。但由于组装偏差光轴可能并不穿过正中心。畸变系数(k1, k2, p1, p2, [k3])这是对理想针孔模型的修正。镜头畸变主要有两种径向畸变由镜头形状引起表现为“桶形畸变”图像边缘向内弯曲或“枕形畸变”图像边缘向外膨胀。用k1, k2, k3来校正。切向畸变由镜头组装时与成像平面不平行引起。用p1, p2来校正。内参矩阵K通常是一个3x3的矩阵K [ fx, 0, cx; 0, fy, cy; 0, 0, 1 ]我们的标定目标之一就是求出这个K和畸变系数。2. 外参矩阵相机在哪儿朝哪儿看外参描述了相机坐标系与世界坐标系的相对关系。它由一个3x3的旋转矩阵R和一个3x1的平移向量t组成。[R | t]合起来是一个3x4的矩阵作用是将世界坐标点转换到相机坐标系下。P_camera R * P_world t位姿估计的核心就是求解这个R和t。对于棋盘格我们通常定义其平面为Z0这样世界点就是[X, Y, 0]大大简化了计算。2.2 棋盘格为什么是标定神器棋盘格图案简单角点黑白方格相交的点检测稳定、精确。OpenCV提供了现成的函数cv2.findChessboardCorners来寻找这些角点。我们预先知道棋盘格上每个角点的物理世界坐标例如间距30mm这些是“已知的真实3D点”。同时我们在图像中检测到它们的“2D像素坐标”。有了多组这样的3D-2D点对就可以通过张正友标定法等算法求解出相机的内参和畸变系数。当内参已知后对于一张新的包含棋盘格的图片已知世界3D点棋盘格角点和对应的图像2D点求解相机相对于这个棋盘格的R和t就变成了一个Perspective-n-Point (PnP)问题。OpenCV的cv2.solvePnP函数就是干这个的。注意这里有一个关键理解。标定时我们用的是多张不同姿态的棋盘格图片来求解内参这是一个全局优化过程。而位姿估计是在内参已知的前提下针对单张图片求解当前棋盘格的外参。3. 手把手环境搭建与数据采集理论有点烧脑我们开始动手。确保你的Python环境是3.6以上然后我们一步步来。3.1 安装OpenCV别在第一步踩坑打开你的终端或命令提示符最稳妥的安装命令是pip install opencv-python opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simpleopencv-python是主模块。opencv-contrib-python包含了主模块以及额外的贡献模块有些高级功能在里面。对于标定来说主模块就够了但装contrib可以避免未来用到某些功能时找不到。-i参数指定了清华镜像源下载速度会快很多。安装完成后在Python中运行import cv2; print(cv2.__version__)验证。如果报错“ModuleNotFoundError”99%是环境问题。请检查你是否在正确的Python环境下安装比如系统有多个Python或者用了Anaconda但没激活环境。对于VSCode用户务必在右下角选择正确的Python解释器。3.2 制作与打印标定板细节决定精度你可以从OpenCV官网下载棋盘格图或者用代码生成。我更推荐后者因为可以精确控制参数。import cv2 import numpy as np # 定义棋盘格参数 pattern_size (9, 6) # 内部角点数量 (width, height)注意是角点数不是方格数 square_size 30 # 每个方格的实际物理尺寸单位毫米这个值至关重要 image_size (1000, 1000) # 生成图片的像素尺寸 # 计算每个方格的像素大小假设打印DPI为300 dpi 300 mm_per_inch 25.4 pixel_per_mm dpi / mm_per_inch square_pixel int(square_size * pixel_per_mm) # 生成一个纯白画布 board_image np.ones((image_size[1], image_size[0]), dtypenp.uint8) * 255 # 绘制棋盘格 for i in range(pattern_size[1] 1): for j in range(pattern_size[0] 1): if (i j) % 2 0: start_point (j * square_pixel, i * square_pixel) end_point ((j 1) * square_pixel, (i 1) * square_pixel) cv2.rectangle(board_image, start_point, end_point, 0, -1) # 填充黑色 # 保存 cv2.imwrite(chessboard.png, board_image) print(f棋盘格已生成每个方格约 {square_size}mm。请使用高质量纸张打印并确保平整。)关键细节pattern_size (9, 6)表示棋盘格有9列、6行内部角点。一个常见的错误是数成方格数。一个(9,6)的角点棋盘格实际黑白方格是(8,5)。square_size 30这个值单位毫米是你后续所有计算的尺度基础。必须精确测量建议用游标卡尺测量打印出来后10个方格的总长度然后除以10来反推更准确的值。打印时务必选择“实际大小”或“100%缩放”不要让它适应页面。打印出来后贴在平整、坚硬的表面如亚克力板、硬纸板。3.3 采集标定图像姿态多样性与光照控制用你的摄像头USB摄像头、笔记本自带摄像头均可拍摄棋盘格。目标是拍摄15-20张覆盖不同姿态的图片。姿态多样性将棋盘格前后移动、左右旋转、上下倾斜、远近变化。尽量让棋盘格出现在图像的不同区域中心、四个角落。保持平整尽量让棋盘格板子本身不要弯曲。光照均匀避免强烈的反光或阴影覆盖角点区域。均匀的漫射光最好。对焦清晰确保角点区域成像清晰模糊的图片会导致角点检测不准。保存图片将图片保存为calib_01.jpg,calib_02.jpg... 放在一个专门的文件夹里。我个人的经验是拍20-25张最后能成功检测到角点的有15-18张就足够做一次高精度的标定了。如果有些图片检测失败比如棋盘格只露出一小部分直接舍弃即可。4. 核心代码实现标定与位姿估计全流程现在进入核心环节。我们将代码分成两部分A. 相机标定离线一次性的B. 实时位姿估计在线持续的。4.1 相机标定计算内参与畸变系数创建一个名为camera_calibration.py的脚本。import cv2 import numpy as np import glob import os def calibrate_camera(images_folder, pattern_size(9,6), square_size30.0): 执行相机标定 :param images_folder: 存放标定图片的文件夹路径 :param pattern_size: 棋盘格内部角点数量 (width, height) :param square_size: 每个棋盘格方格的物理尺寸毫米 :return: 相机矩阵畸变系数旋转向量平移向量重投影误差 # 1. 准备物体点 (0,0,0), (1,0,0), (2,0,0) ....,(8,5,0) objp np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * square_size # 乘以物理尺寸赋予真实世界尺度 # 2. 用于存储所有图像的对象点和图像点 objpoints [] # 真实3D点 imgpoints [] # 图像2D点 images glob.glob(os.path.join(images_folder, *.jpg)) \ glob.glob(os.path.join(images_folder, *.png)) if not images: print(f在文件夹 {images_folder} 中未找到图片) return None, None, None, None, None image_size None success_count 0 for idx, fname in enumerate(images): print(f正在处理: {os.path.basename(fname)}) img cv2.imread(fname) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 3. 查找棋盘格角点 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) # 如果找到优化角点位置 if ret: success_count 1 objpoints.append(objp) # 亚像素级角点检测提高精度 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) imgpoints.append(corners_refined) # 记录第一张成功图片的尺寸 if image_size is None: image_size gray.shape[::-1] # (width, height) # 可视化角点可选 cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret) cv2.imshow(Detected Corners, img) cv2.waitKey(300) # 显示300毫秒 else: print(f - 未在 {os.path.basename(fname)} 中找到角点已跳过。) cv2.destroyAllWindows() if success_count 10: print(f警告仅成功检测到 {success_count} 张图片的角点。建议至少提供10张以上有效图片以获得稳定标定结果。) # 可以继续但精度可能受影响 print(f\n角点检测完成。成功 {success_count}/{len(images)} 张。开始标定计算...) # 4. 执行标定 if objpoints and imgpoints and image_size: ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, image_size, None, None ) # 5. 计算平均重投影误差评估标定质量 mean_error 0 for i in range(len(objpoints)): imgpoints_reproj, _ cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) error cv2.norm(imgpoints[i], imgpoints_reproj, cv2.NORM_L2) / len(imgpoints_reproj) mean_error error mean_error / len(objpoints) print(\n 标定结果 ) print(f相机矩阵 (内参) K:\n{mtx}) print(f\n畸变系数 (k1, k2, p1, p2, k3):\n{dist.ravel()}) print(f\n重投影误差 (像素): {mean_error:.6f}) print(误差越小越好通常小于0.5像素可以认为标定质量很高。) # 6. 保存标定结果 np.savez(camera_calib_params.npz, mtxmtx, distdist, image_sizeimage_size) print(标定参数已保存至 camera_calib_params.npz) return mtx, dist, rvecs, tvecs, mean_error else: print(无法进行标定未找到有效的对象点或图像点。) return None, None, None, None, None if __name__ __main__: # 使用示例 calib_params calibrate_camera( images_folder./calibration_images, # 你的图片文件夹路径 pattern_size(9, 6), square_size30.0 # 单位毫米 )代码关键点解析objp构建棋盘格角点的世界坐标系坐标。我们设定棋盘格平面为Z0所以每个点都是(X, Y, 0)。square_size赋予了它物理尺度。cv2.findChessboardCorners核心检测函数。返回的corners是图像上的亚像素坐标。cv2.cornerSubPix对初步检测的角点进行迭代优化达到亚像素精度这是提高标定精度的关键一步。cv2.calibrateCamera执行标定的核心函数。它使用所有成功图片的objpoints和imgpoints通过最小化重投影误差求解出最优的mtx内参矩阵、dist畸变系数、rvecs和tvecs每张标定图片的棋盘格姿态。重投影误差这是衡量标定质量的黄金指标。它计算的是用标定出的参数将世界点重新投影回图像与最初检测到的角点之间的平均像素距离。这个值越小越好通常低于0.5像素就算非常优秀1像素以内可以接受大于2像素就需要检查标定板、图片质量或检测过程了。运行这个脚本如果一切顺利你会得到内参矩阵和畸变系数并保存为.npz文件。这个文件就是相机的“身份证”后续所有操作都依赖它。4.2 实时位姿估计让相机“知道”棋盘格在哪儿有了内参我们就可以进行实时位姿估计了。创建一个新脚本pose_estimation.py。import cv2 import numpy as np def load_calibration_params(file_pathcamera_calib_params.npz): 加载之前保存的标定参数 data np.load(file_path) mtx data[mtx] dist data[dist] # image_size data[image_size] # 如果需要可以加载 return mtx, dist def create_world_points(pattern_size(9,6), square_size30.0): 生成与世界坐标系对应的3D点棋盘格角点 objp np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * square_size return objp def draw_axis(img, corners, imgpts): 在图像上绘制3D坐标系X红Y绿Z蓝 corner tuple(corners[0].ravel().astype(int)) img cv2.line(img, corner, tuple(imgpts[0].ravel().astype(int)), (0,0,255), 5) # X - 红 img cv2.line(img, corner, tuple(imgpts[1].ravel().astype(int)), (0,255,0), 5) # Y - 绿 img cv2.line(img, corner, tuple(imgpts[2].ravel().astype(int)), (255,0,0), 5) # Z - 蓝 return img def main(): # 1. 加载标定参数 mtx, dist load_calibration_params() if mtx is None: print(无法加载标定参数请先运行标定程序。) return # 2. 准备世界坐标点 pattern_size (9, 6) square_size 30.0 # 必须与标定时一致 objp create_world_points(pattern_size, square_size) # 3. 定义3D坐标系轴点用于可视化 axis np.float32([[3*square_size,0,0], [0,3*square_size,0], [0,0,-3*square_size]]).reshape(-1,3) # Z轴负方向指向相机根据相机坐标系定义 # 4. 打开摄像头 cap cv2.VideoCapture(0) # 0 通常是默认摄像头 if not cap.isOpened(): print(无法打开摄像头) return print(按 q 键退出程序。) print(将棋盘格置于摄像头前尝试不同角度和距离。) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 5. 查找棋盘格角点 ret_corners, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret_corners: # 亚像素优化 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) # 6. 求解PnP问题得到旋转向量和平移向量 # 使用迭代法SOLVEPNP_ITERATIVE它需要至少4个点且对初始值不敏感适合通用场景。 ret_val, rvec, tvec cv2.solvePnP(objp, corners_refined, mtx, dist, flagscv2.SOLVEPNP_ITERATIVE) if ret_val: # 7. 将旋转向量转换为旋转矩阵可选便于理解 # rmat, _ cv2.Rodrigues(rvec) # print(fRotation Matrix:\n{rmat}) # print(fTranslation Vector (mm):\n{tvec}) # 8. 投影3D轴点到2D图像平面用于可视化 imgpts, jac cv2.projectPoints(axis, rvec, tvec, mtx, dist) # 9. 在图像上绘制坐标系和角点 frame draw_axis(frame, corners_refined, imgpts) cv2.drawChessboardCorners(frame, pattern_size, corners_refined, ret_corners) # 10. 在图像上显示位姿信息欧拉角形式更直观 # 将旋转向量转换为欧拉角绕X,Y,Z轴的旋转角度 rmat, _ cv2.Rodrigues(rvec) # 注意OpenCV的旋转顺序通常是ZYX航偏角、俯仰角、滚转角但具体定义取决于你的坐标系约定。 # 这里提供一个常见的转换方法可能存在万向锁问题仅用于简单显示 sy np.sqrt(rmat[0,0]**2 rmat[1,0]**2) singular sy 1e-6 if not singular: x np.arctan2(rmat[2,1], rmat[2,2]) y np.arctan2(-rmat[2,0], sy) z np.arctan2(rmat[1,0], rmat[0,0]) else: x np.arctan2(-rmat[1,2], rmat[1,1]) y np.arctan2(-rmat[2,0], sy) z 0 # 弧度转角度 x_deg, y_deg, z_deg np.degrees([x, y, z]) # 显示信息 info_text fPos: ({tvec[0][0]:.1f}, {tvec[1][0]:.1f}, {tvec[2][0]:.1f}) mm info_text2 fRot: ({x_deg:.1f}, {y_deg:.1f}, {z_deg:.1f}) deg cv2.putText(frame, info_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) cv2.putText(frame, info_text2, (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) # 显示结果 cv2.imshow(Real-Time Pose Estimation, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() if __name__ __main__: main()代码关键点与深度解析cv2.solvePnP这是位姿估计的灵魂函数。它接受世界3D点objp、对应的图像2D点corners_refined、相机内参mtx和畸变系数dist求解出从世界坐标系到相机坐标系的旋转向量rvec和平移向量tvec。flagscv2.SOLVEPNP_ITERATIVE这是最常用的方法基于Levenberg-Marquardt优化需要较好的初始值通常设为0向量对于棋盘格这种平面目标效果很好。其他方法SOLVEPNP_EPNP效率高、SOLVEPNP_IPPE专门用于平面目标速度极快且精度高。如果实时性要求苛刻可以尝试IPPE。旋转向量rvec这是一个3x1的向量其方向表示旋转轴模长表示旋转角度弧度。OpenCV常用它来表示旋转因为它更紧凑。可以用cv2.Rodrigues()函数在旋转向量和3x3旋转矩阵之间转换。平移向量tvec单位是毫米因为我们标定时square_size用的是毫米。tvec[0],tvec[1],tvec[2]分别表示棋盘格原点在相机坐标系下的 X, Y, Z 坐标。重要在OpenCV的相机坐标系中通常X轴向右Y轴向下Z轴指向相机前方。所以如果棋盘格在相机正前方tvec[2]Z值应该是正的。我们绘制坐标系时让Z轴指向棋盘格平面内部即远离相机所以用了负值[0,0,-3*square_size]。欧拉角转换旋转矩阵对人类不直观。代码中提供了一个将旋转矩阵转换为欧拉角俯仰、偏航、滚转的方法。注意欧拉角存在万向锁问题且转换公式不唯一取决于旋转顺序。这里的代码仅用于简单可视化在严肃的工程中如机器人控制应直接使用旋转矩阵或四元数。可视化我们在棋盘格原点绘制了一个3D坐标系红-X绿-Y蓝-Z。当你在摄像头前移动棋盘格时这个坐标系会牢牢“长”在棋盘格上直观地展示了其姿态。运行这个脚本将打印好的棋盘格放在摄像头前你应该能看到角点被检测出来并且一个彩色坐标系被绘制在棋盘格上。移动棋盘格坐标系会随之旋转和平移旁边的数字显示了精确的位置和角度信息。5. 实战避坑指南与精度提升技巧跑通代码只是第一步要获得稳定、可靠、高精度的结果下面这些坑你必须知道怎么绕过去。5.1 标定环节的常见问题与排查问题1角点检测失败或时灵时不灵。原因光照不均、棋盘格反光、图案模糊、棋盘格在图像中占比太小或太大、部分被遮挡。解决光照使用均匀的漫射光源避免点光源造成的高光和阴影。对焦确保相机对焦清晰。手动对焦到棋盘格平面是个好习惯。占比让棋盘格占据图像区域的1/3到2/3为宜。OpenCV参数cv2.findChessboardCorners有一个flags参数可以尝试cv2.CALIB_CB_ADAPTIVE_THRESH cv2.CALIB_CB_NORMALIZE_IMAGE来增强在光照不均下的检测能力。预处理可以对图像进行直方图均衡化或高斯模糊轻微来提升检测鲁棒性。问题2重投影误差很大 2像素。原因这是标定质量差的直接体现。可能源于棋盘格物理尺寸square_size测量不准。标定板不平整在拍照时发生了弯曲。采集的图片姿态分布太集中例如全是正面小幅旋转。角点检测不准确见问题1。解决复核square_size用游标卡尺精确测量打印后10个格子的总长除以9因为9个间隔得到更准确的单个格子尺寸。检查标定板贴在玻璃或亚克力板等刚性平面上。增加姿态多样性重新采集图片确保棋盘格在画面中上下左右、远近、倾斜各种姿态都有。剔除坏图计算每张图片的单独重投影误差把误差明显偏大的图片从标定集中移除重新标定。问题3标定出的焦距fx,fy离谱。现象比如一个1080p的摄像头焦距应该在1000-2000像素之间但标出来只有几百或好几千。原因最可能的原因是square_size单位错了。如果你量的是厘米但代码里写的毫米数就会差10倍。或者pattern_size数错了数成了方格数而非角点数。解决仔细检查square_size的数值和单位以及pattern_size的定义。5.2 位姿估计环节的稳定性与精度优化问题1位姿估计结果抖动严重。原因角点检测的像素级抖动被PnP求解放大。尤其是在远距离或倾斜角度下角点像素的微小变化会导致位姿的较大变化。解决角点优化务必使用cv2.cornerSubPix进行亚像素优化。滤波对求解出的tvec和rvec进行低通滤波如卡尔曼滤波或简单的移动平均。这对于实时应用是必须的。使用SOLVEPNP_IPPE对于平面目标这个算法更快且数值稳定性更好。增加solvePnP的迭代次数和精度修改solvePnP的criteria参数虽然默认值通常够用。问题2估计出的距离Z值不准。原因这是单目视觉的固有缺陷——尺度不确定性。虽然我们通过square_size赋予了物理尺度但这个尺度的误差会直接传递到距离估计上。此外镜头畸变校正不彻底也会影响远处点的精度。解决高精度标定确保标定环节的误差尽可能小。多视角融合如果物体是运动的可以使用视觉里程计或SLAM技术通过多帧信息来优化尺度。传感器融合引入其他传感器如IMU惯性测量单元或激光测距来约束尺度。这是机器人领域的常见做法。控制距离范围在标定使用的距离范围内进行位姿估计精度最高。不要期望用近距离标定的参数去估计很远物体的距离还能很准。问题3当棋盘格倾斜角度很大时检测或估计失败。原因透视变形过大角点检测算法可能失效。或者棋盘格平面接近与光轴平行导致PnP问题病态解不稳定。解决保证棋盘格可见性在应用场景中尽量让标定板以较大面积、较小倾斜角出现在画面中。使用更鲁棒的角点检测可以尝试OpenCV的findChessboardCornersSB自OpenCV 4.5.1引入据说对遮挡和变形更鲁棒。备用方案如果必须处理大角度可以考虑使用AprilTag或ArUco标记。它们具有更强的抗遮挡和抗透视变形能力其位姿估计原理与棋盘格类似也是PnP但检测更稳定。5.3 进阶从棋盘格到任意物体的位姿估计棋盘格只是一个标定和演示工具。在实际项目中我们需要估计任意物体的位姿。思路是获取物体的3D模型你需要知道物体上一系列特征点的3D坐标类似于棋盘格的objp。这可以通过CAD模型、3D扫描或手动测量获得。在图像中检测对应的2D点这需要特征检测与匹配算法。例如对于纹理丰富的物体可以用SIFT、ORB等检测特征点并通过特征描述子进行匹配找到模型3D点与图像2D点的对应关系。使用solvePnP或solvePnPRansac一旦有了至少4组对于平面物体或6组对于非平面物体正确的3D-2D匹配点对就可以调用solvePnP。如果匹配点对中存在外点错误匹配强烈推荐使用solvePnPRansac它利用RANSAC算法鲁棒地估计位姿能自动剔除错误匹配。这个过程将计算机视觉的多个领域特征检测、匹配、几何视觉串联起来是实现物体6D位姿估计3D位置3D旋转的完整 pipeline。我们今天用棋盘格走通的路正是这个复杂 pipeline 中最核心、最基础的几何部分。