视觉SLAM实践入门——(18)视觉里程计之单层光流法

LK光流法在无人机视觉导航中的应用:如何用C++优化特征点跟踪性能 本文深入探讨了金字塔Lucas-Kanade(LK)光流法在无人机视觉导航中的应用与优化策略。通过现代C++实现和OpenCV4.x的集成,提供了针对无人机快速运动场景的特征点跟踪性能优化方案,包括内存预分配、并行化处理和自适应迭代终止等关键技术。 阅读详情

单层(稀疏)光流法的过程

1、寻找GFTT角点

2、对于每个角点、每次迭代,使用8x8窗口计算:

    (1)求误差

    (2)求雅可比(源码中多处添加负号,不直观,下面附的代码已经修改为书上公式的直观表达)

    (3)求H、b

    (4)求解增量方程,更新优化变量,重复循环

其中,源码中并没有直接使用某点的像素深度,而是使用2x2窗口进行局部平滑处理后的像素深度。对于opencv的光流法,好像是先对图像进行全局平滑处理,再取像素深度

另外,源码的单层光流法并没有假定8x8窗口的像素都具有相同运动,对于下一节的多层光流法却是假定了相同运动

 

注意事项

 编译本节代码需要使用 opencv4,安装和使用的注意事项参考 https://blog.youkuaiyun.com/floatinglong/article/details/117173740

 

对于下面这种找不到 CV_GRAY2BGR 的错误,原因在于新版的 opencv 修改了它的名字

error: ‘COLOR_GRAY2BGR’ was not declared in this scope

将代码中的 CV_GRAY2BGR 改为 COLOR_GRAY2BGR 即可

 

对于找不到 COLOR_GRAY2BGR 的错误,原因可能在于没有引用作用域,在代码头部添加

using namespace cv;

 

直接运行代码可能会出现错误,提示没有安装 libgtk2.0-dev and pkg-config,使用apt安装,重新对opencv进行配置编译安装即可(删除CMakeCache.txt,重新编译不会花太长时间,因为只是更改某些配置项,不需要全部重新编译)

sudo apt install libgtk2.0-dev pkg-config

 

 

#include <iostream>
#include <string>
using namespace std;

//#include <list>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
using namespace cv;

#include <Eigen/Core>
#include <Eigen/Dense>


//定义一个用于光流跟踪的类
class OpticalFlowTracker
{
public:
	OpticalFlowTracker(
		const Mat &img1,
		const Mat &img2,
		const vector<KeyPoint> &kp1,
		vector<KeyPoint> &kp2,
		vector<bool> &success,
	) : _img1(img1), _img2(img2), _kp1(kp1), _kp2(kp2), _success(success){};

	void calculateOpticalFlow(const Range &range);
		 
private:
	const Mat &_img1;
	const Mat &_img2;
	const vector<KeyPoint> &_kp1;
	vector<KeyPoint> &_kp2;
	vector<bool> &_success;
};


//下面使用了源码,没有修改
inline float GetPixelValue(const cv::Mat &img, float x, float y) {
    //不严谨的边界检测
	//1:后面使用了2x2窗口进行平滑处理,如果传入点在最后一行会导致指针越界
	//2:如果传入点在最后一列,不再是使用2*2方块进行平滑处理(其中一个点位于下一行的第一列)
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x >= img.cols) x = img.cols - 1;
    if (y >= img.rows) y = img.rows - 1;
																			
    unsigned char *data = &img.data[int(y) * img.step + int(x)];		
	
    float xx = x - floor(x);
    float yy = y - floor(y);
	
	//平滑处理
    return 
	float(
        (1 - xx) * (1 - yy) * data[0] +
        xx * (1 - yy) * data[1] +
        (1 - xx) * yy * data[img.step] +
        xx * yy * data[img.step + 1]
    );
}

