R3live笔记:(图像处理)视觉-惯性里程计VIO部分_r3live vio-程序员宅基地

技术标签: # R3live  计算机视觉  图像处理  opencv  

R3LIVE相关参考:
R3LIVE(升级R2LIVE):编译与运行

https://blog.csdn.net/handily_1/article/details/122271243?spm=1001.2014.3001.5502
R3live:整体分析
https://blog.csdn.net/handily_1/article/details/122360134?spm=1001.2014.3001.5502
运行代码报错:Failed to load module “canberra-gtk-module“
https://blog.csdn.net/handily_1/article/details/122359275
R3live笔记:(图像处理)视觉-惯性里程计VIO部分
https://blog.csdn.net/handily_1/article/details/122377514

r3live VIO

r3live的VIO子系统绘制了全局地图的纹理,通过最小化光度误差来估计系统的状态,即:
将全局地图中一定数量的点(即跟踪的点)投影到当前图像中
然后最小化这些点的光度误差,在ESIKF框架内迭代估计系统状态。
为了提高效率,跟踪的地图点是稀疏的,这通常需要构建输入图像的金字塔。
然而,金字塔对平移或旋转都不是不变的,也需要被估计。
r3live框架中,利用单个地图点的颜色来计算光度误差,
因为颜色是地图点的固有属性,对相机的平移和旋转都是不变的。
根据r3live框图的VIO部分:
在这里插入图片描述
首先利用一个帧对帧(frame-to-frame)的光流来跟踪地图点,
通过最小化跟踪地图点的
Perspective-n-Point(PnP)投影误差

然后,通过最小化跟踪点之间的帧到地图(frame-to-map的光度误差来进一步细化系统的状态估计,
有了收敛的状态估计和原始输入图像,执行纹理渲染来更新全局地图中点的颜色。

1. 帧对帧(frame-to-frame)

假设在最后一个图像帧中Ik-1跟踪到了 m个地图点 ,它们在 Ik-1上的投影为 [公式] ,利用Lucas-Kanade光流来找出它们在当前图像帧 Ik中的位置,标记为 。然后,通过ESIKF滤波器,我们计算和优化它们的投影误差来估计状态。
在这里插入图片描述

1)PnP投影误差 Perspective-n-Point projection error

投影误差计算如下:
在这里插入图片描述
其中:
在这里插入图片描述
公式(5)中,第一项是针孔相机模型的投影函数,第二项为在线时间校正因子(Online-temporal correction factor)
误差公式(4)中的测量噪声包含两个源:
一个是像素跟踪误差
在这里插入图片描述
另一个是地图点位置误差
在这里插入图片描述
在这里插入图片描述

2)帧到帧视觉里程计ESIKF更新 Frame-to-frame VIO ESIKF update

公式(8)构成了xk的观测分布,它可以与IMU传播的先验分布相结合,以得到最大后验MAP估计:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

2.帧到地图 视觉惯性里程计 Frame-to-map Visual-Inertial odometry

在这里插入图片描述
在这里插入图片描述

3.渲染全局地图的纹理 Render the texture of global map

在帧到地图的VIO更新后,我们得到了当前图像的精确位姿,然后我们执行渲染功能来更新地图点的颜色。
在这里插入图片描述

4.视觉惯性里程计子系统的跟踪点更新 Update of the tracking points of VIO subsystem

在这里插入图片描述
参考:【论文阅读】R3LIVE:A Robust RealTime RGB-colored, LiDAR-Inertial-Visual tightly-coupled state Estimation


代码

1. IMAGE_topic

订阅:

        sub_img = m_ros_node_handle.subscribe(IMAGE_topic.c_str(), 1000000, &R3LIVE::image_callback, this, ros::TransportHints().tcpNoDelay());

图像topic数据传入的时候,回调函数image_callback:

void R3LIVE::image_callback( const sensor_msgs::ImageConstPtr &msg )
{
    
    std::unique_lock< std::mutex > lock( mutex_image_callback );
    if ( sub_image_typed == 2 )
    {
    
        return; // Avoid subscribe the same image twice.
    }
    sub_image_typed = 1;

    // 判断是否是第一帧
    if ( g_flag_if_first_rec_img )
    {
    
        g_flag_if_first_rec_img = 0;
        m_thread_pool_ptr->commit_task( &R3LIVE::service_process_img_buffer, this );
    }

    // 将图像编码转换为BGR8
    cv::Mat temp_img = cv_bridge::toCvCopy( msg, sensor_msgs::image_encodings::BGR8 )->image.clone();
    // 图像数据进行处理
    process_image( temp_img, msg->header.stamp.toSec() );
}

图像数据进行处理process_image:

void   R3LIVE::process_image( cv::Mat &temp_img, double msg_time )
{
    
    cv::Mat img_get;
    if ( temp_img.rows == 0 )
    {
    
        cout << "Process image error, image rows =0 " << endl;
        return;
    }
// 这一帧的时间戳小于上一帧的时间戳    错误
    if ( msg_time < last_accept_time )
    {
    
        cout << "Error, image time revert!!" << endl;
        return;
    }
// 这一帧的时间戳与上一帧的时间戳间隔小于发布频率 错误
    if ( ( msg_time - last_accept_time ) < ( 1.0 / m_control_image_freq ) * 0.9 )
    {
    
        cout << "Error, image time interval wrong!!" << endl;
        return;
    }
    last_accept_time = msg_time;

    // 实际上是第一帧图像进来的时候进行的初始化操作
    if ( m_camera_start_ros_tim < 0 )
    {
    
        m_camera_start_ros_tim = msg_time;
        m_vio_scale_factor = 1280 * m_image_downsample_ratio / temp_img.cols; // 320 * 24
        // load_vio_parameters();
        // 相机参数初始化,包括内参、外参、畸变参数等
        set_initial_camera_parameter( g_lio_state, m_camera_intrinsic.data(), m_camera_dist_coeffs.data(), m_camera_ext_R.data(),
                                      m_camera_ext_t.data(), m_vio_scale_factor );
        cv::eigen2cv( g_cam_K, intrinsic );
        cv::eigen2cv( g_cam_dist, dist_coeffs );

        // TODO QJF:畸变校正,opencv提供的可以直接使用的矫正算法,和remap()组合来处理
        // 这个函数用于计算无畸变和修正转换关系,为了重映射,将结果以映射的形式表达。无畸变的图像看起来就像原始的图像,就像这个图像是用内参为newCameraMatrix的且无畸变的相机采集得到的。 
        initUndistortRectifyMap( intrinsic, dist_coeffs, cv::Mat(), intrinsic, cv::Size( 1280 / m_vio_scale_factor, 1024 / m_vio_scale_factor ),
                                 CV_16SC2, m_ud_map1, m_ud_map2 );
        m_thread_pool_ptr->commit_task( &R3LIVE::service_pub_rgb_maps, this);
        m_thread_pool_ptr->commit_task( &R3LIVE::service_VIO_update, this);
        m_mvs_recorder.init( g_cam_K, 1280 / m_vio_scale_factor, &m_map_rgb_pts );
        m_mvs_recorder.set_working_dir( m_map_output_dir );
    }
    // 降采样=>图像大小重新 设置
    if ( m_image_downsample_ratio != 1.0 )
    {
    
        // 图像大小重新 设置
        cv::resize( temp_img, img_get, cv::Size( 1280 / m_vio_scale_factor, 1024 / m_vio_scale_factor ) );
    }
    else
    {
    
        img_get = temp_img; // clone ?
    }
    std::shared_ptr< Image_frame > img_pose = std::make_shared< Image_frame >( g_cam_K );

    // 原图复制给img_pose
    if ( m_if_pub_raw_img )
    {
    
        img_pose->m_raw_img = img_get;
    }
    // 重映射,就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。
    // TODO 什么作用?
    // INTER_LINEAR – 双线性插值
    cv::remap( img_get, img_pose->m_img, m_ud_map1, m_ud_map2, cv::INTER_LINEAR );
    cv::imshow("sub Img", img_pose->m_img);
    img_pose->m_timestamp = msg_time;
    img_pose->init_cubic_interpolation();// Cubic interpolation立方插值初始化
    img_pose->image_equalize();// 直方图均衡化处理
    m_camera_data_mutex.lock();
    m_queue_image_with_pose.push_back( img_pose );
    m_camera_data_mutex.unlock();
    total_frame_count++;

    if ( m_queue_image_with_pose.size() > buffer_max_frame )
    {
    
        buffer_max_frame = m_queue_image_with_pose.size();
    }

    // cout << "Image queue size = " << m_queue_image_with_pose.size() << endl;
}

2. IMAGE_topic_compressed

解压缩compressed格式图像
订阅:

        sub_img_comp = m_ros_node_handle.subscribe(IMAGE_topic_compressed.c_str(), 1000000, &R3LIVE::image_comp_callback, this, ros::TransportHints().tcpNoDelay());

回调:


void R3LIVE::image_comp_callback( const sensor_msgs::CompressedImageConstPtr &msg )
{
    
    // 互斥锁
    std::unique_lock< std::mutex > lock2( mutex_image_callback );
    if ( sub_image_typed == 1 ) // 0: TBD 1: sub_raw, 2: sub_comp
    {
    
        return; // Avoid subscribe the same image twice.
    }
    sub_image_typed = 2;
    g_received_compressed_img_msg.push_back( msg );
    if ( g_flag_if_first_rec_img )
    {
    
        g_flag_if_first_rec_img = 0;
        m_thread_pool_ptr->commit_task( &R3LIVE::service_process_img_buffer, this );
    }
    return;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/handily_1/article/details/122377514

智能推荐

如何生成 .plist文件,plist文件制作教程_plist生成-程序员宅基地

文章浏览阅读8.1k次,点赞4次,收藏7次。如何生成 .plist文件,plist文件制作教程如何生成 plist文件plist文件制作教程准备好已经切分好的序列图打开软件按图提示操作选择位置保存生成的两个文件 plist png example前期准备软件:TexturePacker要制作的序列图1.准备好已经切分好的序列图2.打开软件3.按图提示操作选择位置保存生成的两个文件 *.plist *.png examp_plist生成

JTable的应用(二)-程序员宅基地

文章浏览阅读69次。目录如何使用Table(1)创建一个简单的表格(2)向容器添加表格(3)改变每每一列的宽度(4)用户选择(5)创建表格模型(6)监听数据改变(7)点燃数据改变事件(8)概念:编辑器和渲染器(Editors and Renderers)(9)使用自定义渲染器(10)为单元格指定提示工具(11)为列头指定工具集(12)排序和过滤..._jtable对象两个参数类型

uoj #139-程序员宅基地

文章浏览阅读79次。树链剖分//模板题由于存在换根操作对所有关于节点 u 的修改和查询操作进行分类讨论若 Root 在 u 的子树中,则不处理 u 所在的 Root 的那颗子树否则不会有影响寻找 Root 所在的那颗子树的根可以用倍增求#include <iostream>#include <cstdio>#include <algorithm>#include..._onuoj

宇宙时光推论-程序员宅基地

文章浏览阅读273次。象理主义者认为:时间和空间是无限的,在时间上,没有开始和终了,在空间上没有边界和尽头,而宇宙间的万事万物,小到朝菌、蝇虫,大到整个宇宙,都是有限的产生、存在和无限的循环,任何事物都有开始和结束。静久必静,物极必反。对于整个宇宙而言,物质永不毁灭,循环不已生生不息。第一节太极宇宙一:太易宇宙即宇宙形成过程中“未见气之前”的阶段,阴阳不分,不产生吸引,也不产生排斥,为静止的空间,通过观..._时间有没有开始,空间有没有尽头

【论文阅读】Webshell检测方法研究综述_webshell研究现状-程序员宅基地

文章浏览阅读924次。目录一、论文题目二、作者信息三、论文地址四、论文内容1.webshell检测的分类2.基于静态文本的检测3.基于动态行为的检测4.基于日志分析的监测5.future works一、论文题目Webshell 检测方法研究综述二、作者信息南京林业大学,端木怡婷三、论文地址https://kns.cnki.net/kcms/detail/detail.aspx?dbcode=CJFD&dbname=CJFDLAST2021&filename=RJZZ202011020四、论文内容1_webshell研究现状

多线程求素数_多线程输出素数-程序员宅基地

文章浏览阅读2k次。一、前言 最近在学习golang的并发语法,想来想去,发现学习的多线程算法着实不多,除了一些传统排序算法的并发版本外,第一时间想起的也就是这个并发求素数的算法。 大部分人了解的单线程求素数算法,应该是两重循环判断是否能够整除,外层循环为被除数,内层循环为除数。而这种形式的求法能做文章的地方无非就是两点:被除数和除数的取值范围。 我所知比较好的被..._多线程输出素数

随便推点

Android Studio APK在真机上运行的方法(1)-程序员宅基地

文章浏览阅读710次,点赞8次,收藏17次。这时候软件可能就会识别手机。如果以上方法不可行,又不想花太多时间去找问题,就可以采用安装安装包的方式。我们可以在每一个对应项目对应目录下面的app\build\outputs\apk\debug查找到软件自动生成的安装包。如果是新版的AS的话可能只会看到一个app-debug.apk,并没有什么网上说的app-debug-unaligned.apk什么的两个.apk结尾的文件,这都不要紧,可以把这个拿到手机上直接安装。

Unity 划线 - 使用Image实现划线_unity quest3 在image上画线-程序员宅基地

文章浏览阅读1w次,点赞11次,收藏38次。unity使用Image实现划线功能_unity quest3 在image上画线

获取表单内部元素的N种方法_提取所有表单元素的方法-程序员宅基地

文章浏览阅读3.1k次。今天讲讲获取表单元素的N种方法~以上是部分资料参考的地方:http://blog.csdn.net/h12kjgj/article/details/61624509先给出一个实例。输入数字1~10,弹出输入的数字,并计算该数字的阶乘;如果输入的数字不在该范围内,则输出“balabala自己编的一些话”源代码: function Count(){var i,r,_提取所有表单元素的方法

脑肠轴——看不见的Crosstalk_脑肠轴检测指标-程序员宅基地

文章浏览阅读1.8w次,点赞5次,收藏11次。文献导读肠道菌群是指存在于宿主肠道内的微生物集合,它参与了宿主多种重要的生理作用,如影响机体的营养代谢、调节机体免疫系统的发育与成熟及抗菌作用,因此肠道菌群又被人称之为"被遗忘的器官"。随着科学技术的发展,人们逐渐意识到到肠道菌群与全身各个系统的疾病的发生发展都存在着密切的关联,从而提出一些像"肠脑轴"和"肠肝轴"等名词。肠脑轴背景介绍据统计,定植于人体肠道内数量约是人体细胞数量的..._脑肠轴检测指标

Qt安装教程(Qt 6.4)_qt6.4安装-程序员宅基地

文章浏览阅读6.5w次,点赞56次,收藏261次。Qt6.4安装教程、组件介绍_qt6.4安装

解决ubuntu1604 64位安装海思V400编译器之后仍然报No such file or directory_hisilicon_v400-程序员宅基地

文章浏览阅读648次。ubuntu1604 64位安装海思V400编译器之后仍然报No such file or directory环境:ubuntu 1604 64位安装:arm-hisiv400-linux工具链安装成功后执行:arm-hisiv400-linux-gcc -v报错:bash: /opt/hisi-linux/x86-arm/arm-hisiv400-linux/target/bin/arm-hisiv400-linux-gcc: No such file or directory。但其实工具链已经安_hisilicon_v400

推荐文章

热门文章

相关标签