用Python+OpenCV给摄像头测距:从A4纸到真实世界的距离感知(附完整代码)
用Python+OpenCV实现单目视觉测距:从A4纸标定到实战应用
在计算机视觉领域,单目测距一直是个既基础又实用的技术方向。想象一下,你只需要一个普通的USB摄像头和一张随处可见的A4纸,就能让计算机"看懂"物体距离——这听起来像是科幻场景,但通过OpenCV和Python的组合,我们完全可以在自己的电脑上实现这个功能。不同于需要昂贵硬件设备的深度相机方案,单目测距技术以其低成本、易实现的特性,成为许多创客、机器人爱好者和计算机视觉初学者的首选方案。
1. 环境准备与基础概念
在开始编码前,我们需要先理解几个核心概念。单目测距的基本原理其实源于初中物理的光学知识——相似三角形原理。当我们在固定焦距下拍摄已知尺寸的物体时,物体在图像中的像素尺寸与实际距离存在确定的数学关系。
1.1 必备工具安装
首先确保你的Python环境已经就绪(推荐Python 3.6+),然后通过pip安装必要的库:
pip install opencv-python numpy对于国内用户,如果下载速度较慢,可以使用清华镜像源:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python numpy1.2 硬件准备清单
- 普通USB摄像头(720p以上分辨率效果更佳)
- 标准A4纸(210×297mm)
- 测量工具(如卷尺,用于初始距离测量)
- 均匀光照环境(避免强光直射或阴影干扰)
提示:摄像头最好固定在一个位置,避免在标定过程中移动。手机摄像头也可以使用,但需要通过相应接口获取视频流。
2. 核心原理与数学推导
单目测距的核心公式基于相似三角形原理:
F = (P × D) / W其中:
- F:相机焦距(像素单位)
- P:物体在图像中的像素宽度
- D:相机到物体的实际距离
- W:物体的实际宽度
这个公式的推导过程其实非常直观:
- 在初始距离D处拍摄已知宽度W的物体(如A4纸),测量其在图像中的像素宽度P
- 计算得到焦距F
- 当物体移动时,通过检测新的像素宽度P',即可计算新的距离D' = (W × F)/P'
2.1 实际测量中的误差来源
在实际应用中,我们需要考虑多种误差因素:
| 误差类型 | 影响程度 | 缓解方法 |
|---|---|---|
| 镜头畸变 | 高 | 使用cv2.calibrateCamera进行相机标定 |
| 测量不准确 | 中 | 使用精确测量工具,多次测量取平均 |
| 光照变化 | 中 | 保持稳定光照条件,或使用自适应阈值 |
| 角度偏差 | 高 | 确保相机与目标平面平行 |
3. 完整代码实现与分步解析
下面我们来实现一个完整的单目测距系统。代码分为三个主要部分:标记检测、焦距计算和距离测量。
3.1 标记检测函数
import cv2 import numpy as np def find_marker(image): # 转换为灰度图并降噪 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) # 边缘检测 edged = cv2.Canny(gray, 35, 125) # 寻找轮廓 contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 获取面积最大的轮廓 c = max(contours, key=cv2.contourArea) # 返回最小外接矩形 return cv2.minAreaRect(c)3.2 焦距计算函数
def calculate_focal_length(known_width, known_distance, image_path): # 读取参考图像 image = cv2.imread(image_path) marker = find_marker(image) # 计算焦距 focal_length = (marker[1][0] * known_distance) / known_width print(f"计算得到的焦距: {focal_length:.2f} pixels") return focal_length3.3 距离测量函数
def distance_to_camera(known_width, focal_length, per_width): return (known_width * focal_length) / per_width def measure_distance(image_path, focal_length, known_width): image = cv2.imread(image_path) marker = find_marker(image) # 计算距离 distance = distance_to_camera(known_width, focal_length, marker[1][0]) # 绘制边界框和距离信息 box = cv2.boxPoints(marker) box = np.int0(box) cv2.drawContours(image, [box], -1, (0, 255, 0), 2) cv2.putText(image, f"{distance:.2f}cm", (image.shape[1] - 300, image.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3) cv2.imshow("Distance Measurement", image) cv2.waitKey(0) return distance4. 实战应用与效果优化
现在我们已经实现了基础功能,接下来让我们看看如何在实际项目中应用并优化这个系统。
4.1 完整工作流程
初始标定:
- 将A4纸放置在距离摄像头30cm处
- 拍摄照片并保存为reference.jpg
- 运行calculate_focal_length函数获取焦距
距离测量:
- 移动A4纸到不同位置
- 对每个位置拍照并运行measure_distance函数
- 比较测量结果与实际距离
4.2 提高精度的实用技巧
- 多角度标定法:在不同距离下拍摄多张参考图像,计算平均焦距
- 动态阈值调整:根据光照条件自动调整Canny边缘检测的阈值
- 轮廓过滤:只处理符合A4纸长宽比的轮廓,排除干扰物体
- 移动平均滤波:对视频流中的连续帧进行平均,减少瞬时误差
# 改进版的标记检测函数,增加轮廓过滤 def find_marker_enhanced(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(gray, 35, 125) contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) valid_contours = [] for c in contours: rect = cv2.minAreaRect(c) width, height = rect[1] aspect_ratio = max(width, height) / min(width, height) # A4纸的长宽比约为1.414 (297/210) if 1.3 < aspect_ratio < 1.5: valid_contours.append(c) if valid_contours: c = max(valid_contours, key=cv2.contourArea) return cv2.minAreaRect(c) else: return None4.3 实际应用场景扩展
这个基础技术可以扩展到许多有趣的应用中:
- 智能小车避障:实时测量前方障碍物距离
- 物品尺寸测量:结合已知距离,计算物体实际尺寸
- 互动装置:基于距离的人机交互界面
- 简易3D扫描:多角度距离测量重建物体形状
在最近的一个创客项目中,我使用这个技术为自动导引车(AGV)开发了低成本避障系统。通过将摄像头安装在车体前方,系统能够实时检测2米范围内的障碍物,精度达到了±3cm,完全满足室内低速导航的需求。
