一步步实现slam3-初始位置估计2

视觉里程计初始化
本文详细介绍了一种基于两帧图像匹配的视觉里程计初始化方法,包括单应矩阵与基础矩阵的计算,以及如何根据匹配点选择合适的矩阵进行位姿初始化。

上一步已经找到了初始的两帧,并且得到对应的匹配,那下一步就根据匹配点计算相应的单应矩阵和基础矩阵。
具体计算就是随机选取8个匹配点,启动两个线程分别计算单应矩阵和基础矩阵。具体计算两个矩阵就不在叙述,我在代码中添加了比较详细的注释。
计算单应矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
cv::Mat Initializer::calcHFromMatches(const std::vector<cv::Point2f> &points_ref, const std::vector<cv::Point2f> &points_cur)
{
	/**
	* x = H y ,则对向量 x和Hy 进行叉乘为0,即:
	*
	* | 0 -1  v2|   |a b c|   |u1|    |0|
	* | 1  0 -u2| * |d e f| * |v1| =  |0|
	* |-v2  u2 0|   |g h 1|   |1 |    |0|
	*
	* 矩阵化简得:
	*
	* (-d+v2*g)*u1    + (-e+v2*h)*v1 + -f+v2          |0|
	* (a-u2*g)*u1     + (b-u2*h)*v1  + c-u2         = |0|
	* (-v2*a+u2*d)*u1 + (-v2*b+u2*e)*v1 + -v2*c+u2*f  |0|
	*
	* 0*a + 0*b + 0*c - u1*d - v1*e - 1*f + v2*u1*g + v2*v1*h + v2 = 0
	* u1*a+v1*b + 1*c + 0*d  + 0*e   +0*f - u2*u1*g - u2*v1*h - u2 = 0
	* -v2*u1*a -v2*v1*b -v2*c +u2*u1*d +u2*v1*e +u2*f +0*g +0*h + 0 = 0
	*/
	const int N = points_ref.size();

	cv::Mat A(2 * N, 9, CV_32F);

	for (int i = 0; i < N; i++)
	{
		const float u1 = points_ref[i].x;
		const float v1 = points_ref[i].y;
		const float u2 = points_cur[i].x;
		const float v2 = points_cur[i].y;

		A.at<float>(2 * i, 0) = 0.0;
		A.at<float>(2 * i, 1) = 0.0;
		A.at<float>(2 * i, 2) = 0.0;
		A.at<float>(2 * i, 3) = -u1;
		A.at<float>(2 * i, 4) = -v1;
		A.at<float>(2 * i, 5) = -1;
		A.at<float>(2 * i, 6) = v2*u1;
		A.at<float>(2 * i, 7) = v2*v1;
		A.at<float>(2 * i, 8) = v2;

		A.at<float>(2 * i + 1, 0) = u1;
		A.at<float>(2 * i + 1, 1) = v1;
		A.at<float>(2 * i + 1, 2) = 1;
		A.at<float>(2 * i + 1, 3) = 0.0;
		A.at<float>(2 * i + 1, 4) = 0.0;
		A.at<float>(2 * i + 1, 5) = 0.0;
		A.at<float>(2 * i + 1, 6) = -u2*u1;
		A.at<float>(2 * i + 1, 7) = -u2*v1;
		A.at<float>(2 * i + 1, 8) = -u2;

	}

	cv::Mat u, w, vt;
	// 通过svd进行最小二乘求解
	cv::SVDecomp(A, w, u, vt, cv::SVD::MODIFY_A | cv::SVD::FULL_UV);

	return vt.row(8).reshape(0, 3);
}

 

