# 摄像头测距方案
***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.***
---
该应用是一个基于 **Quectel Pi M1** 智能主控板,通过双目摄像头采集画面,在预览界面点击目标区域,测量摄像头到该物体的真实世界距离的项目。
该项目通过调用双目摄像头同时采集左右目画面,利用双目视差原理计算目标距离,并提供了摄像头标定、参数调节等辅助功能,可作为双目测距、立体视觉及机器人视觉导航类应用的参考示例。
```{image} images/image_Z6JwbwJrIoDWiWx7ftIc555anHe.webp
:width: 800px
:height: 531px
```
## 开发资源汇总
## 开发配件清单
|
配件名称
|
数量
|
规格参数
|
| --- | --- | --- |
| Quectel Pi M1智能主控板 |
1块
|
-
|
| USB-C电源线充电器 |
1个
| |
| Micro HDMI线 |
1根
|
规格:Micro HDMI 2.0;线长:1m;接口:HDMI-A(公头)-HDMI-D(公头)
|
| 网线 |
1根
|
线长:1m;传输速率:千兆;
|
| 显示屏 |
1个
|
24英寸HDMI显示器
|
| 显示屏 |
1
| 24英寸HDMI显示器 |
| USB双目摄像头 |
1
| 推荐分辨率:2560×720(左右各1280×720);输出格式:YUYV/MJPG |
## 配件实物参考
```{image} images/image_Bu26b1yKeo9HGTxFgXQcipUentc.webp
:width: 964px
:height: 615px
:align: center
```
**双目摄像头:**
```{image} images/image_ZyFKbN8Reonc7bx2mEucGiyHn1g.webp
:width: 800px
:height: 468px
:align: center
```
# 快速上手
## 开发准备
**Quectel Pi M1智能主控板** 出厂预装 **Debian 13** 系统镜像,因此无需重新烧录系统镜像,按照以下步骤操作即可。
## 硬件连接
按照以下步骤完成所有硬件连接(步骤编号与接口示意图中的标注一致):
```{image} images/image_FRZRbsvIvol0Uux3pj7cC9dAnXb.webp
:width: 1033px
:height: 713px
:align: center
```
1. **显示连接**:将HDMI线的一端连接到智能主控板的HDMI接口,另一端连接到显示器的HDMI接口。
2. **网络连接**:确保路由器已连接互联网,然后将网线的一端连接到智能主控板的千兆以太网端口,另一端连接到路由器的以太网端口。
3. **输入设备连接**:将USB键盘、鼠标和USB双目摄像头等输入设备连接到智能主控板的USB接口。如果使用无线输入设备,将无线接收器连接到扩展坞的USB接口即可。
4. **供电连接**:将USB-A电源线的USB-A端连接到电源适配器的输出端,将USB-C端连接到智能主控板的电源输入接口(通常标注为 **POWER IN**)。
## 项目实现
### 安装前置
确认有网络连接后,打开终端输入命令:
```plaintext
sudo apt update && sudo apt install -y v4l-utils python3-pip
```
上述命令将更新软件源并安装项目运行所需的一些库,具体包括:
- **v4l-utils**:提供 `v4l2-ctl` 等摄像头工具,用于读取和设置摄像头参数;
- **python3-pip**:Python 包管理器,用于安装项目依赖。
### 获取代码
[下载代码]()
代码解压到设备
### 安装 Python 依赖
```plaintext
pip install -r requirements.txt
```
依赖包说明:
- **PySide6**:Qt6 的 Python 绑定,用于构建图形用户界面;
- **opencv-python**:OpenCV 图像处理库,用于摄像头采集、图像处理和立体视觉计算;
- **numpy**:数值计算库,用于矩阵运算和标定参数处理。
### 采集标定图像
运行标定图像采集工具:
```plaintext
python3 tools/capture_calib_images.py
```
程序会自动检测双目摄像头并显示预览画面,按照提示操作:
1. 将棋盘格标定板放置在摄像头前方不同位置和角度
2. 按 `s` 键保存当前图像对(建议采集 15-20 对)
3. 按 `q` 键退出采集程序
**棋盘标定板:**
```{image} images/image_AV3Ob4wimoBMZHxAXeOc0GGanBf.webp
:width: 1830px
:height: 1330px
:align: center
```
### 生成标定参数
采集完成后,运行标定参数生成工具:
```plaintext
python3 tools/generate_calib_params.py
```
程序会自动读取标定图像并计算双目标定参数,生成 `tools/stereo_calib_params.npz` 文件。
### 运行测距应用
完成标定后,运行主程序:
```plaintext
cd src
python3 main.py
```
程序启动后将显示图形界面,提供以下功能:
```{image} images/image_JZ9ebxHZ7o1omPx6ODqcL2JAnjb.webp
:width: 1416px
:height: 939px
```
#### 功能按钮说明
[电子表格]
#### 日志显示区域
右侧上方区域可输出应用运行过程中的日志信息
```{image} images/image_TA83bib5woMv4Gxx3XSc48nrneg.webp
:width: 1416px
:height: 939px
```
#### 摄像头参数调节
程序右侧提供摄像头参数调节面板,可调节以下参数(也可根据具体摄像头可调节参数设置范围):
[电子表格]
### 进行测距
1. 点击 **"Start Ranging Mode"** 进入测距模式
2. 在预览画面上点击目标位置
3. 等待距离计算结果显示在顶部提示栏
# 应用演示
distance_video
# 摄像头测距注意事项及常见问题解决方案
## 代码问题修改
### 设备缺少 pip
Debian 13 默认不预装 pip 与 venv,需要手动安装并配置虚拟环境
```plaintext
adb shell "apt-get install -y python3-pip python3-venv"
```
### `int('-camera0')` 导致摄像头枚举崩溃
**现象:**
```plaintext
ValueError: invalid literal for int() with base 10: '-camera0'
```
这是因为RK3576平台的设备上存在一个符号链接,需要修改代码
```python
def _video_index(path):
"""从 /dev/videoN 提取整数索引,非法名称返回 None"""
suffix = os.path.basename(path).replace("video", "")
return int(suffix) if suffix.isdigit() else None# 跳过符号链接(如 /dev/video-camera0 -> video51),只取真实节点
devices = [d for d in glob.glob("/dev/video*") if not os.path.islink(d)]
devices = [(i, d) for i, d in ((_video_index(d), d) for d in devices) if i is not None]
devices.sort(key=lambda x: x[0])
```
### 摄像头选错:rkisp 虚拟节点被误判为双目摄像头
问题在于 **`rkisp`虚拟节点会"接受"任意分辨率设置并原样回报**, 但实际 `read()` 读不出任何数据
**解决**
检测时 **必须真正读一帧** 才能确认节点可用:
```python
# 对 UVC 双目摄像头,MJPG 才能跑满高分辨率/高帧率
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 4096)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 2160)
...
# 关键:只有真正读到一帧,才说明这个节点是可用的采集设备
ok, frame = cap.read()
can_capture = bool(ok and frame is not None and getattr(frame, "size", 0) > 0)
...
if is_stereo and can_capture: # 两个条件都满足才作为候选
fh, fw = frame.shape[:2] # 用实际帧尺寸修正上报值
candidates.append((dev, fw, fh))
```
## 标定图像采集注意事项
### 图像质量
**要求:**
- **清晰度保证**:确保棋盘格在左右两个摄像头中完全可见,避免运动模糊
- **光照条件**:避免强光直射和严重阴影,保证均匀光照
- **避免反光**:如果使用手机屏幕显示棋盘格,注意避免屏幕反光导致角点检测失败
**采集策略:**
- **不同距离**:在摄像头前0.3m-2m范围内变化距离
- **不同角度**:左右倾斜±30°,上下倾斜±20°,旋转±15°
- **不同位置**:棋盘格应覆盖画面的左上、右上、左下、右下、中心等不同区域
- **数量建议**:至少15对有效图像,建议20对以上以提高标定精度
**注意事项:**
- **内角点数量**:`CHESSBOARD_SIZE` 表示内角点数量,不是方格数量。例如9×6的内角点对应10×7的方格
- **物理尺寸准确性**:`SQUARE_SIZE` 必须与实际方格边长一致,直接影响基线距离和测距精度
- **打印建议**:
- 打印纸质棋盘格时,确保平整无皱褶
- 使用手机屏幕显示时,注意屏幕尺寸测量准确
- 推荐方格边长:8-12mm
**提高精度的方法:**
- **增加标定图像数量**(建议20对以上)
- **确保图像覆盖更多角度和位置**
- **提高图像清晰度,避免模糊**
- **检查棋盘格物理尺寸设置是否正确**
**验证要点:**
- **基线距离**:`baseline` 应与实际双目摄像头物理间距接近(通常40-120mm)
- **焦距一致性**:左右相机焦距应相近,差异过大说明标定有问题
- **畸变系数**:`dist_l` 和 `dist_r` 数值应在合理范围内(通常绝对值<1)
## 双目测距过程注意事项
### 测距原理与公式
```python
# 测距公式:Z = (f × B) / d# 其中:# - f: 焦距(像素)# - B: 基线距离(米)# - d: 视差(像素)
distance = (f * self._baseline) / disparity
```
**影响测距精度的关键因素:**
- **视差计算准确性**:受立体匹配算法和图像质量影响
- **标定参数准确性**:基线距离和焦距参数
- **目标点特征**:纹理丰富度影响视差计算
### SGBM立体匹配参数调优
```python
stereo = cv2.StereoSGBM_create(
minDisparity=0, # 最小视差
numDisparities=16*12, # 视差搜索范围(必须是16的倍数)
blockSize=11, # 匹配块大小(奇数,3-21)
P1=8*3*11*11, # 视差平滑惩罚项1
P2=32*3*11*11, # 视差平滑惩罚项2
disp12MaxDiff=1, # 左右一致性检查最大差异
uniquenessRatio=10, # 唯一性比率
speckleWindowSize=100, # 散斑过滤窗口大小
speckleRange=32, # 散斑过滤范围
mode=cv2.STEREO_SGBM_MODE_HH # 高精度模式)
```
**参数调优建议:**
[电子表格]
### 测距精度影响因素
#### 有效测距范围
```python
# 测距范围受视差搜索范围限制
max_disparity = numDisparities # 192 (16*12)
min_distance = (f * baseline) / max_disparity
```
**示例计算:**
- 焦距 f = 695 像素
- 基线 B = 0.0735 米
- 最大视差 = 192
- 最近测距距离 = (695 × 0.0735) / 192 ≈ 0.27米
#### 视差有效性过滤
```python
# 代码中的视差过滤逻辑for dy in range(-kernel//2, kernel//2 + 1):for dx in range(-kernel//2, kernel//2 + 1):
d = disparity_map[y, x]if d > 0.5: # 过滤弱视差噪声
disparity += d
valid_count += 1
```
**注意事项:**
- 视差值过小(<0.5)通常表示无效匹配
- 采用5×5邻域平均减少噪声影响
- 有效点数过少时应提示用户重新点击
#### 距离合理性检查
```python
# 对计算结果进行合理性验证
z_3d = point_3d[2]if 0.01 < z_3d < 100.0:
distance = z_3d
else:# Z不合理时用公式计算
distance = (f * self._baseline) / disparity
```
**测距误差来源:**
- **标定误差**:基线距离和焦距参数不准确
- **视差误差**:立体匹配算法精度限制
- **图像质量**:光照、模糊、纹理不足
- **距离因素**:距离越远,视差越小,相对误差越大
---
## 常见问题及解决方案
### 标定相关问题
#### 角点检测失败率高
**现象:**
```plaintext
Warning: Skip 5th pair - chessboard not detected in left image
Valid image pairs for calibration: 8 (need ≥10)
```
**解决方案:**
- **改善光照条件,避免阴影和反光**
- **确保棋盘格完全在画面内**
- **增加图像对比度**
- **减小棋盘格尺寸或增大拍摄距离**
- **使用更清晰的棋盘格图像源**
#### 基线距离异常
**现象:**
```plaintext
Baseline Length: 0.1234 meters (应该约为0.06米)
```
**解决方案:**
- **重新测量方格实际物理尺寸**
- **检查`SQUARE_SIZE`****参数设置**
- **确保棋盘格平面平整**
- **重新进行标定**
### 测距相关问题
#### 测距结果为0或无效
**现象:**
```plaintext
Error: Ranging failed - No valid disparity points
```
**原因分析:**
- **点击区域纹理不足或重复纹理**
- **点击区域过远或过近**
- **图像质量差(模糊、光照不足)**
- **未加载标定参数**
**解决方案:**
```python
# 检查标定参数是否加载if not self._is_calibrated:
LogManager.append_log("Warning: No calibration loaded!")
```
- **确保已加载标定参数文件**
- **点击纹理丰富的区域**
- **改善光照条件**
- **调整SGBM参数增大`numDisparities`**
#### 测距精度低、误差大
**现象:**
实际距离1米,测量结果0.8米或1.2米
**解决方案:**
- **检查标定质量**:重投影误差应<1.0
- **优化SGBM参数**:根据测距范围调整 `numDisparities` 和 `blockSize`
- **改善图像质量**:
```python
# 增强对比度
clahe = cv2.createCLAHE(clipLimit=6.0, tileGridSize=(8, 8))
```
#### 视差图质量差
**现象:**
视差图充满噪点,无法识别物体轮廓
**解决方案:**
- **调整预处理参数**:
```python
# 增强对比度
clahe = cv2.createCLAHE(clipLimit=6.0, tileGridSize=(8, 8))# 增强降噪
gray_left = cv2.GaussianBlur(gray_left, (5, 5), 0)
gray_left = cv2.medianBlur(gray_left, 5)
```
- **调整SGBM参数**:
```python
# 增强匹配严格性
uniquenessRatio=15
speckleWindowSize=150
```
- **检查标定参数**:重新标定以获得更好的校正效果