【从零开始写视觉SLAM】v0.1基于特征点的简单VO

news/2024/11/28 6:25:29/

v0.1版本的oSLAM实现了基于orb特征点的简单视觉里程计,通过连续两帧的rgbd数据实现相机相对位姿的估计。

读取RGBD数据
orb特征点提取
PnP/ICP位姿估计

这部分理论上相对简单一点,咱们就直接上实现。

  1. 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;}}
}
  1. 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


http://www.ppmy.cn/news/83685.html

相关文章

字段信息 详解,以易举例,创建数据库,程序自动创建数据库的前提,程序读写数据库的第一步

今天要做一个处理比较多数据的工具&#xff0c;就是桌面小软件&#xff0c;重新收拾起以前的易语言来编写&#xff0c;C#等也可以&#xff0c;反正就是最后的成品是绿色免安装。 数据多&#xff0c;优先考虑的就是数据库操作了&#xff0c;又快又好是吧&#xff1f; 第一步&am…

遍历 globals() 时必不可少的 RuntimeError

文章目录 参考描述globals() 函数For Loop 过程中产生的迭代变量Runtime Errordictionary changed size during iteration异常产生原因解决方案copy 方法绕过 RuntimeError 产生 RuntimeError 异常的基本要求遍历 locals() 时可能产生的 RuntimeError 参考 项目描述Python 官方…

HR不会告诉你!Java程序员月薪8K和20K的区别!

昨天有同学问好程序员&#xff0c;为啥都是干Java程序员&#xff0c;别人可以拿20k&#xff0c;我才拿8k呢&#xff1f;为啥人家能提前转正我就得晚俩月&#xff1f;小源一听大事不妙&#xff0c;赶紧连夜整理了以下清单供大家check&#xff01; 对于刚入职场还有跳槽成功的同学…

NRK3303语音识别芯片在照明灯上的运用,一款可分布式语音IC方案

随着科技的不断进步&#xff0c;人们对于家居生活中的照明设备的要求也逐渐提高。传统的照明方式已经不能满足人们对智能家居的需求&#xff0c;我们需要更加智能、易于操作、高效节能的智能化照明系统。因此&#xff0c;智能照明应运而生&#xff0c;为我们提供了更加智能化、…

Java Swing 快速入门

Java Swing 快速入门 文章目录 Java Swing 快速入门一、Java Swing 简单介绍1、Java Swing 介绍2、Java Swing 使用步骤3、Java Swing 组件二、Java Swing 简单使用案例1、Hello World 程序2、一个用户登录框实例三、附录1、概念解析2、Swing 的坐标图一、Java Swing 简单介绍 …

【LeetCode】字符串转换整数 (atoi) [M](模拟)

8. 字符串转换整数 (atoi) - 力扣&#xff08;LeetCode&#xff09; 一、题目 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a…

第十二章 异常(Exception)

一、异常的概念&#xff08;P444&#xff09; Java 语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 执行过程中所发生的异常事件可分为两大类&#xff1a; &#xff08;1&#xff09;Error&…

C#入门:编写运行第一个C#程序Helloworld

参考链接&#xff1a; C#入门学习-希里安 下载安装Visual Studio&#xff0c;创建项目 在官网下载安装Professional 2022即可. https://visualstudio.microsoft.com/zh-hans/ 下载时选择C#、.Net框架等支持&#xff0c;安装后运行&#xff0c;新建模板选择 Visual C#、Windo…