用Python和YOLOv5给摄像头装上‘尺子’:手把手实现单目测距(附完整代码)
·
用Python和YOLOv5打造智能测距系统:从原理到工业级实现
在计算机视觉领域,单目测距技术因其硬件成本低、部署简单而备受关注。想象一下,只需一个普通摄像头,就能让机器感知物体距离——这在智能仓储、机器人导航、辅助驾驶等领域具有巨大应用潜力。本文将带您从零构建一个基于YOLOv5的工业级单目测距系统,不仅包含完整代码实现,还会深入探讨误差优化和工程化封装技巧。
1. 环境配置与项目初始化
1.1 创建虚拟环境
为避免依赖冲突,建议使用conda创建独立环境:
conda create -n monocular_ranging python=3.8
conda activate monocular_ranging
1.2 安装核心依赖
pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 -f https://download.pytorch.org/whl/torch_stable.html
pip install opencv-python==4.5.5.64 numpy==1.21.6
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt
注意:CUDA版本需与显卡驱动匹配,无NVIDIA显卡时可使用CPU版本,但性能会显著下降
1.3 项目结构设计
规范的目录结构是工程化的第一步:
monocular_ranging/
├── configs/ # 参数配置文件
├── data/ # 样本数据集
│ ├── calibration/ # 标定图像
│ └── test/ # 测试图像
├── models/ # 自定义模型
├── utils/ # 工具函数
│ └── ranging.py # 测距核心逻辑
├── demo.py # 演示脚本
└── README.md
2. 单目测距核心原理深度解析
2.1 几何模型数学推导
单目测距基于小孔成像模型,其核心公式为:
D = (W × F) / P
其中:
- D:物体到摄像头距离(cm)
- W:物体实际宽度(已知量)
- F:摄像头焦距(像素单位)
- P:物体在图像中的像素宽度
参数获取策略 :
- 物体实际宽度W:使用卡尺直接测量
- 焦距F:通过标定过程计算获得
- 像素宽度P:通过目标检测框的x_max - x_min获得
2.2 误差来源分析
| 误差类型 | 产生原因 | 影响程度 | 解决方案 |
|---|---|---|---|
| 标定误差 | 测量距离不准 | ★★★★ | 使用激光测距仪校准 |
| 检测误差 | YOLO框不准 | ★★★ | 使用更精确的模型 |
| 姿态误差 | 非正面拍摄 | ★★★★ | 增加角度补偿算法 |
| 镜头畸变 | 广角变形 | ★★ | 相机标定去畸变 |
3. 工业级代码实现
3.1 相机标定模块
class CameraCalibrator:
def __init__(self, known_width, known_distance):
self.known_width = known_width
self.known_distance = known_distance
def calculate_focal_length(self, image_path):
"""通过标定图像计算焦距"""
img = cv2.imread(image_path)
results = model(img) # YOLOv5检测
box = results.xyxy[0][0] # 获取检测框
pixel_width = box[2] - box[0]
return (pixel_width * self.known_distance) / self.known_width
3.2 实时测距模块
class DistanceMeasurer:
def __init__(self, focal_length, known_width):
self.focal_length = focal_length
self.known_width = known_width
def measure(self, detection_results):
"""处理YOLOv5检测结果并计算距离"""
distances = []
for *box, conf, cls in detection_results:
pixel_width = box[2] - box[0]
distance = (self.known_width * self.focal_length) / pixel_width
distances.append({
'class': classes[int(cls)],
'distance': round(distance, 1),
'confidence': float(conf)
})
return distances
3.3 可视化增强
def annotate_image(image, results):
"""在图像上绘制检测框和距离信息"""
for item in results:
label = f"{item['class']} {item['distance']}cm (conf: {item['confidence']:.2f})"
color = COLORS[item['class']]
cv2.rectangle(image, (x1,y1), (x2,y2), color, 2)
cv2.putText(image, label, (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
return image
4. 性能优化与工程实践
4.1 多帧融合策略
为提高测量稳定性,可采用时间域滤波:
class TemporalFilter:
def __init__(self, window_size=5):
self.buffer = deque(maxlen=window_size)
def update(self, new_value):
self.buffer.append(new_value)
return np.median(self.buffer)
4.2 角度补偿算法
当摄像头与物体存在俯仰角时,需进行几何校正:
修正距离 = 原始距离 × cos(θ)
其中θ可通过以下方式估计:
- 使用目标高度与宽度比
- 添加惯性测量单元(IMU)
- 基于多视角几何计算
4.3 部署优化技巧
- TensorRT加速 :将YOLOv5模型转换为TensorRT格式
- 多线程处理 :分离图像采集和推理线程
- 边缘部署 :使用OpenVINO优化Intel平台性能
5. 实战:USB摄像头实时测距
5.1 完整流程实现
def main():
# 初始化
calibrator = CameraCalibrator(known_width=15, known_distance=20)
focal_length = calibrator.calculate_focal_length("calib.jpg")
measurer = DistanceMeasurer(focal_length, known_width=15)
filter = TemporalFilter()
# 摄像头初始化
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
while True:
ret, frame = cap.read()
results = model(frame)
distances = measurer.measure(results)
# 应用滤波
for d in distances:
d['distance'] = filter.update(d['distance'])
# 显示结果
frame = annotate_image(frame, distances)
cv2.imshow("Monocular Ranging", frame)
if cv2.waitKey(1) == 27: # ESC退出
break
5.2 常见问题排查
-
检测框跳动严重 :
- 检查光照条件,避免过曝或过暗
- 尝试调整YOLOv5的conf-thres参数
- 增加TemporalFilter的窗口大小
-
距离测量偏差大 :
- 重新校准焦距,确保标定距离准确
- 检查物体实际宽度输入是否正确
- 确认摄像头与物体保持水平
-
性能瓶颈分析 :
# 使用PyTorch Profiler分析 python -m torch.utils.bottleneck demo.py
6. 进阶方向与扩展应用
6.1 多目标协同测距
当场景中存在多个已知尺寸物体时,可交叉验证测量结果:
def cross_validate(distances):
valid_results = []
for i, d1 in enumerate(distances):
for j, d2 in enumerate(distances[i+1:]):
if abs(d1['distance'] - d2['distance']) < 10: # 阈值
valid_results.append((d1['distance']+d2['distance'])/2)
return valid_results
6.2 三维空间定位
结合相机姿态信息,可将距离转换为三维坐标:
def to_3d_position(distance, pixel_x, pixel_y, camera_matrix):
"""将像素坐标转换为三维坐标"""
fx = camera_matrix[0,0]
fy = camera_matrix[1,1]
cx = camera_matrix[0,2]
cy = camera_matrix[1,2]
x = (pixel_x - cx) * distance / fx
y = (pixel_y - cy) * distance / fy
z = distance
return (x, y, z)
6.3 工业应用案例
- 智能货架 :实时监控货物摆放距离
- AGV导航 :障碍物距离检测
- 质量控制 :零件安装位置验证
在完成基础实现后,尝试用不同物体测试系统表现。实际项目中,建议使用已知尺寸的校准板定期校验系统精度。当测量距离超过5米时,考虑改用双目或多传感器融合方案。
更多推荐
所有评论(0)