ORBSLAM2的地图点MapPoint和地图Map
MapPoint
MapPoint是一个三维点,即为空间中的特征点,在单目SLAM中通过对多帧图像的特征点进行三角化得到。
在ORBSLAM2中,MapPoint类还增加一些额外信息负责维护地图点和加速匹配特征点。
地图点描述子
每帧图像会计算出特征点,当不同帧特征点匹配后可以三角化出地图点,由于后续图像帧也可能观测到同样的地图点,所以需要为每个地图点计算出地图点描述子,以供后续帧匹配。
在ORBSLAM2中,通过计算匹配到该地图点的特征点描述子的的中值作为地图点的描述子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
void MapPoint::ComputeDistinctiveDescriptors() {
...
const size_t N = vDescriptors.size();
std::vector<std::vector<float>> Distances; Distances.resize(N, vector<float>(N, 0)); for (size_t i = 0; i < N; i++) { Distances[i][i] = 0; for (size_t j = i + 1; j < N; j++) { int distij = ORBmatcher::DescriptorDistance(vDescriptors[i], vDescriptors[j]); Distances[i][j] = distij; Distances[j][i] = distij; } }
int BestMedian = INT_MAX; int BestIdx = 0; for (size_t i = 0; i < N; i++) { vector<int> vDists(Distances[i].begin(), Distances[i].end()); sort(vDists.begin(), vDists.end());
int median = vDists[0.5 * (N - 1)];
if (median < BestMedian) { BestMedian = median; BestIdx = i; } }
{ unique_lock<mutex> lock(mMutexFeatures); mDescriptor = vDescriptors[BestIdx].clone(); } }
|
观测距离范围与反投影金字塔层级
观测距离范围
由于ORBSLAM2使用了尺度不变的描述子,而尺度不变性通常由图像金字塔来处理,当一个特征只有在放大时才能被检测到(即在图像金字塔下层),说明该特征点对应的地图点较远。
由于每个地图点存在一个参考关键帧,ORBSLAM2通过这个参考关键帧来计算这个地图点大致会在什么深度范围内被观测到。地图点到参考帧相机中心距离,乘上参考帧中描述子获取金字塔放大尺度得到最大距离mfMaxDistance,而最大距离除以整个金字塔最高层的放大尺度得到最小距离mfMinDistance。
1 2 3 4 5 6 7 8 9 10 11
| void MapPoint::UpdateNormalAndDepth() { ...
{ mfMaxDistance = dist * levelScaleFactor; mfMinDistance = mfMaxDistance / pRefKF->mvScaleFactors[nLevels - 1]; } }
|
最终的范围是。通过这两个距离可以粗略的判断一个地图点可不可以被某一帧检测到,对应于bool Frame::isInFrustum(MapPoint *pMP, float viewingCosLimit)
函数。
这里计算出来的深度本质是一个等效,因为图像金字塔层级是有限的(规定第0层最小,第7层最大),所以放大倍率也是有限的。
比如,某个地图点距离参考帧距离为1m,在第6层金字塔被检测到,那么最大距离为,就是说,相机在距离该地图点大约2.98m时拍摄的图像帧,在原始图像(第0层金字塔)提取出的该地图点对应的特征点,与相机距离该地图点1米时在最深层金字塔图像帧(第7层金字塔)提取的该地图点对应的特征点相似。而一旦超过这个距离,该地图点就会被投影到第7层以上的金字塔,而ORBSLAM2最大只有第7层金字塔,所以这个地图点就极大概率不会被提取到。
最小距离也是类似的,不存在第-1层金字塔,当距离过近时也同样无法在金字塔里被提取出来。
反投影金字塔层级
由于ORBSLAM2会通过对地图点反投影来扩大匹配,可以通过计算地图点--帧光心的距离来估计这个地图点大致会在那一层被检测到来缩小搜索范围。计算公式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
int MapPoint::PredictScale(const float ¤tDist, KeyFrame *pKF) { float ratio; { unique_lock<mutex> lock(mMutexPos); ratio = mfMaxDistance / currentDist; }
int nScale = ceil(log(ratio) / pKF->mfLogScaleFactor); if (nScale < 0) nScale = 0; else if (nScale >= pKF->mnScaleLevels) nScale = pKF->mnScaleLevels - 1;
return nScale; }
|
平均观测方向
观测到所有该地图点的关键帧的相机光心到地图点的方向向量的平均值,主要用于判断地图点是否在视野内,当地图点方向与相机光心方向夹角小于一定阈值时,认为地图点在视野内,对应于bool Frame::isInFrustum(MapPoint *pMP, float viewingCosLimit)
函数。
其物理意义描述了特征点的朝向,比如桌角的特征点,只有面对桌角时才能看到,所以这个方向向量就是桌角的方向,而背对桌角时就看不到这个特征点。
被观测到次数与应该被观测到次数
这两个数据主要用于判断一个地图点的质量,用于地图点的剔除。
- 被观测到次数
mnFound
即所有帧(不止关键帧)存在特征点匹配到该地图点的次数
- 应该被观测到次数
mnVisible
即所有帧(不止关键帧)应该能观测到该地图点的次数
mnFound
很好理解,每匹配成功就增加一次计数即可;而mnVisible
则是在tracking线程中调用bool Frame::isInFrustum(MapPoint *pMP, float viewingCosLimit)
来判断,这个函数会校验地图点投影深度、投影区域、地图点距离和夹角。具体调用时机与校验的地图点范围等留在tracking线程中细说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
bool Frame::isInFrustum(MapPoint *pMP, float viewingCosLimit) { ...
const cv::Mat Pc = mRcw * P + mtcw; const float &PcX = Pc.at<float>(0); const float &PcY = Pc.at<float>(1); const float &PcZ = Pc.at<float>(2);
if (PcZ < 0.0f) return false;
const float invz = 1.0f / PcZ; const float u = fx * PcX * invz + cx; const float v = fy * PcY * invz + cy; if (u < mnMinX || u > mnMaxX) return false; if (v < mnMinY || v > mnMaxY) return false;
const float maxDistance = pMP->GetMaxDistanceInvariance(); const float minDistance = pMP->GetMinDistanceInvariance(); const cv::Mat PO = P - mOw; const float dist = cv::norm(PO); if (dist < minDistance || dist > maxDistance) return false;
cv::Mat Pn = pMP->GetNormal(); const float viewCos = PO.dot(Pn) / dist; if (viewCos < viewingCosLimit) return false;
... return true; }
|
Map
Map即地图,维护了所有的地图点和关键帧,由于ORBSLAM2仅支持一张地图,所以每次只有会一个Map对象。
其本身非常简单,主要是一些增删查的接口,不再赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Map { public: vector<KeyFrame *> mvpKeyFrameOrigins;
std::mutex mMutexMapUpdate;
std::mutex mMutexPointCreation;
protected: std::set<MapPoint *> mspMapPoints;
std::set<KeyFrame *> mspKeyFrames;
std::vector<MapPoint *> mvpReferenceMapPoints;
long unsigned int mnMaxKFid;
std::mutex mMutexMap; }
|