简介:本文系统阐述单目相机姿态精准估计与测距的Python实现方法,包含相机标定、特征匹配、PnP解算及测距误差分析等核心环节,提供完整代码示例与工程优化建议。
单目视觉系统通过单个摄像头实现空间定位与距离测量,其核心在于建立图像像素坐标与三维世界坐标的映射关系。相较于双目视觉,单目方案具有硬件成本低、结构简单的优势,但需要解决尺度模糊性这一关键问题。
在机器人导航、增强现实等应用场景中,精确的相机姿态(位置与旋转)估计和距离测量是基础能力。典型技术路线包括:基于特征点的PnP(Perspective-n-Point)解算、基于深度学习的单目深度估计、以及运动恢复结构(SfM)方法。本文重点探讨基于特征点的传统几何方法,因其具有可解释性强、计算效率高的特点。
相机标定是建立图像坐标系与世界坐标系转换关系的关键步骤。采用张正友棋盘格标定法,通过拍摄不同角度的棋盘图像,计算相机内参矩阵:
import cv2import numpy as npimport glob# 准备标定图像路径images = glob.glob('calibration_images/*.jpg')# 棋盘格参数square_size = 2.5 # cmpattern_size = (9, 6) # 内部角点数量# 初始化对象点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) * square_size# 存储对象点和图像点objpoints = [] # 3D空间点imgpoints = [] # 2D图像点for fname in images:img = cv2.imread(fname)gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 查找角点ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)if ret: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)# 相机标定ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)print("相机内参矩阵:\n", mtx)print("畸变系数:\n", dist)
通过匹配点对计算基础矩阵F,建立两视图间的对极几何关系:
def compute_fundamental_matrix(pts1, pts2):"""pts1, pts2: Nx2 numpy数组,匹配点坐标返回: 基础矩阵F (3x3)"""assert pts1.shape == pts2.shapeassert pts1.shape[1] == 2# 构造A矩阵A = np.zeros((len(pts1), 9))for i, (p1, p2) in enumerate(zip(pts1, pts2)):x1, y1 = p1x2, y2 = p2A[i] = [x2*x1, x2*y1, x2, y2*x1, y2*y1, y2, x1, y1, 1]# SVD分解_, _, V = np.linalg.svd(A)F = V[-1].reshape(3, 3)# 强制秩为2U, S, V = np.linalg.svd(F)S[-1] = 0F = U @ np.diag(S) @ Vreturn F / np.linalg.norm(F)
给定3D-2D点对应关系,通过PnP算法估计相机位姿。采用RANSAC框架提高鲁棒性:
def solve_pnp_ransac(obj_pts, img_pts, camera_matrix, dist_coeffs):"""obj_pts: 3D点坐标 (Nx3)img_pts: 对应的2D图像点 (Nx2)camera_matrix: 相机内参dist_coeffs: 畸变系数返回: (成功标志, 旋转向量, 平移向量, 内点索引)"""assert len(obj_pts) >= 4 # 至少需要4个点# 使用SOLVEPNP_EPNP方法,适用于任意点数success, rvec, tvec, inliers = cv2.solvePnPRansac(obj_pts, img_pts, camera_matrix, dist_coeffs,flags=cv2.SOLVEPNP_EPNP,reprojectionError=3.0, # 重投影误差阈值(像素)iterationsCount=1000, # RANSAC迭代次数confidence=0.99 # 置信度)return success, rvec, tvec, inliers
将估计的位姿应用于3D点投影验证:
def visualize_pose(obj_pts, img_pts, rvec, tvec, camera_matrix, img):"""可视化投影结果与原始点对比"""# 投影3D点到图像平面pts_proj, _ = cv2.projectPoints(obj_pts, rvec, tvec, camera_matrix, None)pts_proj = pts_proj.reshape(-1, 2)# 绘制原始点和投影点img_vis = img.copy()for p1, p2 in zip(img_pts, pts_proj):cv2.circle(img_vis, tuple(p1.astype(int)), 5, (0, 255, 0), -1) # 绿色: 原始点cv2.circle(img_vis, tuple(p2.astype(int)), 3, (0, 0, 255), -1) # 红色: 投影点# 计算重投影误差reproj_errors = np.sqrt(np.sum((img_pts - pts_proj)**2, axis=1))mean_error = np.mean(reproj_errors)print(f"平均重投影误差: {mean_error:.2f} 像素")return img_vis
当目标物体尺寸已知时,可通过相似三角形原理计算距离:
def distance_estimation(pixel_width, real_width, focal_length):"""pixel_width: 物体在图像中的像素宽度real_width: 物体的实际物理宽度focal_length: 相机焦距(像素单位)返回: 估计距离(单位与real_width一致)"""if pixel_width <= 0 or focal_length <= 0:raise ValueError("参数必须为正数")return (real_width * focal_length) / pixel_width# 示例使用focal_length = 1500 # 从标定获得real_width = 0.2 # 物体实际宽度(米)pixel_width = 50 # 检测到的像素宽度distance = distance_estimation(pixel_width, real_width, focal_length)print(f"估计距离: {distance:.2f} 米")
测距精度受以下因素影响:
优化策略:
本文提供的Python实现方案经过实际项目验证,在Intel RealSense D435和普通USB摄像头上均能达到亚分米级定位精度。开发者可根据具体硬件调整参数,建议从静态场景开始测试,逐步过渡到动态场景应用。