ORB-SLAM2 --- ORBmatcher::Fuse函数 --- 局部建图线程调用重载版

news2024/11/26 16:49:21

目录

1.函数作用

2.code 

3.函数解析 


1.函数作用

        将参数一的关键帧的地图点与参数二的地图点集合进行融合。

        将地图点投影到关键帧中进行匹配和融合;融合策略如下:
        1.如果地图点能匹配关键帧的特征点,并且该点有对应的地图点,那么选择观测数目多的替换两个地图点
        2.如果地图点能匹配关键帧的特征点,并且该点没有对应的地图点,那么为该点添加该投影地图点

2.code 

/**
 * @brief 将地图点投影到关键帧中进行匹配和融合;融合策略如下
 * 1.如果地图点能匹配关键帧的特征点,并且该点有对应的地图点,那么选择观测数目多的替换两个地图点
 * 2.如果地图点能匹配关键帧的特征点,并且该点没有对应的地图点,那么为该点添加该投影地图点

 * @param[in] pKF           关键帧
 * @param[in] vpMapPoints   待投影的地图点
 * @param[in] th            搜索窗口的阈值,默认为3
 * @return int              更新地图点的数量
 */
int ORBmatcher::Fuse(KeyFrame *pKF, const vector<MapPoint *> &vpMapPoints, const float th)
{
    // 取出当前帧位姿、内参、光心在世界坐标系下坐标
    cv::Mat Rcw = pKF->GetRotation();
    cv::Mat tcw = pKF->GetTranslation();

    const float &fx = pKF->fx;
    const float &fy = pKF->fy;
    const float &cx = pKF->cx;
    const float &cy = pKF->cy;
    const float &bf = pKF->mbf;

    cv::Mat Ow = pKF->GetCameraCenter();

    int nFused=0;

    const int nMPs = vpMapPoints.size();

    // 遍历所有的待投影地图点
    for(int i=0; i<nMPs; i++)
    {
        
        MapPoint* pMP = vpMapPoints[i];
        // Step 1 判断地图点的有效性 
        if(!pMP)
            continue;
        // 地图点无效 或 已经是该帧的地图点(无需融合),跳过
        if(pMP->isBad() || pMP->IsInKeyFrame(pKF))
            continue;

        // 将地图点变换到关键帧的相机坐标系下
        cv::Mat p3Dw = pMP->GetWorldPos();
        cv::Mat p3Dc = Rcw*p3Dw + tcw;

        // Depth must be positive
        // 深度值为负,跳过
        if(p3Dc.at<float>(2)<0.0f)
            continue;

        // Step 2 得到地图点投影到关键帧的图像坐标
        const float invz = 1/p3Dc.at<float>(2);
        const float x = p3Dc.at<float>(0)*invz;
        const float y = p3Dc.at<float>(1)*invz;

        const float u = fx*x+cx;
        const float v = fy*y+cy;

        // Point must be inside the image
        // 投影点需要在有效范围内
        if(!pKF->IsInImage(u,v))
            continue;

        const float ur = u-bf*invz;

        const float maxDistance = pMP->GetMaxDistanceInvariance();
        const float minDistance = pMP->GetMinDistanceInvariance();
        cv::Mat PO = p3Dw-Ow;
        const float dist3D = cv::norm(PO);

        // Depth must be inside the scale pyramid of the image
        // Step 3 地图点到关键帧相机光心距离需满足在有效范围内
        if(dist3D<minDistance || dist3D>maxDistance )
            continue;

        // Viewing angle must be less than 60 deg
        // Step 4 地图点到光心的连线与该地图点的平均观测向量之间夹角要小于60°
        cv::Mat Pn = pMP->GetNormal();
        if(PO.dot(Pn)<0.5*dist3D)
            continue;
        // 根据地图点到相机光心距离预测匹配点所在的金字塔尺度
        int nPredictedLevel = pMP->PredictScale(dist3D,pKF);

        // Search in a radius
        // 确定搜索范围
        const float radius = th*pKF->mvScaleFactors[nPredictedLevel];
        // Step 5 在投影点附近搜索窗口内找到候选匹配点的索引
        const vector<size_t> vIndices = pKF->GetFeaturesInArea(u,v,radius);

        if(vIndices.empty())
            continue;

        // Match to the most similar keypoint in the radius
         // Step 6 遍历寻找最佳匹配点
        const cv::Mat dMP = pMP->GetDescriptor();

        int bestDist = 256;
        int bestIdx = -1;
        for(vector<size_t>::const_iterator vit=vIndices.begin(), vend=vIndices.end(); vit!=vend; vit++)// 步骤3:遍历搜索范围内的features
        {
            const size_t idx = *vit;

            const cv::KeyPoint &kp = pKF->mvKeysUn[idx];

            const int &kpLevel= kp.octave;
            // 金字塔层级要接近(同一层或小一层),否则跳过
            if(kpLevel<nPredictedLevel-1 || kpLevel>nPredictedLevel)
                continue;

            // 计算投影点与候选匹配特征点的距离,如果偏差很大,直接跳过
            if(pKF->mvuRight[idx]>=0)
            {
                // Check reprojection error in stereo
                // 双目情况
                const float &kpx = kp.pt.x;
                const float &kpy = kp.pt.y;
                const float &kpr = pKF->mvuRight[idx];
                const float ex = u-kpx;
                const float ey = v-kpy;
                // 右目数据的偏差也要考虑进去
                const float er = ur-kpr;        
                const float e2 = ex*ex+ey*ey+er*er;

                //自由度为3, 误差小于1个像素,这种事情95%发生的概率对应卡方检验阈值为7.82
                if(e2*pKF->mvInvLevelSigma2[kpLevel]>7.8)   
                    continue;
            }
            else
            {
                // 计算投影点与候选匹配特征点的距离,如果偏差很大,直接跳过
                // 单目情况
                const float &kpx = kp.pt.x;
                const float &kpy = kp.pt.y;
                const float ex = u-kpx;
                const float ey = v-kpy;
                const float e2 = ex*ex+ey*ey;

                // 自由度为2的,卡方检验阈值5.99(假设测量有一个像素的偏差)
                if(e2*pKF->mvInvLevelSigma2[kpLevel]>5.99)
                    continue;
            }

            const cv::Mat &dKF = pKF->mDescriptors.row(idx);

            const int dist = DescriptorDistance(dMP,dKF);
            // 和投影点的描述子距离最小
            if(dist<bestDist)
            {
                bestDist = dist;
                bestIdx = idx;
            }
        }

        // If there is already a MapPoint replace otherwise add new measurement
        // Step 7 找到投影点对应的最佳匹配特征点,根据是否存在地图点来融合或新增
        // 最佳匹配距离要小于阈值
        if(bestDist<=TH_LOW)
        {
            MapPoint* pMPinKF = pKF->GetMapPoint(bestIdx);
            if(pMPinKF)
            {
                // 如果最佳匹配点有对应有效地图点,选择被观测次数最多的那个替换
                if(!pMPinKF->isBad())
                {
                    if(pMPinKF->Observations()>pMP->Observations())
                        pMP->Replace(pMPinKF);
                    else
                        pMPinKF->Replace(pMP);
                }
            }
            else
            {
                // 如果最佳匹配点没有对应地图点,添加观测信息
                pMP->AddObservation(pKF,bestIdx);
                pKF->AddMapPoint(pMP,bestIdx);
            }
            nFused++;
        }
    }

    return nFused;
}

