ORB-SLAM代码详解之SLAM.TrackMonocular
来源:互联网 发布:房产网络端口是什么 编辑:程序博客网 时间:2024/05/25 18:12
- 总述
- TrackMonocular
- mpTracker-GrabImageMonocular
- Frame
- ExtractORB
- ComputePyramid
- ComputeKeyPointsOctTree
- DistributeOctTree
- computeDescriptors
- computeOrientation
- Track
- Frame
转载请注明出处:http://blog.csdn.net/c602273091/article/details/54955663
总述
在mono_kitti.cc中运行kitti数据集的时候,初始化完了system之后,就出现了并行的多个线程:追踪、局部地图构建、闭环检测、地图显示等等。这个时候我们就需要喂入数据给整个系统,所以就是:
SLAM.TrackMonocular(im,tframe);
把传感器采集的图片以及时间戳传入,我们就可以更新系统的状态,获取新的数据,更新地图。
这个时候TrackMonocular的流程图为:
TrackMonocular
对TrackMonocular进行解释,具体如下:
cv::Mat System::TrackMonocular(const cv::Mat &im, const double ×tamp){ // 传感器不是单目摄像头、退出 if(mSensor!=MONOCULAR) { cerr << "ERROR: you called TrackMonocular but input sensor was not set to Monocular." << endl; exit(-1); } // Check mode change // 这一部分主要是对局部地图线程进行操作. // mbActivateLocalizationMode是是否停止局部地图线程 // mbDeactivateLocalizationMode是是否清空局部地图. { // 独占锁,主要是为了mbActivateLocalizationMode和mbDeactivateLocalizationMode // 不会发生混乱,没有死锁或者在临界区 unique_lock<mutex> lock(mMutexMode); // mbActivateLocalizationMode为true会关闭局部地图线程 if(mbActivateLocalizationMode) { mpLocalMapper->RequestStop(); // 设置local map的mbStopRequested,mbAbortBA为true. // 当这两个为true的时候,那么进行就会去关闭局部地图的线程 // Wait until Local Mapping has effectively stopped // mbStopped为true,说明局部地图线程已经关闭了 while(!mpLocalMapper->isStopped()) { usleep(1000); } // 局部地图关闭以后,只进行追踪的线程 // 只计算相机的位姿,没有对局部地图进行更新 // 设置mbOnlyTracking为真 mpTracker->InformOnlyTracking(true); // 执行完当前的部分之和把mbActivateLocalizationMode再置回false. // 当然这里设置mbActivateLocalizationMode为true的部分应该是没有新的关键帧和点云的时候 // 关闭线程可以使得别的线程得到更多的资源 mbActivateLocalizationMode = false; } // 如果mbDeactivateLocalizationMode是true // 设置mbActivateLocalizationMode为false // 局部地图线程就被释放, 关键帧从局部地图中删除. // mbStopped和mbStopRequested被置为false. if(mbDeactivateLocalizationMode) { mpTracker->InformOnlyTracking(false); mpLocalMapper->Release(); mbDeactivateLocalizationMode = false; } } // Check reset // 检查是否需要进行复位重置. { // 给mbReset加锁,防止被别的线程修改 unique_lock<mutex> lock(mMutexReset); if(mbReset) { // mpViwer暂停,视图停止更新 // 局部地图:mpLocalMapper和闭环检测:mpLoopClosing被停止. // Bow:mpKeyFrameDB和mpMap被清空 // 就是把所有资源释放 mpTracker->Reset(); mbReset = false; } } // 可以看出上面这两部分都是对于各个线程状态的反馈. // 其实可以看做是对上一个状态的反馈. // 接下来的部分才是最重要的部分,获取数据,对各个线程的数据进行更新, 对地图重新进行计算. return mpTracker->GrabImageMonocular(im,timestamp);}
mpTracker->GrabImageMonocular
这部分是上一部分的核心。对GrabImageMonocular的解释如下:
cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im, const double ×tamp){ // 把输入的图片设为当前帧. mImGray = im; // 若图片是三\四通道的,还需要转化为灰度图. if(mImGray.channels()==3) { if(mbRGB) cvtColor(mImGray,mImGray,CV_RGB2GRAY); else cvtColor(mImGray,mImGray,CV_BGR2GRAY); } else if(mImGray.channels()==4) { if(mbRGB) cvtColor(mImGray,mImGray,CV_RGBA2GRAY); else cvtColor(mImGray,mImGray,CV_BGRA2GRAY); } // 若状态是未初始化或者当前获取的图片是第一帧的时候,那么就需要进行初始化. // 传入的参数就是当前帧(灰度图)、时间戳、ORBextractor(ORB特征)、DBoW2的字典、标定矩阵、畸变参数、立体视觉的(baseline)x(fx)、可靠点的距离的阈值 // mCurrentFrame就是对当前帧进行处理. 获取ORB特征、获取尺度信息. // 这里需要进行更加深入的研究. if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET) mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth); else mCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth); // 追踪,初始化以及特征匹配. // 这里需要更加深入的研究. Track(); // mTcw是位姿 // clone是cv::Mat特有的方法. // 实现的就是完全拷贝, 把数据完全拷贝而不共享数据. return mCurrentFrame.mTcw.clone();}
Frame
对于这一部分,需要特别研究一下:
// 若状态是未初始化或者当前获取的图片是第一帧的时候,那么就需要进行初始化.// 传入的参数就是当前帧(灰度图)、时间戳、ORBextractor(ORB特征)、DBoW2的字典、标定矩阵、畸变参数、立体视觉的(baseline)x(fx)、可靠点的距离的阈值// mCurrentFrame就是对当前帧进行处理. 获取ORB特征、获取尺度信息.// 这里需要进行更加深入的研究.if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET) mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth); else mCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);
整个Frame的流程图如上所示,比较清晰。
对Frame部分进行注释得到:
Frame::Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth) :mpORBvocabulary(voc),mpORBextractorLeft(extractor),mpORBextractorRight(static_cast<ORBextractor*>(NULL)), mTimeStamp(timeStamp), mK(K.clone()),mDistCoef(distCoef.clone()), mbf(bf), mThDepth(thDepth){ // Frame ID // 当前帧的ID // 每一帧都有唯一的一个ID做标识 // 从0开始 mnId=nNextId++; // Scale Level Info // 获取尺度信息,目前还不了解这些是做什么的? // 这些是属于Frame的变量. mnScaleLevels = mpORBextractorLeft->GetLevels(); mfScaleFactor = mpORBextractorLeft->GetScaleFactor(); mfLogScaleFactor = log(mfScaleFactor); mvScaleFactors = mpORBextractorLeft->GetScaleFactors(); mvInvScaleFactors = mpORBextractorLeft->GetInverseScaleFactors(); mvLevelSigma2 = mpORBextractorLeft->GetScaleSigmaSquares(); mvInvLevelSigma2 = mpORBextractorLeft->GetInverseScaleSigmaSquares(); // ORB extraction // 提取ORB特征. // 可以继续研究. ExtractORB(0,imGray); // KeyPoint (float x, float y, float _size, float _angle=-1, float _response=0, int _octave=0, int _class_id=-1) // 进行ORB特征提取以后可以得到关键点的数量. 存储在关键点的向量内. N = mvKeys.size(); // 没有找到关键点,返回. if(mvKeys.empty()) return; // 进行畸变校正,找到关键点实际应该在普通摄像头中的位置. UndistortKeyPoints(); // Set no stereo information // 把立体信息部分设置为-1. mvuRight = vector<float>(N,-1); mvDepth = vector<float>(N,-1); // 初始化点和各个点是否是outlier的状态. mvpMapPoints = vector<MapPoint*>(N,static_cast<MapPoint*>(NULL)); mvbOutlier = vector<bool>(N,false); // This is done only for the first Frame (or after a change in the calibration) // 第一帧或者是标定矩阵发生了变化以后 if(mbInitialComputations) { // 计算畸变矫正之后的边界. ComputeImageBounds(imGray); // mnMax(Min)X(Y)是畸变矫正以后的边界. mfGridElementWidthInv=static_cast<float>(FRAME_GRID_COLS)/static_cast<float>(mnMaxX-mnMinX); mfGridElementHeightInv=static_cast<float>(FRAME_GRID_ROWS)/static_cast<float>(mnMaxY-mnMinY); // 从配置文件中读取数据赋给相应元素. fx = K.at<float>(0,0); fy = K.at<float>(1,1); cx = K.at<float>(0,2); cy = K.at<float>(1,2); invfx = 1.0f/fx; invfy = 1.0f/fy; mbInitialComputations=false; } // Stereo baseline in meters. // 计算立体匹配的时候的baseline. // mbf来自与配置文件. mb = mbf/fx; // 把每一帧分割成48x64个网格 // 根据关键点的畸变矫正后的位置分在不同的网格里面. AssignFeaturesToGrid();}
ExtractORB
ExtractORB(0,imGray);
这里提取的就是左边的摄像头的数据。
void Frame::ExtractORB(int flag, const cv::Mat &im){ if(flag==0) (*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors); else (*mpORBextractorRight)(im,cv::Mat(),mvKeysRight,mDescriptorsRight);}
这个是在ORBExtractor.h的ORBExtractor类里重载的操作符’()’: 这里忽略了mask这个变量。
void operator()( cv::InputArray image, cv::InputArray mask, std::vector<cv::KeyPoint>& keypoints, cv::OutputArray descriptors);
在ORBextractor.cc里面对这个有详细的介绍。接下来对这个模块进行详细的介绍。
// 输入的变量:// _image: 获取的图片像素信息(灰度图)// _mask: 掩码,这个部分的位置就不需要计算描述子// _keypoints: 关键点的位置// _descriptors: 描述子,没有使用引用,看来是没有用到了 void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>& _keypoints, OutputArray _descriptors){ // 如果没有获取图片,那么返回 if(_image.empty()) return; // 获取图片信息赋给Mat类型的image Mat image = _image.getMat(); // 判断通道是否为单通道灰度图 assert(image.type() == CV_8UC1 ); // Pre-compute the scale pyramid // 计算尺度的金字塔,可以稍微看一眼 ComputePyramid(image); // 保存所有的关键点 vector < vector<KeyPoint> > allKeypoints; // 计算关键点,找到FAST关键点,值得看一下 ComputeKeyPointsOctTree(allKeypoints); //ComputeKeyPointsOld(allKeypoints); Mat descriptors; int nkeypoints = 0; for (int level = 0; level < nlevels; ++level) nkeypoints += (int)allKeypoints[level].size(); if( nkeypoints == 0 ) _descriptors.release(); else { _descriptors.create(nkeypoints, 32, CV_8U); descriptors = _descriptors.getMat(); } _keypoints.clear(); _keypoints.reserve(nkeypoints); int offset = 0; for (int level = 0; level < nlevels; ++level) { vector<KeyPoint>& keypoints = allKeypoints[level]; int nkeypointsLevel = (int)keypoints.size(); if(nkeypointsLevel==0) continue; // preprocess the resized image Mat workingMat = mvImagePyramid[level].clone(); // 使用高斯模糊为了计算BRIEF的时候去噪 GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101); // Compute the descriptors // 计算rBRIEF描述子,采用的是高斯分布取点 Mat desc = descriptors.rowRange(offset, offset + nkeypointsLevel); computeDescriptors(workingMat, keypoints, desc, pattern); offset += nkeypointsLevel; // 对关键点的位置做尺度恢复,恢复到原图的位置 // Scale keypoint coordinates if (level != 0) { float scale = mvScaleFactor[level]; //getScale(level, firstLevel, scaleFactor); for (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) keypoint->pt *= scale; } // And add the keypoints to the output // _keypoints.insert(_keypoints.end(), keypoints.begin(), keypoints.end()); }}
在这里我发现还是要复习一下ORB特征【1】【2】。看【2】会更好,因为在这里我把算法流程讲得还算清楚。
在这里的level和scalefactor我不清楚,我把它打印出来看了一下:
图片金字塔一共是8层,随着level越来越大,scale factor越来越大,图片越来越小。当恢复金字塔关键点的原图坐标的时候就把它乘以scale factor。
ComputePyramid()
计算尺度金字塔。
void ORBextractor::ComputePyramid(cv::Mat image){ // 计算nlevel个尺度的图片 for (int level = 0; level < nlevels; ++level) { // 获取尺度 float scale = mvInvScaleFactor[level]; // 计算当前尺度下图片的大小 Size sz(cvRound((float)image.cols*scale), cvRound((float)image.rows*scale)); Size wholeSize(sz.width + EDGE_THRESHOLD*2, sz.height + EDGE_THRESHOLD*2); Mat temp(wholeSize, image.type()), masktemp; // 图片初始化 mvImagePyramid[level] = temp(Rect(EDGE_THRESHOLD, EDGE_THRESHOLD, sz.width, sz.height)); // Compute the resized image // 计算图片金字塔在该尺度下的金字塔 if( level != 0 ) { // 对图片进行尺度变换 // 在这里我们一般是把更清晰的图片变化更加模糊的图片 // 从这里可以看出,这里的尺度的值应该是小于1的,所以才会命名为mvIneScale,就是scale的倒数。 resize(mvImagePyramid[level-1], mvImagePyramid[level], sz, 0, 0, INTER_LINEAR); // 【3】这里主要是为了方便做一些卷积计算来做一些边界补充 copyMakeBorder(mvImagePyramid[level], temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, BORDER_REFLECT_101+BORDER_ISOLATED); } else { copyMakeBorder(image, temp, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, EDGE_THRESHOLD, BORDER_REFLECT_101); } }}
ComputeKeyPointsOctTree()
计算关键点。
void ORBextractor::ComputeKeyPointsOctTree(vector<vector<KeyPoint> >& allKeypoints){ // 一共计算nlevel个尺度的关键点 allKeypoints.resize(nlevels); // 窗口的大小 const float W = 30; // 对每个尺度计算它的关键点 for (int level = 0; level < nlevels; ++level) { // 计算边界,在这个边界内计算FAST关键点 const int minBorderX = EDGE_THRESHOLD-3; const int minBorderY = minBorderX; const int maxBorderX = mvImagePyramid[level].cols-EDGE_THRESHOLD+3; const int maxBorderY = mvImagePyramid[level].rows-EDGE_THRESHOLD+3; vector<cv::KeyPoint> vToDistributeKeys; vToDistributeKeys.reserve(nfeatures*10); // 计算宽度和高度 const float width = (maxBorderX-minBorderX); const float height = (maxBorderY-minBorderY); // 计算有多少个窗口 const int nCols = width/W; const int nRows = height/W; // 计算每个窗口的大小 const int wCell = ceil(width/nCols); const int hCell = ceil(height/nRows); // 对每个窗口进行遍历 for(int i=0; i<nRows; i++) { const float iniY =minBorderY+i*hCell; float maxY = iniY+hCell+6; // 已经出了图片的有效区域了 if(iniY>=maxBorderY-3) continue; // 超出边界的话就使用计算最宽的边界 if(maxY>maxBorderY) maxY = maxBorderY; for(int j=0; j<nCols; j++) { // ********************************* // 计算每列的位置 const float iniX =minBorderX+j*wCell; float maxX = iniX+wCell+6; if(iniX>=maxBorderX-6) continue; if(maxX>maxBorderX) maxX = maxBorderX; // 计算FAST关键点 vector<cv::KeyPoint> vKeysCell; FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX), vKeysCell,iniThFAST,true); if(vKeysCell.empty()) { FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX), vKeysCell,minThFAST,true); } // 如果找到的关键点不为空,就加入到vToDistributeKeys. if(!vKeysCell.empty()) { for(vector<cv::KeyPoint>::iterator vit=vKeysCell.begin(); vit!=vKeysCell.end();vit++) { // 计算实际的位置 (*vit).pt.x+=j*wCell; (*vit).pt.y+=i*hCell; vToDistributeKeys.push_back(*vit); } } } } vector<KeyPoint> & keypoints = allKeypoints[level]; // 至少保留n个特征点 keypoints.reserve(nfeatures); // 根据Harris角点的score进行排序,保留正确的 keypoints = DistributeOctTree(vToDistributeKeys, minBorderX, maxBorderX, minBorderY, maxBorderY,mnFeaturesPerLevel[level], level); // 这里的PATCH_SIZE是31 const int scaledPatchSize = PATCH_SIZE*mvScaleFactor[level]; // Add border to coordinates and scale information const int nkps = keypoints.size(); for(int i=0; i<nkps ; i++) { keypoints[i].pt.x+=minBorderX; keypoints[i].pt.y+=minBorderY; keypoints[i].octave=level; keypoints[i].size = scaledPatchSize; } } // compute orientations // 计算nlevels个尺度下各个关键点的方向 for (int level = 0; level < nlevels; ++level) computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);}
DistributeOctTree()
计算用FAST选出来的特征点是否合格。
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX, const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
这个地方看得不是很懂,这里是计算出每个level的关键点的score,接着进行排序,选出了n个关键点。
Mark 一下,改日回来继续看。
computeDescriptors()
计算rBRIEF描述子。
在这个函数里计算了rBrief的描述子。
static void computeDescriptors(const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors, const vector<Point>& pattern){ // 初始化描述子 descriptors = Mat::zeros((int)keypoints.size(), 32, CV_8UC1); for (size_t i = 0; i < keypoints.size(); i++) // 计算每个关键点的描述子 computeOrbDescriptor(keypoints[i], image, &pattern[0], descriptors.ptr((int)i));}
计算描述子如下:
计算256维的描述子。
const float factorPI = (float)(CV_PI/180.f);static void computeOrbDescriptor(const KeyPoint& kpt, const Mat& img, const Point* pattern, uchar* desc){ float angle = (float)kpt.angle*factorPI; float a = (float)cos(angle), b = (float)sin(angle); const uchar* center = &img.at<uchar>(cvRound(kpt.pt.y), cvRound(kpt.pt.x)); const int step = (int)img.step; #define GET_VALUE(idx) \ center[cvRound(pattern[idx].x*b + pattern[idx].y*a)*step + \ cvRound(pattern[idx].x*a - pattern[idx].y*b)] for (int i = 0; i < 32; ++i, pattern += 16) { int t0, t1, val; t0 = GET_VALUE(0); t1 = GET_VALUE(1); val = t0 < t1; t0 = GET_VALUE(2); t1 = GET_VALUE(3); val |= (t0 < t1) << 1; t0 = GET_VALUE(4); t1 = GET_VALUE(5); val |= (t0 < t1) << 2; t0 = GET_VALUE(6); t1 = GET_VALUE(7); val |= (t0 < t1) << 3; t0 = GET_VALUE(8); t1 = GET_VALUE(9); val |= (t0 < t1) << 4; t0 = GET_VALUE(10); t1 = GET_VALUE(11); val |= (t0 < t1) << 5; t0 = GET_VALUE(12); t1 = GET_VALUE(13); val |= (t0 < t1) << 6; t0 = GET_VALUE(14); t1 = GET_VALUE(15); val |= (t0 < t1) << 7; desc[i] = (uchar)val; } #undef GET_VALUE}
描述子是采用了高斯分布采样得到的。在里面定义为:
static int bit_pattern_31_[256*4] ={ 8,-3, 9,5/*mean (0), correlation (0)*/, 4,2, 7,-12/*mean (1.12461e-05), correlation (0.0437584)*/, -11,9, -8,2/*mean (3.37382e-05), correlation (0.0617409)*/, 7,-12, 12,-13/*mean (5.62303e-05), correlation (0.0636977)*/, 2,-13, 2,12/*mean (0.000134953), correlation (0.085099)*/, 1,-7, 1,6/*mean (0.000528565), correlation (0.0857175)*/, -2,-10, -2,-4/*mean (0.0188821), correlation (0.0985774)*/, -13,-13, -11,-8/*mean (0.0363135), correlation (0.0899616)*/, -13,-3, -12,-9/*mean (0.121806), correlation (0.099849)*/, 10,4, 11,9/*mean (0.122065), correlation (0.093285)*/, -13,-8, -8,-9/*mean (0.162787), correlation (0.0942748)*/, -11,7, -9,12/*mean (0.21561), correlation (0.0974438)*/, 7,7, 12,6/*mean (0.160583), correlation (0.130064)*/, ......}
computeOrientation()
在computeOctKeypointTree里调用了这个函数,调用为:
computeOrientation(mvImagePyramid[level], allKeypoints[level], umax);
static void computeOrientation(const Mat& image, vector<KeyPoint>& keypoints, const vector<int>& umax){ for (vector<KeyPoint>::iterator keypoint = keypoints.begin(), keypointEnd = keypoints.end(); keypoint != keypointEnd; ++keypoint) { // 计算nlevel这个尺度的图片的关键点的方向 keypoint->angle = IC_Angle(image, keypoint->pt, umax); }}
IC_Angle: 这个部分对照我介绍的ORB特征的那篇博客,里面有详细介绍【2】。里面有详细的介绍。
static float IC_Angle(const Mat& image, Point2f pt, const vector<int> & u_max){ int m_01 = 0, m_10 = 0; const uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x)); // Treat the center line differently, v=0 for (int u = -HALF_PATCH_SIZE; u <= HALF_PATCH_SIZE; ++u) m_10 += u * center[u]; // Go line by line in the circuI853lar patch int step = (int)image.step1(); for (int v = 1; v <= HALF_PATCH_SIZE; ++v) { // Proceed over the two lines int v_sum = 0; int d = u_max[v]; for (int u = -d; u <= d; ++u) { int val_plus = center[u + v*step], val_minus = center[u - v*step]; v_sum += (val_plus - val_minus); m_10 += u * (val_plus + val_minus); } m_01 += v * v_sum; } return fastAtan2((float)m_01, (float)m_10);}
Track();
在上面对一帧的关键提取出来以后,就需要进行追踪了。
关键帧提取出了需要的关键点和描述子,接下来就是进行追踪了。
在trcking.cc里面,有void Tracking::Track()
这里面的东西还需要继续挖掘~ 已经Mark
参考:
【1】ORB:an efficient alternative to SIFT or SURF
【2】ORB特征讲解: http://blog.csdn.net/c602273091/article/details/56008370
【3】copyMakeBorder:http://blog.csdn.net/viewcode/article/details/8287599
【4】图像金字塔:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/pyramids/pyramids.html
【5】OpenCV resize:http://www.cnblogs.com/korbin/p/5612427.html
【6】OpenCV Copyboarder: http://blog.csdn.net/viewcode/article/details/8287599
【7】OpenCV PI: http://www.zybang.com/question/5768af44d5df172aedf5d9d6c9dc7c1e.html
【8】Fastatan2: http://blog.csdn.net/mingzhentanwo/article/details/45155307
【9】OpenCV FAST 源码分析:http://blog.csdn.net/zhaocj/article/details/40301561
【10】OpenCV FAST使用: http://www.bkjia.com/ASPjc/976906.html
【11】FAST API:http://opencv.jp/opencv-2.2_org/cpp/features2d_feature_detection_and_description.html?highlight=fast#cv-fast
- ORB-SLAM代码详解之SLAM.TrackMonocular
- ORB-SLAM代码详解之代码框架
- ORB-SLAM代码详解之SLAM系统初始化
- ORB-Slam详解2 代码流程
- SLAM代码(ORB-SLAM阅读)
- SLAM代码(ORB-SLAM阅读2)
- SLAM系列之1 - ORB SLAM
- ORB-Slam
- ORB-SLAM
- ORB SLAM
- 【视觉 SLAM-2】 视觉SLAM- ORB 源码详解 2
- ORB-SLAM之Relocalization,SLAM重定位流程
- ORB-SLAM中 ORBextract.cpp代码解读
- ORB-SLAM代码理解及笔记
- DVO-SLAM详解之代码逻辑
- orb-slam中的orb特征
- ORB-SLAM使用方法
- ORB-SLAM-2
- 2016冬天学习记录-2.09
- 计算字符串最后一个单词的长度,单词以空格隔开。
- FAAC源码阅读(3)——FAAC的接口
- 运动跟踪 Visual Tracker Benchmark OTB-100 OTB-50 评价 使用
- 类加载器简单分析
- ORB-SLAM代码详解之SLAM.TrackMonocular
- git命令大全
- 结点最大深度
- oj1071
- IP地址、子网掩码、默认网关
- Oracle客户端配置
- Uva 489 Hangman Judge
- 重返设计模式--观察者模式
- .NET Core 最小化发布