# 用Python玩转VSLAM:ORB-SLAM3环境搭建到实时建图完整教程
如果你是一名对机器人、自动驾驶或者增强现实感兴趣的Python开发者,可能早就对“视觉SLAM”这个词心痒难耐了。它听起来很酷——让机器仅凭摄像头就能理解周围环境,同时构建地图并确定自己的位置。但当你真正想动手试试时,面对C++的复杂编译、晦涩的数学理论和海量的依赖库,热情可能瞬间被浇灭。别担心,这篇文章就是为你准备的。我们将绕开那些令人头疼的底层细节,聚焦于如何用你最熟悉的Python生态,快速搭建起当前最成熟、最强大的开源视觉SLAM系统之一:ORB-SLAM3,并让它实时跑起来,看到实实在在的建图效果。整个过程就像搭积木,我们一步步来,所有命令和代码都经过验证,你可以直接复制粘贴。
## 1. 环境准备:打造你的SLAM工作站
在开始任何激动人心的算法实践之前,一个稳定、兼容的开发环境是基石。对于ORB-SLAM3,官方推荐在Ubuntu系统上运行,因为它与ROS(机器人操作系统)生态结合紧密,而ROS对Ubuntu的支持最为完善。我们选择Ubuntu 20.04 LTS,这是一个长期支持版本,社区资源丰富,遇到问题也更容易找到解决方案。
### 1.1 系统与基础依赖安装
首先,确保你的系统已更新到最新状态。打开终端,执行以下命令:
```bash
sudo apt update
sudo apt upgrade -y
```
接下来,安装ORB-SLAM3编译和运行所必需的基础工具和库。这些包括编译器、CMake构建工具、Python开发环境以及关键的图像处理和线性代数库。
```bash
sudo apt install -y build-essential cmake git pkg-config
sudo apt install -y python3-dev python3-pip python3-numpy
sudo apt install -y libeigen3-dev libboost-all-dev
```
> 注意:`libeigen3-dev`是用于矩阵运算的线性代数模板库,ORB-SLAM3的核心数学计算严重依赖它。`libboost-all-dev`则提供了系统、线程、文件系统等C++扩展功能。
图像处理是视觉SLAM的核心。我们需要安装OpenCV,它是计算机视觉的“瑞士军刀”。ORB-SLAM3需要OpenCV 4.4以上版本,但Ubuntu 20.04默认仓库的版本可能较低,因此我们从源码编译安装。
```bash
# 安装OpenCV的额外依赖
sudo apt install -y libjpeg-dev libpng-dev libtiff-dev
sudo apt install -y libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt install -y libgtk-3-dev libcanberra-gtk3-module
# 下载OpenCV 4.5.5源码并编译
cd ~
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git
cd opencv
mkdir build && cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
-D WITH_CUDA=OFF \
-D BUILD_EXAMPLES=OFF ..
make -j$(nproc) # 使用所有CPU核心加速编译
sudo make install
sudo ldconfig # 更新动态链接库缓存
```
编译过程可能需要一些时间,取决于你的电脑性能。完成后,可以通过`python3 -c “import cv2; print(cv2.__version__)”`来验证OpenCV是否安装成功。
### 1.2 Python接口与工具链搭建
虽然ORB-SLAM3的核心是C++,但我们将通过Python来调用它、传递数据并可视化结果。这需要一个桥梁,通常由ROS或自定义的Python绑定(如PyBind11)实现。为了简化,我们选择一种更直接的方式:利用ORB-SLAM3提供的示例,并通过Python脚本来准备数据、启动进程和显示结果。
首先,安装一些有用的Python包,用于数据读取、进程控制和可视化:
```bash
pip3 install opencv-python opencv-contrib-python
pip3 install numpy matplotlib
pip3 install pyyaml rospkg # 即使不使用完整ROS,某些工具也需要这些包
```
为了管理项目依赖和环境,强烈建议使用`virtualenv`或`conda`创建一个独立的Python虚拟环境。这里以`venv`为例:
```bash
cd ~
python3 -m venv vslam_env
source ~/vslam_env/bin/activate
```
你的终端提示符前会出现`(vslam_env)`,表示已激活该环境。后续所有Python包都应安装在这个环境内。
## 2. 获取与编译ORB-SLAM3
环境就绪后,是时候请出今天的主角了。ORB-SLAM3是ORB-SLAM系列的最新版本,支持视觉、视觉惯导以及多地图系统,其代码结构清晰,是学习SLAM的绝佳范本。
### 2.1 克隆源码与依赖库
在终端中,切换到你的工作目录,克隆ORB-SLAM3的官方仓库及其必需的子模块:
```bash
cd ~
git clone https://github.com/UZ-SLAMLab/ORB_SLAM3.git ORB_SLAM3
cd ORB_SLAM3
git submodule update --init --recursive
```
ORB-SLAM3依赖两个重要的第三方库:`Pangolin`用于可视化,`DBoW2`用于词袋模型和回环检测。它们通常作为子模块已经包含,但我们需要单独编译安装Pangolin。
```bash
# 编译安装Pangolin
cd Thirdparty/Pangolin
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
```
### 2.2 编译ORB-SLAM3核心库
现在,编译ORB-SLAM3本身。回到ORB_SLAM3根目录:
```bash
cd ~/ORB_SLAM3
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```
如果一切顺利,你将在`lib`目录下看到生成的`libORB_SLAM3.so`共享库文件,以及在`Examples`目录下看到各种可执行文件(如`mono_tum`, `stereo_euroc`等)。
> 提示:编译过程中最常见的错误是依赖库版本不匹配或路径找不到。如果遇到关于Eigen、OpenCV或Pangolin的错误,请仔细检查它们是否已正确安装,并且CMake能够找到它们。有时需要手动指定路径,例如`cmake .. -DEigen3_DIR=/usr/include/eigen3`。
为了后续Python调用方便,我们可以将编译好的库路径添加到系统环境变量中。编辑你的`~/.bashrc`文件,在末尾添加:
```bash
export ORB_SLAM3_DIR=~/ORB_SLAM3
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORB_SLAM3_DIR/lib
```
然后执行`source ~/.bashrc`使更改生效。
## 3. 运行第一个SLAM示例:使用TUM数据集
在连接真实摄像头之前,先用一个标准数据集来测试整个流程是否畅通。这能排除硬件问题,让你专注于算法本身。我们使用著名的TUM RGB-D数据集中的`fr1/desk`序列。
### 3.1 下载与准备数据集
首先,下载数据集文件。TUM提供了简单的脚本,但我们也可以手动下载并解压。
```bash
cd ~
wget https://vision.in.tum.de/rgbd/dataset/freiburg1/rgbd_dataset_freiburg1_desk.tgz
tar -xzvf rgbd_dataset_freiburg1_desk.tgz
```
这个数据集包含彩色图像、深度图像以及一个记录时间戳和相机轨迹的`groundtruth`文件。ORB-SLAM3需要两个文本文件来告诉它图像路径和时间戳:
1. `associations.txt`: 关联彩色图和深度图的时间戳。
2. `rgb.txt` 和 `depth.txt`: 分别列出彩色图和深度图的文件名与时间戳。
ORB-SLAM3的源码中提供了一个Python脚本来自动生成关联文件。进入工具目录并运行:
```bash
cd ~/ORB_SLAM3
python3 Examples/Tools/associate.py ../rgbd_dataset_freiburg1_desk/rgb.txt ../rgbd_dataset_freiburg1_desk/depth.txt > ../rgbd_dataset_freiburg1_desk/associations.txt
```
### 3.2 执行RGB-D SLAM
现在,使用编译好的RGB-D示例程序来运行SLAM。你需要提供词汇文件(用于特征匹配加速)、配置文件、数据集路径和关联文件。
```bash
cd ~/ORB_SLAM3
./Examples/RGB-D/rgbd_tum Vocabulary/ORBvoc.txt Examples/RGB-D/TUM1.yaml ../rgbd_dataset_freiburg1_desk ../rgbd_dataset_freiburg1_desk/associations.txt
```
运行后,会弹出两个窗口:一个Pangolin可视化窗口,显示相机跟踪的当前帧、地图点(稀疏点云)和相机轨迹;另一个窗口显示当前相机看到的图像。你可以按键盘上的键与系统交互,例如:
- `空格键`: 暂停/继续
- `r`: 重置
- `q`: 退出
运行结束后,程序会在当前目录下生成一个名为`CameraTrajectory.txt`的文件,里面保存了估计的相机位姿(轨迹)。你可以使用EVO等工具将其与真实轨迹(`groundtruth.txt`)进行比较,评估精度。
### 3.3 用Python脚本自动化流程
手动输入长命令容易出错。我们可以写一个简单的Python脚本来自动化这个过程,并增加一些友好提示。创建一个`run_tum_dataset.py`文件:
```python
#!/usr/bin/env python3
import subprocess
import os
import sys
def run_orb_slam3_on_tum():
# 定义路径
orb_slam3_dir = os.path.expanduser(“~/ORB_SLAM3”)
dataset_dir = os.path.expanduser(“~/rgbd_dataset_freiburg1_desk”)
vocab_file = os.path.join(orb_slam3_dir, “Vocabulary”, “ORBvoc.txt”)
config_file = os.path.join(orb_slam3_dir, “Examples”, “RGB-D”, “TUM1.yaml”)
assoc_file = os.path.join(dataset_dir, “associations.txt”)
# 检查文件是否存在
for f in [vocab_file, config_file, dataset_dir, assoc_file]:
if not os.path.exists(f):
print(f“错误:文件或目录不存在 - {f}”)
sys.exit(1)
# 构建命令
executable = os.path.join(orb_slam3_dir, “Examples”, “RGB-D”, “rgbd_tum”)
cmd = [executable, vocab_file, config_file, dataset_dir, assoc_file]
print(“正在启动ORB-SLAM3 RGB-D示例...”)
print(f“命令: {‘ ‘.join(cmd)}”)
try:
# 运行ORB-SLAM3,并将其输出实时打印到控制台
process = subprocess.Popen(cmd, cwd=orb_slam3_dir)
process.wait()
except KeyboardInterrupt:
print(“\n程序被用户中断。”)
except Exception as e:
print(f“运行过程中出现错误: {e}”)
if __name__ == “__main__”:
run_orb_slam3_on_tum()
```
给脚本添加执行权限并运行:`chmod +x run_tum_dataset.py && python3 run_tum_dataset.py`。这样,你就拥有了一个可复用的数据集测试工具。
## 4. 连接真实摄像头进行实时建图
让SLAM系统处理离线数据集固然重要,但实时与物理世界交互才是其魅力所在。接下来,我们将ORB-SLAM3与你的笔记本电脑摄像头或一个USB摄像头连接起来,实现实时单目视觉SLAM。
### 4.1 准备工作:摄像头驱动与标定
首先,确保系统能识别你的摄像头。在终端输入`ls /dev/video*`,通常能看到`/dev/video0`。你可以使用`guvcview`或`cheese`等工具测试摄像头能否正常打开。
**相机标定至关重要**。SLAM算法需要知道相机的内参(焦距、主点坐标)和畸变系数,才能将图像中的像素位置正确转换为三维空间中的方向。ORB-SLAM3的配置文件(YAML格式)需要这些参数。
你可以使用OpenCV的标定工具,或者更简单一点,如果你使用的是常见的笔记本摄像头,可以在网上找到近似的标定参数。这里,我们假设你已经通过OpenCV完成了标定,得到了如下参数(这是一个示例,**你必须替换为自己摄像头的真实标定结果**):
| 参数类型 | 参数值 | 说明 |
| :--- | :--- | :--- |
| 相机模型 | PinHole | 针孔模型 |
| 图像宽度 | 640 | 像素 |
| 图像高度 | 480 | 像素 |
| 焦距 fx | 517.3 | 像素单位 |
| 焦距 fy | 516.5 | 像素单位 |
| 主点 cx | 318.6 | 像素单位 |
| 主点 cy | 255.3 | 像素单位 |
| 畸变系数 k1 | 0.2624 | 径向畸变 |
| 畸变系数 k2 | -0.9531 | 径向畸变 |
| 畸变系数 p1 | -0.0054 | 切向畸变 |
| 畸变系数 p2 | 0.0026 | 切向畸变 |
基于这些参数,创建一个YAML配置文件,例如`my_camera.yaml`,保存在`ORB_SLAM3/Examples/Monocular/`目录下:
```yaml
%YAML:1.0
# 相机标定参数
Camera.type: “PinHole”
Camera.width: 640
Camera.height: 480
Camera.fx: 517.3
Camera.fy: 516.5
Camera.cx: 318.6
Camera.cy: 255.3
Camera.k1: 0.2624
Camera.k2: -0.9531
Camera.p1: -0.0054
Camera.p2: 0.0026
Camera.k3: 0.0 # 如果没有k3,设为0
# ORB特征参数
ORBextractor.nFeatures: 1000
ORBextractor.scaleFactor: 1.2
ORBextractor.nLevels: 8
ORBextractor.iniThFAST: 20
ORBextractor.minThFAST: 7
# Viewer参数
Viewer.KeyFrameSize: 0.05
Viewer.KeyFrameLineWidth: 1
Viewer.GraphLineWidth: 0.9
Viewer.PointSize: 2
Viewer.CameraSize: 0.08
Viewer.CameraLineWidth: 3
Viewer.ViewpointX: 0
Viewer.ViewpointY: -0.7
Viewer.ViewpointZ: -1.8
Viewer.ViewpointF: 500
```
### 4.2 编写Python实时捕获与调用脚本
ORB-SLAM3的单目示例程序`mono_tum`是从图像序列文件读取的。为了实时处理摄像头流,我们需要写一个Python脚本,它做两件事:
1. 使用OpenCV捕获摄像头帧。
2. 将每一帧图像保存为临时文件,并调用ORB-SLAM3的可执行文件来处理这个文件(模拟实时流)。
这是一种“桥接”方法。更高级的做法是修改ORB-SLAM3源码,直接接收内存中的图像数据,但这需要C++编程。我们的Python方法虽然有一点延迟,但足够演示和学习了。
创建一个名为`run_realtime_mono.py`的脚本:
```python
#!/usr/bin/env python3
import cv2
import subprocess
import threading
import time
import os
import signal
import sys
class RealtimeORBSLAM:
def __init__(self, orb_slam3_path, vocab_path, config_path, camera_id=0):
self.orb_slam3_path = orb_slam3_path
self.vocab_path = vocab_path
self.config_path = config_path
self.camera_id = camera_id
self.temp_image_path = “/tmp/orbslam_frame.jpg”
self.process = None
self.running = False
# 检查路径
self.executable = os.path.join(orb_slam3_path, “Examples”, “Monocular”, “mono_tum”)
if not os.path.exists(self.executable):
print(f“错误:未找到ORB-SLAM3可执行文件 {self.executable}”)
print(“请确保已成功编译ORB-SLAM3。”)
sys.exit(1)
def start_slam_process(self):
“”“启动ORB-SLAM3进程,并准备接收图像序列”“”
# 构建命令。mono_tum需要词汇表、配置文件和一个存放图像路径的‘关联文件’。
# 我们动态生成一个只包含一行的关联文件。
self.assoc_file = “/tmp/associations.txt”
with open(self.assoc_file, ‘w’) as f:
f.write(f“0.0 {self.temp_image_path}\n”)
cmd = [self.executable, self.vocab_path, self.config_path, “/tmp”, self.assoc_file]
print(f“启动命令: {‘ ‘.join(cmd)}”)
# 以子进程方式启动,并重定向其输出到当前终端
self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
self.running = True
# 启动一个线程来读取并打印ORB-SLAM3的输出
def output_reader():
while self.running:
output = self.process.stdout.readline()
if output:
print(f“[ORB-SLAM3] {output.strip()}”)
time.sleep(0.01)
threading.Thread(target=output_reader, daemon=True).start()
# 给SLAM进程一点初始化时间
time.sleep(2)
def capture_and_feed(self):
“”“打开摄像头,捕获图像,并不断更新临时图像文件”“”
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print(“无法打开摄像头!”)
self.stop()
return
print(“开始实时SLAM。按 ‘q‘ 退出,按 ’s‘ 保存当前轨迹。”)
frame_count = 0
try:
while self.running:
ret, frame = cap.read()
if not ret:
print(“获取帧失败。”)
break
# 将图像保存到临时文件
cv2.imwrite(self.temp_image_path, frame)
frame_count += 1
# 简单显示捕获的图像(可选)
cv2.imshow(‘Camera Feed’, frame)
# 检测按键
key = cv2.waitKey(1) & 0xFF
if key == ord(‘q’):
break
elif key == ord(‘s’):
# 发送信号给ORB-SLAM3保存轨迹(需要ORB-SLAM3支持该信号)
# 这里我们简单打印提示
print(“用户请求保存轨迹。”)
# 在实际中,你可能需要修改ORB-SLAM3源码来响应自定义信号
except KeyboardInterrupt:
print(“捕获被中断。”)
finally:
cap.release()
cv2.destroyAllWindows()
self.stop()
def stop(self):
“”“停止SLAM进程和捕获循环”“”
self.running = False
if self.process:
print(“正在终止ORB-SLAM3进程...”)
self.process.send_signal(signal.SIGINT) # 发送中断信号
time.sleep(1)
self.process.terminate()
self.process.wait()
print(“程序已停止。”)
def run(self):
“”“主运行函数”“”
self.start_slam_process()
self.capture_and_feed()
if __name__ == “__main__”:
# 配置你的路径
ORB_SLAM3_DIR = os.path.expanduser(“~/ORB_SLAM3”)
VOCAB_FILE = os.path.join(ORB_SLAM3_DIR, “Vocabulary”, “ORBvoc.txt”)
CONFIG_FILE = os.path.join(ORB_SLAM3_DIR, “Examples”, “Monocular”, “my_camera.yaml”) # 使用你创建的配置文件
slam = RealtimeORBSLAM(ORB_SLAM3_DIR, VOCAB_FILE, CONFIG_FILE, camera_id=0)
slam.run()
```
这个脚本创建了一个类,它启动ORB-SLAM3子进程,并不断用摄像头的最新帧覆盖一个临时图像文件。ORB-SLAM3进程则被配置为从这个文件读取“图像序列”。由于文件被不断重写,它就相当于一个实时视频流。
运行脚本前,请确保:
1. `my_camera.yaml`中的标定参数已更新为你摄像头的真实参数。
2. 你已成功编译ORB-SLAM3。
3. 摄像头设备号正确(通常是0)。
在终端运行:`python3 run_realtime_mono.py`。如果一切正常,你将看到两个窗口:一个OpenCV窗口显示摄像头实时画面,另一个Pangolin窗口显示ORB-SLAM3构建的稀疏地图和估计的相机轨迹。
### 4.3 优化与问题排查
第一次运行时,你可能会遇到跟踪丢失、地图点稀少或延迟明显的问题。别灰心,这是正常的。以下是一些优化和排查思路:
- **调整ORB特征参数**:在`my_camera.yaml`中,`ORBextractor.nFeatures`控制每帧提取的特征点数量。增加它(如从1000到2000)可以提升鲁棒性,但会增加计算量。`scaleFactor`和`nLevels`控制图像金字塔,影响尺度不变性。
- **改善光照与环境**:视觉SLAM在纹理丰富、光照均匀的环境中表现最佳。避免纯色墙面、快速运动或光线剧烈变化。
- **检查标定精度**:不准确的相机内参是导致跟踪失败的主要原因。务必进行仔细的棋盘格标定。
- **性能瓶颈**:实时处理对算力有要求。如果感到卡顿,可以尝试降低图像分辨率(在OpenCV捕获时设置`cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)`等),或者使用更快的ORB特征提取参数(降低`nFeatures`)。
- **理解输出信息**:关注ORB-SLAM3在终端打印的信息。`TRACKING LOST`意味着跟踪失败,可能需要你移动相机慢一些,或者让环境特征更明显。
## 5. 深入探索与进阶方向
成功运行实时建图只是一个开始。ORB-SLAM3是一个功能丰富的系统,你可以从以下几个方向深入探索,将其应用到更具体的项目中。
### 5.1 尝试不同的传感器模式
我们演示了单目模式。ORB-SLAM3还支持:
- **双目模式**:使用两个摄像头,可以直接获取深度信息,尺度是确定的,比单目更稳定。你需要一个双目摄像头,并提供左右图像的标定参数。
- **RGB-D模式**:使用深度摄像头(如Intel RealSense, Azure Kinect)。它直接提供像素级深度,建图效果更好。配置文件需要同时包含彩色和深度相机的参数。
- **视觉惯导模式**:结合相机和IMU(惯性测量单元)。IMU数据可以弥补相机在快速运动或纹理缺失时的不足,提供更平滑、更鲁棒的估计。这需要硬件支持并能同步获取图像和IMU数据。
每种模式在`Examples`目录下都有对应的可执行文件和配置文件模板。
### 5.2 保存与重用地图
ORB-SLAM3支持保存构建好的稀疏特征点地图,并在后续启动时加载,实现**持久化地图**和**重定位**。这对于机器人长期在同一个环境工作至关重要。
相关的关键类和方法在`System.cc`和`Map.cc`中。你需要:
1. 在运行结束时调用`System::SaveMap(“map.bin”)`。
2. 在初始化系统时,通过配置文件或参数指定加载地图文件`map.bin`。
3. 系统启动后,即使没有初始化运动,也能利用已有地图进行快速重定位。
这需要对ORB-SLAM3的C++代码进行修改和重新编译,是进阶学习的好课题。
### 5.3 与ROS集成
虽然我们避开了完整的ROS安装以简化流程,但ROS是机器人领域的标准中间件。ORB-SLAM3提供了ROS接口包(在`Examples/ROS/ORB_SLAM3`目录下)。在ROS中,你可以:
- 轻松订阅摄像头话题(`/camera/image_raw`)和IMU话题(`/imu/data`)。
- 将估计的位姿发布为ROS话题(`/orb_slam3/camera_pose`)或TF变换。
- 方便地与导航、路径规划等其他ROS节点集成。
如果你计划将SLAM用于实际的机器人项目,学习ROS集成是必经之路。
### 5.4 算法定制与改进
ORB-SLAM3的模块化设计使其易于修改和实验。例如:
- **更换特征点**:ORB特征速度快,但在某些场景下可能不够稳定。你可以尝试集成SIFT或SuperPoint等特征,虽然会牺牲一些速度,但可能提升匹配精度。
- **修改回环检测**:回环检测依赖于DBoW2词袋模型。你可以尝试使用更现代的基于深度学习的图像描述符或地点识别方法。
- **稠密建图**:ORB-SLAM3本身是稀疏建图。你可以将它的输出位姿作为输入,结合深度图或RGB-D数据,运行一个独立的稠密建图线程(例如使用Open3D或Voxblox库),生成可用于导航的三维网格或八叉树地图。
这些深入的修改需要你对SLAM理论和C++编程有较好的掌握,但它们代表了从“使用者”到“贡献者”的跨越。
当你第一次看到自己摄像头前的杂乱书桌,在Pangolin窗口中逐渐被重构为一个由稀疏但清晰的特征点构成的三维地图,并且一个虚拟的相机图标随着你的移动而流畅轨迹时,那种感觉是难以言喻的。它不再是论文里晦涩的公式,而是你亲手搭建并运行起来的、有感知能力的程序。从数据集验证到实时运行,这个过程里遇到的每一个编译错误、每一个参数调优的夜晚,都会让你对“特征点”、“关键帧”、“优化束调整”这些概念有血肉般的理解。我自己的经验是,不要怕程序崩溃或跟踪丢失,每一次失败的信息都是系统在告诉你它“看到”了什么、“理解”不了什么。接下来,试着拿着手机在房间里慢慢走一圈,或者挑战一下光线较暗的走廊,观察系统的表现,你会对视觉SLAM的边界和能力有更直观的认识。