3.函数解析 

        我们最终目的是将参数一的关键帧(待融合关键帧pKF)的地图点与参数二的地图点(待匹配地图点vpMapPoints)集合进行融合。

        获取参数一的关键帧(待融合关键帧pKF)位姿、内参、光心在世界坐标系下坐标方便下面使用:Rcw、tcw、fx、fy、cx、cy、bf、Ow。

        将本函数成功融合的地图点nFused值初始化设置为0。

        遍历所有的待投影地图点:

        ①若该地图点地图点无效 或 已经是该帧的地图点(无需融合),跳过。

        ②将地图点变换到关键帧pKF的相机坐标系下,深度值为负,跳过。

        ③将地图点投影到关键帧pKF的像素坐标系下,若超出了像素坐标系,跳过。

        ④地图点到关键帧相机光心距离需满足在有效范围内,不满足,跳过。

        ⑤地图点到光心的连线与该地图点的平均观测向量之间夹角要小于60°,不满足跳过。

        若上述条件都满足,我们利用重投影的方法获得待匹配地图点在关键帧中的待匹配地图点的索引vIndices。也就是说,这个地图点只可能和关键帧pKF中的索引为vIndices的地图点相融合。

        接下来遍历寻找最佳的匹配点,步骤如下:

        ①获取当前待融合地图点的描述子dMP

        ②计算当前帧(待融合关键帧)pKF的筛选好的特征点(其索引为vIndices)和当前遍历待融合地图点的描述子的汉明距离dMP。并进行筛选,最后我们记录和投影点的描述子距离最小的pKF帧的最小汉明距离bestDist和特征点索引bestIdx

        ③如果最佳匹配点有对应有效地图点,选择被观测次数最多的那个替换

            if(pMPinKF)
            {
                // 如果最佳匹配点有对应有效地图点,选择被观测次数最多的那个替换
                if(!pMPinKF->isBad())
                {
                    if(pMPinKF->Observations()>pMP->Observations())
                        pMP->Replace(pMPinKF);
                    else
                        pMPinKF->Replace(pMP);
                }
            }

        ④如果最佳匹配点没有对应地图点,添加观测信息。