void OpticalFlowTracker::calculateOpticalFlow(const Range &range)
{
	//8x8窗口
	int half_patch_size = 4;
	int iterations = 10;

	for(size_t i = range.start; i < range.end; i++)
	{
		auto kp = _kp1[i];
		double dx = 0, dy = 0;
		//多层光流,通过上/下层光流知道kp2[i].pt
		if(_has_initial)
		{
			dx = _kp2[i].pt.x - kp.pt.x;
			dy = _kp2[i].pt.y - kp.pt.y;
		}

		double cost = 0, lastCost = 0;
		bool success = true;

		Eigen::Matrix2d H = Eigen::Matrix2d::Zero();
		Eigen::Vector2d b = Eigen::Vector2d::Zero();
		Eigen::Vector2d J;
		for(int iter = 0; iter < iterations; iter++)
		{
            H = Eigen::Matrix2d::Zero();
            b = Eigen::Vector2d::Zero();
		
			cost = 0;
			//对于每个点,使用8x8窗口(-4~+3)求增量方程
			for(int x = -half_patch_size; x < half_patch_size; x++)
				for(int y = -half_patch_size; y < half_patch_size; y++)
				{
					double err = GetPixelValue(_img2, kp.pt.x + x + dx, kp.pt.y + y + dy) - 
								GetPixelValue(_img1, kp.pt.x + x, kp.pt.y + y);

					//求雅可比
                    J = 1.0 * Eigen::Vector2d(
					    //dI/dx=[(x3-x2)+(x2-x1)] / 2
                        0.5 * (GetPixelValue(_img2, kp.pt.x + dx + x + 1, kp.pt.y + dy + y) -
                               GetPixelValue(_img2, kp.pt.x + dx + x - 1, kp.pt.y + dy + y)),
                        0.5 * (GetPixelValue(_img2, kp.pt.x + dx + x, kp.pt.y + dy + y + 1) -
                               GetPixelValue(_img2, kp.pt.x + dx + x, kp.pt.y + dy + y - 1))
				    );			
					
					//求H、b
					cost += err*err;
					b += err*J;
					H += J*J.transpose();
				}
			//解增量方程
			Eigen::Vector2d update = H.ldlt().solve(b);

			if(isnan(update[0]))
			{
				cout << (int)i << "\t:isnan" << endl;
				success = false;			
				break;
			}

			if(iter > 0 && cost > lastCost)			
				break;			
				
			dx += update[0];
			dy += update[1];
			lastCost = cost;
			success = true;

			if(update.norm() < 1e-2)		
				break;																				
		}		
		
		_success[i] = success;
		_kp2[i].pt = kp.pt + Point2f(dx, dy);
	}

}

void OpticalFlowSingleLevel(const Mat &img1, const Mat &img2,
	const vector<KeyPoint> &kp1, vector<KeyPoint> &kp2, vector<bool> &success)
{
	kp2.resize(kp1.size());
	success.resize(kp1.size());
	OpticalFlowTracker tracker(img1, img2, kp1, kp2, success);
	//并行计算函数,参数:循环列表,函数调用(这里使用了绑定,参数:函数调用,对象,绑定参数列表(不需要绑定的使用占位符_1、_2、…))
	//opencv3.2.0好像是不支持给parallel_for_传入bind,需要升级opencv4的原因可能在这里
	//测试发现,源码中使用并行运算会出现一些随机错误,推测是多线程之间数据共享的原因
	//parallel_for_(Range(0, kp1.size()),
	//				std::bind(&OpticalFlowTracker::calculateOpticalFlow, &tracker, placeholders::_1));
		
	//不使用并行计算
	tracker.calculateOpticalFlow(Range(0, kp1.size()));
}

int main(int argc, char **argv)
{
	if(argc != 3)
	{
		cout << "err" << endl;
		return -1;
	}
	
	//读取灰度图
	Mat img1 = imread(argv[1], IMREAD_GRAYSCALE);
	Mat img2 = imread(argv[2], IMREAD_GRAYSCALE);
	
	//检测GFTT角点,最多500个,最小特征值0.01,最小距离20
	vector<KeyPoint> kp1;
	Ptr<GFTTDetector> detector = GFTTDetector::create(500, 0.01, 20);
	detector->detect(img1, kp1);

	//使用单层光流法跟踪img1的特征点在img2的位置,success_single标记特征点是否跟丢
	vector<KeyPoint> kp2_single;
	vector<bool> success_single;
	OpticalFlowSingleLevel(img1, img2, kp1, kp2_single, success_single);
	
/*
	//跟踪前后坐标信息
	for(int i = 0; i < (int)kp1.size(); i++)
	{
		Mat p1 = (Mat_<double>(1, 2) << kp1[i].pt.x, kp1[i].pt.y);		
		
		cout << i << "\t:" << p1 << endl;
	}	
	//跟丢的点被标记为 0
	//从这里可以看出,源码使用并行计算会随机跟丢一些点,opencv库的光流法并不会有这种问题,将源码程序改为不使用并行计算,也不会跟丢
	for(int i = 0; i < (int)kp1.size(); i++)
		cout << i << "\t:" << success_single[i] << endl;
*/
	
	//画点
	Mat img_single;
	cv::cvtColor(img2, img_single, COLOR_GRAY2BGR);
	for(int i = 0; i < (int)kp1.size(); i++)
	{
		if(success_single[i])
		{
            cv::circle(img_single, kp2_single[i].pt, 2, cv::Scalar(0, 250, 0), 2);
			//从kp1指向kp2
            cv::line(img_single, kp1[i].pt, kp2_single[i].pt, cv::Scalar(0, 250, 0));		
		}
	}
	
	imshow("single optical flow", img_single);

	waitKey(0);

	return 0;
}




 

 

 

 

 

 