计算基础矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
cv::Mat Initializer::calcFFromMatches(const std::vector<cv::Point2f> &points_ref, const std::vector<cv::Point2f> &points_cur)
{
	/**
	*      构建基础矩阵的约束方程,给定一对点对应m=(u1,v1,1)T,m'=(u2,v2,1)T
	*  	   满足基础矩阵F m'TFm=0,令F=(f_ij),则约束方程可以化简为:
	*  	    u2u1f_11+u2v1f_12+u2f_13+v2u1f_21+v2v1f_22+v2f_23+u1f_31+v1f_32+f_33=0
	*  	    令f = (f_11,f_12,f_13,f_21,f_22,f_23,f_31,f_32,f_33)
	*  	    则(u2u1,u2v1,u2,v2u1,v2v1,v2,u1,v1,1)f=0;
	*  	    这样,给定N个对应点就可以得到线性方程组Af=0
	*  	    A就是一个N*9的矩阵,由于基础矩阵是非零的,所以f是一个非零向量,即
	*  	    线性方程组有非零解,另外基础矩阵的秩为2,重要的约束条件
	*/
	const int N = points_ref.size();

	cv::Mat A(N, 9, CV_32F);

	for (int i = 0; i < N; i++)
	{
		const float u1 = points_ref[i].x;
		const float v1 = points_ref[i].y;
		const float u2 = points_cur[i].x;
		const float v2 = points_cur[i].y;

		A.at<float>(i, 0) = u2*u1;
		A.at<float>(i, 1) = u2*v1;
		A.at<float>(i, 2) = u2;
		A.at<float>(i, 3) = v2*u1;
		A.at<float>(i, 4) = v2*v1;
		A.at<float>(i, 5) = v2;
		A.at<float>(i, 6) = u1;
		A.at<float>(i, 7) = v1;
		A.at<float>(i, 8) = 1;
	}

	cv::Mat u, w, vt;

	cv::SVDecomp(A, w, u, vt, cv::SVD::MODIFY_A | cv::SVD::FULL_UV);

	cv::Mat Fpre = vt.row(8).reshape(0, 3);

	cv::SVDecomp(Fpre, w, u, vt, cv::SVD::MODIFY_A | cv::SVD::FULL_UV);

	w.at<float>(2) = 0;

	return  u*cv::Mat::diag(w)*vt;
}

 

下一步就是如何选择,也就是到底何时采用基础矩阵,何时采用单应矩阵。这边作者将计算好的单应矩阵或者基础矩阵重新计算投影误差,具体作者设置的阈值这边我也不是很明白,把论文中这个部分贴在这边。