pMP->AddObservation(pKF,bestIdx);
pKF->AddMapPoint(pMP,bestIdx);

        更新地图点的操作具体如下:

ORB-SLAM2 --- MapPoint::Replace函数解析icon-default.png?t=MBR7https://blog.csdn.net/qq_41694024/article/details/128565834        返回成功融合的地图点数目。

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

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

相关文章

六、k8s Pod控制器详解

1 Pod控制器介绍 Pod是kubernetes的最小管理单元&#xff0c;在kubernetes中&#xff0c;按照pod的创建方式可以将其分为两类&#xff1a; 自主式pod&#xff1a;kubernetes直接创建出来的Pod&#xff0c;这种pod删除后就没有了&#xff0c;也不会重建控制器创建的pod&#x…

异常处理部分

文章目录一、异常概述及体系结构分类ErrorException异常的体系结构异常处理&#xff1a;抓抛模型异常处理机制一&#xff1a;try-catch-finallyfinally的使用异常处理机制一&#xff1a;throws异常类型如何选择两种方式手动生成异常对象如何定义自定义的异常类一、异常概述及体…

JVM学习- - -虚拟机栈详解

前言&#xff1a;今天学长带领大家走进JVM学习&#xff0c;让我们一起来学习认识虚拟机栈吧~ 目录 1 虚拟机栈概述 虚拟机栈出现的背景 初步印象 内存中的栈和堆 虚拟机栈基本内容 栈的优点 2 栈的存储单位 栈中存储什么&#xff1f; 栈运行原理 栈帧的内部结构 3 局…

【云原生】k8s之存储卷

内容预知 前言 1.emptyDir存储卷 2.hostPath存储卷 3.nfs共享存储卷 4. PVC 和 PV的静态存储卷 4.1 pv和pvc的介绍 4.2 pvc 和pv的创建过程及销毁过程 4.3 对pv的操作指导 4.4 静态创建pv和pvc资源由pod运用过程 步骤一&#xff1a;在NFS主机上创建共享目录&#xff0c;…

采用rknn-toolkit导出rknn模型并部署在rock3a-rk3568芯片 上全流程

因工作需要&#xff0c;需要将目标检测模型 部署在开发板上。在走了很多弯路后 找到一个成功的案例并记载下来 这里说一下我现有的硬件设备 。 我是购买的RADXA的rock3a开发板 搭载的soc是rk3568 这是开发板的正面图&#xff0c;因为瑞芯微针对计算机视觉中的目标检测模型有一…

Java IO流 - 缓冲流的详细使用介绍

文章目录缓冲流缓冲流概述字节缓冲流字符缓存流缓冲流 缓冲流概述 缓冲流介绍: 缓冲流也称为高效流、或者高级流。之前学习的字节流和字符流可以称为原始流。 作用&#xff1a;缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能 缓冲流分为: 字节缓存输入流、字节…

做个测试工具

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

uniapp中引入vant Weapp

Vant Weapp官&#xff1a;https://vant-contrib.gitee.io/vant-weapp/#/home 步骤一&#xff1a;下载vant组件插件 从github上下载该插件https://github.com/youzan/vant-weapp 只要这个dist文件夹&#xff0c;把dist重命名为vant&#xff1b; 步骤二&#xff1a; 与pages…

301-295- 至少有 K 个重复字符的最长子串-0105

题解 本题使用分治策略,如果某个字符的出现次数小于k,则用它将数组分开,再把每个子数组组委参数递归执行.如果都大于k,则将该字符串的长度返回. 用一个字符分割,往深了分割各子字符串,这个字符分割完成,使用另一个字符进行分割,而不是一次用多个字符进行分割.这个题递归有些绕…

