# KeyFrame.cpp ### KeyFrame::KeyFrame() 創建關鍵幀 在[Tracking::CreateInitialMapMonocular()](#Tracking::CreateInitialMapMonocular()),[Tracking::CreateNewKeyFrame()](#Tracking::CreateNewKeyFrame())中引用 #### 定義 ``` KeyFrame::KeyFrame(Frame &F, //輸入幀 Map *pMap, KeyFrameDatabase *pKFDB) ``` #### 函式流程 1. 獲取ID 2. 將輸入幀的特徵點根據網格進行分配 3. 設置當前關鍵幀的姿態 #### 函式詳解 1. 獲取ID ``` mnId=nNextId++; ``` 2. 將輸入幀的特徵點根據網格進行分配 ``` mGrid.resize(mnGridCols); //遍歷網格列 for(int i=0; i<mnGridCols;i++) { mGrid[i].resize(mnGridRows); //遍歷網格行 for(int j=0; j<mnGridRows; j++) mGrid[i][j] = F.mGrid[i][j]; } ``` 3. 設置當前關鍵幀的姿態 ``` SetPose(F.mTcw); ``` ### KeyFrame::AddMapPoint() 將地圖點加入到當前關鍵幀中的相對印索引值中 在[Tracking::CreateInitialMapMonocular()](#Tracking::CreateInitialMapMonocular())中引用 #### 定義 ``` void KeyFrame::AddMapPoint(MapPoint *pMP, //地圖點 const size_t &idx) //地圖點在關鍵幀中的索引 ``` #### 函式流程 1. 將地圖點加入到當前關鍵幀中的相對印索引值中 #### 函式詳解 1. 將地圖點加入到當前關鍵幀中的相對印索引值中 ``` unique_lock<mutex> lock(mMutexFeatures); mvpMapPoints[idx]=pMP; ``` ### KeyFrame::ComputeSceneMedianDepth() 對當前關鍵幀下所有地圖點的深度進行由小到大的排序,返回距離頭部其中1/q處的深度值作為當前場景的平均深度。 在[Tracking::CreateInitialMapMonocular()](#Tracking::CreateInitialMapMonocular()),[LocalMapping::CreateNewMapPoints()](#LocalMapping::CreateNewMapPoints())中引用 #### 定義 ``` float KeyFrame::ComputeSceneMedianDepth(const int q) //中值 ``` #### 函式流程 1. 計算變換矩陣 2. 計算所有地圖點深度 3. 計算平均深度 #### 函式詳解 1. 計算變換矩陣 ``` vector<MapPoint*> vpMapPoints; cv::Mat Tcw_; { unique_lock<mutex> lock(mMutexFeatures); unique_lock<mutex> lock2(mMutexPose); vpMapPoints = mvpMapPoints; Tcw_ = Tcw.clone(); } vector<float> vDepths; vDepths.reserve(N); // 提取深度需要轉換的數值 cv::Mat Rcw2 = Tcw_.row(2).colRange(0,3); Rcw2 = Rcw2.t(); float zcw = Tcw_.at<float>(2,3); ``` 2. 計算所有地圖點深度 ``` //遍歷每一個地圖點 for(int i=0; i<N; i++) { //如果三維點存在 if(mvpMapPoints[i]) { MapPoint* pMP = mvpMapPoints[i]; cv::Mat x3Dw = pMP->GetWorldPos(); float z = Rcw2.dot(x3Dw)+zcw; // (R*x3Dw+t)的第三行,即z vDepths.push_back(z); } } sort(vDepths.begin(),vDepths.end()); ``` 3. 計算平均深度 ``` return vDepths[(vDepths.size()-1)/q]; ``` ### KeyFrame::TrackedMapPoints() 關鍵幀觀測到的地圖點的數目 在[Tracking::CreateInitialMapMonocular()](#Tracking::CreateInitialMapMonocular()),與[Tracking::NeedNewKeyFrame()](#Tracking::NeedNewKeyFrame())中引用。 #### 定義 ``` int KeyFrame::TrackedMapPoints(const int &minObs) //最小觀測數目 ``` #### 函式流程 1. 計算有多少成功跟蹤的特徵點 #### 函式詳解 1. 計算有多少成功跟蹤的特徵點 ``` unique_lock<mutex> lock(mMutexFeatures); int nPoints=0; const bool bCheckObs = minObs>0; //遍歷當前幀中所有特徵點 for(int i=0; i<N; i++) { MapPoint* pMP = mvpMapPoints[i]; if(pMP) { if(!pMP->isBad()) { if(bCheckObs) { // 该MapPoint是一个高质量的MapPoint if(mvpMapPoints[i]->Observations()>=minObs) nPoints++; } else nPoints++; } } } return nPoints; ``` ### KeyFrame::UnprojectStereo() 在雙目與RGBD模式下將特徵點反投影到空間中,得到世界座標系下的三維點 在[Tracking::UpdateLastFrame()](#Tracking::UpdateLastFrame())中引用 #### 定義 ``` cv::Mat KeyFrame::UnprojectStereo(int i) //輸入特徵點索引 ``` #### 函式流程 1. 由2維影像投影到相機坐標系 2. 由相機坐標系到世界坐標系 #### 函式詳解 1. 由2維影像投影到相機坐標系 ``` const float z = mvDepth[i]; if(z>0) { //2維影像投影到相機坐標系 const float u = mvKeys[i].pt.x; const float v = mvKeys[i].pt.y; const float x = (u-cx)*z*invfx; const float y = (v-cy)*z*invfy; cv::Mat x3Dc = (cv::Mat_<float>(3,1) << x, y, z); ``` 2. 由相機坐標系到世界坐標系 ``` unique_lock<mutex> lock(mMutexPose); //相機坐標系到世界坐標系 return Twc.rowRange(0,3).colRange(0,3)*x3Dc+Twc.rowRange(0,3).col(3); } else return cv::Mat(); ``` ### KeyFrame::UpdateConnections() 更新關鍵幀之間的連接關係 在[Tracking::CreateInitialMapMonocular()](#Tracking::CreateInitialMapMonocular()),[LocalMapping::ProcessNewKeyFrame()](#LocalMapping::ProcessNewKeyFrame()),[LocalMapping::SearchInNeighbors()](#LocalMapping::SearchInNeighbors()),[LoopClosing::CorrectLoop()](#LoopClosing::CorrectLoop()) 中引用 #### 函式流程 1. 獲得該關鍵幀的所有地圖點 2. 通過地圖點被關鍵幀觀測來間接統計關鍵幀之間的共視程度 3. 找到對應權重最大的關鍵幀(共視程度最高的關鍵幀) 4. 如果沒有超過閾值的權重,則對權重最大的關鍵幀建立連接 5. 對滿足共視程度的關鍵幀對更新連接關係及權重(從大到小) 6. 更新生成樹的連接 #### 函式詳解 1. 獲得該關鍵幀的所有地圖點 ``` // 關鍵幀-權重,權重為其它關鍵幀與當前關鍵幀共視地圖點的個數,也稱為共視程度 map<KeyFrame*,int> KFcounter; vector<MapPoint*> vpMP; { // 獲得該關鍵幀的所有地圖點 unique_lock<mutex> lockMPs(mMutexFeatures); vpMP = mvpMapPoints; } ``` 2. 通過地圖點被關鍵幀觀測來間接統計關鍵幀之間的共視程度 ``` // 統計每一個地圖點都有多少關鍵幀與當前關鍵幀存在共視關係,統計結果放在KFcounter for(vector<MapPoint*>::iterator vit=vpMP.begin(), vend=vpMP.end(); vit!=vend; vit++) { MapPoint* pMP = *vit; if(!pMP) continue; if(pMP->isBad()) continue; // 對於每一個地圖點,observations記錄了可以觀測到該地圖點的所有關鍵幀 map<KeyFrame*,size_t> observations = pMP->GetObservations(); for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++) { // 除去自身,自己與自己不算共視 if(mit->first->mnId==mnId) continue; // 這裡的操作非常精彩! // map[key] = value,當要插入的鍵存在時,會覆蓋鍵對應的原來的值。如果鍵不存在,則添加一組鍵值對 // mit->first 是地圖點看到的關鍵幀,同一個關鍵幀看到的地圖點會累加到該關鍵幀計數 // 所以最後KFcounter 第一個參數表示某個關鍵幀,第2個參數表示該關鍵幀看到了多少當前幀的地圖點,也就是共視程度 KFcounter[mit->first]++; } } // This should not happen // 沒有共視關係,直接退出 if(KFcounter.empty()) return; // If the counter is greater than threshold add connection // In case no keyframe counter is over threshold add the one with maximum counter int nmax=0; // 記錄最高的共視程度 KeyFrame* pKFmax=NULL; // 至少有15個共視地圖點才會添加共視關係 int th = 15; ``` 3. 找到對應權重最大的關鍵幀(共視程度最高的關鍵幀) ``` // vPairs記錄與其它關鍵幀共視幀數大於th的關鍵幀 // pair<int,KeyFrame*>將關鍵幀的權重寫在前面,關鍵幀寫在後面方便後面排序 vector<pair<int,KeyFrame*> > vPairs; vPairs.reserve(KFcounter.size()); for(map<KeyFrame*,int>::iterator mit=KFcounter.begin(), mend=KFcounter.end(); mit!=mend; mit++) { if(mit->second>nmax) { nmax=mit->second; pKFmax=mit->first; } // 建立共視關係至少需要大於等於th個共視地圖點 if(mit->second>=th) { // 對應權重需要大於閾值,對這些關鍵幀建立連接 vPairs.push_back(make_pair(mit->second,mit->first)); // 對方關鍵幀也要添加這個信息 // 更新KFcounter中該關鍵幀的mConnectedKeyFrameWeights // 更新其它KeyFrame的mConnectedKeyFrameWeights,更新其它關鍵幀與當前幀的連接權重 (mit->first)->AddConnection(this,mit->second); } } ``` 4. 如果沒有超過閾值的權重,則對權重最大的關鍵幀建立連接 ``` if(vPairs.empty()) { // 如果每個關鍵幀與它共視的關鍵幀的個數都少於th, // 那就只更新與其它關鍵幀共視程度最高的關鍵幀的mConnectedKeyFrameWeights // 這是對之前th這個閾值可能過高的一個補丁 vPairs.push_back(make_pair(nmax,pKFmax)); pKFmax->AddConnection(this,nmax); } ``` 5. 對滿足共視程度的關鍵幀對更新連接關係及權重(從大到小) ``` // vPairs裡存的都是相互共視程度比較高的關鍵幀和共視權重,接下來由大到小進行排序 sort(vPairs.begin(),vPairs.end()); // sort函數默認升序排列 // 將排序後的結果分別組織成為兩種數據類型 list<KeyFrame*> lKFs; list<int> lWs; for(size_t i=0; i<vPairs.size();i++) { // push_front 後變成了從大到小順序 lKFs.push_front(vPairs[i].second); lWs.push_front(vPairs[i].first); } { unique_lock<mutex> lockCon(mMutexConnections); // mspConnectedKeyFrames = spConnectedKeyFrames; // 更新當前幀與其它關鍵幀的連接權重 // ?bug 這裡直接賦值,會把小於閾值的共視關係也放入mConnectedKeyFrameWeights,會增加計算量 // ?但後續主要用mvpOrderedConnectedKeyFrames來取共視幀,對結果沒影響 mConnectedKeyFrameWeights = KFcounter; mvpOrderedConnectedKeyFrames = vector<KeyFrame*>(lKFs.begin(),lKFs.end()); mvOrderedWeights = vector<int>(lWs.begin(), lWs.end()); ``` 6. 更新生成樹的連接 ``` if(mbFirstConnection && mnId!=0) { // 初始化該關鍵幀的父關鍵幀為共視程度最高的那個關鍵幀 mpParent = mvpOrderedConnectedKeyFrames.front(); // 建立雙向連接關係,將當前關鍵幀作為其子關鍵幀 mpParent->AddChild(this); mbFirstConnection = false; } } ``` ### KeyFrame::GetFeaturesInArea() 獲取某個特徵點的鄰域中的特徵點id,其實這個和Frame.cpp中的GetFeaturesInArea函數基本上都是一致的; 在[ORBmatcher::SearchByProjection()局部地圖點](#ORBmatcher::SearchByProjection()局部地圖點),[ORBmatcher::Fuse()](#ORBmatcher::Fuse())中引用 #### 定義 ``` vector<size_t> KeyFrame::GetFeaturesInArea(const float &x, // 特徵點坐標x const float &y, // 特徵點坐標y const float &r // 邊長(半徑) ) const ``` #### 函式流程 #### 函式詳解 ``` vector<size_t> vIndices; vIndices.reserve(N); // 計算要搜索的cell的範圍 // floor向下取整,mfGridElementWidthInv 為每個像素佔多少個格子 const int nMinCellX = max(0,(int)floor((x-mnMinX-r)*mfGridElementWidthInv)); if(nMinCellX>=mnGridCols) return vIndices; // ceil向上取整 const int nMaxCellX = min((int)mnGridCols-1,(int)ceil((x-mnMinX+r)*mfGridElementWidthInv)); if(nMaxCellX<0) return vIndices; const int nMinCellY = max(0,(int)floor((y-mnMinY-r)*mfGridElementHeightInv)); if(nMinCellY>=mnGridRows) return vIndices; const int nMaxCellY = min((int)mnGridRows-1,(int)ceil((y-mnMinY+r)*mfGridElementHeightInv)); if(nMaxCellY<0) return vIndices; // 遍歷每個cell,取出其中每個cell中的點,並且每個點都要計算是否在鄰域內 for(int ix = nMinCellX; ix<=nMaxCellX; ix++) { for(int iy = nMinCellY; iy<=nMaxCellY; iy++) { const vector<size_t> vCell = mGrid[ix][iy]; for(size_t j=0, jend=vCell.size(); j<jend; j++) { const cv::KeyPoint &kpUn = mvKeysUn[vCell[j]]; const float distx = kpUn.pt.x-x; const float disty = kpUn.pt.y-y; if(fabs(distx)<r && fabs(disty)<r) vIndices.push_back(vCell[j]); } } } return vIndices; ``` ### KeyFrame::EraseMapPointMatch()ID 根據關鍵幀ID刪除與地圖點的匹配關係 在[MapPoint::SetBadFlag()](#MapPoint::SetBadFlag())中引用 #### 定義 ``` void KeyFrame::EraseMapPointMatch(const size_t &idx) //輸入關鍵幀ID ``` #### 函式流程 1. 根據關鍵幀ID刪除與地圖點的匹配關係 #### 函式詳解 1. 根據關鍵幀ID刪除與地圖點的匹配關係 ``` unique_lock<mutex> lock(mMutexFeatures); mvpMapPoints[idx]=static_cast<MapPoint*>(NULL); ``` ### KeyFrame::GetBestCovisibilityKeyFrames() 得到與該關鍵幀連接的前N個關鍵幀(按大到小排序) 在[LocalMapping::SearchInNeighbors()](#LocalMapping::SearchInNeighbors()),[KeyFrameDatabase::DetectLoopCandidates()](#KeyFrameDatabase::DetectLoopCandidates())中引用 #### 定義 ``` vector<KeyFrame*> KeyFrame::GetBestCovisibilityKeyFrames(const int &N) //要得到幾個關鍵幀 ``` #### 函式流程 1. 回傳前N個關鍵幀 #### 函式詳解 1. 回傳前N個關鍵幀 ``` unique_lock<mutex> lock(mMutexConnections); if((int)mvpOrderedConnectedKeyFrames.size()<N) return mvpOrderedConnectedKeyFrames; else return vector<KeyFrame*>(mvpOrderedConnectedKeyFrames.begin(),mvpOrderedConnectedKeyFrames.begin()+N); ``` ### KeyFrame::GetConnectedKeyFrames() 得到與該關鍵幀連接的關鍵幀 在[KeyFrameDatabase::DetectLoopCandidates()](#KeyFrameDatabase::DetectLoopCandidates()),[LoopClosing::DetectLoop()](#LoopClosing::DetectLoop())中引用 #### 函式流程 1. 得到與該關鍵幀連接的關鍵幀 #### 函式詳解 1. 得到與該關鍵幀連接的關鍵幀 ``` unique_lock<mutex> lock(mMutexConnections); set<KeyFrame*> s; for(map<KeyFrame*,int>::iterator mit=mConnectedKeyFrameWeights.begin();mit!=mConnectedKeyFrameWeights.end();mit++) s.insert(mit->first); return s; ``` mConnectedKeyFrameWeights : 在[KeyFrame::UpdateConnections()](#KeyFrame::UpdateConnections())中更新