ORB-SLAM3 是第一个能够使用单目、立体和 RGB-D 相机,采用针孔和鱼眼镜头模型,实现视觉、视觉-惯性以及多地图 SLAM 的实时 SLAM 库。在所有传感器配置中,ORB-SLAM3 的鲁棒性不亚于文献中可用的最佳系统,并且精度显著更高。

关于这两个模型:

针孔模型:如果使用的是普通的相机(如单目、立体或RGB-D相机),这些相机通常使用的是针孔模型,该模型假设相机的成像是通过一个小孔投影到图像平面上的,适用于大多数标准相机系统。

鱼眼镜头模型:当使用具有鱼眼镜头的相机时(例如超广角相机),则使用鱼眼镜头模型来描述更大视场角和显著的畸变。鱼眼镜头模型能够更好地适应和校正由于镜头畸变带来的影响,通常会用于需要宽视场角的应用场景。

这两个模型是根据所用的相机类型动态选择的,而不是同时运行。换句话说,ORB-SLAM3会根据你选择的相机配置来决定使用针孔模型或鱼眼模型,并不会同时使用这两种模型。

我们已在 Ubuntu 16.04 和 18.04 上测试了该库,但它应该很容易在其他平台上编译。一台强大的计算机(例如 i7)将确保实时性能并提供更稳定、更准确的结果。

这里虽然使用的是Ubuntu16和18测试,但是20情况也可以使用的。

关于ROS 

我们提供了一些示例,用于处理使用 ROS 的单目、单目惯性、立体、立体惯性或 RGB-D 相机的输入。构建这些示例是可选的。这些示例已在 Ubuntu 18.04 下的 ROS Melodic 上进行了测试。

使用自己的相机运行代码

使用普通的 USB 相机运行 ORB-SLAM3,主要涉及以下步骤:

标定相机:获得内参文件(your_camera.yaml)。

修改示例代码:适配你的相机。

构建程序:编译并链接程序。

连接并运行:连接相机并运行 SLAM 系统。

标定的过程之前有,这是文件路径

/home/croco/桌面/SLAM3/ORB_SLAM3/Examples/ROS/ORB_SLAM3/Asus.yaml

可能是opencv版本的问题,等后面去实验一下。

在之前slam2的项目当中版本是

成功完成了在SLAM3当中使用相机实时检测:

这个文件是myslam.cpp文件和相关的文件是平行关系。

#include <opencv2/opencv.hpp>
#include "System.h"
#include <string>
#include <chrono>
#include <iostream>
#include <cstdlib>  // for system()

using namespace std;

string parameterFile = "/home/croco/桌面/SLAM3/ORB_SLAM3/Examples/Monocular/TUM1.yaml";
string vocFile = "/home/croco/桌面/SLAM3/ORB_SLAM3/Vocabulary/ORBvoc.txt";
string saveDir = "./SLAM_Output";  // 轨迹文件保存路径

int main(int argc, char **argv) {
    // 自动创建保存目录(Linux/macOS,兼容C++14)
    string mkdirCommand = "mkdir -p " + saveDir;
    system(mkdirCommand.c_str());

    // 初始化 SLAM 系统
    ORB_SLAM3::System SLAM(vocFile, parameterFile, ORB_SLAM3::System::MONOCULAR, true);

    cv::VideoCapture cap(0);  // 改成你自己的摄像头编号
    if (!cap.isOpened()) {
        cerr << "无法打开摄像头!" << endl;
        return -1;
    }

    cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);

    auto start = chrono::system_clock::now();

    while (1) {
        cv::Mat frame;
        cap >> frame;
        if (frame.empty()) {
            cerr << "捕获到空帧!" << endl;
            break;
        }

        auto now = chrono::system_clock::now();
        auto timestamp = chrono::duration_cast<chrono::milliseconds>(now - start);
        SLAM.TrackMonocular(frame, double(timestamp.count()) / 1000.0);

        if (cv::waitKey(1) == 27) break;  // ESC 退出
    }

    // 保存轨迹和关键帧
    SLAM.SaveTrajectoryTUM(saveDir + "/Trajectory.txt");
    SLAM.SaveKeyFrameTrajectoryTUM(saveDir + "/KeyFrameTrajectory.txt");
    SLAM.Shutdown();

    cout << "保存完成,文件保存在: " << saveDir << endl;

    return 0;
}

使用的yaml文件是系统中TUM数据集的相关文件。当然这个也可以根据自己的相机进行标定。

运行代码之后会生成两个相关文件

使用evo工具可以进行对比

evo_traj tum CameraTrajectory.txt KeyFrameTrajectory.txt -p

这样会生成一个对比图

evo_ape tum CameraTrajectory.txt KeyFrameTrajectory.txt -a -r full --save_results results.zip --plot --plot_mode xyz

生成使用 evo 进行误差评估

#include <opencv2/opencv.hpp>
#include "System.h"
#include <string>
#include <chrono>
#include <iostream>
#include <cstdlib>  // for system()