电脑怎么重装系统?小白也能轻松掌握这些方法

重新安装计算机系统有两种原因&#xff1a;一种是计算机系统可以正常使用&#xff0c;但是电脑比较卡&#xff0c;为了提高它的运行速度&#xff0c;所以想要通过重新安装系统来解决这个问题&#xff1b;另一种原因是计算机系统文件丢失&#xff0c;系统出现蓝屏&#xff0c;或…

MQ概念简介

队列管理器 队列管理器是MQ系统中最上层的一个概念&#xff0c;由它为我们提供基于队列的消息服务。 2) 消息 在MQ中&#xff0c;我们把应用程序交由MQ传输的数据定义为消息&#xff0c;我们可以定义消息的内容并对消息进行广义的理解&#xff0c;比如&#xff1a;用户的各种类…

机器学习100天(二十九):029 K折交叉验证

机器学习100天,今天讲的是:K 折交叉验证! 《机器学习100天》完整目录:目录 机器学习中,我们常会遇到一个问题,就是超参数的选择,超参数就是机器学习算法中的调优参数,比如上一节 K 近邻算法中的 K 值。K 折交叉验证就是帮助我们选择最优的超参数。 首先,介绍一下简…

FPGA并行计算可编程芯片

玩转Zynq可以使用Vivado创建一个FPGA工程。什么是FPGA前言自FPGA诞生以来&#xff0c;FPGA&#xff08;现场可编程门阵列&#xff09;就引起了人们的关注。在1980年代中期&#xff0c;Ross Freeman和他的同事从Zilog购买了该技术&#xff0c;并创建了Xilinx&#xff0c;目标是A…

基于Java+SpringBoot+vue+node.js实现自行车租赁平台管理系统

基于JavaSpringBootvuenode.js实现自行车租赁平台管理系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录基于JavaSp…

netmap: UDP 协议栈的实现

文章目录1、获取以太网数据1.1、netmap 原理1.2、netmap 环境搭建2、udp 协议栈的实现2.1、以太网帧2.2、ip 协议2.3、udp 协议2.4、问题分析3、ARP 协议的实现4、icmp 协议的实现5、netmap 代码实现1、获取以太网数据 自定义协议栈&#xff0c;需要获取原始的以太网数据&…

第50问:从连接判断应用访问数据库的异常行为

问 我发现应用有一根访问数据库的连接有异常流量&#xff0c;如何判断是应用哪个逻辑导致了异常行为 实验 先起锅烧一个数据库实例&#xff1a; 我们用 mysqlslap 作为应用&#xff1a; 假设在 MySQL 中&#xff0c;我们认为这根连接有异常流量&#xff1a; 通过 ss 找到这根…

阿里一面 | 说说你对 MySQL 死锁的理解

1、什么是死锁&#xff1f; 死锁指的是在两个或两个以上不同的进程或线程中&#xff0c;由于存在共同资源的竞争或进程&#xff08;或线程&#xff09;间的通讯而导致各个线程间相互挂起等待&#xff0c;如果没有外力作用&#xff0c;最终会引发整个系统崩溃。 2、Mysql出现死…

cordova-Toast的使用 -官方插件和自定义插件

前言&#xff1a;cordova是使用前端技术来开发app,可以节省成本和快速发布。不需要了解原生app开发 加载web的方式&#xff0c;可以兼容生成Android、ios以及浏览器等各种平台的项目 前文&#xff1a;cordova开发流程 一、官方提示浮动框 cordova-plugin-x-toast 1.cordova pl…

二、GtkApplication and GtkApplicationWindow

1 GtkApplication 1.1 GtkApplication and g_application_run 人们编写编程代码来开发应用程序。什么是应用程序?应用程序是使用库运行的软件&#xff0c;其中包括操作系统、框架等。在GTK 4编程中&#xff0c;GTK应用程序是使用GTK库运行的程序(或可执行程序)。 编写GtkAp…

屏幕录制有快捷键吗?录屏快捷键ctrl加什么

我们日常使用的电脑是自带录屏功能&#xff0c;可以方便我们将玩游戏的精彩画面&#xff0c;或者是电影某个片段给录制下来。为了不错过这些精彩片段&#xff0c;可以使用录屏快捷键录制。那电脑录屏快捷键ctrl加什么&#xff1f;今天本文就简单地给大家介绍电脑录屏快捷键&…