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在其它方面有用,见下图

希望对大家有用

相关推荐
咚咚王者6 小时前
人工智能之数据分析 numpy:第十章 副本视图
人工智能·数据分析·numpy
Dev7z6 小时前
让阅卷不再繁琐:图像识别与数据分析提升智能答题卡评分效率
人工智能·计算机视觉
咚咚王者6 小时前
人工智能之数据分析 numpy:第十一章 字符串与字节交换
人工智能·数据分析·numpy
数字孪生家族8 小时前
视频孪生与空间智能:重构数字时空认知,定义智能决策新范式
人工智能·重构·空间智能·视频孪生与空间智能
FL171713149 小时前
Pytorch保存pt和pkl
人工智能·pytorch·python
jieshenai9 小时前
5090显卡,基于vllm完成大模型推理
人工智能·自然语言处理
逻极10 小时前
云智融合:AIGC与云计算服务新范式(深度解析)
人工智能·云计算·aigc·云服务
雪兽软件11 小时前
人工智能(AI)的商业模式创新路线图
人工智能
俊哥V11 小时前
AI一周事件(2025年11月12日-11月18日)
人工智能·ai