精华内容
下载资源
问答
  • 2021-03-30 19:01:52

    ORBSLAM2


    25关键帧定义应用与选择方法、创建及插入关键帧


    关键帧

    什么是关键帧?
    通俗来说,关键帧就是几帧普通帧里面具有代表性的一帧。

    为什么需要关键帧

    1.相近帧之间信息冗余度很高,关键帧是取局部相近帧中最有代表性的一帧,可以降低信息冗余度。

    2.关键帧选择时还会对图片质量、特征点质量等进行考察,在Bundle Fusion、RKD SLAM等RGB-D-SLAM相关方案中常常用普通帧的深度投影到关键帧上进行深度图优化,一定程度上关键帧是普通帧滤波和优化的结果,防止无用的或错误的信息进入优化过程而破坏定位建图的准确性。

    3.如果所有帧全部参与计算,不仅浪费了算力,对内存也是极大的考验,这一点在前端vo中表现不明显,但在后端优化里是一个大问题,所以关键帧主要作用是面向后端优化的算力与精度的折中,使得有限的计算资源能够用在刀刃上,保证系统的平稳运行。假如你放松ORB_SLAM2 关键帧选择条件,大量产生的关键帧不仅耗计算资源,还会导致local mapping 计算不过来,出现误差累积

    代码

    bool Tracking::NeedNewKeyFrame()
    {
        if(mbOnlyTracking)//纯vo模式下不需要插入关键帧
            return false;
    
        // If Local Mapping is freezed by a Loop Closure do not insert keyframes
        if(mpLocalMapper->isStopped() || mpLocalMapper->stopRequested())//如果局部建图线程被闭环检测使用,就不插入关键帧
            return false;
    
        const int nKFs = mpMap->KeyFramesInMap();//取出地图中的关键帧总数
    
        // Do not insert keyframes if not enough frames have passed from last relocalisation
        if(mCurrentFrame.mnId<mnLastRelocFrameId+mMaxFrames && nKFs>mMaxFrames)//离重定位结果较近或者超出了关键帧的限制,就不插入关键帧
            return false;
    
        // Tracked MapPoints in the reference keyframe
        int nMinObs = 3;//地图点最小观测数目
        if(nKFs<=2)
            nMinObs=2;
        int nRefMatches = mpReferenceKF->TrackedMapPoints(nMinObs);//参考关键帧中地图点的观测数目>=nminOBS的观测数目
    
        // Local Mapping accept keyframes?
        bool bLocalMappingIdle = mpLocalMapper->AcceptKeyFrames();//局部建图线程是否繁忙??
    
        // Check how many "close" points are being tracked and how many could be potentially created.
        int nNonTrackedClose = 0;
        int nTrackedClose= 0;
        if(mSensor!=System::MONOCULAR)
        {
            for(int i =0; i<mCurrentFrame.N; i++)
            {
                if(mCurrentFrame.mvDepth[i]>0 && mCurrentFrame.mvDepth[i]<mThDepth)//深度值在有效范围内
                {
                    if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i])//是否有地图点
                        nTrackedClose++;
                    else
                        nNonTrackedClose++;
                }
            }
        }
    
        bool bNeedToInsertClose = (nTrackedClose<100) && (nNonTrackedClose>70);//跟踪的地图点太少,或者没跟踪到的三维点太多,就可以插入关键帧,单目为false
    
        // Thresholds
        float thRefRatio = 0.75f;
        if(nKFs<2)
            thRefRatio = 0.4f;
    
        if(mSensor==System::MONOCULAR)//如果是单目,就设置阈值
            thRefRatio = 0.9f;
    
        // Condition 1a: More than "MaxFrames" have passed from last keyframe insertion
        const bool c1a = mCurrentFrame.mnId>=mnLastKeyFrameId+mMaxFrames;//当前帧的ID>上一个关键帧的ID+30,长时间没插入
        // Condition 1b: More than "MinFrames" have passed and Local Mapping is idle
        const bool c1b = (mCurrentFrame.mnId>=mnLastKeyFrameId+mMinFrames && bLocalMappingIdle);//当前帧的ID>=上一关键帧+最小时间且局部建图线程是空闲状态的话
        //Condition 1c: tracking is weak
        const bool c1c =  mSensor!=System::MONOCULAR && (mnMatchesInliers<nRefMatches*0.25 || bNeedToInsertClose) ;//不是单目且当前帧和地图匹配点的数目非常少且需要插入,就插入
        // Condition 2: Few tracked points compared to reference keyframe. Lots of visual odometry compared to map matches.
        const bool c2 = ((mnMatchesInliers<nRefMatches*thRefRatio|| bNeedToInsertClose) && mnMatchesInliers>15);//当前帧的内点数比上参考帧的内点数,小于上面的阈值,并且 内点数要>15
    
        if((c1a||c1b||c1c)&&c2)
        {
            // If the mapping accepts keyframes, insert keyframe.
            // Otherwise send a signal to interrupt BA
            if(bLocalMappingIdle)//局部建图线程是否空闲?
            {
                return true;
            }
            else
            {
                mpLocalMapper->InterruptBA();//不空闲的话要把局部建图线程的ba给关掉
                if(mSensor!=System::MONOCULAR)//非单目?
                {
                    if(mpLocalMapper->KeyframesInQueue()<3)//局部见图线程关键帧是否太少
                        return true;
                    else
                        return false;
                }
                else
                    return false;
            }
        }
        else
            return false;
    }
    

    创建关键帧

    bool Tracking::NeedNewKeyFrame()
    {
        if(mbOnlyTracking)//纯vo模式下不需要插入关键帧
            return false;
    
        // If Local Mapping is freezed by a Loop Closure do not insert keyframes
        if(mpLocalMapper->isStopped() || mpLocalMapper->stopRequested())//如果局部建图线程被闭环检测使用,就不插入关键帧
            return false;
    
        const int nKFs = mpMap->KeyFramesInMap();//取出地图中的关键帧总数
    
        // Do not insert keyframes if not enough frames have passed from last relocalisation
        if(mCurrentFrame.mnId<mnLastRelocFrameId+mMaxFrames && nKFs>mMaxFrames)//离重定位结果较近或者超出了关键帧的限制,就不插入关键帧
            return false;
    
        // Tracked MapPoints in the reference keyframe
        int nMinObs = 3;//地图点最小观测数目
        if(nKFs<=2)
            nMinObs=2;
        int nRefMatches = mpReferenceKF->TrackedMapPoints(nMinObs);//参考关键帧中地图点的观测数目>=nminOBS的观测数目
    
        // Local Mapping accept keyframes?
        bool bLocalMappingIdle = mpLocalMapper->AcceptKeyFrames();//局部建图线程是否繁忙??
    
        // Check how many "close" points are being tracked and how many could be potentially created.
        int nNonTrackedClose = 0;
        int nTrackedClose= 0;
        if(mSensor!=System::MONOCULAR)
        {
            for(int i =0; i<mCurrentFrame.N; i++)
            {
                if(mCurrentFrame.mvDepth[i]>0 && mCurrentFrame.mvDepth[i]<mThDepth)//深度值在有效范围内
                {
                    if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i])//是否有地图点
                        nTrackedClose++;
                    else
                        nNonTrackedClose++;
                }
            }
        }
    
        bool bNeedToInsertClose = (nTrackedClose<100) && (nNonTrackedClose>70);//跟踪的地图点太少,或者没跟踪到的三维点太多,就可以插入关键帧,单目为false
    
        // Thresholds
        float thRefRatio = 0.75f;
        if(nKFs<2)
            thRefRatio = 0.4f;
    
        if(mSensor==System::MONOCULAR)//如果是单目,就设置阈值
            thRefRatio = 0.9f;
    
        // Condition 1a: More than "MaxFrames" have passed from last keyframe insertion
        const bool c1a = mCurrentFrame.mnId>=mnLastKeyFrameId+mMaxFrames;//当前帧的ID>上一个关键帧的ID+30,长时间没插入
        // Condition 1b: More than "MinFrames" have passed and Local Mapping is idle
        const bool c1b = (mCurrentFrame.mnId>=mnLastKeyFrameId+mMinFrames && bLocalMappingIdle);//当前帧的ID>=上一关键帧+最小时间且局部建图线程是空闲状态的话
        //Condition 1c: tracking is weak
        const bool c1c =  mSensor!=System::MONOCULAR && (mnMatchesInliers<nRefMatches*0.25 || bNeedToInsertClose) ;//不是单目且当前帧和地图匹配点的数目非常少且需要插入,就插入
        // Condition 2: Few tracked points compared to reference keyframe. Lots of visual odometry compared to map matches.
        const bool c2 = ((mnMatchesInliers<nRefMatches*thRefRatio|| bNeedToInsertClose) && mnMatchesInliers>15);//当前帧的内点数比上参考帧的内点数,小于上面的阈值,并且 内点数要>15
    
        if((c1a||c1b||c1c)&&c2)
        {
            // If the mapping accepts keyframes, insert keyframe.
            // Otherwise send a signal to interrupt BA
            if(bLocalMappingIdle)//局部建图线程是否空闲?
            {
                return true;
            }
            else
            {
                mpLocalMapper->InterruptBA();//不空闲的话要把局部建图线程的ba给关掉
                if(mSensor!=System::MONOCULAR)//非单目?
                {
                    if(mpLocalMapper->KeyframesInQueue()<3)//局部见图线程关键帧是否太少
                        return true;
                    else
                        return false;
                }
                else
                    return false;
            }
        }
        else
            return false;
    }
    
    
    更多相关内容
  • 关键帧有什么用?

    2021-06-12 18:58:38
    关键帧作用是通过影片图层实现叠加的效果。帧——就是动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。在动画软件的时间轴上帧表现为一格或一个标记。关键帧——相当于二维动画中的原画。指角色或者...

    7f4adca0710a82aa30ff156f2de49124.png

    关键帧的作用是通过影片图层实现叠加的效果。

    帧——就是动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。在动画软件的时间轴上帧表现为一格或一个标记。

    关键帧——相当于二维动画中的原画。

    指角色或者物体运动或变化中的关键动作所处的那一帧。关键帧与关键帧之间的动画可以由软件来创建,叫做过渡帧或者中间帧。

    关键帧的用途又分为:普通关键帧(用于处理图形图像和动画);动作脚本关键帧(用于存放动作脚本,关键帧可以通过动作脚本控制flash影片和其中的影片剪辑);

    引导层关键帧:

    引导层关键帧(基于引导图层创建的普通关键帧,该种关键帧在播放flash影片时是不可见的,仅用于注释flash影片);

    当然,每个关键帧都同时可以被赋予几种用途(除 引导层关键帧外),关键帧也可以通过影片图层实现叠加的效果。

    空白关键帧:

    空白关键帧是没有任何对象存在的帧,主要用于在画面与画面之间形成间隔,它在时间轴上是以空心圆的形式显示,用户可以在其上绘制图形,一旦在空白关键帧中创建了内容,空白关键帧就会自动转变为关键帧,按F7快捷键可创建空白关键帧。

    更多相关知识请关注web前端课程

    展开全文
  • 二、关键帧1.什么是关键帧?2.如何选择关键帧?3.关键帧的类型及更新连接关系三、 共视图 本质图 拓展树1.共视图 (Covisibility Graph)2.本质图(Essential Graph) 前言 迎浏览我的SLAM专栏,包括slam安装运行、...


    前言

    迎浏览我的SLAM专栏,包括slam安装运行、代码注释、原理详解,一起加油淦穿SLAM。


    一、 地图点

    1.地图点代表性描述子的计算

    找最有代表性的描述子示意图
    最有代表的描述子与其他描述子具有最小的距离中值
    在这里插入图片描述
    地图点法线朝向的计算
    在这里插入图片描述

    2.地图点和特征点的区别?

    地图点是三维点,来自真实世界的三维物体,有唯一的id。不同帧里的特征点可能对应三维空间中同一个三维点,
    特征点是二维点,是特征提取的点,大部分二维点在三维空间中没有对应地图点
    生成地图点
    关于生成地图点,主要有以下几个地方:

    1. 单目初始化时前两帧匹配生成地图点,双目左右目匹配生成地图点,RGB-D测量得到的地图点
    2. local mapping里共视关键帧之间用 LocalMapping::CreateNewMapPoints() 生成地图点
    3. Tracking::UpdateLastFrame() 和 Tracking::CreateNewKeyFrame()
      中为双目和RGB-D生成了新的临时地图点,单目不生成

    二、关键帧

    1.什么是关键帧?

    通俗来说,关键帧就是几帧普通帧里面具有代表性的一帧。

    在这里插入图片描述
    为什么需要关键帧

    1. 相近帧之间信息冗余度很高,关键帧是取局部相近帧中最有代表性的一帧,可以降低信息冗余度。举例来说,摄像头放在原处不动,普通帧还是要记录的,但关键帧不会增加。
    2. 关键帧选择时还会对图片质量、特征点质量等进行考察,在Bundle Fusion、RKD SLAM等RGB-D SLAM相关方案中常常用普通帧的深度投影到关键帧上进行深度图优化,一定程度上关键帧是普通帧滤波和优化的结果,防止无用的或错误的信息进入优化过程而破坏定位 建图的准确性。
    3. 如果所有帧全部参与计算,不仅浪费了算力,对内存也是极大的考验,这一点在前端vo中表现不明显,但在后端优化里是一个大问题,所以关键帧主要作用是面向后端优化的算力与精度的折中,使得有限的计算资源能够用在刀刃上,保证系统的平稳运行。假如你放松 ORB_SLAM2关键帧选择条件,大量产生的关键帧不仅耗计算资源,还会导致local mapping 计算不过来,出现误差累积

    2.如何选择关键帧?

    选择关键帧主要从关键帧自身和关键帧与其他关键帧的关系2方面来考虑。

    1. 关键帧自身质量要好,例如不能是非常模糊的图像、特征点数量要充足、特征点分布要尽量均匀等等;
    2. 关键帧与其他关键帧之间的关系,需要和局部地图中的其他关键帧有一定的共视关系但又不能重复度太高,以达到既存在约束,又尽量
      少的信息冗余的效果。

    选取的指标主要有:

    1. 距离上一关键帧的帧数是否足够多(时间)——比如我每隔固定帧数选择一个关键帧,这样编程简单但效果不好。比如运动很慢的时候,就会选择大量相似的关键帧,冗余,运动快的时候又丢失了很多重要的帧。

    2. 距离最近关键帧的距离是否足够远(空间)/运动——比如相邻帧根据pose计算运动的相对大小,可以是位移也可以是旋转或者两个都考虑,运动足够大(超过一定阈值)就新建一个关键帧,这种方法比第一种好。但问题是如果对着同一个物体来回扫就会出现大量相似关键帧。

    3. 跟踪局部地图质量(共视特征点数目)——记录当前视角下跟踪的特征点数或者比例,当相机离开当前场景时(双目或比例明显降低)才会新建关键帧,避免了第2种方法的问题。缺点是数据结构和逻辑比较复杂。

    在这里插入图片描述
    在关键帧的运用上,我认为orbslam2做的非常好,跟踪线程选择关键帧标准较宽松,局部建图线程再跟据共视冗余度进行剔除,尤其是在回环检测中使用了以关键帧为代表的帧“簇”的概念,回环筛选中有一步将关键帧前后10帧为一组,计算组内总分,以最高分的组的0.75为阈值,滤除一些组,再在剩下的组内各自找最高分的一帧作为备选帧,这个方法非常好地诠释了“关键帧代表局部”的这个理念。

    3.关键帧的类型及更新连接关系

    父子关键帧

    //KeyFrame.h 文件中
    bool mbFirstConnection; // 是否是第一次生成树
    KeyFrame* mpParent; // 当前关键帧的父关键帧 (共视程度最高的)
    std::set<KeyFrame*> mspChildrens; // 存储当前关键帧的子关键帧
    

    更新连接关系

    //KeyFrame.cc
    KeyFrame::UpdateConnections()
    {
    //省略...
    // Step 5 更新生成树的连接
    if(mbFirstConnection && mnId!=0)
    {
    // 初始化该关键帧的父关键帧为共视程度最高的那个关键帧
    mpParent = mvpOrderedConnectedKeyFrames.front();
    // 建立双向连接关系,将当前关键帧作为其子关键帧
    mpParent->AddChild(this);
    mbFirstConnection = false;
    }
    }
    // 添加子关键帧(即和子关键帧具有最大共视关系的关键帧就是当前关键帧)
    void KeyFrame::AddChild(KeyFrame *pKF)
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    mspChildrens.insert(pKF);
    }
    // 删除某个子关键帧
    void KeyFrame::EraseChild(KeyFrame *pKF)
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    mspChildrens.erase(pKF);
    }
    // 改变当前关键帧的父关键帧
    void KeyFrame::ChangeParent(KeyFrame *pKF)
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    // 添加双向连接关系
    mpParent = pKF;
    pKF->AddChild(this);
    }
    //获取当前关键帧的子关键帧
    set<KeyFrame*> KeyFrame::GetChilds()
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    return mspChildrens;
    }
    //获取当前关键帧的父关键帧
    KeyFrame* KeyFrame::GetParent()
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    return mpParent;
    }
    // 判断某个关键帧是否是当前关键帧的子关键帧
    bool KeyFrame::hasChild(KeyFrame *pKF)
    {
    unique_lock<mutex> lockCon(mMutexConnections);
    return mspChildrens.count(pKF);
    }
    

    更新局部关键帧

    void Tracking::UpdateLocalKeyFrames()
    {
    //省略...
    // 策略2.2:将自己的子关键帧作为局部关键帧(将邻居的子孙们拉拢入伙)
    const set<KeyFrame*> spChilds = pKF->GetChilds();
    for(set<KeyFrame*>::const_iterator sit=spChilds.begin(), send=spChilds.end(); sit!=send; sit++)
    {
    KeyFrame* pChildKF = *sit;
    if(!pChildKF->isBad())
    {
    if(pChildKF->mnTrackReferenceForFrame!=mCurrentFrame.mnId)
    {
    mvpLocalKeyFrames.push_back(pChildKF);
    pChildKF->mnTrackReferenceForFrame=mCurrentFrame.mnId;
    //? 找到一个就直接跳出for循环?
    break;
    }
    }
    }
    // 策略2.3:自己的父关键帧(将邻居的父母们拉拢入伙)
    KeyFrame* pParent = pKF->GetParent();
    if(pParent)
    {
    // mnTrackReferenceForFrame防止重复添加局部关键帧
    if(pParent->mnTrackReferenceForFrame!=mCurrentFrame.mnId)
    {
    mvpLocalKeyFrames.push_back(pParent);
    pParent->mnTrackReferenceForFrame=mCurrentFrame.mnId;
    //! 感觉是个bug!如果找到父关键帧会直接跳出整个循环
    break;
    }
    }
    // 省略....
    }
    
    

    三、 共视图 本质图 拓展树

    在这里插入图片描述

    1.共视图 (Covisibility Graph)

    共视图是无向加权图,每个节点是关键帧,如果两个关键帧之间满足一定的共视关系(至少15个共同观测地图点)他们就连成一条边,边的权重就是共视地图点数目
    在这里插入图片描述
    共视图的作用:

    1. 跟踪局部地图,扩大搜索范围
      Tracking:UpdateLocalKeyFrames()
    2. 局部建图里关键帧之间新建地图点 LocalMapping::CreateNewMapPoints()
      LocalMapping:SearchlnNeighbors()
    3. 闭环检测、重定位检测 LoopClosing::DetectLoop()、LoopClosing:CorrectLoop()
      KeyFrameDatabase::DetectLoopCandidates
      KeyFrameDatabase::DetectRelocalizationCandidates
    4. 优化 Optimizer::OptimizeEssentialGraph

    2.本质图(Essential Graph)

    共视图比较稠密,本质图比共视图更稀疏,这是因为本质图的作用是用在闭环矫正时,用相似变换来矫正尺度漂移,把闭环误差均摊在本质
    图中。本质图中节点也是所有关键帧,但是连接边更少,只保留了联系紧密的边来使得结果更精确。本质图中包含:

    1. 生成树连接关系
    2. 形成闭环的连接关系,闭环后地图点变动后新增加的连接关系
    3. 共视关系非常好(至少100个共视地图点)的连接关系

    本质图优化

    //Optimizer.cc
    Optimizer::OptimizeEssentialGraph()
    {
    // 省略....
    // Spanning tree edge
    // Step 4.1:添加生成树的边(有父关键帧)
    // 父关键帧就是和当前帧共视程度最高的关键帧
    if(pParentKF)
    {
    int nIDj = pParentKF->mnId;
    g2o::Sim3 Sjw;
    LoopClosing::KeyFrameAndPose::const_iterator itj = NonCorrectedSim3.find(pParentKF);
    // 尽可能得到未经过Sim3传播调整的位姿
    if(itj!=NonCorrectedSim3.end())
    Sjw = itj->second;
    else
    Sjw = vScw[nIDj];
    // 计算父子关键帧之间的相对位姿
    g2o::Sim3 Sji = Sjw * Swi;
    g2o::EdgeSim3* e = new g2o::EdgeSim3();
    e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(nIDj)));
    e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(nIDi)));
    // 希望父子关键帧之间的位姿差最小
    e->setMeasurement(Sji);
    // 所有元素的贡献都一样;每个误差边对总误差的贡献也都相同
    e->information() = matLambda;
    optimizer.addEdge(e);
    }
    // 省略....
    }
    

    本质图优化和全局BA结果对比
    从结果来看,

    1. 全局BA存在收敛问题。即使迭代100次,相对均方误差RMSE 也比较高
    2. essential graph 优化可以快速收敛并且结果更精确。θmin 表示被选为essential
      graph至少需要的共视地图点数目,从结果来看,θmin的大小对精度影响不大,但是较大的θmin值可以显著减少运行时间
    3. essential graph 优化 后增加全局 full BA 可以提升精度(但比较有限),但是会耗时较多

    在这里插入图片描述

    在这里插入图片描述

    四、 生成树(spanning tree)

    子关键帧和父关键帧构成

    在这里插入图片描述

    展开全文
  • ORB-SLAM2代码详解05: 关键帧KeyFrame

    千次阅读 多人点赞 2021-05-14 11:31:38
    ORB-SLAM2代码详解05: 关键帧KeyFrame各成员函数/变量共视图`mConnectedKeyFrameWeights`基于对地图点的观测构造共视图`UpdateConnections()`生成树`mpParent`、`mspChildrens`关键帧的删除参与回环检测的关键帧具有...

    pdf版本笔记的下载地址: ORB-SLAM2代码详解05_关键帧KeyFrame,排版更美观一点,这个网站的默认排版太丑了(访问密码:3834)

    可以看看我录制的视频5小时让你假装大概看懂ORB-SLAM2源码

    请添加图片描述

    各成员函数/变量

    共视图: mConnectedKeyFrameWeights

    能看到同一地图点的两关键帧之间存在共视关系,共视地图点的数量被称为权重.

    请添加图片描述

    成员函数/变量访问控制意义
    std::map<KeyFrame*, int> mConnectedKeyFrameWeightsprotected当前关键帧的共视关键帧及权重
    std::vector<KeyFrame*> mvpOrderedConnectedKeyFramesprotected所有共视关键帧,按权重从大到小排序
    std::vector<int> mvOrderedWeightsprotected所有共视权重,按从大到小排序
    void UpdateConnections()public基于当前关键帧对地图点的观测构造共视图
    void AddConnection(KeyFrame* pKF, int &weight)public
    应为private
    添加共视关键帧
    void EraseConnection(KeyFrame* pKF)public
    应为private
    删除共视关键帧
    void UpdateBestCovisibles()public
    应为private
    基于共视图信息修改对应变量
    std::set<KeyFrame*> GetConnectedKeyFrames()publicget方法
    std::vector<KeyFrame*> GetVectorCovisibleKeyFrames()publicget方法
    std::vector<KeyFrame*> GetBestCovisibilityKeyFrames(int &N)publicget方法
    std::vector<KeyFrame*> GetCovisiblesByWeight(int &w)publicget方法
    int GetWeight(KeyFrame* pKF)publicget方法

    共视图结构由3个成员变量维护:

    • mConnectedKeyFrameWeights是一个std::map,无序地保存当前关键帧的共视关键帧权重.
    • mvpOrderedConnectedKeyFramesmvOrderedWeights权重降序分别保存当前关键帧的共视关键帧列表和权重列表.

    基于对地图点的观测重新构造共视图: UpdateConnections()

    这3个变量由函数KeyFrame::UpdateConnections()进行初始化和维护,基于当前关键帧看到的地图点信息重新生成共视关键帧.

    void KeyFrame::UpdateConnections() {
        
        // 1. 通过遍历当前帧地图点获取其与其它关键帧的共视程度,存入变量KFcounter中
        vector<MapPoint *> vpMP;
        {
            unique_lock<mutex> lockMPs(mMutexFeatures);
            vpMP = mvpMapPoints;
        }
        map<KeyFrame *, int> KFcounter; 
        for (MapPoint *pMP : vpMP) {
            map<KeyFrame *, size_t> observations = pMP->GetObservations();
            for (map<KeyFrame *, size_t>::iterator mit = observations.begin(); mit != observations.end(); mit++) {
                if (mit->first->mnId == mnId)		// 与当前关键帧本身不算共视
                    continue;
                KFcounter[mit->first]++;
            }
        }
      
        // step2. 找到与当前关键帧共视程度超过15的关键帧,存入变量vPairs中
        vector<pair<int, KeyFrame *> > vPairs;
        int th = 15;
        int nmax = 0;
        KeyFrame *pKFmax = NULL;   
        for (map<KeyFrame *, int>::iterator mit = KFcounter.begin(), mend = KFcounter.end(); mit != mend; mit++) {
            if (mit->second > nmax) {
                nmax = mit->second;
                pKFmax = mit->first;
            }
            if (mit->second >= th) {
                vPairs.push_back(make_pair(mit->second, mit->first));
                (mit->first)->AddConnection(this, mit->second);				// 对超过阈值的共视边建立连接
            }
        }
    
        //  step3. 对关键帧按照共视权重降序排序,存入变量mvpOrderedConnectedKeyFrames和mvOrderedWeights中
        sort(vPairs.begin(), vPairs.end());
        list<KeyFrame *> lKFs;
        list<int> lWs;
        for (size_t i = 0; i < vPairs.size(); i++) {
            lKFs.push_front(vPairs[i].second);
            lWs.push_front(vPairs[i].first);
        }
        {
            unique_lock<mutex> lockCon(mMutexConnections);
            mConnectedKeyFrameWeights = KFcounter;
            mvpOrderedConnectedKeyFrames = vector<KeyFrame *>(lKFs.begin(), lKFs.end());
            mvOrderedWeights = vector<int>(lWs.begin(), lWs.end());
    
            // step4. 对于第一次加入生成树的关键帧,取共视程度最高的关键帧为父关键帧
            if (mbFirstConnection && mnId != 0) {
                mpParent = mvpOrderedConnectedKeyFrames.front();
                mpParent->AddChild(this);
                mbFirstConnection = false;
            }
        }
    }
    

    只要关键帧与地图点间的连接关系发生变化(包括关键帧创建地图点重新匹配关键帧特征点),函数KeyFrame::UpdateConnections()就会被调用.具体来说,函数KeyFrame::UpdateConnections()的调用时机包括:

    • Tracking线程中初始化函数Tracking::StereoInitialization()Tracking::MonocularInitialization()函数创建关键帧后会调用KeyFrame::UpdateConnections()初始化共视图信息.
    • LocalMapping线程接受到新关键帧时会调用函数LocalMapping::ProcessNewKeyFrame()处理跟踪过程中加入的地图点,之后会调用KeyFrame::UpdateConnections()初始化共视图信息.(实际上这里处理的是Tracking线程中函数Tracking::CreateNewKeyFrame()创建的关键帧)
    • LocalMapping线程处理完毕缓冲队列内所有关键帧后会调用LocalMapping::SearchInNeighbors()融合当前关键帧和共视关键帧间的重复地图点,之后会调用KeyFrame::UpdateConnections()更新共视图信息.
    • LoopClosing线程闭环矫正函数LoopClosing::CorrectLoop()会多次调用KeyFrame::UpdateConnections()更新共视图信息.

    请添加图片描述


    函数AddConnection(KeyFrame* pKF, const int &weight)EraseConnection(KeyFrame* pKF)先对变量mConnectedKeyFrameWeights进行修改,再调用函数UpdateBestCovisibles()修改变量mvpOrderedConnectedKeyFramesmvOrderedWeights.

    这3个函数都只在函数KeyFrame::UpdateConnections()内部被调用了,应该设为私有成员函数.

    void KeyFrame::AddConnection(KeyFrame *pKF, const int &weight) {
    	// step1. 修改变量mConnectedKeyFrameWeights
        {
            unique_lock<mutex> lock(mMutexConnections);
    
            if (!mConnectedKeyFrameWeights.count(pKF) || mConnectedKeyFrameWeights[pKF] != weight)
                mConnectedKeyFrameWeights[pKF] = weight;
            else
                return;
        }
    	
        // step2. 调用函数UpdateBestCovisibles()修改变量mvpOrderedConnectedKeyFrames和mvOrderedWeights
        UpdateBestCovisibles();
    }
    
    
    void KeyFrame::EraseConnection(KeyFrame *pKF) {
        // step1. 修改变量mConnectedKeyFrameWeights
        bool bUpdate = false;
        {
            unique_lock<mutex> lock(mMutexConnections);
            if (mConnectedKeyFrameWeights.count(pKF)) {
                mConnectedKeyFrameWeights.erase(pKF);
                bUpdate = true;
            }
        }
    
        // step2. 调用函数UpdateBestCovisibles()修改变量mvpOrderedConnectedKeyFrames和mvOrderedWeights
        if (bUpdate)
            UpdateBestCovisibles();
    }
    
    void KeyFrame::UpdateBestCovisibles() {    
        unique_lock<mutex> lock(mMutexConnections);
    	
        // 取出所有关键帧进行排序,排序结果存入变量mvpOrderedConnectedKeyFrames和mvOrderedWeights中
        vector<pair<int, KeyFrame *> > vPairs;
        vPairs.reserve(mConnectedKeyFrameWeights.size());
        for (map<KeyFrame *, int>::iterator mit = mConnectedKeyFrameWeights.begin(), mend = mConnectedKeyFrameWeights.end(); mit != mend; mit++)
            vPairs.push_back(make_pair(mit->second, mit->first));
    
        sort(vPairs.begin(), vPairs.end());
        list<KeyFrame *> lKFs; 
        list<int> lWs; 
        for (size_t i = 0, iend = vPairs.size(); i < iend; i++) {
            lKFs.push_front(vPairs[i].second);
            lWs.push_front(vPairs[i].first);
        }
    
        mvpOrderedConnectedKeyFrames = vector<KeyFrame *>(lKFs.begin(), lKFs.end());
        mvOrderedWeights = vector<int>(lWs.begin(), lWs.end());
    }
    

    生成树: mpParentmspChildrens

    生成树是一种稀疏连接,以最小的边数保存图中所有节点.对于含有N个节点的图,只需构造一个N-1条边的最小生成树就可以将所有节点连接起来.

    下图表示含有一个10个节点,20条边的稠密图;粗黑线代表其最小生成树,只需9条边即可将所有节点连接起来.

    请添加图片描述

    在ORB-SLAM2中,保存所有关键帧构成的最小生成树(优先选择权重大的边作为生成树的边),在回环闭合时只需对最小生成树做BA优化就能以最小代价优化所有关键帧和地图点的位姿,相比于优化共视图大大减少了计算量.(实际上并没有对最小生成树做BA优化,而是对包含生成树的本质图做BA优化)

    请添加图片描述

    成员函数/变量访问控制意义
    bool mbFirstConnectionprotected当前关键帧是否还未加入到生成树
    构造函数中初始化为true,加入生成树后置为false
    KeyFrame* mpParentprotected当前关键帧在生成树中的父节点
    std::set<KeyFrame*> mspChildrensprotected当前关键帧在生成树中的子节点列表
    KeyFrame* GetParent()publicmpParent的get方法
    void ChangeParent(KeyFrame* pKF)public
    应为private
    mpParent的set方法
    std::set<KeyFrame*> GetChilds()publicmspChildrens的get方法
    void AddChild(KeyFrame* pKF)public
    应为private
    添加子节点,mspChildrens的set方法
    void EraseChild(KeyFrame* pKF)public
    应为private
    删除子节点,mspChildrens的set方法
    bool hasChild(KeyFrame* pKF)public判断mspChildrens是否为空

    生成树结构由成员变量mpParentmspChildrens维护.我们主要关注生成树结构发生改变的时机.

    • 关键帧增加到生成树中的时机:

      成功创建关键帧之后会调用函数KeyFrame::UpdateConnections(),该函数第一次被调用时会将该新关键帧加入到生成树中.

      新关键帧的父关键帧会被设为其共视程度最高的共视关键帧.

      void KeyFrame::UpdateConnections() {
          
          // 更新共视图信息
      	// ...
          
          // 更新关键帧信息: 对于第一次加入生成树的关键帧,取共视程度最高的关键帧为父关键帧
          // 该操作会改变当前关键帧的成员变量mpParent和父关键帧的成员变量mspChildrens
          unique_lock<mutex> lockCon(mMutexConnections);
          if (mbFirstConnection && mnId != 0) {
              mpParent = mvpOrderedConnectedKeyFrames.front();
              mpParent->AddChild(this);
              mbFirstConnection = false;
          }
      }
      
    • 共视图的改变(除了删除关键帧以外)不会引发生成树的改变.

    • 只有当某个关键帧删除时,与其相连的生成树结构在会发生改变.(因为生成树是个单线联系的结构,没有冗余,一旦某关键帧删除了就得更新树结构才能保证所有关键帧依旧相连).生成树结构改变的方式类似于最小生成树算法中的加边法,见后文对函数setbadflag()的分析.

    关键帧的删除

    成员函数/变量访问控制意义初值
    bool mbBadprotected标记是坏帧false
    bool isBad()publicmbBad的get方法
    void SetBadFlag()public真的执行删除
    bool mbNotEraseprotected当前关键帧是否具有不被删除的特权false
    bool mbToBeErasedprotected当前关键帧是否曾被豁免过删除false
    void SetNotErase()publicmbNotErase的set方法
    void SetErase()public

    MapPoint类似,函数KeyFrame::SetBadFlag()KeyFrame的删除过程也采取先标记再清除的方式: 先将坏帧标记mBad置为true,再依次处理其各成员变量.

    参与回环检测的关键帧具有不被删除的特权: mbNotErase

    参与回环检测的关键帧具有不被删除的特权,该特权由成员变量mbNotErase存储,创建KeyFrame对象时该成员变量默认被初始化为false.

    若某关键帧参与了回环检测,LoopClosing线程就会就调用函数KeyFrame::SetNotErase()将该关键帧的成员变量mbNotErase设为true,标记该关键帧暂时不要被删除.

    void KeyFrame::SetNotErase() {
        unique_lock<mutex> lock(mMutexConnections);
        mbNotErase = true;
    }
    

    在删除函数SetBadFlag()起始先根据成员变量mbNotErase判断当前KeyFrame是否具有豁免删除的特权.若当前KeyFramembNotErasetrue,则函数SetBadFlag()不能删除当前KeyFrame,但会将其成员变量mbToBeErased置为true.

    void KeyFrame::SetBadFlag() {
        // step1. 特殊情况:豁免 第一帧 和 具有mbNotErase特权的帧
        {
            unique_lock<mutex> lock(mMutexConnections);
    
            if (mnId == 0)
                return;
            else if (mbNotErase) {
                mbToBeErased = true;
                return;
            }
        }
        
        // 两步删除: 先逻辑删除,再物理删除...
    }
    

    成员变量mbToBeErased标记当前KeyFrame是否被豁免过删除特权.LoopClosing线程不再需要某关键帧时,会调用函数KeyFrame::SetErase()剥夺该关键帧不被删除的特权,将成员变量mbNotErase复位为false;同时检查成员变量mbToBeErased,若mbToBeErasedtrue就会调用函数KeyFrame::SetBadFlag()删除该关键帧.

    void KeyFrame::SetErase() {
        {
            unique_lock<mutex> lock(mMutexConnections);
    		// 若当前关键帧没参与回环检测,但其它帧与当前关键帧形成回环关系,也不应当删除当前关键帧
            if (mspLoopEdges.empty()) {
                mbNotErase = false;
            }
        }
    
        // mbToBeErased:删除之前记录的想要删但时机不合适没有删除的帧
        if (mbToBeErased) {
            SetBadFlag();
        }
    }
    

    删除关键帧时维护共视图和生成树

    函数SetBadFlag()在删除关键帧的时维护其共视图生成树结构.共视图结构的维护比较简单,这里主要关心如何维护生成树的结构.

    当一个关键帧被删除时,其父关键帧所有子关键帧的生成树信息也会受到影响,需要为其所有子关键帧寻找新的父关键帧,如果父关键帧找的不好的话,就会产生回环,导致生成树就断开.

    被删除关键帧的子关键帧所有可能的父关键帧包括其兄弟关键帧和其被删除关键帧的父关键帧.以下图为例,关键帧4可能的父关键帧包括关键帧3567.

    请添加图片描述

    采用类似于最小生成树算法中的加边法重新构建生成树结构: 每次循环取权重最高的候选边建立父子连接关系,并将新加入生成树的子节点到加入候选父节点集合sParentCandidates中.

    请添加图片描述

    void KeyFrame::SetBadFlag() {
        // step1. 特殊情况:豁免 第一帧 和 具有mbNotErase特权的帧
        {
            unique_lock<mutex> lock(mMutexConnections);
    
            if (mnId == 0)
                return;
            else if (mbNotErase) {
                mbToBeErased = true;
                return;
            }
        }
    
        // step2. 从共视关键帧的共视图中删除本关键帧
    	for (auto mit : mConnectedKeyFrameWeights)
            mit.first->EraseConnection(this);
    
        // step3. 删除当前关键帧中地图点对本帧的观测
        for (size_t i = 0; i < mvpMapPoints.size(); i++)
            if (mvpMapPoints[i])
                mvpMapPoints[i]->EraseObservation(this);
    
        {
            // step4. 删除共视图
            unique_lock<mutex> lock(mMutexConnections);
            unique_lock<mutex> lock1(mMutexFeatures);
            mConnectedKeyFrameWeights.clear();
            mvpOrderedConnectedKeyFrames.clear();
    
            // step5. 更新生成树结构
            set<KeyFrame *> sParentCandidates;
            sParentCandidates.insert(mpParent);
    
            while (!mspChildrens.empty()) {
                bool bContinue = false;
                int max = -1;
                KeyFrame *pC;
                KeyFrame *pP;
                for (KeyFrame *pKF : mspChildrens) {
                    if (pKF->isBad())
                        continue;
    
                    vector<KeyFrame *> vpConnected = pKF->GetVectorCovisibleKeyFrames();
    
                    for (size_t i = 0, iend = vpConnected.size(); i < iend; i++) {
                        for (set<KeyFrame *>::iterator spcit = sParentCandidates.begin(), spcend = sParentCandidates.end();
                             spcit != spcend; spcit++) {
                            if (vpConnected[i]->mnId == (*spcit)->mnId) {
                                int w = pKF->GetWeight(vpConnected[i]);
                                if (w > max) {
                                    pC = pKF;                   
                                    pP = vpConnected[i];        
                                    max = w;                    
                                    bContinue = true;           
                                }
                            }
                        }
                    }
                }
    
                if (bContinue) {
                    pC->ChangeParent(pP);
                    sParentCandidates.insert(pC);
                    mspChildrens.erase(pC);
                } else
                    break;
            }
    
            if (!mspChildrens.empty())
                for (set<KeyFrame *>::iterator sit = mspChildrens.begin(); sit != mspChildrens.end(); sit++) {
                    (*sit)->ChangeParent(mpParent);
                }
    
            mpParent->EraseChild(this);
    		mTcp = Tcw * mpParent->GetPoseInverse();
            // step6. 将当前关键帧的 mbBad 置为 true
            mbBad = true;
        } 
        
    	// step7. 从地图中删除当前关键帧
        mpMap->EraseKeyFrame(this);
        mpKeyFrameDB->erase(this);
    }
    

    对地图点的观测

    KeyFrame类除了像一般的Frame类那样保存二维图像特征点以外,还保存三维地图点MapPoint信息.

    关键帧观测到的地图点列表由成员变量mvpMapPoints保存,下面是一些对该成员变量进行增删改查的成员函数,就是简单的列表操作,没什么值得说的地方.

    成员函数/变量访问控制意义
    std::vector<MapPoint*> mvpMapPointsprotected当前关键帧观测到的地图点列表
    void AddMapPoint(MapPoint* pMP, const size_t &idx)public
    void EraseMapPointMatch(const size_t &idx)public
    void EraseMapPointMatch(MapPoint* pMP)public
    void ReplaceMapPointMatch(const size_t &idx, MapPoint* pMP)public
    std::set<MapPoint*> GetMapPoints()public
    std::vector<MapPoint*> GetMapPointMatches()public
    int TrackedMapPoints(const int &minObs)public
    MapPoint* GetMapPoint(const size_t &idx)public

    值得关心的是上述函数的调用时机,也就是说参考帧何时与地图点发生关系:

    • 关键帧增加对地图点观测的时机:
      1. Tracking线程和LocalMapping线程创建新地图点后,会马上调用函数KeyFrame::AddMapPoint()添加当前关键帧对该地图点的观测.
      2. LocalMapping线程处理完毕缓冲队列内所有关键帧后会调用LocalMapping::SearchInNeighbors()融合当前关键帧和共视关键帧间的重复地图点,其中调用函数ORBmatcher::Fuse()实现融合过程中会调用函数KeyFrame::AddMapPoint().
      3. LoopClosing线程闭环矫正函数LoopClosing::CorrectLoop()将闭环关键帧与其匹配关键帧间的地图进行融合,会调用函数KeyFrame::AddMapPoint().
    • 关键帧替换和删除对地图点观测的时机:
      1. MapPoint删除函数MapPoint::SetBadFlag()或替换函数MapPoint::Replace()会调用KeyFrame::EraseMapPointMatch()KeyFrame::ReplaceMapPointMatch()删除和替换关键针对地图点的观测.
      2. LocalMapping线程调用进行局部BA优化的函数Optimizer::LocalBundleAdjustment()内部调用函数KeyFrame::EraseMapPointMatch()删除对重投影误差较大的地图点的观测.

    回环检测与本质图

    成员函数/变量访问控制意义
    std::set<KeyFrame*> mspLoopEdgeprotected和当前帧形成回环的关键帧集合
    set<KeyFrame *> GetLoopEdges()publicmspLoopEdge的get函数
    void AddLoopEdge(KeyFrame *pKF)publicmspLoopEdge的set函数

    LoopClosing线程中回环矫正函数LoopClosing::CorrectLoop()在调用本质图BA优化函数Optimizer::OptimizeEssentialGraph()之前会调用函数KeyFrame::AddLoopEdge(),在当前关键帧和其闭环匹配关键帧间添加回环关系.

    在调用本质图BA优化函数Optimizer::OptimizeEssentialGraph()中会调用函数KeyFrame::GetLoopEdges()将所有闭环关系加入到本质图中进行优化.

    KeyFrame的用途

    KeyFrame类的生命周期

    请添加图片描述

    • KeyFrame的创建:

      Tracking线程中通过函数Tracking::NeedNewKeyFrame()判断是否需要关键帧,若需要关键帧,则调用函数Tracking::CreateNewKeyFrame()创建关键帧.

    • KeyFrame的销毁:

      LocalMapping线程剔除冗余关键帧函数LocalMapping::KeyFrameCulling()中若检查到某关键帧为冗余关键帧,则调用函数KeyFrame::SetBadFlag()删除关键帧.

    pdf版本笔记的下载地址: ORB-SLAM2代码详解05_关键帧KeyFrame,排版更美观一点,这个网站的默认排版太丑了(访问密码:3834)

    展开全文
  • 关键帧目前是一种非常常用的方法,可以减少待优化的帧数,并且可以代表其附近的帧。可以理解为一个学校里有100个班级,每个班的班长就是一个关键帧,他可以代表他班里的人,那么如何选取关键帧呢? 选取的指标主要有...
  • rtcp 关键帧请求总结

    千次阅读 2019-10-10 09:59:26
    主要包括SLI/PLI/FIR,作用是在关键帧丢失无法解码时,请求发送方重新生成并发送一个关键帧。 这本质是一种重传,但是跟传输层的重传的区别是,它重传是最新生成的帧。 PLI 是Picture Loss Indication,SLI ...
  • css3之关键帧的解说

    千次阅读 2017-08-03 13:41:06
    本文主要讲解CSS3中的关键帧的知识点,关键帧类似于animate动画的效果,但是关键帧是css的内容,可以省略掉js。
  • 关键帧提取技术,对基于内容的视频检索有着重要的作用。为了从不同类型的视频中有效地提取关键帧,提出了改进的蚁群算法与凝聚相结合的关键帧提取算法。该方法提取视频中每帧的颜色与边缘特征向量,利用改进的蚁群...
  • 关键帧提取技术,对基于内容的视频检索有着重要的作用。为了能从不同类型的视频里有效的提取关键帧,提出了一种新的关键帧提取算法。首先通过主成分分析法提取视频特征信息,然后根据视频内容的复杂度自适应获得聚类...
  • YTAnimation iOS 动画主要是指 Core Animation 框架, Core Animation是 iOS 和 OS X 平台上负责图形渲染与动画的基础框架。...本文主要总结下平时常用的动画, 如: 基础动画(CABasicAnimation)、关键帧动画(CAKeyframeAn
  • 关键帧动画

    千次阅读 2015-10-09 09:47:47
    关键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),这种动画的好处就是开发者不用逐个控制每个动画帧...
  • 关键帧动画, 关键帧动画就是在动画控制过程中开发者指定主要的动画状态, 至于各种状态间动画如何进行则由系统自动运算补充(每个两个关键帧之间系统形成的动画成为补间动画), 这种动画的好处就是开发者不用逐个每个...
  • 视频云直播中的关键帧技术探秘

    千次阅读 2017-07-16 23:46:05
    H.264视频标准中普通I与IDR的区别 普通I与IDR(Instantaneous Decoding Refresh,瞬时解码刷新)为均采用内预测技术的视频,同属于I。 区别是:采用IDR编码,会导致DPB(Decoded ...
  • 一、 x264 编码器参数设置引入、 二、 获取 x264 编码器参数、 三、 设置 x264 编码器编码规格、 四、 设置 x264 编码器编码图像数据格式...七、 设置 x264 编码器 编码相关参数、 八、 x264 编码器参数设置代码示例
  •  关键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),这种动画的好处就是开发者不用逐个控制每个动画...
  • /**转载自...* This file is part of ORB-SLAM2. * * LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中; * 处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。 ...
  • 1. 在运动很慢或没有运动的情况下,我们也采用关键帧实例用于无漂移的估计:没有采用连续时间位姿的优化窗口,我们保留关键帧,可能位置相差很远(时间上连续,但是不同位置的关键帧);保留视觉约束,但仍然会参考...
  • H264关键帧和丢帧策略研究

    万次阅读 2012-04-25 10:25:32
    p帧是根据前面的I帧和P帧预测而来 ...67可能代表的就是关键帧,41为非关键帧 然后与0x1F做与运算,观察结果的不同,来判断是否为关键帧 int type = packet.data[4]&0x1F;  if (type == 0) {  NSLog(@"%d",type);
  • 时间轴和帧 今天我们这个视频的主要目就是一起来了解和认识一下Flash的时间轴和帧 ...1帧类型 1普通帧 普通帧是一般的帧是flash中最多的帧其他类型的帧都是由它转变过来的图3-3-19/20 2关键帧 关键帧分为关键帧和空白关
  • 原创文章,转载请联系作者 若待明朝风雨过,人在天涯!...项目地址传送门此篇文章,主要是分享如何用MediaCodeC解码视频指定时间的一,回调Bitmap对象。之前还有一篇MediaCodeC硬解码视频,并将视频存储为图...
  • //关键帧动画 -(void)layerKeyFrameAnimation { //画一个path UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(-40, 100)]; [path addLineToPoint:CGPoint
  • H264、H265编码概念及IPB

    千次阅读 2021-12-09 19:20:15
    P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据),通过充分将低于...
  • 1. 在运动很慢或没有运动的情况下,我们也采用关键帧实例用于无漂移的估计:没有采用连续时间位姿的优化窗口,我们保留关键帧,可能位置相差很远(时间上连续,但是不同位置的关键帧);保留视觉约束,但仍然会参考...
  • 同步

    千次阅读 2019-05-07 11:49:29
    同步技术是早期RTS游戏常用的一种同步技术,本篇文章要给大家介绍的是RTX游戏中同步实现,同步是一种前后端数据同步的方式,一般应用于对实时性要求很高的网络游戏,想要了解更多同步的知识,继续往下看。
  • H264编码 GOP组 以及 I B P 说明

    千次阅读 2021-11-25 23:45:43
    I B P 说明 H264 未压缩的码流: Byte 字节单位的码流 = 640x480x1.5x15 = 691200 所有在网上传输的数据都是按照比特位计算的 所以要用 bit为单位 Bit位码流 = 640x480x1.5x15x8 = 55296000 即 55M 而 H...
  • 视频序列中的第一个帧始终都是I帧,因为它是关键帧。如果传输过程中I真丢失,画面最直接的影响就是会卡顿,因为后面的帧都无法正确解码,只能等待下一个GOP。IDR帧即时解码刷新,其实就是I帧,不过他是第一个I帧,...
  • 动画的多种实现方式与性能对比

    万次阅读 2018-07-11 10:42:47
    前面我分享了《Web动画形式》,各种动画形式都可以制作出一种类型的动画,那就是动画,也叫序列动画,定格动画,逐动画等,这里我们统一用动画来表述,接下来我们就来看看动画有哪些打开方式吧。...
  • 是什么?

    2022-01-07 21:07:38
    数据在网络上是以很小的称为(Frame)的单位传输的,由几部分组成,不同的部分执行不同的功能。数据在网络上是以很小的称为(Frame)的单位传输的,由几部分组成,不同的部分执行不同的功能。通过特定的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,923
精华内容 17,169
关键字:

关键帧的主要作用