【Windows + VSCode】DBoW2 从零下载、编译到运行 Demo 完整复现教程
目标是在一台新的 Windows 主机上,从下载安装到最终运行
demo.exe,完整复现 DBoW2 工程。
文章目录
- [【Windows + VSCode】DBoW2 从零下载、编译到运行 Demo 完整复现教程](#【Windows + VSCode】DBoW2 从零下载、编译到运行 Demo 完整复现教程)
- 前言
- 安装基础编译环境
-
- [安装 MSVC x64 编译器](#安装 MSVC x64 编译器)
- [安装 CMake](#安装 CMake)
- [安装 Git](#安装 Git)
- [安装 VSCode 和插件](#安装 VSCode 和插件)
- [下载第三方库 OpenCV 和官方源码 DBoW2](#下载第三方库 OpenCV 和官方源码 DBoW2)
-
- [下载第三方库 OpenCV](#下载第三方库 OpenCV)
- [下载官方源码 DBoW2](#下载官方源码 DBoW2)
- 命令行编译和运行
-
- [Visual Studio 2022 编译](#Visual Studio 2022 编译)
- [运行 demo](#运行 demo)
- 独立工具:训练与查询分离
前言
在视觉 SLAM、图像检索和回环检测任务中,经常需要判断"当前看到的画面,是否和历史上某一帧很像"。如果直接拿两张图片逐像素比较,不仅计算量大,而且对视角、光照、局部遮挡都很敏感。因此,实际系统通常会先从图像中提取局部特征,例如 ORB、BRIEF、SIFT 等,再把这些局部特征转换成更适合快速检索的图像表示。
DBoW2,全称可以理解为 Dynamic Bag of Words 2,是一个经典的 C++ 视觉词袋库。它的核心思想是把图像中的局部描述子映射到一棵视觉词汇树上,把一张图像表示成一个 Bag-of-Words 向量。这样,图像之间的相似度比较就可以从大量局部特征匹配,简化为稀疏向量之间的快速打分。
在 ORB-SLAM、ORB-SLAM2、ORB-SLAM3 等视觉 SLAM 系统中,DBoW2 常被用于回环检测和重定位。简单来说,它可以帮助系统快速回答:
text
当前帧是否和历史关键帧相似?
当前相机是否回到了以前到过的位置?
跟踪丢失后,能不能通过图像检索找回当前位置?
DBoW2 本身不是完整的 SLAM 系统,也不负责相机位姿优化。它更像是 SLAM 系统中的"图像检索模块"或"视觉词袋数据库模块"。它负责把图像特征变成词袋向量,建立图像数据库,并根据相似度返回候选匹配结果。后续是否接受这些候选结果,还需要几何验证、PnP、RANSAC、位姿图优化等模块继续判断。
本文的目标不是讲解 DBoW2 的全部算法细节,而是先解决一个更基础也更容易卡住的问题:如何在 Windows + VSCode 环境下,从零下载依赖、配置 CMake、编译工程,并成功运行 DBoW2 自带 demo。只要能把 demo 跑通,后续再阅读源码、接入 ORB-SLAM、修改词袋模型或做实验,就有了一个可靠的起点。
推荐运行环境:
| 注意事项 | 说明 |
|---|---|
| 操作系统 | Windows 10 / Windows 11 |
| 编译器 | Visual Studio 2022 的 MSVC x64 编译器 |
| 构建工具 | CMake |
| 编辑器 | VSCode |
| 第三方库 | OpenCV Windows 预编译包 |
| 示例根目录 | D:\ORBSLAM |
| DBoW2 示例路径 | D:\ORBSLAM\DBoW2 |
| OpenCV 示例路径 | D:\ORBSLAM\opencv\build |
如果你的工程放在其他盘,例如
E:\ORBSLAM或C:\dev\ORBSLAM,后文所有D:\ORBSLAM都需要替换成你的真实路径。
安装基础编译环境
| 软件下载 | 说明 |
|---|---|
| Visual Studio 2022 Community | 包含了 MSVC 编译器工具链,同时还包含完整的 IDE(编辑器、调试器、设计器等) |
| Visual Studio 2022 Build Tools (推荐) | 包含了 MSVC 编译器工具链,但只提供命令行环境,不含 IDE 图形界面 |
| CMake | 跨平台构建工具,现代 C++ 项目必备 |
| Git for Windows | 用于 git clone 下载源码 |
| VSCode | 编辑器 |
安装 MSVC x64 编译器
VSCode 只是编辑器,不自带 C++ 编译器。DBoW2 的 .cpp 文件最终需要 Visual Studio 提供的 MSVC 编译器编译。(博主这里选择安装 Visual Studio 2022 Build Tools)

安装 Visual Studio Community 或 Build Tools 时,必须勾选:
text
Desktop development with C++
建议确认包含:
- MSVC C++ x64/x86 build tools
- Windows 10 SDK 或 Windows 11 SDK
- C++ CMake tools for Windows

安装完成后,打开开始菜单中的:
text
x64 Native Tools Command Prompt for VS 2022
输入:
bat
cl

如果看到 Microsoft C/C++ 编译器版本信息,说明 MSVC 可用。若提示
'cl' 不是内部或外部命令,通常是没有安装 C++ workload,或者打开的是普通 CMD/PowerShell。
安装 CMake
推荐 Windows x64 Installer(.msi 安装包):双击安装自动配好环境变量,还带图形界面。

安装 CMake 时注意勾选:将 CMake 的 bin 文件夹路径添加到系统环境变量中。
text
Add CMake to the PATH environment variable

安装完成后,关闭旧终端,重新打开 PowerShell:
powershell
cmake --version

能显示版本号即安装成功。
注意:必须安装 CMake,后续 VSCode 的 CMake Tools 插件不能代替 CMake 本体,插件只是帮你调用
cmake.exe。
安装 Git
直接选择"Standalone Installer"下的"Git for Windows/x64 Setup":这是最标准、最推荐普通用户的安装方式(.exe 安装包)。

安装 Git 后,重新打开 PowerShell:
powershell
git --version

能显示版本号即可。若不想安装 Git,也可以从 GitHub 下载 ZIP 源码包。
安装 VSCode 和插件
先安装 VSCode 本体:

安装完成后打开 VSCode,快捷键Ctrl + Shift + X在左侧扩展栏中搜索并安装插件:
text
C/C++
CMake Tools


下载第三方库 OpenCV 和官方源码 DBoW2
| 代码下载 | 说明 |
|---|---|
| OpenCV 4.8.0 Windows 包 | 使用的 OpenCV 版本(Windows 自解压安装包) |
| DBoW2 官方仓库 | DBoW2(回环检测词袋模型)原始开源仓库 |
建议把 OpenCV 和 DBoW2 放在同一个父目录下:
text
D:\ORBSLAM
├── DBoW2
│ ├── CMakeLists.txt
│ ├── include
│ ├── src
│ ├── demo
│ ├── docs
│ └── README.md
└── opencv
└── build
├── OpenCVConfig.cmake
├── x64\vc16\bin
└── x64\vc16\lib
下载第三方库 OpenCV
下载
text
opencv-4.8.0-windows.exe
双击运行,它本质上是自解压包。建议解压到:
text
D:\ORBSLAM

解压后应得到:
text
D:\ORBSLAM\opencv\build

用 PowerShell 检查关键文件:
powershell
Test-Path D:\ORBSLAM\opencv\build\OpenCVConfig.cmake
Test-Path D:\ORBSLAM\opencv\build\x64\vc16\bin\opencv_world480.dll

两个都输出 True,说明 OpenCV 位置正确。
下载官方源码 DBoW2
-
使用 Git 下载(推荐):
powershellcd D:\ORBSLAM # 从 GitHub 官方源克隆 DBoW2 git clone https://github.com/dorian3d/DBoW2.git DBoW2
-
如果不用 Git,使用 ZIP 下载:打开
https://github.com/dorian3d/DBoW2点击Code选择Download ZIP下载,解压后把文件夹改名为DBoW2放到D:\ORBSLAM\DBoW2。

检查源码结构
powershell
cd D:\ORBSLAM\DBoW2
dir

至少应看到:
text
CMakeLists.txt
README.md
include
src
demo
命令行编译和运行
Visual Studio 2022 编译
打开:
text
Developer PowerShell for VS 2022
执行:
powershell
cd D:\ORBSLAM\DBoW2
# 配置 CMake
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DOpenCV_DIR=D:/ORBSLAM/opencv/build -DBUILD_Demo=ON "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" -DOpenCV_RUNTIME=vc16 -DOpenCV_ARCH=x64
# 编译
cmake --build build --config Release
参数说明
| 参数 | 含义 |
|---|---|
-S . |
源码目录是当前目录 |
-B build |
构建目录是 build |
-G "Visual Studio 17 2022" |
使用 VS2022 生成器 |
-A x64 |
使用 64 位编译 |
-DOpenCV_DIR=... |
指定 OpenCV 的 CMake 配置目录 |
-DBUILD_Demo=ON |
编译 demo 程序 |
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 |
绕过新版 CMake 对 cmake_minimum_required(VERSION 3.0) 的兼容性检查 |
-DOpenCV_RUNTIME=vc16 -DOpenCV_ARCH=x64 |
手动指定 OpenCV 运行时版本和架构,当 MSVC 版本超出 OpenCVConfig 匹配范围时必须使用 |
--config Release |
编译 Release 版本 |
注 :
-DCMAKE_POLICY_VERSION_MINIMUM=3.5解决新版 CMake 兼容性,-DOpenCV_RUNTIME=vc16 -DOpenCV_ARCH=x64解决新版 MSVC 兼容性。
运行 demo
编译成功后会生成:

运行 demo:
powershell
cd D:\ORBSLAM\DBoW2\build
$env:PATH = "D:\ORBSLAM\opencv\build\x64\vc16\bin;" + $env:PATH
.\Release\demo.exe
注意必须在 build 目录运行,因为 demo 读取图片时使用相对路径,程序中途出现:
text
Press enter to continue

按回车继续,成功运行后会生成:
text
# 视觉词汇表(YAML + gzip 压缩),包含视觉单词及 TF-IDF 权重
D:\ORBSLAM\DBoW2\build\small_voc.yml.gz
# 图像数据库,包含词汇表 + 图的倒排索引,加载后可直接查询
D:\ORBSLAM\DBoW2\build\small_db.yml.gz


| 文件 | 存了什么 | 干什么用 |
|---|---|---|
small_voc.yml.gz |
词汇树结构 + 单词的 IDF 权重 | 特征 → 单词:新图像的特征沿树往下走,每层找最近的聚类中心,最终落到叶子节点得到单词 ID |
small_db.yml.gz |
词汇表 + 倒排索引 | 单词 → 图像:每个单词下挂着包含它的图像列表,查询时只比较共同单词的图像,快速返回 Top-N |
独立工具:训练与查询分离
原始的 demo.cpp 把训练和测试写在一起。在实际使用中,训练词汇表、建库、查新图往往是分开的步骤------词汇表可以用多样化的数据集训练一次,然后对不同的场景独立建库和查询。为此新增了三个独立工具:
| 文件 | 用途 |
|---|---|
demo/train.cpp |
传入图片文件夹,训练词汇表,保存 small_voc.yml.gz |
demo/build_db.cpp |
加载词汇表,传入图片文件夹建库,保存 small_db.yml.gz |
demo/query.cpp |
加载词汇表 + 数据库,查任意新图 |
train.cpp 文件
cpp
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include <algorithm>
#include "DBoW2.h"
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
using namespace DBoW2;
using namespace std;
namespace fs = std::filesystem;
static void extractFeatures(const vector<string> &paths, vector<vector<cv::Mat> > &features)
{
cv::Ptr<cv::ORB> orb = cv::ORB::create();
for(size_t i = 0; i < paths.size(); i++) {
cv::Mat img = cv::imread(paths[i], 0);
if(img.empty()) { cerr << "Cannot read " << paths[i] << endl; continue; }
vector<cv::KeyPoint> kps; cv::Mat desc;
orb->detectAndCompute(img, cv::Mat(), kps, desc);
vector<cv::Mat> f(desc.rows);
for(int j = 0; j < desc.rows; j++) f[j] = desc.row(j);
features.push_back(f);
cout << " [" << (i+1) << "/" << paths.size() << "] " << paths[i] << ": " << desc.rows << " features" << endl;
}
}
int main(int argc, char **argv)
{
if(argc < 2) { cerr << "Usage: " << argv[0] << " <image_dir>" << endl; return 1; }
vector<string> images;
for(auto &entry : fs::directory_iterator(argv[1])) {
string ext = entry.path().extension().string();
if(ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".pgm")
images.push_back(entry.path().string());
}
if(images.empty()) { cerr << "No images found in " << argv[1] << endl; return 1; }
sort(images.begin(), images.end());
cout << "Found " << images.size() << " images" << endl;
cout << "Extracting features ..." << endl;
vector<vector<cv::Mat> > features;
extractFeatures(images, features);
cout << "Training vocabulary (k=9, L=3, TF-IDF, L1) ..." << endl;
OrbVocabulary voc(9, 3, TF_IDF, L1_NORM);
voc.create(features);
cout << " " << voc << endl;
voc.save("small_voc.yml.gz");
cout << "Saved small_voc.yml.gz" << endl;
return 0;
}
build_db.cpp
cpp
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include <algorithm>
#include <fstream>
#include "DBoW2.h"
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
using namespace DBoW2;
using namespace std;
namespace fs = std::filesystem;
static void extractFeatures(const vector<string> &paths, vector<vector<cv::Mat> > &features)
{
cv::Ptr<cv::ORB> orb = cv::ORB::create();
for(size_t i = 0; i < paths.size(); i++) {
cv::Mat img = cv::imread(paths[i], 0);
if(img.empty()) { cerr << "Cannot read " << paths[i] << endl; continue; }
vector<cv::KeyPoint> kps; cv::Mat desc;
orb->detectAndCompute(img, cv::Mat(), kps, desc);
vector<cv::Mat> f(desc.rows);
for(int j = 0; j < desc.rows; j++) f[j] = desc.row(j);
features.push_back(f);
cout << " [" << (i+1) << "/" << paths.size() << "] " << paths[i] << ": " << desc.rows << " features" << endl;
}
}
int main(int argc, char **argv)
{
if(argc < 2) { cerr << "Usage: " << argv[0] << " <image_dir>" << endl; return 1; }
cout << "Loading vocabulary from small_voc.yml.gz ..." << endl;
OrbVocabulary voc("small_voc.yml.gz");
cout << " " << voc << endl;
vector<string> images;
for(auto &entry : fs::directory_iterator(argv[1])) {
string ext = entry.path().extension().string();
if(ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".pgm")
images.push_back(entry.path().string());
}
if(images.empty()) { cerr << "No images found in " << argv[1] << endl; return 1; }
sort(images.begin(), images.end());
cout << "Found " << images.size() << " images" << endl;
cout << "Extracting features ..." << endl;
vector<vector<cv::Mat> > features;
extractFeatures(images, features);
cout << "Building database ..." << endl;
OrbDatabase db(voc, false, 0);
for(size_t i = 0; i < features.size(); i++) db.add(features[i]);
cout << " " << db << endl;
db.save("small_db.yml.gz");
cout << "Saved small_db.yml.gz" << endl;
// Save filename index
ofstream idx("db_index.txt");
for(size_t i = 0; i < images.size(); i++) idx << images[i] << endl;
cout << "Saved db_index.txt (" << images.size() << " entries)" << endl;
return 0;
}
query.cpp
cpp
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <fstream>
#include "DBoW2.h"
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/features2d.hpp>
using namespace DBoW2;
using namespace std;
static void extractFeatures(const string &path, vector<cv::Mat> &features)
{
cv::Mat img = cv::imread(path, 0);
if(img.empty()) return;
cv::Ptr<cv::ORB> orb = cv::ORB::create();
vector<cv::KeyPoint> kps; cv::Mat desc;
orb->detectAndCompute(img, cv::Mat(), kps, desc);
features.resize(desc.rows);
for(int i = 0; i < desc.rows; i++) features[i] = desc.row(i);
}
int main(int argc, char **argv)
{
if(argc < 2) { cerr << "Usage: " << argv[0] << " <image_path>" << endl; return 1; }
string img_path = argv[1];
cout << "Loading vocabulary: small_voc.yml.gz ..." << endl;
OrbVocabulary voc("small_voc.yml.gz");
cout << " " << voc << endl;
cout << "Loading database: small_db.yml.gz ..." << endl;
OrbDatabase db("small_db.yml.gz");
cout << " " << db << endl;
// Load filename index
vector<string> idx;
ifstream inf("db_index.txt");
string line;
while(getline(inf, line)) idx.push_back(line);
cout << " Index: " << idx.size() << " filenames loaded" << endl;
cout << "Extracting features: " << img_path << endl;
vector<cv::Mat> features;
extractFeatures(img_path, features);
if(features.empty()) { cerr << "Error: no features extracted" << endl; return 1; }
cout << " " << features.size() << " features" << endl;
cout << endl << "Query results:" << endl;
QueryResults ret;
db.query(features, ret, min((int)db.size(), 20));
for(unsigned int i = 0; i < ret.size(); i++) {
int eid = ret[i].Id;
string fname = (eid < (int)idx.size()) ? idx[eid] : "unknown";
cout << " #" << (i+1) << " Score=" << fixed << setprecision(4) << ret[i].Score
<< " " << fname << endl;
}
return 0;
}
要编译这两个工具,在 CMakeLists.txt 的 if(BUILD_Demo) 块中找到 demo 的 add_executable,在它后面添加:
cmake
if(BUILD_Demo)
add_executable(demo demo/demo.cpp) # 原始 demo(训练+测试一体)
target_link_libraries(demo ${PROJECT_NAME} ${OpenCV_LIBS})
set_target_properties(demo PROPERTIES CXX_STANDARD 11)
file(COPY demo/images DESTINATION ${CMAKE_BINARY_DIR}/)
add_executable(train demo/train.cpp) # 新增:训练词汇表
target_link_libraries(train ${PROJECT_NAME} ${OpenCV_LIBS})
set_target_properties(train PROPERTIES CXX_STANDARD 17)
add_executable(build_db demo/build_db.cpp) # 新增:加载词汇表+建库
target_link_libraries(build_db ${PROJECT_NAME} ${OpenCV_LIBS})
set_target_properties(build_db PROPERTIES CXX_STANDARD 17)
add_executable(query demo/query.cpp) # 新增:查询新图
target_link_libraries(query ${PROJECT_NAME} ${OpenCV_LIBS})
set_target_properties(query PROPERTIES CXX_STANDARD 11)
endif(BUILD_Demo)
然后重新 cmake configure + build。
打开:
text
Developer PowerShell for VS 2022
执行:
powershell
cd D:\ORBSLAM\DBoW2
# 配置 CMake
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DOpenCV_DIR=D:/ORBSLAM/opencv/build -DBUILD_Demo=ON "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" -DOpenCV_RUNTIME=vc16 -DOpenCV_ARCH=x64
# 编译
cmake --build build --config Release

典型使用流程: LoopDB 是专为回环检测设计的公开数据集【数据集地址:LoopDB Dataset (Google Drive)】,包含 1000+ 张室内外图像。
博主把数据放到放到DBoW2工程目录下,并且将原数据拆分出三个数据集:
- 训练集(left_train,600 张):用来训练词汇表。提取每张图的 ORB 特征,做层次化 K-means 聚类,生成 729 个视觉单词及 IDF 权重。这个词汇表相当于"词典",把图像特征映射到单词 ID。
- 建库集(left_db,420 张):用来构建图像数据库。加载词汇表,把每张图的特征转成 BoW 向量,建立倒排索引。这张表记录了"哪个单词出现在哪张图里"。
- 测试集(left_test,20 张):用来验证检索效果。从建库集中随机抽取,确保这些图原本属于建库集的场景,但不加入数据库。拿它们去查询数据库,看能否找回同场景的相似图。
拆分原因:
- 训练集和建库集分开:词汇表一旦训练好可以重复使用。这样换一组新图建库时不用重新训练,省时间。
- 测试集从建库集里抽:如果测试图跟数据库里的图是不同场景的,查不到是正常的,没法验证效果。从建库集里随机抽出几张,保证数据库里有同场景的"邻居",这样查询分数高的结果才是真正有效。
下面用 LoopDB 演示三个工具的完整使用。
powershell
cd D:\ORBSLAM\DBoW2\build
$env:PATH = "D:\ORBSLAM\opencv\build\x64\vc16\bin;$env:PATH"
# 1. 训练词汇表
.\Release\train.exe D:\ORBSLAM\DBoW2\left_train
# 生成 small_voc.yml.gz

powershell
# 2. 建库
.\Release\build_db.exe D:\ORBSLAM\DBoW2\left_db
# 加载 small_voc.yml.gz → 提取 left_db 特征 → 生成 small_db.yml.gz

build_db.exe建库时会额外保存db_index.txt(EntryId → 文件名映射),query.exe加载后显示文件名,方便确认检索结果。
powershell
# 3. 查询测试(用 left_test 中的图,在 left_db 中找最相似的)
.\Release\query.exe D:\ORBSLAM\DBoW2\left_test\IMG_20240702_124457.jpg

查询的图片:

返回的对应数据库的图片:
