LineSlam线特征投影融合(Fuse) 中pML->GetLineNormalVector()的理解代码理解

理解LineSlam中线段融合部分,里面有很多限制条件,但是比较明显,但是对于线段的Normal,有些不容易理解,因此研究了一下,以下是个人理解,并且记录

其中fuse的代码代码如下(部分自己已经修改,用于LineSlam的工程中):

cpp 复制代码
int LSDmatcher::Fuse(KeyFrame *pKF, const std::vector<MapLine *> &vpMapLines, const float th)
    {
        Eigen::Matrix3f Rcw_eigen = pKF->GetRotation();
        Eigen::Vector3f tcw_eigen = pKF->GetTranslation();

        cv::Mat Rcw = Converter::toCvMat(Rcw_eigen);;
        cv::Mat tcw = (Mat_<float>(3, 1) << tcw_eigen(0), tcw_eigen(1), tcw_eigen(2));

        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;

        Eigen::Vector3f Ow_eigen = pKF->GetCameraCenter();
        cv::Mat Ow = (Mat_<float>(3, 1) << Ow_eigen(0), Ow_eigen(1), Ow_eigen(2));

        int nFused=0;

        const int nLines = vpMapLines.size();

        // For each candidate MapPoint project and match
        for(int iML=0; iML<nLines; iML++)
        {
            MapLine* pML = vpMapLines[iML];

            // Discard Bad MapLines and already found
            if(!pML || pML->isBad())
                continue;

            //Vector6d P = pML->GetWorldPos();
            std::pair<Eigen::Vector3f, Eigen::Vector3f> lineWorldPosition = pML->GetLineWorldPos();

            cv::Mat SP = (Mat_<float>(3, 1) << lineWorldPosition.first(0), lineWorldPosition.first(1), lineWorldPosition.first(2));
            cv::Mat EP = (Mat_<float>(3, 1) << lineWorldPosition.second(0), lineWorldPosition.second(1), lineWorldPosition.second(2));

            const cv::Mat SPc = Rcw * SP + tcw;
            const auto &SPcX = SPc.at<float>(0);
            const auto &SPcY = SPc.at<float>(1);
            const auto &SPcZ = SPc.at<float>(2);

            const cv::Mat EPc = Rcw * EP + tcw;
            const auto &EPcX = EPc.at<float>(0);
            const auto &EPcY = EPc.at<float>(1);
            const auto &EPcZ = EPc.at<float>(2);

            if (SPcZ < 0.0f || EPcZ < 0.0f)
                continue;

            const float invz1 = 1.0f / SPcZ;
            const float u1 = fx * SPcX * invz1 + cx;
            const float v1 = fy * SPcY * invz1 + cy;

            if (u1 < pKF->mnMinX || u1 > pKF->mnMaxX)
                continue;
            if (v1 < pKF->mnMinY || v1 > pKF->mnMaxY)
                continue;

            const float invz2 = 1.0f / EPcZ;
            const float u2 = fx * EPcX * invz2 + cx;
            const float v2 = fy * EPcY * invz2 + cy;

            if (u2 < pKF->mnMinX || u2 > pKF->mnMaxX)
                continue;
            if (v2 < pKF->mnMinY || v2 > pKF->mnMaxY)
                continue;

            const float maxDistance = pML->GetMaxDistanceInvariance();
            const float minDistance = pML->GetMinDistanceInvariance();

            const cv::Mat OM = 0.5 * (SP + EP) - Ow;
            const float dist = cv::norm(OM);

            if (dist < minDistance || dist > maxDistance)
                continue;

            // Check viewing angle 这个LineNormal是啥意思?
           Eigen::Vector3f Pn = pML->GetLineNormalVector();
					cv::Mat pn = (Mat_<float>(3, 1) << Pn(0), Pn(1), Pn(2));

            if(OM.dot(pn)<0.5*dist)
                continue;

            const int nPredictedLevel = pML->PredictScale(dist, pKF);

            const float radius = th*pKF->mvScaleFactors[nPredictedLevel];

            const vector<size_t> vIndices = pKF->GetLinesInArea(u1,v1, u2, v2, radius);

            if(vIndices.empty())
                continue;

            const cv::Mat dML = pML->GetLineDescriptor();

            int bestDist=INT_MAX;
            int bestIdx =-1 ;

            for(unsigned long idx : vIndices)
            {
                const int &klLevel = pKF->mvKeyLines[idx].octave;

                if(klLevel<nPredictedLevel-1 || klLevel>nPredictedLevel)
                    continue;

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

                const int dist = DescriptorDistance(dML,dKF);

                if(dist<bestDist)
                {
                    bestDist = dist;
                    bestIdx = idx;
                }
            }

            if(bestDist<=TH_LOW)
            {
                MapLine* pMLinKF = pKF->GetMapLine(bestIdx);
                if(pMLinKF)
                {
                    if(!pMLinKF->isBad()) {
                        if(pMLinKF->Observations()>pML->Observations())
                            pML->Replace(pMLinKF);
                        else
                            pMLinKF->Replace(pML);
                    }
                }
                else
                {
                    pML->AddLineObservation(pKF,bestIdx);
                    pKF->AddMapLine(pML,bestIdx);
                }
                nFused++;
            }
        }

        return nFused;
    }

本来对于这段代码pML->GetLineNormal();,没有那么好理解,因此记录

