VINS-MONO代码解读----配置文件,数据结构,前端feature_tracker

news2025/1/19 22:05:02

跑通代码之后可以深入看代码了,整体代码很多,可先从配置文件开始看。

1. VINS-MONO配置文件理解

参考启动文件launch与参数配置文件yaml介绍

启动文件launch:euroc.launch

参数配置文件yaml:euroc_config.yaml:包括通用参数,相机内参,IMU噪声参数,Tbc相机外参,需要的topic,前端feature tracker的参数,后端loop的参数,优化的参数,以及时间戳补偿的参数。

2. 基本数据结构

从ROS源码看一下数据结构,基本上都是一些stl的数据结构的封装

  1. sensor_msgs::ImageConstPtr
typedef  ::std_msgs::Header_<ContainerAllocator>  header;
uint32_t height;
uint32_t width;
std::basic_string encoding;
uint8_t is_bigendian; #大端big endian(从低地址到高地址的顺序存放数据的高位字节到低位字节)和小端little endian
uint32_t step;
vector<uint8_t> data;
typedef ::sensor_msgs::Image_<std::allocator<void> > Image;
typedef boost::shared_ptr< ::sensor_msgs::Image > ImagePtr;
  1. sensor_msgs::PointCloudPtr feature_points

from file:sensor_msgs/PointCloud.msg

//头信息
std_msgs::Header_ header
	feature_points->header = img_msg->header;	
	feature_points->header.frame_id = "world";
//3D landmark(如下是在归一化camera系下)
std::vector< ::geometry_msgs::Point32_> points;
    geometry_msgs::Point32 p;
    p.x = un_pts[j].x;
    p.y = un_pts[j].y;
    p.z = 1;
    feature_points->points.push_back(p);//3D点
//channels
std::vector< ::sensor_msgs::ChannelFloat32_> channels;
sensor_msgs::PointCloudPtr feature_points(new sensor_msgs::PointCloud);
    feature_points->channels.push_back(id_of_point);//这里每个channel都是一个独立的ChannelFloat32_,就使用了多个channel
    feature_points->channels.push_back(u_of_point);
    feature_points->channels.push_back(v_of_point);
    feature_points->channels.push_back(velocity_x_of_point);
    feature_points->channels.push_back(velocity_y_of_point);
//对于ChannelFloat32_
	std::basic_string<char> name;//名称
	std::vector<float> values;//值
	typedef ::sensor_msgs::ChannelFloat32_<std::allocator<void> > ChannelFloat32;
	typedef boost::shared_ptr< ::sensor_msgs::ChannelFloat32 > ChannelFloat32Ptr;
  1. sensor_msgs::PointCloud msg_match_points

上面的feature_points是类成员指针,这个直接是msg类成员

//头信息
std_msgs::Header_ header
	msg_match_points.header.stamp = ros::Time(time_stamp);
//3D landmark(如下是在归一化camera系下)
std::vector< ::geometry_msgs::Point32_> points;
	geometry_msgs::Point32 p;
	p.x = matched_2d_old_norm[i].x;
	p.y = matched_2d_old_norm[i].y;
	p.z = matched_id[i];
	msg_match_points.points.push_back(p);
//channels
std::vector< ::sensor_msgs::ChannelFloat32_> channels;
	Eigen::Vector3d T = old_kf->T_w_i; 
	Eigen::Matrix3d R = old_kf->R_w_i;
	Quaterniond Q(R);
	sensor_msgs::ChannelFloat32 t_q_index;//一个channel是一个通道,这里代表的是一个量就是两帧间的T
	t_q_index.values.push_back(T.x());
	t_q_index.values.push_back(T.y());
	t_q_index.values.push_back(T.z());
	t_q_index.values.push_back(Q.w());
	t_q_index.values.push_back(Q.x());
	t_q_index.values.push_back(Q.y());
	t_q_index.values.push_back(Q.z());
	t_q_index.values.push_back(index);
	msg_match_points.channels.push_back(t_q_index);
  1. sensor_msgs::ImuConstPtr

底层const指针,指向的IMU数据不可变,即只读
from file:sensor_msgs/Imu.msg