using namespace std;

// 配置路径(请根据你的环境调整)
string parameterFile = "/home/croco/桌面/SLAM3/ORB_SLAM3/Examples/Monocular/TUM1.yaml";
string vocFile = "/home/croco/桌面/SLAM3/ORB_SLAM3/Vocabulary/ORBvoc.txt";
string saveDir = "./SLAM_Output";  // 输出路径

int main(int argc, char **argv) {
    // 自动创建输出目录
    string mkdirCommand = "mkdir -p " + saveDir;
    system(mkdirCommand.c_str());

    // 启动 ORB-SLAM3 系统(最后一个 true 表示启用可视化 Viewer)
    ORB_SLAM3::System SLAM(vocFile, parameterFile, ORB_SLAM3::System::MONOCULAR, true);

    // 打开摄像头(改成你自己的编号)
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) {
        cerr << "❌ 无法打开摄像头!" << endl;
        return -1;
    }

    // 设置摄像头分辨率
    cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);

    auto start = chrono::system_clock::now();

    cout << "✅ 启动摄像头,按 ESC 键退出..." << endl;

    while (true) {
        cv::Mat frame;
        cap >> frame;
        if (frame.empty()) {
            cerr << "⚠️ 捕获到空帧!" << endl;
            break;
        }

        auto now = chrono::system_clock::now();
        auto timestamp = chrono::duration_cast<chrono::milliseconds>(now - start);
        SLAM.TrackMonocular(frame, double(timestamp.count()) / 1000.0);

        // 按 ESC 键退出
        if (cv::waitKey(1) == 27) break;
    }

    // 关闭 SLAM 系统
    SLAM.Shutdown();

    // 保存轨迹
    SLAM.SaveTrajectoryTUM(saveDir + "/Trajectory.txt");
    SLAM.SaveKeyFrameTrajectoryTUM(saveDir + "/KeyFrameTrajectory.txt");

    cout << "✅ 轨迹保存完成,保存在目录: " << saveDir << endl;

    return 0;
}

最后又修改了一下代码

使用摄像头运行完之后我们学习使用自己的视频去运行一下。

以上的所有研究都是基于单目的情况,没有惯性单元的情况,单目是日常生活当中最容易模仿的情形。

evo_traj tum KeyFrameTrajectory.txt MonocularTrajectory.txt --ref=KeyFrameTrajectory.txt --align --plot --plot_mode xyz

 这里使用视频

所以SLAM3是没有办法为视频生成相机轨迹文件的。只能模拟一个。

解决办法:

这里写了一个脚本运行一下即可

import cv2
import os

# ==== 参数配置 ====
video_path = 'your_video.mp4'      # 替换为你的视频路径
output_dir = 'output_images'       # 输出图像目录
timestamp_file = 'times.txt'       # 输出时间戳文件名

# ==== 创建输出文件夹 ====
os.makedirs(output_dir, exist_ok=True)

# ==== 打开视频 ====
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

print(f'视频总帧数: {frame_count}, 帧率: {fps} fps')

# ==== 写时间戳文件 ====
with open(os.path.join(output_dir, timestamp_file), 'w') as f:
    frame_idx = 0
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        
        timestamp = frame_idx / fps
        filename = f"image_{frame_idx:06d}.png"
        filepath = os.path.join(output_dir, filename)

        # 保存图像
        cv2.imwrite(filepath, frame)

        # 写入时间戳
        f.write(f"{timestamp:.6f} {filename}\n")

        if frame_idx % 50 == 0:
            print(f"处理第 {frame_idx} 帧")

        frame_idx += 1

cap.release()
print(f"✅ 完成:共导出 {frame_idx} 帧,时间戳保存在 {timestamp_file}")

这样就会跟数据集一样有相关文件了

这里还没有实现。

在重新整理一下数据集的情况。主要现在有三个公开的数据集。

运行双目:

5. Stereo Examples

KITTI Dataset

  1. Download the dataset (grayscale images) from The KITTI Vision Benchmark Suite

  2. Execute the following command. Change KITTIX.yamlto KITTI00-02.yaml, KITTI03.yaml or KITTI04-12.yaml for sequence 0 to 2, 3, and 4 to 12 respectively. Change PATH_TO_DATASET_FOLDER to the uncompressed dataset folder. Change SEQUENCE_NUMBER to 00, 01, 02,.., 11.

./Examples/Stereo/stereo_kitti Vocabulary/ORBvoc.txt Examples/Stereo/KITTIX.yaml PATH_TO_DATASET_FOLDER/dataset/sequences/SEQUENCE_NUMBER

EuRoC Dataset

  1. Download a sequence (ASL format) from kmavvisualinertialdatasets – ASL Datasets

  2. Execute the following first command for V1 and V2 sequences, or the second command for MH sequences. Change PATH_TO_SEQUENCE_FOLDER and SEQUENCE according to the sequence you want to run.