vslam论文18:S-VIO:利用的结构约束的RGB-D视觉惯性里程计(RAL 2023) 基于视觉的定位是自主系统的一个关键问题,而基于视觉的里程计在具有挑战性的场景下性能会下降。这篇文章介绍了S-VIO,一种RGB- D视觉惯性里程计(VIO),它充分利用多传感器测量(即深度,RGB和IMU),异质地标(即点,线和面)和环境的结构规则来获得鲁棒准确的定位结果。为了检测环境的潜在结构规律,提出了一种两步亚特兰大世界推理方法。利用VIO系统估计的重力方向,该算法首先从一组最近优化的平面地标生成水平亚特兰大轴假设。然后,根据观测值的持久性,使用以下平面地标和线簇来过滤掉偶尔观测到的轴。剩下的坐标轴将 阅读详情

相关推荐

【第24话:定位建图】SLAM视觉里程计——光流法(直接法)详解

本文详解了SLAM中视觉里程计的光流法原理与应用。光流法通过追踪像素运动估计相机位姿,基于亮度恒定假设推导光流约束方程。Lucas-Kanade和Horn-Schunck是两种主要求解方法,分别采用局部和全局优化策略。文中提供了Python实现示例,并分析了光流法的优缺点:计算高效但易受光照变化、运动模糊等因素影响。实际应用中需与其他技术融合以减少误差。光流法作为视觉里程计的基础组件,仍需结合多视图几何优化提升精度。

一碗白开水一 863

自动驾驶-机器人-slam-定位面经和面试知识系列05之常考公式推导(02)

自动驾驶-机器人-slam-定位面经和面试知识系列05之常考公式推导(02)

qq_42692305的博客 1404

SLAM面试笔记(1) -《视觉SLAM十四讲》

本内容以《视觉SLAM十四讲》为参考资料,对可能出现的面试题进行汇总,同时也可以帮助复习。

几度春风里的博客 3600

手写高斯牛顿曲线拟合(c++)