{
std_msgs::Header_ header; //头
geometry_msgs::Quaternion_ orientation; //pose rotation
boost::array<double, 9> orientation_covariance;//rotation 协方差
geometry_msgs::Vector3_<> angular_velocity;//角速度
boost::array<double, 9> angular_velocity_covariance;//角速度cov
geometry_msgs::Vector3_<> linear_acceleration;//加速度
boost::array<double, 9> linear_acceleration_covariance;//加速度cov
} //Imu_
typedef ::sensor_msgs::Imu_<std::allocator<void> > Imu;
typedef boost::shared_ptr< ::sensor_msgs::Imu > ImuPtr;
typedef boost::shared_ptr< ::sensor_msgs::Imu const> ImuConstPtr;

挖坑:对ROS数据结构中的理解。

rosrun rqt_graph rqt_graph看图更好理解topic:
在这里插入图片描述

3. 组合数据结构

本节参考自博客:https://blog.csdn.net/qq_41839222/article/details/86030962

  1. measurements
std::vector<std::pair<std::vector<sensor_msgs::ImuConstPtr>, sensor_msgs::PointCloudConstPtr>> measurements;

estimator_node.cpp中getMeasurements()函数将对imu和图像数据进行初步对齐得到的数据结构,确保图像关联着对应时间戳内的所有IMU数据
sensor_msgs::PointCloudConstPtr 表示某一帧图像的feature_points
std::vector<sensor_msgs::ImuConstPtr> 表示当前帧和上一帧时间间隔中的所有IMU数据
将两者组合成一个数据结构,并构建元素为这种结构的vector进行存储

  1. image
map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>>>> image;

在estimator.cpp中的process()中被建立,在Estimator::processImage()中被调用
作用是建立每个特征点(camera_id,[x,y,z,u,v,vx,vy])构成的map,索引为feature_id。
由于是单目,所以pair内的camera_id都为0

for (unsigned int i = 0; i < img_msg->points.size(); i++){
    int v = img_msg->channels[0].values[i] + 0.5;
    int feature_id = v / NUM_OF_CAM;
    int camera_id = v % NUM_OF_CAM;
    double x = img_msg->points[i].x;
    double y = img_msg->points[i].y;
    double z = img_msg->points[i].z;
    double p_u = img_msg->channels[1].values[i];
    double p_v = img_msg->channels[2].values[i];
    double velocity_x = img_msg->channels[3].values[i];
    double velocity_y = img_msg->channels[4].values[i];
    ROS_ASSERT(z == 1);
    Eigen::Matrix<double, 7, 1> xyz_uv_velocity;
    xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y;
    image[feature_id].emplace_back(camera_id,  xyz_uv_velocity);
}
  1. all_image_frame
map<double, ImageFrame> all_image_frame

在estimator.h中作为class Estimator的属性:键是图像帧的时间戳,值是图像帧类。

图像帧类可由图像帧的特征点与时间戳构造,此外还保存了位姿Rt,预积分对象pre_integration,是否是关键帧。

class ImageFrame
{
    public:
        ImageFrame(){};
        ImageFrame(const map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>>>>& _points, double _t):t{_t},is_key_frame{false}
        {
            points = _points;
        };
        map<int, vector<pair<int, Eigen::Matrix<double, 7, 1>> > > points;
        double t;
        Matrix3d R;
        Vector3d T;
        IntegrationBase *pre_integration;
        bool is_key_frame;
};

数据结构暂时总结到这里,下面开始前端部分。

4. 前端:视觉跟踪 feature_trackers

feature_trackers整体pipeline:
在这里插入图片描述

这里的Recursive distortion model还不太懂,需要补一下。

readIamge() 是关键

  • 1. 直方图均衡
  • 2. KLT(用status筛一遍)
  • 3. rejectWithF:选择部分KLT match上的点计算F矩阵:过程中用RANSAC筛一遍(RANSAC需要学习一下,Recursive distortion model不是很懂)
  1. liftProjective 用内参将uv从像素平面转换到归一化平面上(x,y,1)(这里根据camera 类型的不同pinhole或者fisheye有不同的方法和重载函数)
  2. 使用inverse distortion model(proposed by Heikkila)或者Recursive distortion model去畸变
  3. 计算F矩阵并使用FM_RANSAC方法剔除outlier features
    in PinholeCamera.cc