./Examples/Stereo/stereo_euroc Vocabulary/ORBvoc.txt Examples/Stereo/EuRoC.yaml PATH_TO_SEQUENCE/mav0/cam0/data PATH_TO_SEQUENCE/mav0/cam1/data Examples/Stereo/EuRoC_TimeStamps/SEQUENCE.txt
./Examples/Stereo/stereo_euroc Vocabulary/ORBvoc.txt Examples/Stereo/EuRoC.yaml PATH_TO_SEQUENCE/cam0/data PATH_TO_SEQUENCE/cam1/data Examples/Stereo/EuRoC_TimeStamps/SEQUENCE.txt

第二条指令是关于MH系列的,第一条指令是V系列的

使用MH系列做一个例子:
./Examples/Stereo/stereo_euroc ./Vocabulary/ORBvoc.txt ./Examples/Stereo/EuRoC.yaml ../DATASET/MH01 ./Examples/Stereo/EuRoC_TimeStamps/MH01.txt MH01

 

时间戳转换代码:

import csv

input_path = 'MH01/state_groundtruth_estimate0/data.csv'
output_path = 'MH01_groundtruth_tum.txt'

with open(input_path, 'r') as infile, open(output_path, 'w') as outfile:
    reader = csv.reader(infile)
    next(reader)  # 跳过表头
    for row in reader:
        if row[0].startswith('#') or len(row) < 8:
            continue
        timestamp_ns = int(row[0])
        timestamp = timestamp_ns * 1e-9
        tx, ty, tz = row[1], row[2], row[3]
        qw, qx, qy, qz = row[4], row[5], row[6], row[7]
        outfile.write(f"{timestamp:.9f} {tx} {ty} {tz} {qx} {qy} {qz} {qw}\n")

print(f"✅ Ground truth 已保存为: {output_path}")

这样就可以转换然后去对比了

使用相关工具去展示

evo_traj tum f_MH01.txt kf_MH01.txt MH01_groundtruth_tum.txt -p

 结果如下:

发现初始坐标系不一样:

查看错误

found no matching timestamps between MH01_groundtruth_tum.txt and f_MH01.txt with max. time diff 0.01 (s)

报错截图:

 

 这个错误是时间戳文件对不上,所以放大时间容忍度:

evo_ape tum MH01_groundtruth_tum.txt f_MH01.txt -a --align --correct_scale --t_max_diff 0.1 -p

 还是不行,我们来检查一下相关的文件,发现时间戳文件的单位是不一样的,使用转换代码:

# === 文件路径配置 ===
input_file = 'MH01_groundtruth_tum.txt'      # 原始 groundtruth 文件(单位:秒)
output_file = 'MH01_groundtruth_ns.txt'      # 输出文件(单位:纳秒)

# === 开始转换 ===
with open(input_file, 'r') as fin, open(output_file, 'w') as fout:
    for line in fin:
        parts = line.strip().split()
        if len(parts) < 8:
            continue  # 跳过格式错误的行
        try:
            # 秒 → 纳秒
            timestamp_sec = float(parts[0])
            timestamp_ns = int(timestamp_sec * 1e9)

            # 其余 7 个数为位置 + 四元数
            rest = ' '.join(parts[1:])

            # 写入转换后的行
            fout.write(f"{timestamp_ns} {rest}\n")
        except ValueError:
            print(f"⚠️ 跳过无法解析的行: {line.strip()}")

然后进行了转换,单位从秒变成了纳秒不会出现报错了,但是好像世界坐标系还是不统一导致出现的图像还是没有重合。

查阅相关资料后发现:要有一个对齐的参数使用才可以。

evo_traj tum --ref=MH01_groundtruth_ns.txt f_MH01.txt -p -a

evo_traj tum --ref=f_MH01.txt kf_MH01.txt MH01_groundtruth_ns.txt -p -a 

 如果是单目还要加

evo_traj tum --ref=f_MH01.txt kf_MH01.txt MH01_groundtruth_ns.txt -p -a -s

执行后得到如下:正确的展示了。 

下面是evo这个工具的详细使用:

使用evo工具评估ORB_SLAM2在TUM数据集上的运行轨迹-CSDN博客

如果想要在平面展示

evo_traj tum --ref=f_MH01.txt kf_MH01.txt MH01_groundtruth_ns.txt -p -as --plot_mode=xz

 

evo_traj tum --ref=f_MH01.txt kf_MH01.txt MH01_groundtruth_ns.txt -p -as --plot_mode=xy

 

这是比较通用的方式。

evo_ape tum MH01_groundtruth_ns.txt f_MH01.txt -a --align --correct_scale --t_max_diff 0.1 -p

 这会输出:

 

通过 evo_ape 得到的 APE(Absolute Pose Error)统计结果,反映了 ORB-SLAM3 输出轨迹与 Ground Truth 轨迹之间的误差分布情况

相似的还有相对轨迹误差

evo_rpe tum MH01_groundtruth_ns.txt f_MH01.txt -a --align --correct_scale --t_max_diff 0.1 -p

 

Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