精华内容
下载资源
问答
  • 键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成动画称为“补间动画”),这种动画好处就是开发者不用逐个控制每个动画帧,...

    键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),这种动画的好处就是开发者不用逐个控制每个动画帧,而只要关心几个关键帧的状态即可。

    关键帧动画开发分为两种形式:一种是通过设置不同的属性值进行关键帧控制,另一种是通过绘制路径进行关键帧控制。后者优先级高于前者,如果设置了路径则属性值就不再起作用。

    //
    //  ViewController.m
    //  Demo02182
    //
    //  Created by wiseman on 16/2/18.
    //  Copyright (c) 2016年 wiseman. All rights reserved.
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    {
        CALayer *_layer;
    }
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        //自定义一个图层
        _layer=[[CALayer alloc]init];
        _layer.bounds=CGRectMake(0, 0, 10, 20);
        _layer.position=CGPointMake(50, 150);
        _layer.backgroundColor = [UIColor redColor].CGColor;
        [self.view.layer addSublayer:_layer];
    }
    
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        //创建动画
    //    [self translationAnimation];
        [self translationAnimationWithUIBezierPath];
    }
    
    #pragma mark - 关键帧动画,通过四个关键帧状态来改变动画效果
    -(void)translationAnimation{
        //1.创建关键帧动画,并设置动画的属性
        CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        
        //2.设置关键帧
        NSValue *key1 = [NSValue valueWithCGPoint:_layer.position];//对于关键帧动画初始值不能省略
        NSValue *key2=[NSValue valueWithCGPoint:CGPointMake(80, 220)];
        NSValue *key3=[NSValue valueWithCGPoint:CGPointMake(45, 300)];
        NSValue *key4=[NSValue valueWithCGPoint:CGPointMake(55, 400)];
        NSArray *values=@[key1,key2,key3,key4];
        
        keyframeAnimation.values = values;
        keyframeAnimation.duration = 2.0;
        keyframeAnimation.beginTime = CACurrentMediaTime()+2;//设置延迟2秒执行
        
        //3.添加动画到图层,添加动画后就会执行动画
        [_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
    }
    
    #pragma mark - 贝塞尔曲线,通过贝塞尔曲线来绘制路径对关键帧动画进行控制
    -(void)translationAnimationWithUIBezierPath{
        UIBezierPath *path = [[UIBezierPath alloc]init];
        //设置起始端点
        [path moveToPoint:CGPointMake(20, 50)];
        [path addCurveToPoint:CGPointMake(20, 350) controlPoint1:CGPointMake(0, 200) controlPoint2:CGPointMake(40, 200)];
        
        //1.创建关键帧动画
        CAKeyframeAnimation *keyAnima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        //2.设置path
        keyAnima.path = path.CGPath;
        keyAnima.duration = 3.0;
        //3.添加动画到图层,添加动画后就会执行动画
        [_layer addAnimation:keyAnima forKey:@"KCKeyframeAnimation_Position"];
        
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end

     

     

    补充--其他属性

    在关键帧动画中还有一些动画属性初学者往往比较容易混淆,这里专门针对这些属性做一下介绍。

    keyTimes:各个关键帧的时间控制。前面使用values设置了四个关键帧,默认情况下每两帧之间的间隔为:8/(4-1)秒。如果想要控制 动画从第一帧到第二针占用时间4秒,从第二帧到第三帧时间为2秒,而从第三帧到第四帧时间2秒的话,就可以通过keyTimes进行设置。 keyTimes中存储的是时间占用比例点,此时可以设置keyTimes的值为0.0,0.5,0.75,1.0(当然必须转换为NSNumber), 也就是说1到2帧运行到总时间的50%,2到3帧运行到总时间的75%,3到4帧运行到8秒结束。

    caculationMode:动画计算模式。还拿上面keyValues动画举例,之所以1到2帧能形成连贯性动画而不是直接从第1帧经过8/3 秒到第2帧是因为动画模式是连续的(值为kCAAnimationLinear,这是计算模式的默认值);而如果指定了动画模式为 kCAAnimationDiscrete离散的那么你会看到动画从第1帧经过8/3秒直接到第2帧,中间没有任何过渡。其他动画模式还 有:kCAAnimationPaced(均匀执行,会忽略keyTimes)、kCAAnimationCubic(平滑执行,对于位置变动关键帧动画 运行轨迹更平滑)、kCAAnimationCubicPaced(平滑均匀执行)。

    转载于:https://www.cnblogs.com/iOSDeng/p/5198304.html

    展开全文
  • 关键帧提取技术,对基于内容的视频检索有着重要的作用。为了从不同类型的视频中有效地...实验结果表明:使用该算法提取的关键帧不仅可以充分表达出视频的主要内容,而且可以根据视频内容的变化提取出适当数量的关键帧
  • 关键帧提取技术,对基于内容视频检索有着重要的作用。为了能从不同类型视频里有效提取关键帧,提出了一种新的关键帧提取算法。首先通过主成分分析法提取视频特征信息,然后根据视频内容复杂度自适应获得聚类...
  • rtcp 关键帧请求总结

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

    第一类:关键帧请求

    主要包括SLI/PLI/FIR,作用是在关键帧丢失无法解码时,请求发送方重新生成并发送一个关键帧。

    这本质是一种重传,但是跟传输层的重传的区别是,它重传是最新生成的帧。

    PLI 是Picture Loss Indication,SLI 是Slice Loss Indication。

    发送方接收到接收方反馈的PLI或SLI需要重新让编码器生成关键帧并发送给接收端。

    // RFC 4585: Feedback format.
    //
    // Common packet format:
    //
    //   0                   1                   2                   3
    //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |V=2|P|   FMT   |       PT      |          length               |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |                  SSRC of packet sender                        |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |                  SSRC of media source                         |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  :            Feedback Control Information (FCI)                 :
    //  :                                                               :
    
    //
    // Picture loss indication (PLI) (RFC 4585).
    // FCI: no feedback control information.

    FIR 是Full Intra Request,这里面Intra的含义可能很多人不知道。

    Intra的含义是图像内编码,不需要其他图像信息即可解码;Inter指图像间编码,解码需要参考帧。

    故Intra Frame其实就是指I帧,Inter Frame指P帧或B帧。

    // RFC 4585: Feedback format.
    // Common packet format:
    //
    //   0                   1                   2                   3
    //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |V=2|P|   FMT   |       PT      |          length               |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |                  SSRC of packet sender                        |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |             SSRC of media source (unused) = 0                 |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  :            Feedback Control Information (FCI)                 :
    //  :                                                               :
    // Full intra request (FIR) (RFC 5104).
    // The Feedback Control Information (FCI) for the Full Intra Request
    // consists of one or more FCI entries.
    // FCI:
    //   0                   1                   2                   3
    //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |                              SSRC                             |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  | Seq nr.       |    Reserved = 0                               |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    那么为什么在PLI和SLI之外还需要一个FIR呢?

    原因是使用场景不同,FIR更多是在一个中心化的Video Conference中,新的参与者加入,就需要发送一个FIR,其他的参与者给他发送一个关键帧这样才能解码,

    而PLI和SLI的含义更多是在发生丢包或解码错误时使用。

    第二类:重传请求

    主要包括RTX/NACK/RPSI

    这个重传跟关键帧请求的区别是它可以要求任意帧进行重传

    第三类:码率控制

    主要包括REMB/TMMBR/TMMBN

    TMMBR是Temporal Max Media Bitrate Request,表示临时最大码率请求。表明接收端当前带宽受限,告诉发送端控制码率。

    REMB是ReceiverEstimated Max Bitrate,接收端估计的最大码率。
    TMMBN是Temporal Max Media Bitrate Notification


    ————————————————
    版权声明:本文为CSDN博主「wangruihit」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/wangruihit/article/details/47041515

    展开全文
  • 局部建图线程 地图点融合 留下观测...* LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中; * 处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。 * 主要工作在于维护局部地...

    局部建图线程 地图点融合 留下观测次数多的 局部地图 优化 关键帧剔除

    博文末尾支持二维码赞赏哦 

    /**
    * This file is part of ORB-SLAM2.
    * 
    * LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中;
    * 处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。
    * 主要工作在于维护局部地图,也就是SLAM中的Mapping。
    * 
    * 
    * Tracking线程 只是判断当前帧是否需要加入关键帧,并没有真的加入地图,
    * 因为Tracking线程的主要功能是局部定位,
    * 
    * 而处理地图中的关键帧,地图点,包括如何加入,
    * 如何删除的工作是在LocalMapping线程完成的
    * 
    * 建图 
    * 处理新的关键帧 KeyFrame 完成局部地图构建
    * 插入关键帧 ------>  处理地图点(筛选生成的地图点 生成地图点)  -------->  局部 BA最小化重投影误差  -调整-------->   筛选 新插入的 关键帧
    *
    * mlNewKeyFrames     list 列表队列存储关键帧
    * 1】检查队列
    *       CheckNewKeyFrames();
    * 
    * 2】处理新关键帧 Proces New Key Frames 
    * 	ProcessNewKeyFrame(); 更新地图点MapPoints 和 关键帧 KepFrame 的关联关系  UpdateConnections() 更新关联关系
    * 
    * 3】剔除 地图点 MapPoints
    *       删除地图中新添加的但 质量不好的 地图点
    *       a)  IncreaseFound 共视点  / IncreaseVisible  投影在图像上 < 25%
    *       b) 观测到该 点的关键帧太少
    * 
    * 4】生成 地图点 MapPoints
    * 	运动过程中和共视程度比较高的 关键帧 通过三角化 恢复出的一些地图点 
    * 
    * 5】地图点融合 MapPoints
    *       检测当前关键帧和相邻 关键帧(两级相邻) 中 重复的 地图点 留下观测帧高的 地图点
    * 
    * 6】局部 BA 最小化重投影误差
    *      和当前关键帧相邻的关键帧 中相匹配的 地图点对 局部 BA最小化重投影误差优化点坐标 和 位姿
    * 
    * 7】关键帧剔除
    *      其90%以上的 地图点 能够被其他 共视 关键帧(至少3个) 观测到,认为该关键帧多余,可以删除
    * 
    */
    #include "LocalMapping.h"
    #include "LoopClosing.h"
    #include "ORBmatcher.h"
    #include "Optimizer.h"
    
    #include<mutex>
    
    namespace ORB_SLAM2
    {
    
    	LocalMapping::LocalMapping(Map *pMap, const float bMonocular):
    	    mbMonocular(bMonocular), mbResetRequested(false), mbFinishRequested(false), mbFinished(true), mpMap(pMap),
    	    mbAbortBA(false), mbStopped(false), mbStopRequested(false), mbNotStop(false), mbAcceptKeyFrames(true)
    	{
    	}
    
    	void LocalMapping::SetLoopCloser(LoopClosing* pLoopCloser)
    	{
    	    mpLoopCloser = pLoopCloser;
    	}
    
    	void LocalMapping::SetTracker(Tracking *pTracker)
    	{
    	    mpTracker=pTracker;
    	}
    
    	void LocalMapping::Run()
    	{
    
    	    mbFinished = false;
    
    	    while(1)
    	    {
    		// Tracking will see that Local Mapping is busy
    // 步骤1:设置进程间的访问标志 告诉Tracking线程,LocalMapping线程正在处理新的关键帧,处于繁忙状态
                   // LocalMapping线程处理的关键帧都是Tracking线程发过的
                   // 在LocalMapping线程还没有处理完关键帧之前Tracking线程最好不要发送太快
    		SetAcceptKeyFrames(false);
    
    		// Check if there are keyframes in the queue
    	// 等待处理的关键帧列表不为空
    		if(CheckNewKeyFrames())
    		{
    		    // BoW conversion and insertion in Map
    // 步骤2:计算关键帧特征点的词典单词向量BoW映射,将关键帧插入地图
    		    ProcessNewKeyFrame();
    
    		    // Check recent MapPoints
    		  // 剔除ProcessNewKeyFrame函数中引入的不合格MapPoints
    // 步骤3:对新添加的地图点融合 对于 ProcessNewKeyFrame 和 CreateNewMapPoints 中 最近添加的MapPoints进行检查剔除	    
    		  //   MapPointCulling();
    
    		    // Triangulate new MapPoints
    		    
    // 步骤4: 创建新的地图点 相机运动过程中与相邻关键帧通过三角化恢复出一些新的地图点MapPoints	    
    		    CreateNewMapPoints();
    		    
    		    MapPointCulling();// 从上面 移到下面
    
    	      // 已经处理完队列中的最后的一个关键帧
    		    if(!CheckNewKeyFrames())
    		    {
    			// Find more matches in neighbor keyframes and fuse point duplications
    // 步骤5:相邻帧地图点融合 检查并融合当前关键帧与相邻帧(两级相邻)重复的MapPoints
    			SearchInNeighbors();
    		    }
    
    		    mbAbortBA = false;
    
    		// 已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
    		    if(!CheckNewKeyFrames() && !stopRequested())
    		    {
    // 步骤6:局部地图优化 Local BA
    			if(mpMap->KeyFramesInMap() > 2)
    			    Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);
    
    // 步骤7: 关键帧融合 检测并剔除当前帧相邻的关键帧中冗余的关键帧 Check redundant local Keyframes
    	                // 剔除的标准是:该关键帧的90%的MapPoints可以被其它关键帧观测到		
    			// Tracking中先把关键帧交给LocalMapping线程
    			 // 并且在Tracking中InsertKeyFrame函数的条件比较松,交给LocalMapping线程的关键帧会比较密
                            // 在这里再删除冗余的关键帧
    			KeyFrameCulling();
    		    }
    // 步骤8:将当前帧加入到闭环检测队列中
    		    mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
    		}
    // 步骤9:等待线程空闲 完成一帧关键帧的插入融合工作
    		else if(Stop())
    		{
    		    // Safe area to stop
    		    while(isStopped() && !CheckFinish())
    		    {
    			usleep(3000);
    		    }
    		    if(CheckFinish())//检查 是否完成
    			break;
    		}
    		
                   // 检查重置
    		ResetIfRequested();
    
    		// Tracking will see that Local Mapping is not busy
    // 步骤10:告诉 	Tracking 线程  Local Mapping 线程 空闲 可一处理接收 下一个 关键帧
    		SetAcceptKeyFrames(true);
    
    		if(CheckFinish())
    		    break;
    
    		usleep(3000);
    	    }
    
    	    SetFinish();
    	}
    
    /**
     * @brief 插入关键帧
     *
     * 将关键帧插入到地图中,以便将来进行局部地图优化
     * 这里仅仅是将关键帧插入到列表中进行等待
     * @param pKF KeyFrame
     */
    	void LocalMapping::InsertKeyFrame(KeyFrame *pKF)
    	{
    	    unique_lock<mutex> lock(mMutexNewKFs);
    	     // 将关键帧插入到 等待处理的关键帧列表中
    	    mlNewKeyFrames.push_back(pKF);
    	    mbAbortBA=true;// BA优化停止
    	}
    
    /**
     * @brief    查看列表中是否有等待 被处理的关键帧
     * @return 如果存在,返回true
     */
    	bool LocalMapping::CheckNewKeyFrames()
    	{
    	    unique_lock<mutex> lock(mMutexNewKFs);
    	    return(!mlNewKeyFrames.empty());// 等待处理的关键帧列表是否为空
    	}
    
    
      /*
      a. 根据词典 计算当前关键帧Bow,便于后面三角化恢复新地图点;
      b. 将TrackLocalMap中跟踪局部地图匹配上的地图点绑定到当前关键帧
          (在Tracking线程中只是通过匹配进行局部地图跟踪,优化当前关键帧姿态),
          也就是在graph 中加入当前关键帧作为node,并更新edge。
          
          而CreateNewMapPoint()中则通过当前关键帧,在局部地图中添加与新的地图点;
    
      c. 更新加入当前关键帧之后关键帧之间的连接关系,包括更新Covisibility图和Essential图
      (最小生成树spanning tree,共视关系好的边subset of edges from covisibility graph 
        with high covisibility (θ=100), 闭环边)。
        */
      
      /**
     * @brief 处理列表中的关键帧
     * 
     * - 计算Bow,加速三角化新的MapPoints
     * - 关联当前关键帧至MapPoints,并更新MapPoints的平均观测方向和观测距离范围
     * - 插入关键帧,更新Covisibility图和Essential图
     * @see VI-A keyframe insertion
     */
    	void LocalMapping::ProcessNewKeyFrame()
    	{	  
    // 步骤1:从缓冲队列中取出一帧待处理的关键帧
                 // Tracking线程向LocalMapping中插入关键帧存在该队列中
    	    {
    		unique_lock<mutex> lock(mMutexNewKFs);
    		// 从列表中获得一个等待被插入的关键帧
    		mpCurrentKeyFrame = mlNewKeyFrames.front();
    		mlNewKeyFrames.pop_front();// 出队
    	    }
    
    	    // Compute Bags of Words structures
    // 步骤2:计算该关键帧特征点的Bow映射关系    
    	    //  根据词典 计算当前关键帧Bow,便于后面三角化恢复新地图点    
    	    mpCurrentKeyFrame->ComputeBoW();// 帧描述子 用字典单词线性表示的 向量
    
    	    // Associate MapPoints to the new keyframe and update normal and descriptor
    	    // 当前关键帧  TrackLocalMap中跟踪局部地图 匹配上的 地图点
    // 步骤3:跟踪局部地图过程中新匹配上的MapPoints和当前关键帧绑定
    	      // 在TrackLocalMap函数中将局部地图中的MapPoints与当前帧进行了匹配,
    	      // 但没有对这些匹配上的MapPoints与当前帧进行关联    
    	    const vector<MapPoint*> vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();
    	    for(size_t i=0; i<vpMapPointMatches.size(); i++)
    	    {
    		MapPoint* pMP = vpMapPointMatches[i];// 每一个与当前关键帧匹配好的地图点
    		if(pMP)//地图点存在
    		{
    		    if(!pMP->isBad())
    		    {
    		       // 为当前帧在tracking过重跟踪到的MapPoints更新属性
    			if(!pMP->IsInKeyFrame(mpCurrentKeyFrame))//下视野内
    			{
    			    pMP->AddObservation(mpCurrentKeyFrame, i);// 地图点添加关键帧
    			    pMP->UpdateNormalAndDepth();// 地图点更新 平均观测方向 和 观测距离深度
    			    pMP->ComputeDistinctiveDescriptors();// 加入关键帧后,更新地图点的最佳描述子
    			}
    			else // 双目追踪时插入的点 可能不在 帧上this can only happen for new stereo points inserted by the Tracking
    			{
    			   // 将双目或RGBD跟踪过程中新插入的MapPoints放入mlpRecentAddedMapPoints,等待检查
                               // CreateNewMapPoints函数中通过三角化也会生成MapPoints
                               // 这些MapPoints都会经过MapPointCulling函数的检验
    			    mlpRecentAddedMapPoints.push_back(pMP);
    			    // 候选待检查地图点存放在mlpRecentAddedMapPoints
    			}
    		    }
    		}
    	    }    
    
    	    // Update links in the Covisibility Graph
    // 步骤4:更新关键帧间的连接关系,Covisibility图和Essential图(tree)
    	    mpCurrentKeyFrame->UpdateConnections();
    
    	    // Insert Keyframe in Map
    // 步骤5:将该关键帧插入到地图中
    	    mpMap->AddKeyFrame(mpCurrentKeyFrame);
    	}
    	
    	
    /**
     * @brief 剔除ProcessNewKeyFrame(不在帧上的地图点 进入待查list)和
     *             CreateNewMapPoints(两帧三角变换产生的新地图点进入 待查list)
     *             函数中引入的质量不好的MapPoints
     * @see VI-B recent map points culling
     */	
    // 对于ProcessNewKeyFrame和CreateNewMapPoints中最近添加的MapPoints进行检查剔除
    	void LocalMapping::MapPointCulling()
    	{
    	    // Check Recent Added MapPoints
    	    list<MapPoint*>::iterator lit = mlpRecentAddedMapPoints.begin();//待检测的地图点 迭代器
    	    const unsigned long int nCurrentKFid = mpCurrentKeyFrame->mnId;//当前关键帧id
    
    	    //  从添加该地图点的关键帧算起的 初始三个关键帧,
    	    // 第一帧不算,后面两帧看到该地图点的帧数,对于单目<=2,对于双目和RGBD<=3;
    	    int nThObs;
    	    if(mbMonocular)
    		nThObs = 2;
    	    else
    		nThObs = 3;
    	    const int cnThObs = nThObs;
     // 遍历等待检查的地图点MapPoints
    	    while(lit !=mlpRecentAddedMapPoints.end())
    	    {
    		MapPoint* pMP = *lit;//  新添加的地图点
    		if(pMP->isBad())
    		{
     // 步骤1:已经是坏点的MapPoints直接从检查链表中删除	  
    		    lit = mlpRecentAddedMapPoints.erase(lit);
    		}
    		//  跟踪(匹配上)到该地图点的普通帧帧数(IncreaseFound)< 应该观测到该地图点的普通帧数量(25%*IncreaseVisible):
    		// 该地图点虽在视野范围内,但很少被普通帧检测到。 剔除
    		else if(pMP->GetFoundRatio()<0.25f )
    		{
     // 步骤2:将不满足VI-B条件的MapPoint剔除
    		// VI-B 条件1:
    		// 跟踪到该MapPoint的Frame数相比预计可观测到该MapPoint的Frame数的比例需大于25%
    		// IncreaseFound() / IncreaseVisible(该地图点在视野范围内) < 25%,注意不一定是关键帧。		  
    		    pMP->SetBadFlag();
    		    lit = mlpRecentAddedMapPoints.erase(lit);//从待查  list中删除
    		}
    		//
    		// 初始三个关键帧 地图点观测次数 不能太少
    		// 而且单目的要求更严格,需要三帧都看到
    		else if(( (int)nCurrentKFid - (int)pMP->mnFirstKFid) >=2 && pMP->Observations() <= cnThObs)
    		{
      // 步骤3:将不满足VI-B条件的MapPoint剔除
                // VI-B 条件2:从该点建立开始,到现在已经过了不小于2帧,
                // 但是观测到该点的关键帧数却不超过cnThObs帧,那么该点检验不合格
    		    pMP->SetBadFlag();
    		    lit = mlpRecentAddedMapPoints.erase(lit);//从待查  list中删除
    		}
    		else if(((int)nCurrentKFid - (int)pMP->mnFirstKFid ) >= 3)
    // 步骤4:从建立该点开始,已经过了3帧(前三帧地图点比较宝贵需要特别检查),放弃对该MapPoint的检测		  
    		    lit = mlpRecentAddedMapPoints.erase(lit);//从待查  list中删除
    		else
    		    lit++;
    	    }
    	}
    
    
    /**
     * @brief 相机运动过程中和共视程度比较高的关键帧通过三角化恢复出一些MapPoints
     *  根据当前关键帧恢复出一些新的地图点,不包括和当前关键帧匹配的局部地图点(已经在ProcessNewKeyFrame中处理)
     *  先处理新关键帧与局部地图点之间的关系,然后对局部地图点进行检查,
     *  最后再通过新关键帧恢复 新的局部地图点:CreateNewMapPoints()
     * 
     * 步骤1:在当前关键帧的 共视关键帧 中找到 共视程度 最高的前nn帧 相邻帧vpNeighKFs
     * 步骤2:遍历和当前关键帧 相邻的 每一个关键帧vpNeighKFs	
     * 步骤3:判断相机运动的基线在(两针间的相机相对坐标)是不是足够长
     * 步骤4:根据两个关键帧的位姿计算它们之间的基本矩阵 F =  inv(K1 转置) * t12 叉乘 R12 * inv(K2)
     * 步骤5:通过帧间词典向量加速匹配,极线约束限制匹配时的搜索范围,进行特征点匹配	
     * 步骤6:对每对匹配点 2d-2d 通过三角化生成3D点,和 Triangulate函数差不多	
     *  步骤6.1:取出匹配特征点
     *  步骤6.2:利用匹配点反投影得到视差角   用来决定使用三角化恢复(视差角较大) 还是 直接2-d点反投影(视差角较小)
     *  步骤6.3:对于双目,利用双目基线 深度 得到视差角
     *  步骤6.4:视差角较大时 使用 三角化恢复3D点
     *  步骤6.4:对于双目 视差角较小时 二维点 利用深度值 反投影 成 三维点    单目的话直接跳过
     *  步骤6.5:检测生成的3D点是否在相机前方
     *  步骤6.6:计算3D点在当前关键帧下的重投影误差  误差较大跳过
     *  步骤6.7:计算3D点在 邻接关键帧 下的重投影误差 误差较大跳过
     *  步骤6.9:三角化生成3D点成功,构造成地图点 MapPoint
     *  步骤6.9:为该MapPoint添加属性 
     *  步骤6.10:将新产生的点放入检测队列 mlpRecentAddedMapPoints  交给 MapPointCulling() 检查生成的点是否合适
     * @see  
     */	
    	void LocalMapping::CreateNewMapPoints()
    	{
    	    // Retrieve neighbor keyframes in covisibility graph
    	    int nn = 10;// 双目/深度 共视帧 数量
    	    if(mbMonocular)
    		nn=20;//单目
    		
    // 步骤1:在当前关键帧的 共视关键帧 中找到 共视程度 最高的nn帧 相邻帧vpNeighKFs
    	    const vector<KeyFrame*> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);
    
    	    ORBmatcher matcher(0.6,false);// 描述子匹配器 
                // 当前关键帧 旋转平移矩阵向量
    	    cv::Mat Rcw1 = mpCurrentKeyFrame->GetRotation();// 世界---> 当前关键帧
    	    cv::Mat Rwc1 = Rcw1.t();// 当前关键帧---> 世界
    	    cv::Mat tcw1 = mpCurrentKeyFrame->GetTranslation();
    	    cv::Mat Tcw1(3,4,CV_32F);// 世界---> 当前关键帧 变换矩阵
    	    Rcw1.copyTo(Tcw1.colRange(0,3));
    	    tcw1.copyTo(Tcw1.col(3));
    	    // 得到当前当前关键帧在世界坐标系中的坐标
    	    cv::Mat Ow1 = mpCurrentKeyFrame->GetCameraCenter();
               // 相机内参数
    	    const float &fx1 = mpCurrentKeyFrame->fx;
    	    const float &fy1 = mpCurrentKeyFrame->fy;
    	    const float &cx1 = mpCurrentKeyFrame->cx;
    	    const float &cy1 = mpCurrentKeyFrame->cy;
    	    const float &invfx1 = mpCurrentKeyFrame->invfx;
    	    const float &invfy1 = mpCurrentKeyFrame->invfy;
    	    const float ratioFactor = 1.5f*mpCurrentKeyFrame->mfScaleFactor;//匹配点 查找范围
    
    	    int nnew=0;
    
    	    // Search matches with epipolar restriction and triangulate
    // 步骤2:遍历和当前关键帧 相邻的 每一个关键帧vpNeighKFs	    
    	    for(size_t i=0; i<vpNeighKFs.size(); i++)
    	    {
    		if(i>0 && CheckNewKeyFrames())
    		    return;
    
    		KeyFrame* pKF2 = vpNeighKFs[i];//关键帧
    		// Check first that baseline is not too short
    	   // 邻接的关键帧在世界坐标系中的坐标
    		cv::Mat Ow2 = pKF2->GetCameraCenter();
    	   // 基线向量,两个关键帧间的相机相对坐标
    		cv::Mat vBaseline = Ow2-Ow1;
    	   // 基线长度	
    		const float baseline = cv::norm(vBaseline);
    		
    // 步骤3:判断相机运动的基线是不是足够长
    		if(!mbMonocular)
    		{
    		    if(baseline < pKF2->mb)
    		    continue;// 如果是立体相机,关键帧间距太小时不生成3D点
    		}
    		else// 单目相机
    		{
    		   // 邻接关键帧的场景深度中值
    		    const float medianDepthKF2 = pKF2->ComputeSceneMedianDepth(2);//中值深度
    		    // baseline与景深的比例
    		    const float ratioBaselineDepth = baseline/medianDepthKF2; 
                       // 如果特别远(比例特别小),那么不考虑当前邻接的关键帧,不生成3D点
    		    if(ratioBaselineDepth < 0.01)
    			continue;
    		}
    
    		// Compute Fundamental Matrix
    // 步骤4:根据两个关键帧的位姿计算它们之间的基本矩阵	
           // 根据两关键帧的姿态计算两个关键帧之间的基本矩阵 
           // F =  inv(K1 转置)*E*inv(K2) = inv(K1 转置) * t12 叉乘 R12 * inv(K2)
    		cv::Mat F12 = ComputeF12(mpCurrentKeyFrame,pKF2);
    
    		// Search matches that fullfil epipolar constraint
    // 步骤5:通过帧间词典向量加速匹配,极线约束限制匹配时的搜索范围,进行特征点匹配		
    		vector<pair<size_t,size_t> > vMatchedIndices;// 特征匹配候选点
    		matcher.SearchForTriangulation(mpCurrentKeyFrame,pKF2,F12,vMatchedIndices,false);
    
             	 // 相邻关键帧 旋转平移矩阵向量
    		cv::Mat Rcw2 = pKF2->GetRotation();
    		cv::Mat Rwc2 = Rcw2.t();
    		cv::Mat tcw2 = pKF2->GetTranslation();
    		cv::Mat Tcw2(3,4,CV_32F);// 转换矩阵
    		Rcw2.copyTo(Tcw2.colRange(0,3));
    		tcw2.copyTo(Tcw2.col(3));
    		// 相机内参
    		const float &fx2 = pKF2->fx;
    		const float &fy2 = pKF2->fy;
    		const float &cx2 = pKF2->cx;
    		const float &cy2 = pKF2->cy;
    		const float &invfx2 = pKF2->invfx;
    		const float &invfy2 = pKF2->invfy;
    
    		// Triangulate each match
    		// 三角化每一个匹配点对
    // 步骤6:对每对匹配点 2d-2d 通过三角化生成3D点,和 Triangulate函数差不多		
    		const int nmatches = vMatchedIndices.size();
    		for(int ikp=0; ikp<nmatches; ikp++)
    		{
    	 // 步骤6.1:取出匹配特征点	  
    		    const int &idx1 = vMatchedIndices[ikp].first; // 当前匹配对在当前关键帧中的索引
    		    const int &idx2 = vMatchedIndices[ikp].second;// 当前匹配对在邻接关键帧中的索引
    		    //当前关键帧 特征点 和 右图像匹配点横坐标
    		    const cv::KeyPoint &kp1 = mpCurrentKeyFrame->mvKeysUn[idx1];
    		    const float kp1_ur=mpCurrentKeyFrame->mvuRight[idx1];
    		    bool bStereo1 = kp1_ur >= 0;//右图像匹配点横坐标>=0是双目/深度相机
    		     // 邻接关键帧 特征点 和 右图像匹配点横坐标
    		    const cv::KeyPoint &kp2 = pKF2->mvKeysUn[idx2];
    		    const float kp2_ur = pKF2->mvuRight[idx2];
    		    bool bStereo2 = kp2_ur >= 0;
    
    	 // 步骤6.2:利用匹配点反投影得到视差角    
    		    // Check parallax between rays 
    		    // 相机归一化平面上的点坐标
    		    cv::Mat xn1 = (cv::Mat_<float>(3,1) << (kp1.pt.x - cx1)*invfx1, (kp1.pt.y - cy1)*invfy1, 1.0);
    		    cv::Mat xn2 = (cv::Mat_<float>(3,1) << (kp2.pt.x - cx2)*invfx2, (kp2.pt.y - cy2)*invfy2, 1.0);
    		    
    		    // 由相机坐标系转到世界坐标系,得到视差角余弦值
    		    cv::Mat ray1 = Rwc1*xn1;// 相机坐标系 ------> 世界坐标系
    		    cv::Mat ray2 = Rwc2*xn2;
    		    // 向量a × 向量b / (向量a模 × 向量吧模) = 夹角余弦值
    		    const float cosParallaxRays = ray1.dot(ray2) / (cv::norm(ray1)*cv::norm(ray2));
    		    
    		    // 加1是为了让cosParallaxStereo随便初始化为一个很大的值
    		    float cosParallaxStereo = cosParallaxRays+1;
    		    float cosParallaxStereo1 = cosParallaxStereo;
    		    float cosParallaxStereo2 = cosParallaxStereo;
    	 // 步骤6.3:对于双目,利用双目基线 深度 得到视差角
    		    if(bStereo1)
    			cosParallaxStereo1 = cos(2*atan2(mpCurrentKeyFrame->mb/2,mpCurrentKeyFrame->mvDepth[idx1]));
    		    else if(bStereo2)
    			cosParallaxStereo2 = cos(2*atan2(pKF2->mb/2,pKF2->mvDepth[idx2]));
    		    // 得到双目观测的视差角
    		    cosParallaxStereo = min(cosParallaxStereo1,cosParallaxStereo2);
    		    
    	 // 步骤6.4:三角化恢复3D点
    		    cv::Mat x3D;
    		  // cosParallaxRays>0 && (bStereo1 || bStereo2 || cosParallaxRays<0.9998)表明视差角正常
                      // cosParallaxRays < cosParallaxStereo表明视差角很小
                      // 视差角度小时用三角法恢复3D点,视差角大时用双目恢复3D点(双目以及深度有效)
    		    if(cosParallaxRays < cosParallaxStereo && cosParallaxRays>0 && (bStereo1 || bStereo2 || cosParallaxRays<0.9998))
    		    {
    			// Linear Triangulation Method
    		      // p1 = k × [R1 t1] × D       k逆 × p1 =  [R1 t1] × D     x1 = T1 × D    x1叉乘x1 = x1叉乘T1 × D = 0
    		      // p2 = k × [ R2 t2]  × D     k逆 × p2 =  [R2 t2] × D     x2 = T2 × D    x2叉乘x2 = x2叉乘T2 × D = 0
    		      //
    		      //p = ( x,y,1)
    		      //其叉乘矩阵为
    		      //  叉乘矩阵 = [0  -1  y;                T0
    		      //                       1   0  -x;           *  T1  *D  ===>( y * T2 - T1 ) *D = 0
    		      //                      -y   x  0 ] 		    T2                  ( x * T2 - T0 ) *D = 0
    		      //一个点两个方程  两个点 四个方程  A × D =0  求三维点 D  对 A奇异值分解
    			cv::Mat A(4,4,CV_32F);
    			A.row(0) = xn1.at<float>(0)*Tcw1.row(2) - Tcw1.row(0);
    			A.row(1) = xn1.at<float>(1)*Tcw1.row(2) - Tcw1.row(1);
    			A.row(2) = xn2.at<float>(0)*Tcw2.row(2) - Tcw2.row(0);
    			A.row(3) = xn2.at<float>(1)*Tcw2.row(2) - Tcw2.row(1);
    
    			cv::Mat w,u,vt;
    			cv::SVD::compute(A,w,u,vt,cv::SVD::MODIFY_A| cv::SVD::FULL_UV);
    
    			x3D = vt.row(3).t();
    
    			if(x3D.at<float>(3)==0)
    			    continue;
    
    			// Euclidean coordinates
    			x3D = x3D.rowRange(0,3) / x3D.at<float>(3);//其次点坐标 除去尺度
    
    		    }
    	   //   步骤6.4:对于双目 视差角较小时 二维点 利用深度值 反投影 成 三维点
    		    else if(bStereo1 && cosParallaxStereo1<cosParallaxStereo2)// 双目 视差角 小
    		    {
    			x3D = mpCurrentKeyFrame->UnprojectStereo(idx1);// 二维点 反投影 成 三维点               
    		    }
    		    else if(bStereo2 && cosParallaxStereo2 < cosParallaxStereo1)
    		    {
    			x3D = pKF2->UnprojectStereo(idx2);
    		    }
    	       // 单目 视差角 较小时 生成不了三维点
    		    else
    			continue; //没有双目/深度 且两针视角差太小  三角测量也不合适 得不到三维点 No stereo and very low parallax
    
    		    cv::Mat x3Dt = x3D.t();
    		    
    	 // 步骤6.5:检测生成的3D点是否在相机前方
    		    //Check triangulation in front of cameras
    		    float z1 = Rcw1.row(2).dot(x3Dt) + tcw1.at<float>(2);// 只算z坐标值
    		    if(z1<= 0)
    			continue;
    		    float z2 = Rcw2.row(2).dot(x3Dt)+tcw2.at<float>(2);
    		    if(z2 <= 0)
    			continue;
    		    
             // 步骤6.6:计算3D点在当前关键帧下的重投影误差
    		    //Check reprojection error in first keyframe
    		    const float &sigmaSquare1 = mpCurrentKeyFrame->mvLevelSigma2[kp1.octave];//误差 分布参数
    		    const float x1 = Rcw1.row(0).dot(x3Dt) + tcw1.at<float>(0);//相机归一化坐标
    		    const float y1 = Rcw1.row(1).dot(x3Dt) + tcw1.at<float>(1);
    		    const float invz1 = 1.0/z1;
    		    if(!bStereo1)
    		    {// 单目
    			float u1 = fx1*x1*invz1 + cx1;//像素坐标
    			float v1 = fy1*y1*invz1 + cy1;
    			float errX1 = u1 - kp1.pt.x;
    			float errY1 = v1 - kp1.pt.y;
    			if((errX1*errX1+errY1*errY1) > 5.991*sigmaSquare1)
    			    continue;//投影误差过大 跳过
    		    }
    		    else
    		    {// 双目 / 深度 相机   有右图像匹配点横坐标差值
    			float u1 = fx1*x1*invz1+cx1;
    			float u1_r = u1 - mpCurrentKeyFrame->mbf * invz1;//左图像坐标值 - 视差 =   右图像匹配点横坐标 
    			float v1 = fy1*y1*invz1+cy1;
    			float errX1 = u1 - kp1.pt.x;
    			float errY1 = v1 - kp1.pt.y;
    			float errX1_r = u1_r - kp1_ur;
    			// 基于卡方检验计算出的阈值(假设测量有一个一个像素的偏差)
    			if((errX1*errX1 + errY1*errY1 + errX1_r*errX1_r) > 7.8*sigmaSquare1)
    			    continue;//投影误差过大 跳过
    		    }
             // 步骤6.7:计算3D点在 邻接关键帧 下的重投影误差
    		    //Check reprojection error in second keyframe
    		    const float sigmaSquare2 = pKF2->mvLevelSigma2[kp2.octave];
    		    const float x2 = Rcw2.row(0).dot(x3Dt)+tcw2.at<float>(0);
    		    const float y2 = Rcw2.row(1).dot(x3Dt)+tcw2.at<float>(1);
    		    const float invz2 = 1.0/z2;
    		    if(!bStereo2)
    		    {// 单目
    			float u2 = fx2*x2*invz2+cx2;
    			float v2 = fy2*y2*invz2+cy2;
    			float errX2 = u2 - kp2.pt.x;
    			float errY2 = v2 - kp2.pt.y;
    			if((errX2*errX2+errY2*errY2)>5.991*sigmaSquare2)
    			    continue;//投影误差过大 跳过
    		    }
    		    else
    		    {// 双目 / 深度 相机   有右图像匹配点横坐标差值
    			float u2 = fx2*x2*invz2+cx2;
    			float u2_r = u2 - mpCurrentKeyFrame->mbf*invz2;//左图像坐标值 - 视差 =   右图像匹配点横坐标 
    			float v2 = fy2*y2*invz2+cy2;
    			float errX2 = u2 - kp2.pt.x;
    			float errY2 = v2 - kp2.pt.y;
    			float errX2_r = u2_r - kp2_ur;
    			if((errX2*errX2+errY2*errY2+errX2_r*errX2_r)>7.8*sigmaSquare2)
    			    continue;//投影误差过大 跳过
    		    }
    		    
               // 步骤6.8:检查尺度连续性
    		    //Check scale consistency
    		    cv::Mat normal1 = x3D-Ow1;//  世界坐标系下,3D点与相机间的向量,方向由相机指向3D点
    		    float dist1 = cv::norm(normal1);// 模长
    		    cv::Mat normal2 = x3D-Ow2;
    		    float dist2 = cv::norm(normal2);
    		    if(dist1==0 || dist2==0)
    			continue;// 模长为0 跳过
                       // ratioDist是不考虑金字塔尺度下的距离比例
    		    const float ratioDist = dist2/dist1;
    		   // 金字塔尺度因子的比例
    		    const float ratioOctave = mpCurrentKeyFrame->mvScaleFactors[kp1.octave] / pKF2->mvScaleFactors[kp2.octave];
    		    /*if(fabs(ratioDist-ratioOctave)>ratioFactor)
    			continue;*/
    		    // 深度比值和 两幅图像下的金字塔层级比值应该相差不大
    		    // |ratioDist/ratioOctave |<ratioFactor
    		    if(ratioDist * ratioFactor<ratioOctave || ratioDist > ratioOctave*ratioFactor)
    			continue;
    		    
    	  // 步骤6.9:三角化生成3D点成功,构造成地图点 MapPoint
    		    // Triangulation is succesfull
    		    MapPoint* pMP = new MapPoint(x3D,mpCurrentKeyFrame,mpMap);
    		    
    
                // 步骤6.9:为该MapPoint添加属性:
    	       // a.观测到该MapPoint的关键帧 
    		    pMP->AddObservation(mpCurrentKeyFrame,idx1); // 地图点 添加观测帧  
    		    pMP->AddObservation(pKF2,idx2);// 
    		    mpCurrentKeyFrame->AddMapPoint(pMP,idx1);// 关键帧 添加地图点
    		    pKF2->AddMapPoint(pMP,idx2);
                  // b.该MapPoint的描述子
    		    pMP->ComputeDistinctiveDescriptors();
                   // c.该MapPoint的平均观测方向和深度范围
    		    pMP->UpdateNormalAndDepth();
                   // d.地图添加地图点
    		    mpMap->AddMapPoint(pMP);
    	
    	
                // 步骤6.10:将新产生的点放入检测队列 mlpRecentAddedMapPoints
                      // 这些MapPoints都会经过MapPointCulling函数的检验
    		    mlpRecentAddedMapPoints.push_back(pMP);
    
    		    nnew++;
    		}
    	    }
    	}
    	
    
    /**
     * @brief     检查并融合 当前关键帧 与 相邻帧(一级二级相邻帧)重复的地图点 MapPoints
     * 步骤1:获得当前关键帧在covisibility帧连接图中权重排名前nn的一级邻接关键帧(按观测到当前帧地图点次数选取)
     * 步骤2:获得当前关键帧在 其一级相邻帧 在 covisibility图中权重排名前5 的二级邻接关键帧
     * 步骤3:将当前帧的 地图点MapPoints 分别与 其一级二级相邻帧的 地图点 MapPoints 进行融合(保留观测次数最高的)
     * 步骤4:找到一级二级相邻帧所有的地图点MapPoints 与当前帧 的  地图点MapPoints 进行融合
     * 步骤5:更新当前帧 地图点 MapPoints 的描述子,深度,观测主方向等属性
     * 步骤5:更新当前 与其它帧的连接关系 (  观测到互相的地图点的次数等信息 )
     * @return  无
     */	
    	void LocalMapping::SearchInNeighbors()
    	{
    	    // Retrieve neighbor keyframes
     // 步骤1:获得当前关键帧在covisibility图中权重排名前nn的一级邻接关键帧
              // 找到当前帧一级相邻与二级相邻关键帧
    	    int nn = 10;
    	    if(mbMonocular)
    		nn=20;//单目 多找一些	
    	   // 一级相邻	
    	    const vector<KeyFrame*> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);
    	    vector<KeyFrame*> vpTargetKFs;// 最后合格的一级二级相邻关键帧
    	    // 遍历每一个 一级相邻帧
    	    for(vector<KeyFrame*>::const_iterator vit=vpNeighKFs.begin(), vend=vpNeighKFs.end(); vit!=vend; vit++)
    	    {
    		KeyFrame* pKFi = *vit;// 一级相邻关键帧
    		if(pKFi->isBad() || pKFi->mnFuseTargetForKF == mpCurrentKeyFrame->mnId)//坏帧  或者 已经加入过
    		    continue;// 跳过
    		vpTargetKFs.push_back(pKFi);// 加入 最后合格的相邻关键帧
    		pKFi->mnFuseTargetForKF = mpCurrentKeyFrame->mnId;// 已经做过相邻匹配  标记已经加入
    		
     // 步骤2:获得当前关键帧在 其一级相邻帧的  covisibility图中权重排名前5的二级邻接关键帧
    	        // 二级相邻	
    		// Extend to some second neighbors
    		const vector<KeyFrame*> vpSecondNeighKFs = pKFi->GetBestCovisibilityKeyFrames(5);
    		// 遍历每一个 二级相邻帧
    		for(vector<KeyFrame*>::const_iterator vit2=vpSecondNeighKFs.begin(), vend2=vpSecondNeighKFs.end(); vit2!=vend2; vit2++)
    		{
    		    KeyFrame* pKFi2 = *vit2;// 二级相邻关键帧
    		    if(pKFi2->isBad() || pKFi2->mnFuseTargetForKF==mpCurrentKeyFrame->mnId || pKFi2->mnId==mpCurrentKeyFrame->mnId)
    			continue;// 二级相邻关键帧是坏帧 在一级时已经加入 或者 又找回来了找到当前帧了 跳过
    		    vpTargetKFs.push_back(pKFi2);
    		}
    	    }
    
    // 步骤3:将当前帧的 地图点MapPoints 分别与 其一级二级相邻帧的 地图点 MapPoints 进行融合
    	    // Search matches by projection from current KF in target KFs
    	    ORBmatcher matcher;
    	    vector<MapPoint*> vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();//与当前帧 匹配的地图点
    	    // vector<KeyFrame*>::iterator
    	    for(auto  vit=vpTargetKFs.begin(), vend=vpTargetKFs.end(); vit!=vend; vit++)
    	    {
    		KeyFrame* pKFi = *vit;//其一级二级相邻帧
    		// 投影当前帧的MapPoints到相邻关键帧pKFi中,在附加区域搜索匹配关键点,并判断是否有重复的MapPoints
    		// 1.如果MapPoint能匹配关键帧的特征点,并且该点有对应的MapPoint,那么将两个MapPoint合并(选择观测数多的)
    		// 2.如果MapPoint能匹配关键帧的特征点,并且该点没有对应的MapPoint,那么为该点添加MapPoint		
    		matcher.Fuse(pKFi,vpMapPointMatches);
    	    }
    	    
    	    
    // 步骤4:将一级二级相邻帧所有的地图点MapPoints 与当前帧(的MapPoints)进行融合
                // 遍历每一个一级邻接和二级邻接关键帧 找到所有的地图点
    	    // Search matches by projection from target KFs in current KF
    	    // 用于存储一级邻接和二级邻接关键帧所有MapPoints的集合
    	    vector<MapPoint*> vpFuseCandidates;// 一级二级相邻帧所有地图点
    	    vpFuseCandidates.reserve(vpTargetKFs.size() * vpMapPointMatches.size());// 帧数量 × 每一帧地图点数量
                // vector<KeyFrame*>::iterator
    	    for(auto vitKF=vpTargetKFs.begin(), vendKF=vpTargetKFs.end(); vitKF!=vendKF; vitKF++)
    	    {
    		KeyFrame* pKFi = *vitKF;//其一级二级相邻帧
    		vector<MapPoint*> vpMapPointsKFi = pKFi->GetMapPointMatches();//地图点
    		
    		// vector<MapPoint*>::iterator
    		for(auto vitMP=vpMapPointsKFi.begin(), vendMP=vpMapPointsKFi.end(); vitMP!=vendMP; vitMP++)
    		{
    		    MapPoint* pMP = *vitMP;//  一级二级相邻帧 的每一个地图点
    		    if(!pMP)
    			continue;
    		    if(pMP->isBad() || pMP->mnFuseCandidateForKF == mpCurrentKeyFrame->mnId)
    			continue;
    		    // 加入集合,并标记 已经加入
    		    pMP->mnFuseCandidateForKF = mpCurrentKeyFrame->mnId; //标记 已经加
    		    vpFuseCandidates.push_back(pMP); // 加入 一级二级相邻帧 地图点 集合
    		}
    	    }
    	    
                //一级二级相邻帧 所有的 地图点 与当前帧 融合 
                		// 投影 地图点MapPoints到当前帧上,在附加区域搜索匹配关键点,并判断是否有重复的地图点
    		        // 1.如果MapPoint能匹配当前帧的特征点,并且该点有对应的MapPoint,那么将两个MapPoint合并(选择观测数多的)
    		        // 2.如果MapPoint能匹配当前帧的特征点,并且该点没有对应的MapPoint,那么为该点添加MapPoint
    	    matcher.Fuse(mpCurrentKeyFrame,vpFuseCandidates);
    
    // 步骤5:更新当前帧MapPoints的描述子,深度,观测主方向等属性
    	    // Update points
    	    vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();//当前帧 所有的 匹配地图点
    	    for(size_t i=0, iend=vpMapPointMatches.size(); i<iend; i++)
    	    {
    		MapPoint* pMP=vpMapPointMatches[i];//当前帧 每个关键点匹配的地图点
    		if(pMP)//存在
    		{
    		    if(!pMP->isBad())//非 坏点
    		    {
    			pMP->ComputeDistinctiveDescriptors();// 更新 地图点的描述子(在所有观测在的描述子中选出最好的描述子)
    			pMP->UpdateNormalAndDepth();          // 更新平均观测方向和观测距离
    		    }
    		}
    	    }
    // 步骤5:更新当前帧的MapPoints后 更新与其它帧的连接关系 观测到互相的地图点的次数等信息
                // 更新covisibility图
    	    // Update connections in covisibility graph
    	    mpCurrentKeyFrame->UpdateConnections();
    	}
    	
    
    /**
     * @brief    关键帧剔除
     *  在Covisibility Graph 关键帧连接图 中的关键帧,
     *  其90%以上的地图点MapPoints能被其他关键帧(至少3个)观测到,
     *  则认为该关键帧为冗余关键帧。
     * @param  pKF1 关键帧1
     * @param  pKF2 关键帧2
     * @return 两个关键帧之间的基本矩阵 F
     */	
    void LocalMapping::KeyFrameCulling()
    	{
    	    // Check redundant keyframes (only local keyframes)
    	    // A keyframe is considered redundant if the 90% of the MapPoints it sees, are seen
    	    // in at least other 3 keyframes (in the same or finer scale)
    	    // We only consider close stereo points
    	  
    // 步骤1:根据Covisibility Graph 关键帧连接 图提取当前帧的 所有共视关键帧(关联帧)	  
    	    vector<KeyFrame*> vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();
    	    
                // vector<KeyFrame*>::iterator
               // 对所有的局部关键帧进行遍历	    
    	    for(auto  vit=vpLocalKeyFrames.begin(), vend=vpLocalKeyFrames.end(); vit!=vend; vit++)
    	    {
    		KeyFrame* pKF = *vit;// 当前帧的每一个 局部关联帧
    		if(pKF->mnId == 0)//第一帧关键帧为 初始化世界关键帧 跳过
    		    continue;
    		
    // 步骤2:提取每个共视关键帧的 地图点 MapPoints		
    		const vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();// 局部关联帧 匹配的 地图点
    
    		int nObs = 3;
    		const int thObs=nObs; //3
    		int nRedundantObservations=0;
    		int nMPs=0;
    		
    // 步骤3:遍历该局部关键帧的MapPoints,判断是否90%以上的MapPoints能被其它关键帧(至少3个)观测到		
    		for(size_t i=0, iend=vpMapPoints.size(); i<iend; i++)
    		{
    		    MapPoint* pMP = vpMapPoints[i];// 该局部关键帧的 地图点 MapPoints
    		    if(pMP)
    		    {
    			if(!pMP->isBad())
    			{
    			    if(!mbMonocular)// 双目/深度
    			    {  // 对于双目,仅考虑近处的MapPoints,不超过mbf * 35 / fx
    				if(pKF->mvDepth[i] > pKF->mThDepth || pKF->mvDepth[i] < 0)
    				    continue;
    			    }
    
    			    nMPs++;
    			    // 地图点 MapPoints 至少被三个关键帧观测到
    			    if(pMP->Observations() > thObs)// 观测帧个数 > 3
    			    {
    				const int &scaleLevel = pKF->mvKeysUn[i].octave;// 金字塔层数
    				const map<KeyFrame*, size_t> observations = pMP->GetObservations();// 局部 观测关键帧地图
    				int nObs=0;
    				for(map<KeyFrame*, size_t>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)
    				{
    				    KeyFrame* pKFi = mit->first;
    				    if(pKFi==pKF)// 跳过 原地图点的帧
    					continue;
    				    const int &scaleLeveli = pKFi->mvKeysUn[mit->second].octave;// 金字塔层数
    				    
                                       // 尺度约束,要求MapPoint在该局部关键帧的特征尺度大于(或近似于)其它关键帧的特征尺度
    				    if(scaleLeveli <= scaleLevel+1)
    				    {
    					nObs++;
    					if(nObs >= thObs)
    					    break;
    				    }
    				}
    				if(nObs >= thObs)
    				{// 该MapPoint至少被三个关键帧观测到
    				    nRedundantObservations++;
    				}
    			    }
    			}
    		    }
    		}  
    		
     // 步骤4:该局部关键帧90%以上的MapPoints能被其它关键帧(至少3个)观测到,则认为是冗余关键帧
    		if(nRedundantObservations > 0.9*nMPs)
    		    pKF->SetBadFlag();
    	    }
    	}
    	
    /**
     * @brief    根据两关键帧的姿态计算两个关键帧之间的基本矩阵 
     *                 F =  inv(K1 转置)*E*inv(K2) = inv(K1 转置)*t叉乘R*inv(K2)
     * @param  pKF1 关键帧1
     * @param  pKF2 关键帧2
     * @return 两个关键帧之间的基本矩阵 F
     */
    	cv::Mat LocalMapping::ComputeF12(KeyFrame *&pKF1, KeyFrame *&pKF2)
    	{
        // Essential Matrix: t12叉乘R12
        // Fundamental Matrix: inv(K1转置)*E*inv(K2)	  
    	    cv::Mat R1w = pKF1->GetRotation();   // Rc1w
    	    cv::Mat t1w = pKF1->GetTranslation();
    	    cv::Mat R2w = pKF2->GetRotation();   // Rc2w
    	    cv::Mat t2w = pKF2->GetTranslation();// t c2 w
    
    	    cv::Mat R12 = R1w*R2w.t();// R12 =Rc1w *  Rwc2 // c2 -->w --->c1
    	    cv::Mat t12 = -R12*t2w + t1w; // tw2  + t1w    // c2 -->w --->c1
    
    	    // t12 的叉乘矩阵
    	    cv::Mat t12x = SkewSymmetricMatrix(t12);
    
    	    const cv::Mat &K1 = pKF1->mK;
    	    const cv::Mat &K2 = pKF2->mK;
    
    	    return K1.t().inv()*t12x*R12*K2.inv();
    	}
    
    /**
     * @brief    请求停止局部建图线程  设置停止标志
     * @return 无
     */	
    	void LocalMapping::RequestStop()
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    	    mbStopRequested = true;//局部建图 请求停止
    	    unique_lock<mutex> lock2(mMutexNewKFs);
    	    mbAbortBA = true;//停止BA 优化
    	}
    	
    /**
     * @brief    停止局部建图线程  设置停止标志
     * @return 无
     */	
    	bool LocalMapping::Stop()
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    	    if(mbStopRequested && !mbNotStop)
    	    {
    		mbStopped = true;
    		cout << "局部建图停止 Local Mapping STOP" << endl;
    		return true;
    	    }
    	    return false;
    	}
    	
    /**
     * @brief    检查局部建图线程 是否停止
     * @return 是否停止标志
     */	
    	bool LocalMapping::isStopped()
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    	    return mbStopped;
    	}
    	
    /**
     * @brief   返回 请求停止局部建图线程   标志
     * @return 返回 请求停止局部建图线程   标志
     */	
    	bool LocalMapping::stopRequested()
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    	    return mbStopRequested;
    	}
    	
    /**
     * @brief    释放局部建图线程  
     * @return  无
     */	
    	void LocalMapping::Release()
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    	    unique_lock<mutex> lock2(mMutexFinish);
    	    if(mbFinished)
    		return;
    	    mbStopped = false;
    	    mbStopRequested = false;
    	    // list<KeyFrame*>::iterator
    	    for(auto lit = mlNewKeyFrames.begin(), lend=mlNewKeyFrames.end(); lit!=lend; lit++)
    		delete *lit;//删除关键帧
    	    mlNewKeyFrames.clear();
    
    	    cout << "局部建图释放 Local Mapping RELEASE" << endl;
    	}
    
    /**
     * @brief    返回 可以接收新的一个关键帧标志
     * @return 是否 可以接收新的一个关键帧
     */		
    	bool LocalMapping::AcceptKeyFrames()
    	{
    	    unique_lock<mutex> lock(mMutexAccept);
    	    return mbAcceptKeyFrames;
    	}
    	
    /**
     * @brief    设置 可以接收新的一个关键帧标志
     * @return 无
     */
    	void LocalMapping::SetAcceptKeyFrames(bool flag)
    	{
    	    unique_lock<mutex> lock(mMutexAccept);
    	    mbAcceptKeyFrames=flag;
    	}
    	
    /**
     * @brief    设置不要停止标志 
     * @return  成功与否 
     */
    	bool LocalMapping::SetNotStop(bool flag)
    	{
    	    unique_lock<mutex> lock(mMutexStop);
    
    	    if(flag && mbStopped)//  在已经停止的情况下 设置不要停止   错误
    		return false;
    
    	    mbNotStop = flag;
    
    	    return true;
    	}
    /**
     * @brief    停止 全局优化 BA
     * @return 是否 可以接收新的一个关键帧
     */
    	void LocalMapping::InterruptBA()
    	{
    	    mbAbortBA = true;
    	}
    
    	
    	
    /**
     * @brief   计算向量的 叉乘矩阵     变叉乘为 矩阵乘
     * @return 该向量的叉乘矩阵
     */	
    	cv::Mat LocalMapping::SkewSymmetricMatrix(const cv::Mat &v)
    	{
    // 向量 t=(a1 a2 a3)	t叉乘A
    // 等于 向量t的叉乘矩阵 * A
    //  t的叉乘矩阵
    //|0     -a3  a2 |
    //|a3     0   -a1|
    //|-a2   a1    0 |
    	    return (cv::Mat_<float>(3,3) <<             0, -v.at<float>(2), v.at<float>(1),
    		    v.at<float>(2),               0,-v.at<float>(0),
    		    -v.at<float>(1),  v.at<float>(0),              0);
    	}
    	
    /**
     * @brief    请求重置
     * @return  无
     */
    	void LocalMapping::RequestReset()
    	{
    	    {
    		unique_lock<mutex> lock(mMutexReset);
    		mbResetRequested = true;
    	    }
    
    	    while(1)
    	    {
    		{
    		    unique_lock<mutex> lock2(mMutexReset);
    		    if(!mbResetRequested)
    			break;
    		}
    		usleep(3000);
    	    }
    	}
    	
    /**
     * @brief    重置线程
     * @return  无
     */
    	void LocalMapping::ResetIfRequested()
    	{
    	    unique_lock<mutex> lock(mMutexReset);
    	    if(mbResetRequested)
    	    {
    		mlNewKeyFrames.clear();
    		mlpRecentAddedMapPoints.clear();
    		mbResetRequested=false;
    	    }
    	}
    	
    	void LocalMapping::RequestFinish()
    	{
    	    unique_lock<mutex> lock(mMutexFinish);
    	    mbFinishRequested = true;
    	}
    
    	bool LocalMapping::CheckFinish()
    	{
    	    unique_lock<mutex> lock(mMutexFinish);
    	    return mbFinishRequested;
    	}
    
    	void LocalMapping::SetFinish()
    	{
    	    unique_lock<mutex> lock(mMutexFinish);
    	    mbFinished = true;    
    	    unique_lock<mutex> lock2(mMutexStop);
    	    mbStopped = true;
    	}
    
    	bool LocalMapping::isFinished()
    	{
    	    unique_lock<mutex> lock(mMutexFinish);
    	    return mbFinished;
    	}
    
    } //namespace ORB_SLAM
    

    展开全文
  • YTAnimation iOS 动画主要是指 Core Animation 框架, Core Animation是 iOS 和 OS X 平台上负责图形渲染与动画基础框架。...本文主要总结下平时常用动画, 如: 基础动画(CABasicAnimation)、关键帧动画(CAKeyframeAn
  • 时间轴和帧 今天我们这个视频的主要目就是一起来了解和认识一下Flash的时间轴和帧 涉及到帧的类型 时间轴的概述 涉及的真的类型及不同帧的作用时间面板 现在开始学习 编辑帧就是对flash中帧的编辑如选择删除移动改变...
  • 数据链路层主要解决由若干主机组成本地网络通讯问题,主机寻址 和 信道复用 思想在其中发挥着关键作用。 数据链路层有一个非常重要协议—— 以太网协议 。接下来,我们一起来揭开它神秘面纱! ...

    上一小节,我们通过一个虚构的协议,初步认识了数据链路层的工作原理。数据链路层主要解决由若干主机组成的本地网络的通讯问题,主机寻址信道复用 思想在其中发挥着关键作用。

    数据链路层有一个非常重要的协议—— 以太网协议 。接下来,我们一起来揭开它的神秘面纱!

    使用以太网协议进行通信的主机间,必须通过某种介质直接相连。通信介质可以是真实的物理设备,如网线、网卡等;也可以是通过虚拟化技术实现的虚拟设备。

    以太网帧

    在以太网中,数据通信的基本单位是 以太网帧 ( frame ),由 头部 ( header )、数据 ( data )以及 校验和 ( checksum )三部分构成:

    请注意,这图中的单位为字节,而不是比特了。

    头部

    以太网帧头部包含 3 个字段,依次是:

    • 目的地址 ,长度是 6 字节,用于标记数据由哪台机器接收;
    • 源地址 ,长度也是 6 字节,用于标记数据由哪台机器发送;
    • 类型 ,长度是 2 字节,用于标记数据该如何处理, 0x0800 表示该帧数据是一个 IP 包(后续章节介绍)。

    除了字段长度有所拓展之外,以太网帧跟我们虚构出来的协议如出一辙。对了,我们注意到一点小差异——在以太网帧中, 目的地址 放在最前面。 这其中有什么特殊考虑吗?

    确实是有的。接收方收到一个以太网帧后,最先处理 目的地址 字段。如果发现该帧不是发给自己的,后面的字段以及数据就不需要处理了。基础网络协议影响方方面面,设计时处理效率也是一个非常重要的考量。

    数据

    数据 可以是任何需要发送的信息,长度可变, 461500 字节均可。

    上层协议报文,例如 IP 包,可以作为数据封装在以太网帧中,在数据链路层中传输。因此,数据还有另一个更形象的称谓,即 负荷 ( payload )。请自行脑补数据 搭载 在以太网帧这个交通工具上旅行的画面。

    校验和

    由于物理信号可能受到环境的干扰,网络设备传输的比特流可能会出错。一个以太网帧从一台主机传输到另一台主机的过程中,也可能因各种因素而出错。那么当主机收到以太网帧时,如何确定它是完好无损的呢?

    答案是: 校验和 。我们可以用诸如 循环冗余校验 ( CRC )算法,为以太网帧计算校验和。如果以太网帧在传输的过程出错,校验和将发生改变。

    注意到,以太网帧最后面有一个 4 字节字段,用于保存校验和。发送者负责为每个以太网帧计算校验和,并将计算结果填写在校验和字段中;接收者接到以太网帧后,重新计算校验和并与校验和字段进行对比;如果两个校验和不一致,说明该帧在传输时出错了。

    【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

    展开全文
  • 硬件当中,镜头质量好坏起着关键作用。   机器视觉大师提供实时图像帧的清晰度评估功能,通过清晰度曲线,可以看到当前视场图像 清晰度的变化和实际值。这个功能主要通过调用RVB里面清晰度函数实现。很不幸的是...
  • 3 TCP/IP四层介绍及各层的作用 3.1 网络接口层: 这是TCP/IP最低层,负责接收IP数据包并...是TCP/IP协议族中非常关键的一层,主要定义了IP地址格式,从而能够使得不同应用类型数据在Internet上通畅地传输,...
  • QOS几个关键指标

    千次阅读 2012-04-25 09:48:43
    QoS的关键指标主要包括:可用性、吞吐量、时延、时延变化(包括抖动和漂移)和丢失。下面详细叙述。 可用性  是当用户需要时网络即能工作时间百分比。可用性主要是设备可靠性和网络存活性相结合结果。对它起...
  • 直播市场如火如荼,越来越多人涌入到直播这个浪潮中来,想要搭建自己视频直播平台。...我们可以通过后台直播设置对主播推流参数做统一默认设置,推流参数主要包括比特率、关键帧间隔、FPS、品质大小、...
  • 论文的主要 研究成果和创新点是: (1) 在星载目标探测系统的波段选择分析方面,对目标、有云和无云情况下地-气背景 的红外辐射特性进行了详细分析和仿真计算,设计了基于最优信杂比的波段选择方法,针对 目标的早期...
  • LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中;处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。主要工作在于维护局部地图,也就是SLAM中Mapping。 1. 处理新关键帧...
  • ORB-SLAM2——Trackin线程

    2019-11-13 19:27:12
    Tracking整体流程图 作为ORB-SLAM2里三大线程中的第一个,Tracking线程接收...Tracking模块主要作用: 单目地图的初始化 当前帧的位姿估计 当前帧的局部地图跟踪 生成候选关键帧 System::TrackMon...
  • 1 简介 ...MediaFormat:用于存储视频轨或音频轨信息(MIME、时长、帧率、比特率、关键帧间隔、视频宽高等); MediaExtractor:媒体分离器,用于分离音频和视频数据,并且能够遍历帧数据; Me.
  • Local Mapping

    2017-09-10 22:32:05
    LocalMapping作用是将Tracking中送来的关键帧放在mlNewKeyFrame列表中;处理新关键帧,地图点检查剔除,生成新地图点,Local BA,关键帧剔除。主要工作在于维护局部地图,也就是SLAM中Mapping。 我话就直接从...
  • 8: Ctrl+A全选, 然后在帧上, 右键-剪切帧, 然后按Ctrl+F8 新建元件, 命名为"原型", 确定, 然后在第一帧右键粘贴帧.(*此次操作的目的:学习帧的移动, 并且要以原型为模版进行后面的单个动作的编辑,同样的帧的移动也是...
  • 本文对基于特征的视觉 SLAM 方法和直接的 SLAM 方法,视觉 SLAM 的主要标志性成果,SLAM 的主要研究实验室进行了介绍,并介绍了 SIFT,SURF,ORB特征的检测与匹配,关键帧选择方法,并对消除累积误...
  • flash shiti

    2014-03-14 10:32:41
    在时间线上插入一个新空白关键帧 D. 清楚当前位置上或选定关键在时间线上插入一个新关键 20.Flash 菜单Modify→Group快捷操作是? A. Ctrl+G B. Ctrl+Shift+G C. Ctrl+B D. Ctrl+Shift+P 21.Flash中...
  • 并且TCP/IP的帧传输方式,使得带宽利用率不高,一般情况下,NAS设备数据传输带宽仅能达到9-15MB/s,另外,NAS是在TCP/IP技术上,以文件为单元进行传输,TCP/IP在传输时丢包,也限制了NAS速度,甚至威胁到...
  • 一般动画用到几个属性: animation:name duration timing-function delay iteration-count direction 其实这里主要介绍是 timing-function steps(num,...2.它第一个参数 作用于每两个关键帧之间,把他分...
  • 上七章节: 图层树 图层寄宿图 图层几何学 图层视觉效果 ...这篇随笔主要介绍有关图层显式动画。...能对一些属性做指定自定义动画,或者创建非线性动画 ...属性动画作用于图层某个单一属性,并...2.关键帧 ...
  • 1.简介Core Animation(核心动画)使用它能做出非常炫丽...主要提供四种动画:基本动画、关键帧动画、动画组、转场动画 注意:核心动画只有动画,位置是不会变得。 思维导图: 2.动画类型1.基础动画(CABasicAnim
  • 静态路由

    2018-12-15 22:25:30
    路由器的作用: 划分广播域---不同接口就是不同的广播域 实现不同网络间的互联 为它所称再的数据做路径的选择  路由器之所以在互连网络中...路由器的主要工作就是为经过路由器的每个数据寻找一条最佳传输路...

空空如也

空空如也

1 2 3 4
收藏数 63
精华内容 25
关键字:

关键帧的主要作用