void
PinholeCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const
{
    double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u;
    double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d;
    //double lambda;

    // Lift points to normalised plane 将内参的逆变化转化为4个算子invKxx,从uv变为归一化平面内的xy1
    mx_d = m_inv_K11 * p(0) + m_inv_K13;
    my_d = m_inv_K22 * p(1) + m_inv_K23;

    if (m_noDistortion)
    {
        mx_u = mx_d;
        my_u = my_d;
    }
    else
    {
        if (0)
        {
            double k1 = mParameters.k1();
            double k2 = mParameters.k2();
            double p1 = mParameters.p1();
            double p2 = mParameters.p2();

            // Apply inverse distortion model
            // proposed by Heikkila
            mx2_d = mx_d*mx_d;
            my2_d = my_d*my_d;
            mxy_d = mx_d*my_d;
            rho2_d = mx2_d+my2_d;
            rho4_d = rho2_d*rho2_d;
            radDist_d = k1*rho2_d+k2*rho4_d;
            Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d;
            Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d;
            inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d);

            mx_u = mx_d - inv_denom_d*Dx_d;
            my_u = my_d - inv_denom_d*Dy_d;
        }
        else
        {
            // Recursive distortion model(这个递归distortion到底是什么意思?)
            int n = 8;
            Eigen::Vector2d d_u;
            distortion(Eigen::Vector2d(mx_d, my_d), d_u);
            // Approximate value
            mx_u = mx_d - d_u(0);
            my_u = my_d - d_u(1);

            for (int i = 1; i < n; ++i)
            {
                distortion(Eigen::Vector2d(mx_u, my_u), d_u);
                mx_u = mx_d - d_u(0);
                my_u = my_d - d_u(1);
            }
        }
    }

    // Obtain a projective ray
    P << mx_u, my_u, 1.0;
}
  • 4. setMask():使feature分布更均匀。
  1. 对所有track上的feature按照track cnt进行排序
  2. 遍历排序后的features,以feature坐标为圆心,MIN_DIST(程序里取30)为圆心画圆,圆内mask值均设置为0,后续遇到mask值不为255的点直接pass掉,相当于扔掉较为接近的feature,留下的都是track次数较多的,分布较为均匀的feature。
  • 5. 判断是否需要在forw_img上提取新的feature:经过rejectWithF()和setMask()的剔除后,inlier可能不够,如果小于阈值MAX_CNT,则调用 goodFeaturesToTrack() 和当前mask提取新的features(当前mask可能长这样
    在这里插入图片描述

被设为0的区域是黑的,手画的所以没保证半径相同-_- ),在白色区域进行提点,保证了features的均匀分布。

  • 6. addPoints():将新提取的点添加到forw_pts中,ids设为-1(前端处理完,在pub之前会updateID,遍历到-1的就会赋值给当前的feature总数+1),track_cnt设为1
  • 7. prev_,cur_变量更新
  • 8. 去畸变
  1. 遍历cur_pts,调用liftProjective()去畸变,与rejectWithF中相同,构建cur_un_pts(当前features的归一化坐标vector),cur_un_pts_map(用id来查询features的map)。
  2. 补充:cur_un_pts当前共有3类点:1.新点(ids==-1),2.在prev_pts中跟到的点(find!=end),3.没跟到的点(find==end)
    只有在prev中跟到的点才能计算速度,其他均将速度设为0(在Ch7作业中这里的速度没有用到,所以当时直接全部设为了0)

至此,readImage完毕。
如果需要pub则将所有cur_相关的量都打包成feature_points,并pub
打包:

        pub_count++;
        sensor_msgs::PointCloudPtr feature_points(new sensor_msgs::PointCloud);
        sensor_msgs::ChannelFloat32 id_of_point;
        sensor_msgs::ChannelFloat32 u_of_point;
        sensor_msgs::ChannelFloat32 v_of_point;
        sensor_msgs::ChannelFloat32 velocity_x_of_point;
        sensor_msgs::ChannelFloat32 velocity_y_of_point;

        feature_points->header = img_msg->header;
        feature_points->header.frame_id = "world";

        vector<set<int>> hash_ids(NUM_OF_CAM);
        for (int i = 0; i < NUM_OF_CAM; i++)
        {
            auto &un_pts = trackerData[i].cur_un_pts;
            auto &cur_pts = trackerData[i].cur_pts;
            auto &ids = trackerData[i].ids;
            auto &pts_velocity = trackerData[i].pts_velocity;
            for (unsigned int j = 0; j < ids.size(); j++)
            {
                if (trackerData[i].track_cnt[j] > 1)
                {
                    int p_id = ids[j];
                    hash_ids[i].insert(p_id);
                    geometry_msgs::Point32 p;
                    p.x = un_pts[j].x;
                    p.y = un_pts[j].y;
                    p.z = 1;

                    feature_points->points.push_back(p);//3D点
                    id_of_point.values.push_back(p_id * NUM_OF_CAM + i);//track到的特征点都是成对出现的,左目的i=0,就是自身,右目相应的特征点的id就是多加了1,效果是左右目的tracked points的id只相差1
                    u_of_point.values.push_back(cur_pts[j].x);
                    v_of_point.values.push_back(cur_pts[j].y);
                    velocity_x_of_point.values.push_back(pts_velocity[j].x);//光流速度作为该点的速度
                    velocity_y_of_point.values.push_back(pts_velocity[j].y);
                }
            }
        }
        feature_points->channels.push_back(id_of_point);//这里每个channel都是一个独立的量,就使用了多个channel
        feature_points->channels.push_back(u_of_point);
        feature_points->channels.push_back(v_of_point);
        feature_points->channels.push_back(velocity_x_of_point);
        feature_points->channels.push_back(velocity_y_of_point);