先分析它附近的代码,如下:

cpp 复制代码
// Check viewing angle 这个LineNormal是啥意思?
Eigen::Vector3f Pn = pML->GetLineNormalVector();
cv::Mat pn = (Mat_<float>(3, 1) << Pn(0), Pn(1), Pn(2));
if(OM.dot(pn)<0.5*dist)
    continue;

🧠 背景:MapLine 与其法向量 (Normal)

在 ORB-SLAM 或 LSD-SLAM 系列中,每个 MapLine(世界坐标系下的线段) 都维护了一条空间线的信息:

  1. 起点 P1

  2. 终点 P2

  3. 平均观测方向(normal vector)Pn

GetLineNormal() 返回的 Pn 是线段的平均观测方向法向量,用来判断当前相机是否处在这条线的"前方"或"后方"。

判断逻辑

cpp 复制代码
if (OM.dot(pn) < 0.5 * dist)
    continue;

这行的作用是:
判断当前相机视角是否位于线段的可观测半空间内

解释:

  1. OM.dot(pn) 表示相机中心到线段中点方向 与 线段平均法向量之间的夹角。
  2. 若点积过小,说明相机与线段法向量方向几乎相反(即在线段背面),该线在当前视角下不可见或方向反转。
  3. 阈值 0.5 * dist 是一个启发式条件,过滤掉大角度背向的线条。

直观理解:

  1. 假设线段像一根小棍子,
  2. 法向量 Pn 表示线段"朝向相机的方向";
  3. OM 是相机到这根棍子的位置方向;
  4. 点积较大 → 说明相机正看向线段;
  5. 点积较小 → 说明相机在反方向或斜后方,线段不可见

其中线段

这正是理解线特征几何一致性的关键之一。

在 ORB-SLAM / PL-SLAM / Line3D++ 等系统中,
MapLine::GetLineNormal() 所返回的 法向量 (normal, 它的变量为:mNormalVector)(这点非常重要)
并不是线段本身的方向向量
而是它的平均观测方向(Viewing Direction)

我们来分两步说明:
一、Normal 在 MapLine 中的含义

在 ORB-SLAM 的点特征中,MapPoint 存储一个 mNormalVector
用于表示从地图点指向所有观测该点的相机的平均方向

线特征(MapLine )也同理:

当某个关键帧观测到这条线段时,线段在世界坐标下的中点记作 Pc ;相机在世界坐标下的位置记作 Ow

从线段到相机的方向为:

每次有新的关键帧观测该线段,就把这个方向向量累积:

然后归一化:

这就是 mNormalVector ------ "线段的平均观测方向"

通常在 ORB-SLAM 或基于它**(典型 MapLine::UpdateNormalAndDepth)**的系统中,这样写:

cpp 复制代码
void MapLine::UpdateNormalAndDepth()
{
    if (mbBad)
        return;

    std::map<KeyFrame*, std::tuple<int, int>> observations;
    {
        std::unique_lock<std::mutex> lock(mMutexFeatures);
        observations = mLineObservations;
    }

    if (observations.empty())
        return;

    cv::Mat normal = cv::Mat::zeros(3, 1, CV_32F);
    int n = 0;

    for (auto &obs : observations)
    {
        KeyFrame* pKF = obs.first;
        if (pKF->isBad())
            continue;

        // 相机中心
        cv::Mat Ow = pKF->GetCameraCenter();

        // 世界坐标下线段起点与终点
        Eigen::Vector3f SP, EP;
        std::tie(SP, EP) = GetLineWorldPos();

        cv::Mat Pc = (cv::Mat_<float>(3,1) << 
                      0.5f*(SP(0)+EP(0)), 
                      0.5f*(SP(1)+EP(1)), 
                      0.5f*(SP(2)+EP(2)));

        cv::Mat v = Ow - Pc;
        v = v / cv::norm(v);
        normal += v;
        n++;
    }

    mNormalVector = normal / static_cast<float>(n);
    mNormalVector = mNormalVector / cv::norm(mNormalVector);
}

拓展:其中GetLineNormal() ,它的返回值为mNormalVector在其它方面有用,见下图

希望对大家有用

相关推荐
g***B7382 小时前
Java 工程复杂性的真正来源:从语言设计到现代架构的全链路解析
java·人工智能·架构
Shawn_Shawn4 小时前
大模型的奥秘:Token与Transformer简单理解
人工智能·llm
weixin_377634846 小时前
【K-S 检验】Kolmogorov–Smirnov计算过程与示例
人工智能·深度学习·机器学习
菜鸟起航ing6 小时前
Spring AI 全方位指南:从基础入门到高级实战
java·人工智能·spring
Guheyunyi6 小时前
智慧消防管理系统如何重塑安全未来
大数据·运维·服务器·人工智能·安全
ZZY_dl7 小时前
训练数据集(三):真实场景下采集的课堂行为目标检测数据集,可直接用于YOLO各版本训练
人工智能·yolo·目标检测
yiersansiwu123d7 小时前
AI伦理治理:在创新与规范之间寻找动态平衡
人工智能
华清远见成都中心7 小时前
成都理工大学&华清远见成都中心实训,助力电商人才培养
大数据·人工智能·嵌入式
爱好读书7 小时前
AI生成er图/SQL生成er图在线工具
人工智能
CNRio7 小时前
智能影像:AI视频生成技术的战略布局与产业变革
人工智能