【从零开始写视觉SLAM】v0.1基于特征点的简单VO
v0.1版本的oSLAM实现了基于orb特征点的简单视觉里程计,通过连续两帧的rgbd数据实现相机相对位姿的估计。
这部分理论上相对简单一点,咱们就直接上实现。
- VisualOdometer类
VisualOdometer.hpp
#pragma once
#include <vector>
#include <opencv2/opencv.hpp>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "Frame.hpp"namespace oSLAM
{class VisualOdometer{private:std::vector<Frame> frames;int max_key_points_num;double cx, cy, fx, fy;double depth_scale;std::vector<cv::DMatch> matches;void feature_extract(const cv::Mat& rgb, Frame& frame);void calc_depth(const cv::Mat& depth, Frame& frame);void feature_match(const Frame& ref, const Frame& cur, std::vector<cv::DMatch>& matches);void calc_pose_relative(const Frame& ref, Frame& cur, const std::vector<cv::DMatch>& matches);void pose_estimation_3d2d(const std::vector<cv::Point3d> &pts1, const std::vector<cv::Point2d> &pts2, cv::Mat &R, cv::Mat &t);void pose_estimation_3d3d(const std::vector<cv::Point3d> &pts1, const std::vector<cv::Point3d> &pts2, cv::Mat &R, cv::Mat &t);public:void add(double timestamp, const cv::Mat &rgb, const cv::Mat& depth);void set_pose(int frame_idx, const cv::Mat& R, const cv::Mat& T);void get_pose(int frame_idx, cv::Mat& R, cv::Mat& T);void get_3d_points(int frame_idx, std::vector<cv::Point3d> &key_points_3d);VisualOdometer(int max_key_points_num, double cx, double cy, double fx, double fy, double depth_scale);~VisualOdometer();};
}
VisualOdometer.cpp
#include "VisualOdometer.hpp"
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/SVD>using namespace oSLAM;
using namespace std;
using namespace cv;VisualOdometer::VisualOdometer(int max_key_points_num, double cx, double cy, double fx, double fy, double depth_scale)
{VisualOdometer::max_key_points_num = max_key_points_num;VisualOdometer::cx = cx;VisualOdometer::cy = cy;VisualOdometer::fx = fx;VisualOdometer::fy = fy;VisualOdometer::depth_scale = depth_scale;
}VisualOdometer::~VisualOdometer()
{
}void VisualOdometer::feature_extract(const cv::Mat &rgb, Frame &frame)
{Ptr<ORB> orb_detector = ORB::create(max_key_points_num);orb_detector->detect(rgb, frame.key_points);orb_detector->compute(rgb, frame.key_points, frame.descriptors);
}void VisualOdometer::calc_depth(const cv::Mat &depth, Frame &frame)
{for (int i=0;i<frame.key_points.size();i++){double x = frame.key_points[i].pt.x;double y = frame.key_points[i].pt.y;double dis = depth.at<uint16_t>(int(y),int(x)) / depth_scale;frame.key_points_3d.push_back(Point3d((x-cx)/fx*dis, (y-cy)/fy*dis, dis));}
}void VisualOdometer::pose_estimation_3d2d(const std::vector<cv::Point3d> &pts1, const std::vector<cv::Point2d> &pts2, cv::Mat &R, cv::Mat &t)
{// 利用PnP求解位姿初值Mat K = (Mat_<double>(3,3) << fx, 0, cx, 0, fy, cy,0, 0, 1);Mat rvec, tvec;solvePnPRansac(pts1, pts2, K, Mat::zeros(1, 5, CV_64FC1), rvec, tvec);Rodrigues(rvec, R);t = (Mat_<double>(3,1) << tvec.at<double>(0), tvec.at<double>(1), tvec.at<double>(2));// 优化位姿和3D点坐标// ToDo
}void VisualOdometer::pose_estimation_3d3d(const std::vector<cv::Point3d> &pts1, const std::vector<cv::Point3d> &pts2, cv::Mat &R, cv::Mat &t)
{Point3d p1(0, 0, 0), p2(0, 0, 0); // center of massint N = pts1.size();for (int i = 0; i < N; i++){p1 += pts1[i];p2 += pts2[i];}p1 = Point3d(Vec3d(p1) / N);p2 = Point3d(Vec3d(p2) / N);vector<Point3d> q1(N), q2(N); // remove the centerfor (int i = 0; i < N; i++){q1[i] = pts1[i] - p1;q2[i] = pts2[i] - p2;}// compute q1*q2^TEigen::Matrix3d W = Eigen::Matrix3d::Zero();for (int i = 0; i < N; i++){W += Eigen::Vector3d(q1[i].x, q1[i].y, q1[i].z) * Eigen::Vector3d(q2[i].x, q2[i].y, q2[i].z).transpose();}// SVD on WEigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU | Eigen::ComputeFullV);Eigen::Matrix3d U = svd.matrixU();Eigen::Matrix3d V = svd.matrixV();Eigen::Matrix3d R_ = U * (V.transpose());if (R_.determinant() < 0){R_ = -R_;}Eigen::Vector3d t_ = Eigen::Vector3d(p1.x, p1.y, p1.z) - R_ * Eigen::Vector3d(p2.x, p2.y, p2.z);// convert to cv::MatR = (Mat_<double>(3, 3) << R_(0, 0), R_(0, 1), R_(0, 2),R_(1, 0), R_(1, 1), R_(1, 2),R_(2, 0), R_(2, 1), R_(2, 2));t = (Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}void VisualOdometer::calc_pose_relative(const Frame& ref, Frame& cur, const std::vector<cv::DMatch>& matches)
{vector<Point3d> ref_key_points_3d, cur_key_points_3d;vector<Point2d> ref_key_points_2d, cur_key_points_2d;// 筛选3D点for(auto match : matches){Point3d ref_key_point_3d = ref.key_points_3d[match.queryIdx];Point3d cur_key_point_3d = cur.key_points_3d[match.trainIdx];if (ref_key_point_3d.z == 0 || cur_key_point_3d.z == 0){continue;}ref_key_points_3d.push_back(ref_key_point_3d);cur_key_points_3d.push_back(cur_key_point_3d);ref_key_points_2d.push_back(ref.key_points[match.queryIdx].pt);cur_key_points_2d.push_back(cur.key_points[match.trainIdx].pt);}// 3D点计算位姿Mat R, T;//pose_estimation_3d3d(cur_key_points_3d, ref_key_points_3d, R, T);pose_estimation_3d2d(ref_key_points_3d, cur_key_points_2d, R, T);cur.R = R * ref.R;cur.T = R * ref.T + T;
}void VisualOdometer::feature_match(const Frame& ref, const Frame& cur, std::vector<cv::DMatch>& matches)
{vector<DMatch> initial_matches;BFMatcher matcher(NORM_HAMMING);matcher.match(ref.descriptors, cur.descriptors, initial_matches);double min_dis = initial_matches[0].distance;for(auto match : initial_matches){if (match.distance < min_dis)min_dis = match.distance;}matches.clear();for(auto match : initial_matches){if (match.distance <= MAX(min_dis * 2, 30))matches.push_back(match);}
}void VisualOdometer::add(double timestamp, const Mat &rgb, const Mat &depth)
{Frame frame;frame.time_stamp = timestamp;frame.rgb = rgb.clone();frame.depth = depth.clone();// 提取rgb图像的orb特征点VisualOdometer::feature_extract(rgb, frame);// 提取关键点的深度信息VisualOdometer::calc_depth(depth, frame);// 如果不是第一帧if (VisualOdometer::frames.size() == 0){frame.R = Mat::eye(3,3,CV_64FC1);frame.T = Mat::zeros(3,1,CV_64FC1);}else{// 当前帧与上一帧特征点匹配VisualOdometer::feature_match(VisualOdometer::frames[VisualOdometer::frames.size()-1], frame,VisualOdometer::matches);// 计算相对位姿关系VisualOdometer::calc_pose_relative(VisualOdometer::frames[VisualOdometer::frames.size()-1], frame,VisualOdometer::matches);}// 将当前帧加入队列VisualOdometer::frames.push_back(frame);
}void VisualOdometer::get_pose(int frame_idx, Mat &R, Mat &T)
{if (VisualOdometer::frames.size() <= abs(frame_idx)){R = Mat();T = Mat();return;}else{if (frame_idx >= 0){R = VisualOdometer::frames[frame_idx].R.clone();T = VisualOdometer::frames[frame_idx].T.clone();}else{R = VisualOdometer::frames[VisualOdometer::frames.size() + frame_idx].R.clone();T = VisualOdometer::frames[VisualOdometer::frames.size() + frame_idx].T.clone();}}
}void VisualOdometer::set_pose(int frame_idx, const cv::Mat& R, const cv::Mat& T)
{if (VisualOdometer::frames.size() <= abs(frame_idx)){return;}else{if (frame_idx >= 0){VisualOdometer::frames[frame_idx].R = R.clone();VisualOdometer::frames[frame_idx].T = T.clone();}else{VisualOdometer::frames[VisualOdometer::frames.size() + frame_idx].R = R.clone();VisualOdometer::frames[VisualOdometer::frames.size() + frame_idx].T = T.clone();}}
}void VisualOdometer::get_3d_points(int frame_idx, std::vector<cv::Point3d> &key_points_3d)
{if (VisualOdometer::frames.size() <= abs(frame_idx)){key_points_3d.clear();return;}else{if (frame_idx >= 0){key_points_3d = VisualOdometer::frames[frame_idx].key_points_3d;}else{key_points_3d = VisualOdometer::frames[VisualOdometer::frames.size() + frame_idx].key_points_3d;}}
}
- Frame类
#pragma once
#include <vector>
#include <opencv2/opencv.hpp>namespace oSLAM
{class Frame{public:std::vector<cv::KeyPoint> key_points;cv::Mat descriptors;std::vector<cv::Point3d> key_points_3d;cv::Mat R;cv::Mat T;cv::Mat rgb;cv::Mat depth;double time_stamp;};
}
最终在rgbd_dataset_freiburg2_desk数据集上测试类一下效果,感觉有点拉,跑着跑着就飘了(红色的是真值,绿色的是计算结果),等实现了完整的vo在回来分析一下。
结果展示使用了Pangolin和yuntianli91的pangolin_tutorial
相关文章:

【从零开始写视觉SLAM】v0.1基于特征点的简单VO
v0.1版本的oSLAM实现了基于orb特征点的简单视觉里程计,通过连续两帧的rgbd数据实现相机相对位姿的估计。 #mermaid-svg-ibQfHFVHezQD5RWW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ibQfHFVHezQD5RW…...

CentOS-7 安装 MariaDB-10.8
一、安装之前删除已存在的 Mysql/MariaDB 1 查找存在的 MariaDB # 注意大小写 rpm -qa | grep MariaDB # rpm -qa 列出所有被安装的rpm package (-qa:query all) rpm -qa | grep mariadb # grep (缩写来自Globally search a Regular Expre…...

Packet Tracer – 对 VLAN 实施进行故障排除 – 方案 1
Packet Tracer – 对 VLAN 实施进行故障排除 – 方案 1 地址分配表 设备 接口 IP 地址 子网掩码 交换机端口 VLAN PC1 NIC 172.17.10.21 255.255.255.0 S2 F0/11 10 PC2 NIC 172.17.20.22 255.255.255.0 S2 F0/18 20 PC3 NIC 172.17.30.23 255.255.255.0…...

五、c++学习(加餐1:汇编基础学习)
经过前面几节课的学习,我们在一些地方都会使用汇编来分析,我们学习汇编,只是学习一些基础,主要是在我们需要深入分析语法的时候,使用汇编分析,这样会让我们更熟悉c编译器和语法。 从这节课开始,…...

iOS正确获取图片参数深入探究及CGImageRef的使用(附源码)
一 图片参数的正确获取 先拿一张图片作为测试使用 图片参数如下: 图片的尺寸为: -宽1236个像素点 -高748个像素点 -色彩空间为RGB -描述文件为彩色LCD -带有Alpha通道 请记住这几个参数,待会儿我们演示如何正确获取。 将这张图片分别放在…...
Typescript 5.0 发布:快速概览
探索最令人兴奋的功能和更新 作为一种不断发展的编程语言,TypeScript 带来了大量的改进和新功能。在本文中,我们将深入探讨 TypeScript 的最新版本 5.0,并探索其最值得关注的更新。 1. 装饰器 TypeScript 5.0 引入了一个重新设计的装饰器系…...
【图像处理 】卡尔曼滤波器原理
目录 一、说明 二、它是什么? 2.1 我们可以用卡尔曼滤波器做什么? 2.2 卡尔曼滤波器如何看待您的问题...

YOLOv5 实例分割入门
介绍 YOLOv5 目标检测模型以其出色的性能和优化的推理速度而闻名。因此,YOLOv5 实例分割模型已成为实时实例分割中最快、最准确的模型之一。 在这篇文章中,我们将回答以下关于 YOLOv5 实例分割的问题: YOLOv5检测模型做了哪些改动,得到了YOLOv5实例分割架构?使用的 Prot…...

数字城市发展下的技术趋势,你知道多少?
提到数字城市、智慧城市大家都会感觉经常在耳边听到,但是要确切说出具体的概念还是有一点难度的。具体来说:数字城市是一个集合多种技术的系统,以计算机技术、多媒体技术和大规模存储技术为基础,以宽带网络为纽带,运用…...

linux 串口改为固定
在/etc/udev/rules.d 目录下新建定义规则的文件 1. 文件名要按规范写否则改动无效2. 规则文件必须以.rules 结尾3. 规则文件名称必须遵循 xx-name.rules 格式(xx 为数字或字母,name 为规则名称),例如 99-serial-ports.rules。4. 规…...

【SCI一区】考虑P2G和碳捕集设备的热电联供综合能源系统优化调度模型(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

Redis缓存数据库(四)
目录 一、概述 1、Redis Sentinel 1.1、docker配置Redis Sentinel环境 2、Redis存储方案 2.1、哈希链 2.2、哈希环 3、Redis分区(Partitioning) 4、Redis面试题 一、概述 1、Redis Sentinel Redis Sentinel为Redis提供了高可用解决方案。实际上这意味着使用Sentinel…...

View中的滑动冲突
View中的滑动冲突 1.滑动冲突的种类 滑动冲突一般有3种, 第一种是ViewGroup和子View的滑动方向不一致 比如: 父布局是可以左右滑动,子view可以上下滑动 第二种 ViewGroup和子View的滑动方向一致 第三种 第三种类似于如下图 2.滑动冲突的解决方式 滑动冲突一般情况下有2…...

java boot项目基础配置之banner与日志配置演示 并教会你如何使用文档查看配置
上文 我们简单讲了一下 springboot 项目的配置 都是写在resources下的application.properties中 springboot 项目中 配置都写在这一个文件 可以说非常方便 不像之前 写个项目配置这里一个哪里一个 看到是非常费力 我们启动项目 这里有个图案 其实 这叫 banner 我们就用配置来…...

蓝鲸平台通过标准运维 API 安装 Agent
目录 一、背景 二、目的 三、创建安装agent流程 四、通过标准运维 API 安装 Agent 五、总结 一、背景 蓝鲸平台正常情况纳管主机需要在节点管理手工安装agent,不能达到完成自动化安装agent的效果。想通过脚本一键安装agent,而不需要在蓝鲸平台进行过…...
python 图片保存成视频
👨💻个人简介: 深度学习图像领域工作者 🎉工作总结链接:https://blog.csdn.net/qq_28949847/article/details/128552785 链接中主要是个人工作的总结,每个链接都是一些常用demo,…...
uniapp 引入 Less SCSS
✨求关注~ 😀博客:www.protaos.com 本文将介绍如何在 UniApp 中引入 Less 和 SCSS,两种流行的 CSS 预处理器。通过使用 Less 和 SCSS,你可以在 UniApp 项目中更灵活地编写样式,并享受预处理器提供的便利功能。 代码实现…...
Linux程序设计:文件操作
文件操作 系统调用 write //函数定义 #include <unistd.h> size_t write(int fildes, const void *buf, size_t nbytes); //示例程序 #include <unistd.h> #include <stdlib.h> int main() { if ((write(1, “Here is some data\n”, 18)) ! 18)write(2, …...
【自制C++深度学习推理框架】Tensor模板类的设计思路
Tensor模板类的设计思路 为什么要把Armadillo线性代数库arma::fcube封装成Tensor模板类? arma::fcube是Armadillo线性代数库中的一种数据类型,它是一个三维的float类型张量。Armadillo库是一个C科学计算库,提供了高效的线性代数和矩阵运算。…...

linux--systemd、systemctl
linux--systemd、systemctl 1 介绍1.1 发展sysvinitupstart主角 systemd 登场 1.2 简介 2 优点兼容性启动速度systemd 提供按需启动能力采用 linux 的 cgroups 跟踪和管理进程的生命周期启动挂载点和自动挂载的管理实现事务性依赖关系管理日志服务systemd journal 的优点如下&a…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...