发布:pub_img.publish(feature_points);//发布一幅图像中的特征点信息

得到了最新一帧的,归一化平面上的(目前好像没看到rpj to unit sphere的操作),去了畸变的features。

下一节是状态估计器vins_estimator,是VINS的重点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1215393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【kerberos】使用 curl 访问受 Kerberos HTTP SPNEGO 保护的 URL

前言&#xff1a; 大数据集群集成 Kerberos 后&#xff0c;很多 WEBUI 打开都会提示输入用户名和密码。由于我想获取 flink 任务的详情&#xff0c;且KNOX 并不支持Flink api&#xff0c;查看KNOX 直接的列表&#xff1a;https://docs.cloudera.com/cdp-private-cloud-base/7.…

vivado产生报告阅读分析-Report Power4

在布线后会生成“ Power Report ” &#xff08; 功耗报告 &#xff09;&#xff0c; 它基于当前器件工作条件和设计的切换率来报告功耗详情。功耗分析要求网表已完成综合或设计已完成布局布线。 • set_operating_conditions 命令用于设置工作条件。 • set_switching_ac…

想转行互联网行业,是选择网络安全还是人工智能?

随着数字时代的到来&#xff0c;网络安全和人工智能成了科技创新产业的重要组成部分。也逐渐成了大多数人心中热门的行业选择。那么该如何抉择呢&#xff1f; 首先我们来了解下人工智能的发展前景&#xff1a; 如今&#xff0c;人工智能技术无论是在核心技术方面&#xff0c…

插件漏洞导致 60 万个 WordPress 网站遭受攻击

WordPress 插件 WP Fastest Cache 容易受到 SQL 注入漏洞的攻击&#xff0c;该漏洞可能允许未经身份验证的攻击者读取站点数据库的内容。 WP Fastest Cache 是一个缓存插件&#xff0c;用于加速页面加载、改善访问者体验并提高网站在 Google 搜索上的排名。 根据 WordPress.o…

photoshop插件开发入门

photoshop 学习资料和sdk 下载地址https://developer.adobe.com/console/servicesandapis/ps 脚本编程文档 官方文档&#xff1a; https://extendscript.docsforadobe.dev/ 官方文档&#xff1a; https://helpx.adobe.com/hk_en/photoshop/using/scripting.html open(new F…

如何做到百万数据半小时跑批结束

什么是跑批 跑批就是应用程序定时对数据的批量处理。 跑批有以下特性&#xff1a; 大数据量&#xff1a;批量任务一般伴随着大量的数据处理 自动化&#xff1a;要求制定时间或频率自动运行 性能&#xff1a;要求在指定时间内完成批处理任务 健壮性&#xff1a;针对于异常数…

【每周一测】Java阶段三第三周学习

目录 1、事务四个隔离级别中&#xff0c;哪一个不能防止脏读 2、关于sleep()和wait()&#xff0c;以下描述错误的一项是&#xff08;&#xff09; 3、以下关于Servlet生命周期说法错误的是&#xff08; &#xff09; 4、下列概念解释说明错误的是 5、在 JWT 中&#xff0c…

(二)什么是Vite——Vite 和 Webpack 区别(冷启动)

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

云课五分钟-05一段代码修改-AI修改C++

前篇&#xff1a; 云课五分钟-04一段代码学习-大模型分析C 在前一节&#xff0c;使用大模型工具文心一言等可以帮助分析代码&#xff0c;加快理解。 信息时代→智能时代&#xff0c;系统学习转为碎片学习。 发散思维的能力在智能时代尤为重要。 同样我们也可以借助智能化…

