用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)20320426.6725256426.6730213426.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_size5): 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实际距离原始测量优化后测量30cm32.4cm30.8cm50cm56.7cm51.2cm70cm82.3cm72.6cm虽然误差随距离增加而增大但优化后的结果明显更接近真实值。对于日常使用场景这样的精度已经足够。5. 创意应用与扩展思路掌握了基础的单目测距技术后可以尝试许多有趣的扩展应用智能货架监控实时监测货架上商品的取放情况互动艺术装置根据观众距离变化产生不同的视觉效果简易3D扫描结合物体移动轨迹重建粗略的3D模型一个特别实用的扩展是距离警报系统当物体进入预设的危险距离时会发出警告def distance_alert(frame, distance, safe_distance50): 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 # 依赖库