# 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())中更新