爬虫基础之爬虫的基本介绍

一、爬虫概述 爬虫又称网络蜘蛛、网络机器人&#xff0c;网络爬虫按照系统结构和实现技术&#xff0c;大致可以分为以下几种类型&#xff1a; 通用网络爬虫&#xff08;Scalable Web Crawler&#xff09;&#xff1a;抓取互联网上所有数据&#xff0c;爬取对象从一些种子 URL…

开源大模型部署及推理所需显卡成本必读之二

在前面的文章中&#xff0c;我们介绍了大模型占用显卡空间的一些分析情况&#xff0c;这次我们继续来看看具体量化角度上的结论。 因此&#xff0c;本文来来介绍一个偏具体数值量化的工作。 随着各厂商相继发布大型模型&#xff0c;排行榜变化频繁&#xff0c;新旧交替&#xf…

[云原生2.] Kurbernetes资源管理 ---- (陈述式资源管理方式)

文章目录 1. K8s管理资源的方法类别1.1 陈述式资源管理方式1.2 声明式资源管理方式1.3 GUI式资源管理方法 2. 陈述式资源管理方式2.1 命令行工具 ---- Kubelet2.1.1 简介2.1.2 特性2.1.3 kubelet拓展命令2.1.4 kubectl基本语法2.1.5 Kubectl工具的自动补全 2.2 k8s Service 的类…

外汇天眼:失败的投资者经常陷入两个误区!

一、价格与价值的混淆 在金融领域&#xff0c;价格和价值往往被错误视为同义词。然而&#xff0c;审视市场时&#xff0c;我们会逐渐发现一个“安全差”的重要概念&#xff0c;这是由巴菲特的导师本杰明格雷厄姆提出的。 安全差是指股票的内在价值与市场价格之间的差异。内在…

sklearn笔记:neighbors.NearestNeighbors

1 最近邻 class sklearn.neighbors.NearestNeighbors(*, n_neighbors5, radius1.0, algorithmauto, leaf_size30, metricminkowski, p2, metric_paramsNone, n_jobsNone)邻居搜索算法的选择通过关键字 algorithm 控制&#xff0c;它必须是 [auto, ball_tree, kd_tree, brute] …

分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测

分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测 目录 分类预测 | Matlab实现PSO-LSTM-Attention粒子群算法优化长短期记忆神经网络融合注意力机制多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1…

块设备 I/O 请求送达到外部设备

对于 ext4 文件系统&#xff0c;最后调用的是 ext4_file_write_iter&#xff0c;它将 I/O 的调用分成两种情况&#xff1a; 第一是直接 I/O。最终我们调用的是 generic_file_direct_write&#xff0c;这里调用的是 mapping->a_ops->direct_IO&#xff0c;实际调用的是 e…

[nlp] 损失缩放(Loss Scaling)loss sacle

在深度学习中,由于浮点数的精度限制,当模型参数非常大时,会出现数值溢出的问题,这可能会导致模型训练不稳定。为了解决这个问题,损失缩放(Loss Scaling)技术被引入,它通过缩放损失值来解决这个问题。 在深度学习中,损失缩放技术通常是通过将梯度进行缩放来实现的。具…

太激动了!摄像头终于有画面了!

有了放弃的想法 摄像头APP在我这里好好的&#xff0c;到了老外那里就不能 用。反复试了几套源码&#xff0c;都没有画面。后来干脆把老外说通用的APK反编译后&#xff0c;新做了个APP&#xff0c;结果还是没画面。到了这个时候&#xff0c;我是真的有点沮丧&#xff0c;准备放弃…

局部指令和全局指令的注册和使用

全局指令 先写一个js文件 import store from /store const directivePlugin {install(Vue) {Vue.directive(checkBtn, {inserted(el, binding) {// el: 指令绑定的那个元素对象 dom// binding.value: 指令等于号后面绑定的表达式的值 v-if"xxx"// 拿到el 拿到v…

Git目录不对,即当前文件夹不对应git仓库

报错信息是&#xff1a; fatal: not a git repository (or any of the parent directories): .git 如&#xff1a; 是当前文件夹不对应git仓库&#xff0c;一般在git clone之后&#xff0c;需要进入下一级文件夹才对应仓库。 在文件夹看&#xff0c;本层中没有.git文件夹&…