从相机标定到YOLOv5单目测距:原理推导与工程实践全解析

1. 相机模型与单目测距基础

第一次接触单目测距时,我和大多数人一样充满疑惑:用单个摄像头真能测出距离吗?后来在停车场倒车时突然想通了——当我们看到后视镜里越来越近的障碍物,其实大脑就在做单目测距。这个生活场景揭示了单目测距的核心:通过物体在图像中的变化反推真实距离

相机模型可以类比为我们的眼睛。想象你拿着一个纸筒看远处的路灯:纸筒代表镜头,视网膜就是图像传感器。当路灯距离变化时,它在纸筒另一端形成的像会变大变小。这个简单的凸透镜成像原理,就是针孔相机模型的物理基础。

在实际工程中,我们需要用数学语言描述这个过程。相机坐标系(Xc,Yc,Zc)的原点位于镜头光心,Z轴指向拍摄方向。假设地面有个路灯底座点Q,它在图像平面的投影Q'的坐标(y')与真实高度(H)存在关系:

y' = f * (Yc / Zc)

其中f是焦距。当相机以俯角α安装时,通过三角函数推导可得距离公式:

distance = H / (tan(α + atan(y'/f)) * cos(α))

这个公式揭示了单目测距的三大依赖:

  1. 相机高度H:测量误差会线性影响结果
  2. 俯角α:需要精确的水平校准
  3. 焦距f:必须通过标定获得准确值

2. 相机标定实战指南

去年给工厂做AGV导航系统时,我花了整整两天调试标定参数。当时用普通USB摄像头采集的测距数据波动超过20%,后来发现是忽略了镜头的桶形畸变。这个教训让我明白:标定质量直接决定测距精度

推荐使用OpenCV的棋盘格标定法,具体操作流程:

  1. 准备标定板:打印10x7的棋盘格图案(每个方格2cm),贴在平整硬纸板上
  2. 多角度拍摄:在不同距离(0.5m-3m)、不同倾斜角度拍摄15-20张照片
  3. 运行标定脚本
import cv2 import numpy as np # 准备对象点 (0,0,0), (1,0,0),...,(9,6,0) objp = np.zeros((6*9,3), np.float32) objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2) # 检测角点 img = cv2.imread('calib.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners = cv2.findChessboardCorners(gray, (9,6), None) # 标定计算 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( [objp], [corners], gray.shape[::-1], None, None)

关键参数说明:

参数名物理意义典型值范围
mtx[0,0]x轴焦距(fx)500-2000像素
mtx[1,1]y轴焦距(fy)通常接近fx
dist[0]径向畸变k1-0.2~0.2
tvecs[0][2]相机高度根据安装位置

实测建议:

  • 标定板要占图像1/3以上面积
  • 使用cv2.CALIB_FIX_ASPECT_RATIO保持fx/fy比例
  • 标定后要用cv2.undistort()验证矫正效果

3. YOLOv5与测距的工程融合

在最新版的YOLOv5 v7.0中,我们可以通过修改detect.py实现端到端测距。核心思路是:利用检测框底部中点作为测距参考点。但实际测试发现,直接使用原始框会导致10%以上的误差,特别是对远处的小物体。

经过多次实验,我总结出三点优化策略:

  1. 参考点修正:对行人等直立物体,将参考点上移5%框高
  2. 动态滤波:对连续5帧距离变化>15%的结果进行中值滤波
  3. 高度补偿:不同物体类别采用预设高度(轿车1.5m,卡车3m等)

关键代码实现:

def estimate_distance(det, cam_matrix, height=1.5): """ 单目测距核心函数 Args: det: YOLOv5检测结果 [x1,y1,x2,y2,conf,cls] cam_matrix: 相机内参矩阵 height: 物体预设高度 """ # 获取检测框底部中点 (u,v) u = (det[0] + det[2]) / 2 v = det[3] - 0.05 * (det[3] - det[1]) # 上移5% # 坐标转换 fx = cam_matrix[0,0] fy = cam_matrix[1,1] v0 = cam_matrix[1,2] # 角度计算 beta = np.arctan((v - v0) / fy) distance = height / np.tan(beta + np.radians(15)) # 假设俯角15度 return distance

部署时的性能优化技巧:

  • 使用torch.jit.trace将模型转为TorchScript
  • 对640x480图像,在Jetson Nano上可达15FPS
  • 开启half=True使用FP16推理可提升30%速度

4. 误差分析与调优方案

上周测试时遇到个有趣现象:同一辆卡车在10米处测距误差仅8%,但在3米处误差却达到25%。经过反复验证,发现是相机俯角导致的比例失真问题。这引出了单目测距的一个重要结论:误差与距离成非线性关系

常见误差来源及解决方案:

误差类型影响程度解决方法
相机高度误差线性影响使用激光测距仪校准
俯角偏差近距离影响大加装水平仪辅助安装
物体高度假设类别相关建立高度-类别映射表
镜头畸变边缘区域明显采用更高精度标定

建议的验证流程:

  1. 在5m、10m、15m位置放置标定物
  2. 采集100帧数据计算平均误差
  3. 根据误差曲线调整高度参数

对于需要厘米级精度的场景(如自动泊车),可以考虑:

  • 增加距离分段补偿系数
  • 融合IMU数据补偿车身震动
  • 使用车道线等参照物进行动态校准

在工程实践中,单目测距最适合1-20米范围的中距离检测。我曾将这套方案用于智能叉车系统,通过结合二维码定位,最终实现了±5%的测距精度。关键是要根据具体场景做参数调优——就像调整相机焦距一样,需要耐心地反复微调。