ORB-SLAM代码详解之SLAM.TrackMonocular

来源:互联网 发布:房产网络端口是什么 编辑:程序博客网 时间:2024/05/25 18:12

  • 总述
    • TrackMonocular
    • mpTracker-GrabImageMonocular
      • Frame
        • ExtractORB
        • ComputePyramid
        • ComputeKeyPointsOctTree
        • DistributeOctTree
        • computeDescriptors
        • computeOrientation
      • Track

转载请注明出处: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 &timestamp){    // 传感器不是单目摄像头、退出    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 &timestamp){    // 把输入的图片设为当前帧.    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

2 0
原创粉丝点击