1. 单目视觉测距的核心原理单目视觉测距听起来很高大上但其实它的核心原理非常简单——初中数学课上学过的相似三角形。我第一次接触这个概念时也很惊讶原来这么基础的数学知识就能实现距离测量。让我用一个生活中的例子来解释假设你站在路边看到远处有一辆公交车。虽然你不知道它离你多远但你知道公交车的标准长度大约是12米。现在你伸出手臂竖起大拇指闭上一只眼睛发现大拇指刚好能盖住公交车。这时候你的大脑其实在无意识地运用相似三角形原理估算距离——因为你知道大拇指的宽度、公交车的实际长度以及手臂的长度相当于焦距就能推算出公交车离你有多远。在计算机视觉中这个原理完全一样。我们需要三个关键参数物体实际宽度(W)比如我们选择A4纸作为目标物体它的标准宽度是21厘米横放时物体在图像中的像素宽度(P)通过图像处理算法检测出来相机焦距(F)需要提前标定的参数这三个参数的关系可以用这个简单公式表示F (P × D) / W。其中D是物体到相机的实际距离。当我们知道F和W后就能通过检测到的P值反推出距离D (W × F) / P。我在实验室里做过一个具体测试把A4纸放在距离相机50cm的位置测量图像中纸的宽度为420像素。通过计算得出焦距F (420px × 50cm)/21cm 1000。这个F值会作为已知参数用于后续所有距离计算。2. 相机焦距的标定方法很多新手会卡在焦距标定这一步我刚开始时也踩过不少坑。这里分享两种经过验证的可靠方法2.1 实测法这是最直接的方法操作步骤如下准备一张标准尺寸的参照物建议用A4纸21×29.7cm将参照物放置在已知距离D处建议50-100cm用卷尺精确测量拍摄参照物照片测量其在图像中的像素宽度P代入公式计算F (P × D) / W实测时要注意相机镜头要保持水平正对参照物中心测量环境光线要充足均匀避免阴影干扰建议在不同距离多测几次取平均值2.2 相机参数法如果你的相机提供了规格参数可以直接使用厂家给出的焦距值。但需要注意规格中的焦距通常是物理焦距如3.5mm需要转换为像素单位转换公式F(pixels) (物理焦距 × 图像宽度像素) / 传感器宽度例如物理焦距3.5mm传感器宽度4.8mm图像宽度4000像素则F (3.5×4000)/4.8 ≈ 2917像素我个人的经验是对于精度要求不高的场景可以用参数法但要做实际项目还是实测法更可靠。曾经在一个智能仓储项目中使用参数法导致测距误差达到15%改用实测标定后误差降到了3%以内。3. OpenCV环境搭建与配置工欲善其事必先利其器。在开始编码前我们需要准备好开发环境。虽然原文使用的是Ubuntu系统但考虑到很多开发者使用Windows我这里会同时介绍两种系统的配置方法。3.1 Python环境安装推荐使用Miniconda管理Python环境可以避免很多依赖冲突问题# 创建专用环境 conda create -n opencv python3.8 conda activate opencv # 安装OpenCV pip install opencv-python4.5.5.64 pip install opencv-contrib-python4.5.5.643.2 验证安装用这个简单脚本测试OpenCV是否正常工作import cv2 print(cv2.__version__) # 测试摄像头 cap cv2.VideoCapture(0) if not cap.isOpened(): print(摄像头无法打开) else: print(摄像头准备就绪) cap.release()3.3 开发工具选择我尝试过多种IDE最适合OpenCV开发的有PyCharm智能提示完善调试方便适合大型项目VS Code轻量级配合Python插件体验很好Jupyter Notebook适合算法原型快速验证新手建议从VS Code开始它的配置最简单。我在教学过程中发现PyCharm虽然功能强大但初始配置步骤较多容易劝退初学者。4. 完整代码实现与解析现在进入最关键的实战环节。下面这个改进版的单目测距程序我增加了异常处理和参数调节功能鲁棒性比原始版本更好。4.1 初始化设置import cv2 import numpy as np # 配置参数 WIN_WIDTH 1280 # 建议和摄像头分辨率一致 WIN_HEIGHT 720 REAL_WIDTH 21.0 # A4纸短边长度(cm) FOCAL_LENGTH 1000 # 需提前标定 # 初始化摄像头 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIN_WIDTH) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, WIN_HEIGHT) # 检查摄像头 if not cap.isOpened(): raise IOError(无法打开摄像头)4.2 图像处理流程核心处理流程分为五个步骤灰度化将彩色图像转为灰度高斯模糊减少噪声干扰二值化分割目标物体形态学操作优化目标区域轮廓检测定位目标位置对应的代码实现while True: ret, frame cap.read() if not ret: print(获取帧失败) break # 1. 灰度化 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 2. 高斯模糊 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 3. 自适应二值化 binary cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 4. 形态学操作 kernel np.ones((3,3), np.uint8) dilated cv2.dilate(binary, kernel, iterations2) # 5. 轮廓检测 contours, _ cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)4.3 目标筛选与距离计算不是所有检测到的轮廓都有效我们需要设置合理的筛选条件valid_contours [] for cnt in contours: area cv2.contourArea(cnt) x, y, w, h cv2.boundingRect(cnt) # 筛选条件 if area 2000: # 忽略小区域 continue if h w * 1.5: # 宽高比筛选 continue valid_contours.append((x, y, w, h)) # 取最大有效区域 if valid_contours: x, y, w, h max(valid_contours, keylambda c: c[2]*c[3]) # 计算距离 distance (REAL_WIDTH * FOCAL_LENGTH) / w distance_text f{distance:.1f}cm # 绘制结果 cv2.rectangle(frame, (x, y), (xw, yh), (0,255,0), 2) cv2.putText(frame, distance_text, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,255,0), 2)4.4 显示与退出# 显示处理过程 cv2.imshow(Original, frame) cv2.imshow(Processed, dilated) # 退出条件 key cv2.waitKey(30) if key 27: # ESC键退出 break cap.release() cv2.destroyAllWindows()5. 性能优化与实用技巧在实际项目中我发现原始算法有几个可以改进的地方下面分享我的优化经验。5.1 动态参数调整固定阈值在不同光照条件下效果差异很大。我增加了动态参数调节功能# 在循环开始前定义 threshold_value 127 cv2.namedWindow(Controls) cv2.createTrackbar(Threshold, Controls, threshold_value, 255, lambda x: None) # 在循环中获取当前值 threshold_value cv2.getTrackbarPos(Threshold, Controls) _, binary cv2.threshold(blurred, threshold_value, 255, cv2.THRESH_BINARY)这样在程序运行时可以直接滑动条调整二值化阈值快速适应不同环境。5.2 多目标检测原始代码只能检测一个目标改进版可以同时测量多个物体for (x, y, w, h) in valid_contours: distance (REAL_WIDTH * FOCAL_LENGTH) / w cv2.rectangle(frame, (x, y), (xw, yh), (0,255,0), 2) cv2.putText(frame, f{distance:.1f}cm, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)5.3 误差分析与校准测距精度受多种因素影响我总结了几点校准经验相机分辨率越高精度越好建议至少720p目标尺寸已知物体的实际尺寸要测量准确摆放角度目标物体应该尽量正对相机光照条件均匀光照效果最好避免反光在工业现场部署时建议制作专门的校准靶标在不同距离进行多次测量建立误差补偿表。6. 常见问题与解决方案在实际应用中我遇到过各种奇怪的问题这里总结几个典型的案例。6.1 检测不到目标现象明明放好了A4纸但程序检测不到可能原因光线太暗导致二值化效果差背景复杂干扰轮廓检测摄像头焦距没调好目标模糊解决方案增加辅助照明更换单色背景调整摄像头焦距尝试不同的二值化方法6.2 距离测量不准现象测量结果与实际距离偏差大排查步骤检查焦距F标定是否正确确认物体实际宽度W输入无误测量像素宽度P是否准确可以在图像上手动测量验证检查摄像头是否有畸变广角镜头问题更明显6.3 程序运行卡顿优化建议降低处理分辨率减少不必要的图像显示使用更高效的轮廓检测参数考虑改用C实现关键部分我曾经在一个嵌入式设备上部署时通过以下调整将帧率从5fps提升到20fps将处理分辨率从1080p降到720p每3帧处理1帧使用更小的形态学核3×3改为2×27. 进阶应用方向掌握了基础的单目测距后可以尝试以下几个有趣的扩展应用。7.1 三维空间定位通过检测多个特征点可以实现物体的三维姿态估计。例如检测A4纸的四个角点使用solvePnP算法计算物体的三维位置和旋转角度应用场景AR标记识别、机器人抓取定位7.2 移动物体测距结合目标跟踪算法可以实现对移动物体的持续测距使用KCF或CSRT算法跟踪目标在每一帧计算目标大小和距离应用场景车辆测距、运动分析7.3 多相机融合单相机测距有视角限制多相机系统可以提供更全面的覆盖从不同角度安装多个摄像头通过标定确定相机间的相对位置融合各相机的测量结果应用场景智能仓储、体育竞技分析在开发这些进阶应用时我建议先使用现成的算法库如OpenCV的tracking模块快速验证想法后再考虑自定义实现。