这样基本的位姿初始化就结束了,具体添加第二帧的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
bool Initializer::addSecondFrame(FramePtr cur_frame)
{
	cur_features_ = cur_frame->features_;
	if (cur_features_.size() < 100)
	{
		OPENSLAM_WARN << "Second image has less than 100 features. Retry in more textured environment." << std::endl;
		return false;
	}
	// 寻找对应匹配
	ORBmatcher matcher(0.9, true);
	int nmatches = matcher.searchForInitialization(ref_frame_, cur_frame, prev_matched_, init_matchex_, 100);
	// 检测是否有足够的匹配
	if (nmatches < 100)
	{
		OPENSLAM_WARN << "Matching number has less than 100. " << std::endl;
		return false;
	}
	cur_features_ = cur_frame->features_;
	// 给出当前帧与参考帧的匹配
	matches_ref_cur_.clear();
	matches_ref_cur_.reserve(cur_features_.size());
	is_matched_ref_.resize(ref_features_.size());
	for (size_t i = 0, iend = init_matchex_.size(); i < iend; i++)
	{
		if (init_matchex_[i] >= 0)
		{
			matches_ref_cur_.push_back(std::make_pair(i, init_matchex_[i]));
			is_matched_ref_[i] = true;
		}
		else
		{
			is_matched_ref_[i] = false;
		}
	}

	const int N = matches_ref_cur_.size();

	// 用于随机取8组数据的全部索引
	std::vector<size_t> all_indices;
	all_indices.reserve(N);
	std::vector<size_t> available_indices;

	for (int i = 0; i < N; i++)
	{
		all_indices.push_back(i);
	}

	// 生成一组8个点的数据集用于RANSAC的迭代
	ransac_sets_ = std::vector< std::vector<size_t> >(max_iter_num_, std::vector<size_t>(8, 0));
	// 这边随机改time(0)
	srand(time(0));

	for (int it = 0; it < max_iter_num_; it++)
	{
		available_indices = all_indices;
		// 在所有数据集中选择8组
		for (size_t j = 0; j < 8; j++)
		{
			int d = available_indices.size();
			int randi = int(((double)rand() / ((double)RAND_MAX + 1.0)) * d);

			int idx = available_indices[randi];

			ransac_sets_[it][j] = idx;

			available_indices[randi] = available_indices.back();
			available_indices.pop_back();
		}
	}

	// 启动线程用于并行计算基础矩阵和单应矩阵
	std::vector<bool> matches_inliers_H, matches_inliers_F;

	float SH, SF;
	cv::Mat H, F;

	std::thread threadH(&Initializer::findHomography, this, std::ref(matches_inliers_H), std::ref(SH), std::ref(H));
	std::thread threadF(&Initializer::findFundamental, this, std::ref(matches_inliers_F), std::ref(SF), std::ref(F));

	// 等待直到两个线程结束
	threadH.join();
	threadF.join();

	// 计算scores的比值
	float RH = SH / (SH + SF);

	cv::Mat R_cur_ref; //当前相机的旋转,相对于上一帧,上一帧为初始帧姿态为I单位阵
	cv::Mat t_cur_ref; // 当前相机的平移
	std::vector<cv::Point3f> init_3d_points;
	std::vector<bool> is_triangulated; // 初始化匹配当中,三角定位是否成功
	cv::Mat K = cur_frame->cam_->cvK();
	// 具体采用基础矩阵还是单应矩阵分解计算初始结构取决于ratio (0.40-0.45)
	if (RH > 0.40)
	{
		if (!reconstructH(matches_inliers_H, H, K, R_cur_ref, t_cur_ref, init_3d_points, is_triangulated, 1.0, 50))
			return false;
	}
	else
	{
		if (!reconstructF(matches_inliers_F, F, K, R_cur_ref, t_cur_ref, init_3d_points, is_triangulated, 1.0, 50))
			return false;
	}

	for (size_t i = 0, iend = init_matchex_.size(); i < iend; i++)
	{
		if (init_matchex_[i] >= 0 && !is_triangulated[i])
		{
			init_matchex_[i] = -1;
			nmatches--;
		}
	}
	// 设置当前帧的位姿
	cv::Mat Tcw = cv::Mat::eye(4, 4, CV_32F);
	R_cur_ref.copyTo(Tcw.rowRange(0, 3).colRange(0, 3));
	t_cur_ref.copyTo(Tcw.rowRange(0, 3).col(3));
	cur_frame->T_f_w_ = Tcw;
	return true;
}
<think>首先,用户的问题是关于ORB-SLAM3算法的详细流程。用户指定了参考站内引用,但我需要根据系统级指令来回答。系统级指令包括使用特定格式的数学表达式、独立公式、中文回答、生成相关问题等。 回顾用户提供的引用: - 引用[1]描述了ORB-SLAM3的整体流程:跟踪线程获取相机和IMU数据,进行特征提取和位姿估计,局部建图构建和优化地图,回环检测与地图融合处理回环,最终更新地图进行优化和融合。 - 引用[2]提到使用ORB-SLAM2运行数据并评估轨迹。 - 引用[3]讨论了ORB-SLAM3在EuRoc数据集上的运行和评估,以及IMU融合的效果。 用户的问题是:“我想查找ORB-SLAM3算法的流程 请问ORB-SLAM3算法的详细流程”。所以,我需要提供ORB-SLAM3算法的详细流程。 从引用[1]中,我有基本流程: - 跟踪线程:获取数据,特征提取,位姿估计- 局部建图:构建和优化地图。 - 回环检测与地图融合:检测回环并处理。 - 更新地图:优化和融合地图。 我需要扩展这个,基于我的知识或标准描述。ORB-SLAM3是一个视觉SLAM系统,支持单目、立体和IMU传感器。流程主要包括几个线程:Tracking、Local Mapping、Loop Closing和Map Merging。 标准ORB-SLAM3流程: 1. **Tracking (跟踪)**: 处理每一帧输入图像(相机数据),可能包括IMU数据。提取ORB特征,估计相机位姿。 2. **Local Mapping (局部建图)**: 创建新地图,优化局部地图(包括局部Bundle Adjustment)。 3. **Loop Closing (回环检测)**: 检测回环(比如使用BoW),并进行位姿图优化。 4. **Map Merging (地图融合)**: 当检测到多个地图或回环时,融合地图。 ORB-SLAM3还引入了Atlas概念,一个多地图表示,以处理丢失跟踪或长时间操作。 在回答中,我需要: - 使用中文回答。 - 结构化清晰。 - 引用站内引用:在末尾添加引用标识,如[^1],参考给定的引用。 - 生成相关问题。 - 确保数学表达式正确:如果涉及数学,如位姿估计方程,使用$...$或$$...$$格式。 可能的数学元素: - 位姿估计:相机位姿可以用齐次坐标表示,例如$T_c^w$表示世界坐标系到相机坐标系的变换。 - 优化问题:如Bundle Adjustment最小化重投影误差:$$\min \sum_{i} \| \pi(T_c^w X_i) - x_i \|^2$$,其中$\pi$是投影函数。 - IMU预积分:如果有IMU,涉及预积分公式。 在流程描述中,可能不需要复杂的数学,但如果有机会,可以适当添加。 回答结构: 1. **引言**:简要介绍ORB-SLAM32. **详细流程**:分步描述线程和功能。 3. **关键**:突出特色,如多地图支持、IMU融合。 4. **引用**:自然地引用提供的参考文献。 5. **相关问题**:最后生成相关问题。 生成相关问题:基于系统指令,在回答末尾添加相关问题部分。 参考引用: - [^1] 描述了整体流程。 - [^2] 和[^3] 是关于运行和评估的,但用户的问题是关于流程,所以主要参考[^1],但可以提及评估部分。 在回答中,引用[^1]在描述流程时,可以说“如引用[1]所述”。 完整回答大纲: - 开头:ORB-SLAM3是一个先进的视觉SLAM系统,支持多种传感器。 - 流程: - 跟踪线程:输入数据、特征提取、初始位姿估计- 局部建图线程:地图创建、局部BA。 - 回环检测线程:检测回环、位姿图优化。 - 地图融合:在多地图场景中融合。 - 结束:总结优势。 - 相关问题。 确保语言中文。 </think>### ORB-SLAM3算法详解流程 ORB-SLAM3是一个强大的视觉SLAM(Simultaneous Localization and Mapping)系统,支持单目、立体相机和IMU传感器。它通过多线程并行处理实现高精度的实时定位和建图。算法的核心流程主要包括四个模块:跟踪线程、局部建图线程、回环检测与地图融合线程,以及地图更新模块。这些模块协同工作,形成一个完整的SLAM pipeline。下面我将基于引用[1]的描述,结合算法细节,一步步解释详细流程。 #### 1. **跟踪线程(Tracking Thread)** - **功能**:处理每一帧输入数据(包括相机图像和IMU数据),实时估计相机位姿(即位置和姿态)。 - **详细步骤**: - **数据获取**:从传感器读取相机图像和IMU数据(如果可用)。 - **特征提取**:使用ORB(Oriented FAST and Rotated BRIEF)特征检测器提取图像中的关键和描述符。ORB特征具有尺度不变性和旋转不变性,适用于动态环境。 - **初始位姿估计**: - 对于新帧,通过特征匹配(例如使用BoW词袋模型)与上一帧或关键帧进行关联。 - 如果包含IMU数据,采用预积分技术融合IMU测量(如加速度和角速度),以改善运动估计。位姿优化可表示为最小化重投影误差: $$ \min_{T} \sum_{i} \| \pi(T \cdot X_i) - x_i \|^2 $$ 其中,$T$是相机位姿变换矩阵,$X_i$是3D地图,$x_i$是2D图像,$\pi$是相机投影函数。 - **关键帧判断**:如果当前帧包含足够多新特征或运动变化显著,将其标记为关键帧并传递给局部建图线程。 - **输出**:实时相机位姿和跟踪状态(如跟踪成功或丢失)。 #### 2. **局部建图线程(Local Mapping Thread)** - **功能**:基于关键帧构建和优化局部地图,提高地图的一致性和精度。 - **详细步骤**: - **地图创建**:对于新关键帧,通过三角化(triangulation)生成新的3D地图。这涉及到匹配特征在不同视图中的对应关系。 - **局部优化**: - **局部Bundle Adjustment(BA)**:优化局部窗口内的关键帧位姿和地图位置: $$ \min_{T_j, X_k} \sum_{j,k} \| \pi(T_j \cdot X_k) - x_{jk} \|^2 $$ 其中,$T_j$是局部关键帧位姿,$X_k$是地图坐标,$x_{jk}$是观测- 如果使用IMU,融合IMU约束进行紧耦合优化,以减小漂移误差。 - **冗余关键帧剔除**:移除信息量低的关键帧,保持地图效率。 - **局部地图更新**:将优化后的地图加入全局地图。 #### 3. **回环检测与地图融合线程(Loop Closing and Map Merging Thread)** - **功能**:检测回环(即相机返回到之前访问过的位置)并融合地图,以消除累积误差。 - **详细步骤**: - **回环检测**:使用BoW词袋模型比较当前关键帧与历史关键帧的相似性。如果匹配分数超过阈值,则检测到回环。 - **位姿图优化**:一旦检测到回环,通过位姿图优化(Pose Graph Optimization)调整关键帧位姿: $$ \min_{T_i} \sum_{(i,j) \in \mathcal{E}} \| \log(T_{ij}^{-1} T_i^{-1} T_j) \|_{\Sigma}^2 $$ 其中,$T_i$ 和 $T_j$ 是关键帧位姿,$T_{ij}$ 是相对位姿测量,$\mathcal{E}$ 是约束边集。 - **地图融合**:ORB-SLAM3引入了“Atlas”概念(多地图表示)。如果回环涉及不同地图(如在长时间序列中),线程会融合这些地图为一个一致的整体。这包括对齐地图和优化全局一致性。 - **全局BA**:在融合后,启动全局Bundle Adjustment进一步优化整个地图。 #### 4. **地图更新模块(Map Update Module)** - **功能**:整合所有模块的输出,维护和更新全局地图。 - **详细步骤**: - **地图优化**:接收来自回环检测和局部建图线程的优化结果,更新Atlas(多地图数据库)。 - **IMU偏差校正**:如果使用IMU,模块会估计和校正传感器偏差(如加速度计偏置)。 - **输出地图**:生成稠密或半稠密地图,包括3D云和关键帧轨迹,用于导航或可视化。 - **整体优势**:ORB-SLAM3的多线程设计和Atlas支持使其在复杂环境(如动态场景或长序列)中保持鲁棒性,引用[1]指出,这种流程设计实现了高精度定位和建图,尤其在融合IMU数据后,旋转估计显著接近真值[^1][^3]。 #### 关键特性总结 - **多传感器融合**:支持相机-IMU紧耦合,提升运动估计精度(如引用[3]所示,IMU融合在旋转优化上效果明显)。 - **实时性与鲁棒性**:多线程并行处理(跟踪、建图、回环)确保实时性能;Atlas机制处理跟踪丢失,支持地图复用。 - **精度验证**:如引用[2][^2]和[3][^3]所述,算法轨迹可通过evo工具评估,与真值对比验证精度。 ORB-SLAM3的流程通过迭代优化和自我校正,实现了在复杂SLAM任务中的高效表现[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值