拟合曲线: 取100个带有高斯噪声的数,利用高斯牛顿法进行拟合求取估计值 a ,b ,c; 主要步骤: 1.构造误差项; 2,求雅克比,以及cost 3,判断cost是否小于上次cost,小:执行4,大:输出结果; 4,求H g; 5求dx; 6,更新估计量,到步骤2; /* * 高斯牛顿法练习 created by fanguohao 19.08.17 ...

FANGUOHAO的博客 3251

【第22话:定位建图】SLAM视觉里程计——特征点法详解

本文详细介绍了SLAM中视觉里程计的特征点法,包括其核心原理、关键步骤和数学基础。特征点法通过提取图像中的显著特征点(如ORB、SIFT)并匹配不同帧的对应关系,计算相机运动轨迹。主要步骤包括特征提取、匹配、运动估计和优化,涉及对极约束、本质矩阵分解等数学模型。该方法鲁棒性强、效率高,但对低纹理场景敏感且存在累积误差。文章还提供了简化的Python代码示例,展示了特征点法的基本实现流程。特征点法在机器人导航和增强现实等领域广泛应用,是SLAM系统的重要组成部分。

一碗白开水一 781

【第23话:定位建图】SLAM视觉里程计——直接法详解

SLAM视觉里程计特征点法通过检测图像特征点(如FAST、ORB)并生成描述子进行匹配,利用几何约束(PnP、本质矩阵)估计相机运动。其核心步骤包括特征检测、描述、匹配和运动估计,依赖相机模型和重投影误差优化。优点是鲁棒高效,但受限于纹理缺失和动态场景。该方法在机器人导航中广泛应用,是SLAM系统的重要基础。

一碗白开水一 996

GFTT特征点检测

角点检测,避免出现聚簇现象 shi_tomasi的角点检测算法,名称goodFeatureToTrack,opencv的feature2D接口集成了这种算法,名称为GFTTDetector,接口如下   Ptr&lt;GFTTDetector&gt; create( int maxCorners=1000, double qualityLevel=0.01, double minDistan...

绿枯草 4280

光流 | 融合LK光流与特征点的视觉里程计研究(附matlab代码)

【代码】光流 | 融合LK光流与特征点的视觉里程计研究(附matlab代码)

尘世冰封的专栏 316

计算机视觉基础探讨(2):并行处理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 计算机视觉基础探讨(2):并行处理前言一、Opencv_C++ 3.4.1 中的并行机制1.基本概念与理解2.案例:逐个特征点计算光流损失2.1 代码构成2.2 任务的分发机制2.3 数据的汇总机制2.4 数据锁与并发通信总结 前言 一张图像上有数十万个像素,对于单张图片提取出的特征点动辄成百上千个,如果这些特征还要计算描述子,就是一笔不小的时间开销,这还不算日渐复杂的深度学习或者金字塔等算法的加入。这样的处理量却要求具备实时性(至少在前

Ares_WangZiquan的博客 1046

视觉slam十四讲

视觉slam十四讲

数字孪生变电站如何制作?虚拟现实项目强荐广州华锐互动

变电站数字孪生系统可以为提高变电站设备与环境的实时感知能力、在线诊断设备健康状态、促进设备隐藏故障定位和维修效率的提高、实现设备全生命周期提供有力支撑。通过对设备状态的准确评估,可以延长设备的生命周期,实现资产增值。1、3D可视化:建立可视化平台,集中展示所有3D模型信息、数据信息、环境信息和人员信息,包括设备数字模型、建筑外形及内部、周边地形、人员动态、天气、时间等。4、智能监控:采用先进的AI摄像头,通过设备的图像识别,自动智能分析图像,准确分析设备状态、运行动态、外围控制情况。...

gzvrnew的博客 775

SLAM——之Eigen函数库

1. Eigen示例 相较于Ceres而言,Eigen函数库相对较为简单,我们上一篇文章详细描述了Ceres的使用以及注意事项,由于Ceres能够使用ceres::AutoDiffCostFunction这一类的自动求导函数,相对而言更加轻松,所以Eigen更多的是做矩阵运算。这里我们给出上一篇文章最后的Ceres求解的Eigen版本。我们可以看到本质上差别不大,只是Eigen需要自己求解雅克比矩阵J,并在用GN构建增量方程后,使用ldlt求解线性方程HX=g。 #include <iostream&

格物致知 2488

Burp靶场实战:JWT签名绕过漏洞分析与利用

本文通过Burp Suite靶场实战,深入剖析了JWT签名绕过漏洞的多种攻击手法,包括签名未验证、alg:none攻击、弱密钥爆破、JWK/JKU头注入、KID路径遍历及算法混淆攻击。文章详细演示了利用过程,并提供了关键的防御指南,帮助开发和安全人员加固JWT实现,确保认证安全。

weixin_29325515的博客 595

视觉SLAM十四讲——第8讲视觉里程计2(光流法和直接法)

文章目录1.代码运行问题记录1.1.需要OpenCV4版本1.2.修改代码使用OpenCV4接口1.3.Pangolin的fmt依赖问题1.4.其他问题2.光流法详解2.1.光流法原理2.2.高斯牛顿优化实现单层光流2.3.双线性插值获得浮点坐标的灰度值(用于迭代)2.3.1.原理2.3.2.实际操作2.3.3.程序疑问2.4.多层光流原理 参考博客(写的很好): SLAM光流法、直接法代码踩坑记录 1.代码运行问题记录 1.1.需要OpenCV4版本 从CMakeLists.txt文件中的设置就可以看到需

qq_42731705的博客 2507

win11一直弹出用户账户控制怎么解决

用户帐户控制简称UAC,是微软公司在其Windows Vista及更高版本操作系统中采用的一种控制机制。近期有部分Win11用户想要关闭UAC,但是不知道应该如何操作,对此下面我就教给大家操作方法,大家可以去尝试看看。...

xitongzhijia_abc的博客 5万+

unity从3D升级到urp管线,打包ab包后,材质全部变紫色

确保材质中的被转换为 URP 兼容的着色器。在打包 AssetBundle 时,确保所需的着色器被正确打包。确保运行 AB 包的目标项目中正确配置了 URP 渲染管线。如果加载后材质丢失,检查 AB 包的加载逻辑,确保材质正确应用了着色器。通过这些步骤,你应该能够解决从标准渲染管线切换到 URP 渲染管线后,材质在 AB 包中变紫色的问题。如果问题仍然存在,请根据具体错误日志进一步排查。希望如上措施及解决方案能够帮到有需要的你。

**My Coding Family** 1962

【视觉SLAM十四讲】各种库的安装及代码实践

在学习高博的《视觉SLAM十四讲》的过程中,遇到了各种问题。虽说在网上都能找到对应的解决方案,但没有一篇是详细列尽遇到的问题的文章。于是,打算自己写一篇记录学习过程中遇到的一系列问题。希望能为后续的学习提供一点帮助!(自己也是新手小白,还是有很多不懂的地方。

2302_80099075的博客 6054

DRV8311HRRWR直流无刷(BLDC)电机驱动原理图设计

DRV8311HRRWR集成三个MOSFET 半H桥,用于驱动三相无刷直流电机(BLDC),适用于5V、9V、12V或18V直流电源轨或1S至4S电池供电应用。内部集成三个具有集成电流感测功能的电流感测放大器(CSA),可感测BLDC电机的三相电流,从而实现FOC和电流控制系统设计方案。集成保护特性:VM 欠压锁定 (UVLO)、 电荷泵欠压 (CPUV)、过流保护 (OCP)、热警告和热关断 (OTW/OTSD)、故障条件指示引脚 (nFAULT)支持 1.8V、3.3V 和 5V 逻辑输入。

zhusl6688的博客 1228

视觉SLAM从零到一:14讲代码实践完整指南,快速掌握视觉SLAM实现流程 [特殊字符]

视觉SLAM(Simultaneous Localization and Mapping,同时定位与地图构建)是机器人感知领域的关键技术,而《14 Lectures on Visual SLAM: From Theory to Practice》这本书提供了从理论到实践的完整学习路径。本文将带你深入了解如何通过slambook-en代码实践,快速掌握视觉SLAM的完整实现流程,从基础数学到完整系统

gitblog_00813的博客 342

C#实现每日定时执行制定程序(附完整源码)

C#实现每日定时执行制定程序(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 867

YOLOv11改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块

本文记录的是基于StarNet的YOLOv11轻量化改进方法研究。StarNet设计简洁,没有复杂的设计和精细调整的超参数,仅是一个 4 阶段的分层架构。并且其中能够在低维空间计算的同时考虑极高维的特征这一特性,进而提高模型精度。本文在替换骨干网络中配置了原论文中的starnet_s1starnet_s2starnet_s3和starnet_s4七种模型,以满足不同的需求。

Limiiiing的博客 5987

视觉SLAM学习笔记——目录与参考

对SLAM学习内容进行总结和整理,对视觉SLAM十四讲进行整理、解释和补充。

Manii 2364

【32】软考软件设计师——算法题(一):分治法与动态规划深度精讲|下午第4题常考满分攻略

本文是《软件设计师·50讲通关|从零基础到工程师职称》专栏第32篇,正式开启**模块四:应用技术(下午题)算法大题专项**,聚焦下午第4道高频必考大题核心两大算法:分治法与动态规划。全文超4000字深度拆解两大经典算法的底层核心思想、算法策略识别技巧、递归边界设计、状态转移方程推导,全覆盖代码填空高频考点、时间复杂度分析、经典题型解题套路;配套可直接默写的递归代码模板、DP状态设计范式、真题完整填空案例,彻底解决考生分不清分治与DP、递归边界写错、状态转移推不出、复杂度算不对四大核心痛点。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 638

【视觉SLAM】EDPLVO: Efficient Direct Point-Line Visual Odometry

视觉同步定位和建图 (VSLAM) 是许多机器人和计算机视觉应用的基本模块,从自主导航到增强现实 (AR)。没有闭环的轻量级VSLAM系统通常被称为视觉里程计(VO),这对于需要在资源有限的嵌入式设备上进行实时姿态估计的应用非常重要。由于VSLAM和VO的重要性,它们在计算机视觉和机器人社区中获得了广泛的关注。如今,深度学习技术在各种计算机视觉任务中优于传统方法。在VO方面,基于学习的方法近年来取得了重大进展。

振华OPPO的博客世界 1556

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

HybVIO: 突破实时视觉惯性里程计的极限

点击上方“3D视觉工坊”,选择“星标”干货第一时间送达作者丨paopaoslam来源丨泡泡机器人SLAM标题:HybVIO: Pushing the Limits of Real-time Visual-inertial Odometry作者:Otto Seiskari, Pekka Rantalankila, Juho Kannala, Jerry Ylilammi,...

3D视觉工坊 1668

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

SLAM学习笔记

SLAM学习笔记Chapter1Chapter2Chapter3合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 Chapter1 旋转矩阵、变换矩阵、欧式变换 Chapter2 四元数 Chapter3 旋转矩阵、变换矩阵,李群(Lie G

Cloudy_to_sunny的博客 735
上一篇: 视觉SLAM理论入门——(10)视觉里程计之光流和直接法
下一篇: 视觉SLAM实践入门——(19)视觉里程计之多层光流法(光流金字塔)
floatinglong
博客等级 码龄9年 90粉丝 143原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值