用Python和YOLOv5给摄像头装上‘尺子’:一个杯子引发的单目测距实战
用Python和YOLOv5给摄像头装上‘尺子’:一个杯子引发的单目测距实战
当你手边只有一个普通的USB摄像头,却想让它具备测量物体距离的"超能力"时,单目测距技术就是你的魔法棒。这个看似高深的计算机视觉应用,其实用生活中常见的物品(比如一个马克杯)就能轻松实现。本文将带你从零开始,用Python和YOLOv5打造一个低成本、高趣味性的单目测距系统。
1. 环境准备与工具选择
在开始之前,我们需要搭建一个稳定的开发环境。推荐使用Python 3.8或更高版本,这个版本在兼容性和性能上都有不错的表现。以下是需要安装的核心库:
pip install torch torchvision opencv-python numpy matplotlib对于YOLOv5,我们直接从官方仓库克隆最新版本:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt注意:如果使用GPU加速,建议安装CUDA 11.3和对应版本的PyTorch以获得最佳性能。
硬件方面,任何一款支持USB3.0的1080p摄像头都能满足需求。我测试使用的是罗技C920,但几十元的普通摄像头同样可以工作。关键在于标定过程的准确性,而不是设备的高端程度。
2. 摄像头标定:用马克杯当标尺
单目测距的核心在于理解相似三角形原理。我们需要先确定摄像头的焦距,这个过程称为标定。具体步骤如下:
- 准备一个已知尺寸的物体(如宽15cm的马克杯)
- 将物体放置在距离摄像头20cm处并拍照
- 使用YOLOv5检测物体在图像中的像素宽度
- 通过公式计算焦距:
焦距 = (像素宽度 × 实际距离) / 实际宽度
def calculate_focal_length(known_width, known_distance, pixel_width): return (pixel_width * known_distance) / known_width实际操作中,建议拍摄多组不同距离的照片求取平均焦距值,这样可以减少测量误差。下表展示了我用马克杯标定时的三组数据:
| 实际距离(cm) | 像素宽度(px) | 计算焦距(px) |
|---|---|---|
| 20 | 320 | 426.67 |
| 25 | 256 | 426.67 |
| 30 | 213 | 426.00 |
可以看到,三组数据计算出的焦距非常接近,最终我们取平均值426.44px作为标定结果。这个值将作为后续所有距离计算的基础。
3. 构建完整的测距流水线
有了焦距参数,我们就可以构建完整的测距系统了。系统工作流程分为三个主要步骤:
- 物体检测:使用YOLOv5实时检测视频流中的目标物体
- 像素测量:获取物体在图像中的包围框宽度(像素单位)
- 距离计算:应用相似三角形原理转换像素距离为实际距离
核心计算函数如下:
def calculate_distance(known_width, focal_length, pixel_width): return (known_width * focal_length) / pixel_width为了提高实用性,我们可以添加一些增强功能:
- 多物体支持:通过修改YOLOv5的输出处理,可以同时测量多个物体的距离
- 单位转换:添加厘米/英寸的单位切换功能
- 历史记录:保存最近几次的测量结果用于对比分析
一个完整的处理帧函数可能长这样:
def process_frame(frame, model, focal_length, known_width): # 使用YOLOv5进行物体检测 results = model(frame) # 解析检测结果 detections = results.pandas().xyxy[0] for _, det in detections.iterrows(): if det['name'] == 'cup': # 只处理目标类别 pixel_width = det['xmax'] - det['xmin'] distance = calculate_distance(known_width, focal_length, pixel_width) # 在图像上绘制结果 cv2.rectangle(frame, (int(det['xmin']), int(det['ymin'])), (int(det['xmax']), int(det['ymax'])), (0,255,0), 2) cv2.putText(frame, f"{distance:.1f}cm", (int(det['xmin']), int(det['ymin'])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2) return frame4. 误差分析与优化策略
在实际测试中,我发现几个主要误差来源:
- 角度偏差:当摄像头与物体不在同一水平面时,测量结果会偏大
- 镜头畸变:普通摄像头的桶形畸变会影响边缘区域的测量精度
- 标定误差:初始焦距测量的准确性直接影响所有后续结果
针对这些问题,可以采用以下优化策略:
- 多角度标定法:在不同角度拍摄标定物体,建立角度-距离补偿模型
- ROI限制:只使用图像中心区域进行测量,减少镜头畸变影响
- 移动平均滤波:对连续视频帧的结果进行平滑处理
# 简单的移动平均滤波实现 class DistanceFilter: def __init__(self, window_size=5): self.window = [] self.size = window_size def update(self, value): self.window.append(value) if len(self.window) > self.size: self.window.pop(0) return sum(self.window) / len(self.window)下表对比了优化前后的测量误差(单位:cm):
| 实际距离 | 原始测量 | 优化后测量 |
|---|---|---|
| 30cm | 32.4cm | 30.8cm |
| 50cm | 56.7cm | 51.2cm |
| 70cm | 82.3cm | 72.6cm |
虽然误差随距离增加而增大,但优化后的结果明显更接近真实值。对于日常使用场景,这样的精度已经足够。
5. 创意应用与扩展思路
掌握了基础的单目测距技术后,可以尝试许多有趣的扩展应用:
- 智能货架监控:实时监测货架上商品的取放情况
- 互动艺术装置:根据观众距离变化产生不同的视觉效果
- 简易3D扫描:结合物体移动轨迹重建粗略的3D模型
一个特别实用的扩展是距离警报系统,当物体进入预设的危险距离时会发出警告:
def distance_alert(frame, distance, safe_distance=50): if distance < safe_distance: cv2.putText(frame, "WARNING: Too close!", (50,50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,0,255), 3) # 可以添加声音报警 # import winsound # winsound.Beep(1000, 200) return frame在实现这些扩展功能时,记得考虑以下几点:
- 性能优化:对于实时应用,可以考虑使用YOLOv5s等轻量级模型
- 多线程处理:将图像采集、处理和显示放在不同线程提高响应速度
- 用户界面:添加简单的GUI让非技术人员也能方便使用
# 简单的多线程处理示例 import threading class VideoProcessor: def __init__(self): self.frame = None self.running = True def capture_thread(self): cap = cv2.VideoCapture(0) while self.running: ret, self.frame = cap.read() def process_thread(self): while self.running: if self.frame is not None: processed_frame = process_frame(self.frame.copy()) cv2.imshow('Result', processed_frame) if cv2.waitKey(1) == ord('q'): self.running = False6. 实战技巧与常见问题解决
在实际开发过程中,我总结了一些有价值的经验:
- 光照条件:过强或过弱的光线都会影响检测精度,建议在均匀光照环境下使用
- 物体选择:高对比度的纯色物体检测效果最好,避免使用图案复杂的物品
- 摄像头固定:测量时保持摄像头稳定,手持会导致结果波动
遇到检测不稳定的情况时,可以尝试以下调试步骤:
- 检查YOLOv5的置信度阈值(默认0.25),适当提高可减少误检
- 验证标定过程是否正确,特别是实际距离的测量要精确
- 测试不同分辨率,有时降低分辨率反而能提高检测稳定性
# 调整YOLOv5的检测参数 model.conf = 0.5 # 置信度阈值 model.iou = 0.45 # IOU阈值对于想进一步深入学习的开发者,推荐探索以下方向:
- 立体视觉:尝试用两个摄像头实现更精确的测距
- 深度学习:训练自定义的物体检测模型提高特定场景的准确率
- 传感器融合:结合红外或超声波传感器提升系统鲁棒性
在项目开发过程中,版本控制也很重要。我习惯使用如下目录结构:
mono_distance/ ├── configs/ # 配置文件 ├── data/ # 测试图像和视频 ├── models/ # 训练好的模型 ├── utils/ # 工具函数 ├── main.py # 主程序 └── requirements.txt # 依赖库