1. 为什么需要从像素反推三维世界当你用手机拍下一张照片时相机镜头将三维世界压缩成了二维像素阵列。但计算机视觉的魔力在于——我们竟然能从这个二维图像中还原出三维信息这就像通过一张平面地图推测地形海拔关键在于理解三个核心坐标系像素坐标系照片上某个点的(x,y)位置比如(320,240)相机坐标系以镜头光心为原点的3D空间Z轴指向拍摄方向世界坐标系真实场景的绝对坐标比如房间墙角的位置想象你在玩AR测距工具当你点击屏幕上某个像素点时系统需要计算出这个点对应在真实世界中的位置。这个过程就是坐标反投影。2. 坐标系转换的数学原理2.1 从像素到相机坐标系首先需要相机内参矩阵KK [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]其中fx/fy是焦距像素单位(cx,cy)是主点坐标。对于800万像素的手机相机典型值可能是fx 1200, fy 1200cx 400, cy 300给定像素坐标(u,v)和深度值z单位米转换公式为x (u - cx) * z / fx y (v - cy) * z / fy深度z的获取方式使用深度相机直接测量通过立体视觉计算假设目标位于已知平面如地面2.2 从相机到世界坐标系这需要相机的外参——旋转矩阵R和平移向量t。用四元数(qw,qx,qy,qz)表示旋转时转换矩阵T的构建方法import numpy as np def quat_to_matrix(qw, qx, qy, qz, tx, ty, tz): R np.array([ [1-2*qy**2-2*qz**2, 2*qx*qy-2*qz*qw, 2*qx*qz2*qy*qw], [2*qx*qy2*qz*qw, 1-2*qx**2-2*qz**2, 2*qy*qz-2*qx*qw], [2*qx*qz-2*qy*qw, 2*qy*qz2*qx*qw, 1-2*qx**2-2*qy**2] ]) T np.eye(4) T[:3,:3] R T[:3,3] [tx, ty, tz] return T世界坐标逆(T) × 相机坐标3. 深度求解的两种实战方法3.1 基于深度图的方法当使用RGB-D相机时可以直接读取深度信息depth_image cv2.imread(depth.png, cv2.IMREAD_ANYDEPTH) z depth_image[v,u] / 1000.0 # 假设深度图单位是毫米注意陷阱深度图可能存在空洞需要修复不同相机的深度值范围差异很大需要检查深度值的有效性标志位3.2 基于平面假设的方法当测量地面物体时假设目标位于Z0平面# 假设地面方程为 aX bY cZ d 0 plane_normal np.array([a,b,c]) # 单位向量 plane_point np.array([0,0,-d/c]) # 平面上任一点 # 构造从相机中心发出的射线 ray_dir np.linalg.inv(K) [u,v,1] ray_dir ray_dir / np.linalg.norm(ray_dir) # 计算射线与平面交点 t np.dot(plane_point, plane_normal) / np.dot(ray_dir, plane_normal) point_3d t * ray_dir这种方法在机器人导航中很常见比如估算障碍物的位置。4. 完整代码示例AR测距仪下面实现一个点击图片测量真实距离的demoimport cv2 import numpy as np class ARMeasurer: def __init__(self, K, dist_coeffs, image_size): self.K K self.dist_coeffs dist_coeffs self.click_points [] # 假设相机高度1.5米镜头朝下倾斜15度 self.camera_height 1.5 pitch np.radians(-15) self.R np.array([ [1, 0, 0], [0, np.cos(pitch), -np.sin(pitch)], [0, np.sin(pitch), np.cos(pitch)] ]) def on_click(self, event, x, y, flags, param): if event cv2.EVENT_LBUTTONDOWN: # 反投影到地面 uv np.array([x, y, 1]) ray np.linalg.inv(self.K) uv ray self.R.T ray # 转换到世界坐标系 # 计算与地面交点 (Z -camera_height) t -self.camera_height / ray[2] point t * ray print(f测距结果X{point[0]:.2f}m, Y{point[1]:.2f}m) # 在图像上标记 cv2.circle(img, (x,y), 5, (0,0,255), -1) cv2.imshow(AR Measure, img) if __name__ __main__: # 相机参数示例需要实际标定 K np.array([[800, 0, 320], [0, 800, 240], [0, 0, 1]]) measurer ARMeasurer(K, None, (640,480)) img np.zeros((480,640,3), dtypenp.uint8) cv2.imshow(AR Measure, img) cv2.setMouseCallback(AR Measure, measurer.on_click) while True: if cv2.waitKey(1) 27: # ESC退出 break5. 实际应用中的挑战标定误差相机参数的微小误差会导致反投影结果漂移。实测发现焦距误差1%会导致1米处物体定位偏差约3厘米。深度不确定性单目相机无法直接获取深度需要依赖其他信息。我曾在一个项目中尝试用目标尺寸估计深度结果发现对于已知大小的物体如A4纸效果很好但对不规则物体误差可能超过20%数值稳定性当相机俯仰角接近90度时反投影计算会出现奇异点。解决方案是添加异常检测if abs(ray[2]) 0.001: # 接近平行于地面 raise ValueError(视角过于倾斜无法准确测距)6. 进阶技巧多视图几何当有多个角度的图像时可以通过三角测量提高精度。核心是极线几何# 计算基础矩阵F F, mask cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC) # 从F恢复相机运动 E K.T F K _, R, t, _ cv2.recoverPose(E, pts1, pts2, K)这种方法在SLAM系统中广泛应用但需要足够的特征点匹配。