opencv特征匹配算法原理

一、ORB算法原理

	全名Oriented FAST and Rotated BRIEF算法,是指它基于FAST算法提取特征点,并基于BRIEF算法构建特征点的描述子,在他们原有的基础上进行修正,实现特征点的尺度不变性与旋转不变性,即经过了缩放与旋转后的特征点仍能产生与原来相近的描述符。

算法步骤:

1.特征点提取

FAST进行特征点提取是根据当前点领域内的点的差值作为特征点的筛选标准

请添加图片描述
( 1 ) 选择像素 p ,该像素值为 I p ,确定筛选阈值 T ( 2 ) 计算以 p 为圆心 3 为半径的 16 个像素点的灰度值 I x 和圆心 p 的灰度值 I p 的差值,如果存在连续 n 个点(算法第一个版本为 12 ) 满足 ∣ I x − I p ∣ > t 则认为点 p 可以作为一个候选点,否则剔除;对于 16 个点都计算差值时间复杂度过高,因此 F A S T 采用即特 征点过滤的方式:先判断圆上 1 , 5 , 9 , 13 号 4 个点中如果至少 3 个点满足特征点初选的要求在进行逐个计算,否则终止计算; ( 3 ) 遍历图像中每一个像素,重复上述操作,直到遍历结束; \begin{aligned} &(1)选择像素 p,该像素值为 I_p,确定筛选阈值 T \\ &(2)计算以p为圆心3为半径的16个像素点的灰度值 I_x 和圆心 p 的灰度值 I_p 的差值,如果存在连续n个点(算法第一个版本为12)\\ &满足 |I_x - I_p| > t 则认为点 p 可以作为一个候选点,否则剔除;对于16个点都计算差值时间复杂度过高,因此FAST采用即特 \\&征点过滤的方式:先判断圆上1,5,9,13号4个点中如果至少3个点满足特征点初选的要求在进行逐个计算,否则终止计算;\\ &(3)遍历图像中每一个像素,重复上述操作,直到遍历结束; \end{aligned} (1)选择像素p,该像素值为Ip,确定筛选阈值T(2)计算以p为圆心3为半径的16个像素点的灰度值Ix和圆心p的灰度值Ip的差值,如果存在连续n个点(算法第一个版本为12满足IxIp>t则认为点p可以作为一个候选点,否则剔除;对于16个点都计算差值时间复杂度过高,因此FAST采用即特征点过滤的方式:先判断圆上1,5,9,134个点中如果至少3个点满足特征点初选的要求在进行逐个计算,否则终止计算;(3)遍历图像中每一个像素,重复上述操作,直到遍历结束;

2.特征点编码

	BRIEF是一种对已经检测到的特征点进行描述的算法,该算法生成一种二进制描述子来描述已知的特征点。这些描述子可以用来进行特征点匹配等操作。BRIEF摒弃了利用区域直方图描述特征点的传统方法,采用二进制、位异或运算处理,大大的加快了特征点描述符建立的速度,同时也极大的降低了特征点匹配的时间,是一种非常快速的特征点描述子算法。

BRIEF的目标是得到一个二进制串,该串描述了特征点的特性。描述子生成的方式:
( 1 ) 为了减少噪声干扰,首先对图像进行高斯滤波(方差为 2 ,高斯窗口为 9 × 9 ); ( 2 ) 然后以特征点为中心,取 S ∗ S 大小的邻域窗口,在窗口内以一定方式选取一对点 p , q ,比较两个像素点的大小: a ) . 如果 I p > I q ,则当前位二进制值为 1 ; b ) . 否则为 0 ; c ) . 重复这一步骤,产生指定长度的特征描述符;选取点的采样方式可以为: ∗   p , q 都符合 [ − S 2 , S 2 ] 的均匀采样; ∗   p , q 都符合各向同性的高斯分布 [ 0 , 1 25 S 2 ] 采样; ∗   p 符合高斯分布 [ 0 , 1 25 S 2 ] 采样, q 符合 [ 0 , 1 100 S 2 ] 采样,采样方式是首先在原点处为 p 采样,然后以 p 为中心为 q 采样; ∗   p , q 在空间量化极坐标下的离散位置处进行随机采样; ∗   p = ( 0 , 0 ) T , q 在空间量化极坐标下的离散位置处进行随机采样; \begin{aligned} &(1)为了减少噪声干扰,首先对图像进行高斯滤波(方差为2,高斯窗口为9 × 9);\\ &(2)然后以特征点为中心,取S * S大小的邻域窗口,在窗口内以一定方式选取一对点 p,q,比较两个像素点的大小:\\ &\quad a).如果 I_p > I_q,则当前位二进制值为1;\\ &\quad b). 否则为0;\\ &\quad c). 重复这一步骤,产生指定长度的特征描述符;选取点的采样方式可以为:\\ &\qquad \qquad * \ p,q都符合[-\frac{S}{2},\frac{S}{2}]的均匀采样;\\ &\qquad \qquad * \ p,q都符合各向同性的高斯分布[0, \frac{1}{25}S^2]采样;\\ &\qquad \qquad * \ p符合高斯分布[0, \frac{1}{25}S^2]采样,q符合[0, \frac{1}{100}S^2]采样,采样方式是首先在原点处为p采样,然后以p为中心为q采样;\\ &\qquad \qquad * \ p,q在空间量化极坐标下的离散位置处进行随机采样;\\ &\qquad \qquad * \ p=(0,0)^T,q在空间量化极坐标下的离散位置处进行随机采样; \end{aligned} (1)为了减少噪声干扰,首先对图像进行高斯滤波(方差为2,高斯窗口为9×9);(2)然后以特征点为中心,取SS大小的邻域窗口,在窗口内以一定方式选取一对点p,q,比较两个像素点的大小:a).如果Ip>Iq,则当前位二进制值为1b).否则为0c).重复这一步骤,产生指定长度的特征描述符;选取点的采样方式可以为: p,q都符合[2S,2S]的均匀采样; p,q都符合各向同性的高斯分布[0,251S2]采样; p符合高斯分布[0,251S2]采样,q符合[0,1001S2]采样,采样方式是首先在原点处为p采样,然后以p为中心为q采样; p,q在空间量化极坐标下的离散位置处进行随机采样; p=(0,0)T,q在空间量化极坐标下的离散位置处进行随机采样;

以采样(3)为例子:绿色点为原点,蓝色点为采样p,黄色点为采样q

请添加图片描述
BRIEF存在不具备旋转不变性,不具备尺度不变性。

改进后的BRIEF:rBRIEF

(1)缩放不变性
​ ​ 为使特征满足缩放不变性,构建图像金字塔。图像金字塔是单个图像的多尺度表示法,由一系列原始图像的不同分辨率版本组成(比如每个图像以1/2比例向下采样)。

​ ​ ORB 创建好图像金字塔后,会使用 FAST 算法从每个级别不同大小的图像中快速找到关键点。因为金字塔的每个级别由原始图像的更小版本组成,因此原始图像中的任何对象在金字塔的每个级别也会降低大小。

(2)旋转不变性
​ ​ 为满足特征的旋转不变性,ORB 为每个关键点分配一个方向,该方向取决于该关键点周围的灰度是如何变化的。ORB 首先选择金字塔Level 0 中的图像,计算该图像关键点的方向。首先计算以关键点为中心的指定大小方框中的强度形心(灰度强度的形心)。强度形心可以看做给定区域中的平均像素灰度的位置。计算强度形心后,通过画一条从关键点到强度形心的向量,获得该关键点的方向。
​ ​ 为金字塔级别 0 的图像中的每个关键点分配方向后,ORB 现在为所有其他金字塔级别的图像重复相同流程。

3.opencv实现

// nfeatures		期望检测到的特征点数
// scaleFactor		多尺度金字塔的尺度
// nlevels			金字塔层数
// edgeThreshold	边界阈值,靠近边缘edgeThreshold以内的像素是不检测特征点的
// firstLevel		金字塔层的起始层
// wat_k			产生rBRIEF描述子点对的个数
// scoreType		角点响应函数,默认使用HARRIS对角点特征进行排序
// patchSize		邻域大小
// fastThreshold
Ptr<ORB> ORB::create(int nfeatures, float scaleFactor, int nlevels, int edgeThreshold, int firstLevel, int wta_k, int scoreType, int patchSize, int fastThreshold){
    CV_Assert(firstLevel >= 0);
    return makePtr<ORB_Impl>(nfeatures, scaleFactor, nlevels, edgeThreshold, firstLevel, wta_k, scoreType, patchSize, fastThreshold);
}
// _image			计算特征和描述子的输入图像
// _mask			掩模
// keypoints		输出关键点的集合
// _descriptors		描述子
// useProvidedKeypoints 使用提供的关键点

void ORB_Impl::detectAndCompute( InputArray _image, InputArray _mask, std::vector<KeyPoint>& keypoints,
                                 OutputArray _descriptors, bool useProvidedKeypoints ) {
    CV_Assert(patchSize >= 2);
    bool do_keypoints = !useProvidedKeypoints;
    bool do_descriptors = _descriptors.needed();
    if( (!do_keypoints && !do_descriptors) || _image.empty() )
        return;

    //ROI handling
    const int HARRIS_BLOCK_SIZE = 9;
    int halfPatchSize = patchSize / 2;
    // sqrt(2.0) is for handling patch rotation
    int descPatchSize = cvCeil(halfPatchSize*sqrt(2.0));
    int border = std::max(edgeThreshold, std::max(descPatchSize, HARRIS_BLOCK_SIZE/2))+1;

    Mat image = _image.getMat(), mask = _mask.getMat();
    if( image.type() != CV_8UC1 )
        cvtColor(_image, image, COLOR_BGR2GRAY);			//ORB采用点邻域半径内的像素和当前像素的灰度差来表征特征点,颜色信息对于特征描述意义不大

    int i, level, nLevels = this->nlevels, nkeypoints = (int)keypoints.size();
    bool sortedByLevel = true;

    std::vector<Rect> layerInfo(nLevels);			//当前层图像的rect相对于firstLevel层的位置,如果当前层的图像比firstLevel层大则采用当前图源尺寸
    std::vector<int> layerOfs(nLevels);				//当前层的数据index,用于OpenCL优化
    std::vector<float> layerScale(nLevels);			//保存从第0层到第nLevels层缩放的尺度
    Mat imagePyramid, maskPyramid;
    float level0_inv_scale = 1.0f / getScale(0, firstLevel, scaleFactor);	//第一层的缩放尺度
    size_t level0_width = (size_t)cvRound(image.cols * level0_inv_scale);	//第一层图像的宽度
    size_t level0_height = (size_t)cvRound(image.rows * level0_inv_scale);	//第一层图像的高度
    Size bufSize((int)alignSize(level0_width + border*2, 16), 0);  			//第一层图像对齐后的rowbyteperrow,+2border是为方便做HARRIS做的padding

    int level_dy = (int)level0_height + border*2;
    Point level_ofs(0, 0);
	//这里只计算每一层的尺寸并不进行实际的金字塔层的构造工作,下面的一系列计算都是为了防止越界,以及金字塔特征层是在一张Mat上而不是一个vector<Mat>
    for( level = 0; level < nLevels; level++ )
    {
		//getScale->(float)std::pow(scaleFactor, (double)(level - firstLevel))金字塔层第0层就是原图,层数越高,图像的尺度多样性越丰富,图像越小
        float scale = getScale(level, firstLevel, scaleFactor);
        layerScale[level] = scale;
        float inv_scale = 1.0f / scale;				//从这里能够看出第一层图像是最大的然后逐层减小
        Size sz(cvRound(image.cols * inv_scale), cvRound(image.rows * inv_scale));	//当前层图像的尺寸
        Size wholeSize(sz.width + border*2, sz.height + border*2);		//padding边界尺寸之后的实际尺寸
        if( level_ofs.x + wholeSize.width > bufSize.width )
        {//如果当前行存在足够的空间则优先放在当前行,由于图像是从大到小生成的y方向始终是足够的
            level_ofs = Point(0, level_ofs.y + level_dy);
            level_dy = wholeSize.height;
        }

        Rect linfo(level_ofs.x + border, level_ofs.y + border, sz.width, sz.height);
        layerInfo[level] = linfo;
        layerOfs[level] = linfo.y*bufSize.width + linfo.x;
        level_ofs.x += wholeSize.width;
    }
	
    bufSize.height = level_ofs.y + level_dy;
    imagePyramid.create(bufSize, CV_8U);
    if( !mask.empty() )
        maskPyramid.create(bufSize, CV_8U);

    Mat prevImg = image, prevMask = mask;
    // Pre-compute the scale pyramids 构造金字塔层,下
    for (level = 0; level < nLevels; ++level)
    {//从上面计算的尺寸中取出相应的尺寸缩放图像生成对应的金字塔
        Rect linfo = layerInfo[level];
        Size sz(linfo.width, linfo.height);
        Size wholeSize(sz.width + border*2, sz.height + border*2);
        Rect wholeLinfo = Rect(linfo.x - border, linfo.y - border, wholeSize.width, wholeSize.height);
        Mat extImg = imagePyramid(wholeLinfo), extMask;
        Mat currImg = extImg(Rect(border, border, sz.width, sz.height)), currMask;

        if( !mask.empty() )
        {
            extMask = maskPyramid(wholeLinfo);
            currMask = extMask(Rect(border, border, sz.width, sz.height));
        }

        // Compute the resized image 这里生成的图像边界padding的并不是一个固定的像素值而是原图像的镜像,这样能够最大程度的保留图像的信息避免额外引入的噪声的干扰
        if( level != firstLevel )
        {
            resize(prevImg, currImg, sz, 0, 0, INTER_LINEAR_EXACT);
            if( !mask.empty() )
            {
                resize(prevMask, currMask, sz, 0, 0, INTER_LINEAR_EXACT);
                if( level > firstLevel )
                    threshold(currMask, currMask, 254, 0, THRESH_TOZERO);
            }

            copyMakeBorder(currImg, extImg, border, border, border, border,
                           BORDER_REFLECT_101+BORDER_ISOLATED);
            if (!mask.empty())
                copyMakeBorder(currMask, extMask, border, border, border, border,
                               BORDER_CONSTANT+BORDER_ISOLATED);
        }
        else
        {
            copyMakeBorder(image, extImg, border, border, border, border,
                           BORDER_REFLECT_101);
            if( !mask.empty() )
                copyMakeBorder(mask, extMask, border, border, border, border,
                               BORDER_CONSTANT+BORDER_ISOLATED);
        }
        if (level > firstLevel)
        {
            prevImg = currImg;
            prevMask = currMask;
        }
    }

	// Get keypoints, those will be far enough from the border that no check will be required for the descriptor
	computeKeyPoints(imagePyramid, uimagePyramid, maskPyramid,
					 layerInfo, ulayerInfo, layerScale, keypoints,
					 nfeatures, scaleFactor, edgeThreshold, patchSize, scoreType, useOCL, fastThreshold);

    if( do_descriptors )
    {
        int dsize = descriptorSize();	//固定dsize=32byte就是特征的256位的二进制位描述
        nkeypoints = (int)keypoints.size();
        if( nkeypoints == 0 )
        {
            _descriptors.release();
            return;
        }

        _descriptors.create(nkeypoints, dsize, CV_8U);	//这里创建的是一个nxndesc大小的矩阵表示n个特征点的特征描述
        std::vector<Point> pattern;

        const int npoints = 512;						//256对点
        Point patternbuf[npoints];
        const Point* pattern0 = (const Point*)bit_pattern_31_;	//bit_pattern_32_是通过PASCAL 2006数据集训练好的pattern,这部分数据是硬编码。是patchSize为31时,在其邻域内选取的256个点对
        if( patchSize != 31 )
        {
            pattern0 = patternbuf;
            makeRandomPattern(patchSize, patternbuf, npoints);		//如果patchSize不为31的话会随机生成筛选的点对,而且随机数的种子是固定的保证筛选的位置都是固定的,不然同一张图不同时刻输入得到特征点不一样
        }

        CV_Assert( wta_k == 2 || wta_k == 3 || wta_k == 4 );
        if( wta_k == 2 )
            std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));
        else
        {
            int ntuples = descriptorSize()*4;
            initializeOrbPattern(pattern0, pattern, ntuples, wta_k, npoints);
        }

        for( level = 0; level < nLevels; level++ )
        {
            // preprocess the resized image
            Mat workingMat = imagePyramid(layerInfo[level]);
			//对每一层的图像进行高斯模糊
            //boxFilter(working_mat, working_mat, working_mat.depth(), Size(5,5), Point(-1,-1), true, BORDER_REFLECT_101);
            GaussianBlur(workingMat, workingMat, Size(7, 7), 2, 2, BORDER_REFLECT_101);
        }
		
        {
            Mat descriptors = _descriptors.getMat();
            computeOrbDescriptors(imagePyramid, layerInfo, layerScale,
                                  keypoints, descriptors, pattern, dsize, wta_k);
        }
    }
}
static void computeKeyPoints(const Mat& imagePyramid, const UMat& uimagePyramid, const Mat& maskPyramid, const std::vector<Rect>& layerInfo, const UMat& ulayerInfo, const std::vector<float>& layerScale, std::vector<KeyPoint>& allKeypoints, int nfeatures, double scaleFactor, int edgeThreshold, int patchSize, int scoreType, bool useOCL, int fastThreshold  ) {
    int i, nkeypoints, level, nlevels = (int)layerInfo.size();
    std::vector<int> nfeaturesPerLevel(nlevels);

    // fill the extractors and descriptors for the corresponding scales
    float factor = (float)(1.0 / scaleFactor);
    float ndesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)std::pow((double)factor, (double)nlevels));

    int sumFeatures = 0;
	//从这里能够看出检测出的n个特征点是按比例分配在多层金字塔上的,其总数为nfeatures
    for( level = 0; level < nlevels-1; level++ ) {
        nfeaturesPerLevel[level] = cvRound(ndesiredFeaturesPerScale);
        sumFeatures += nfeaturesPerLevel[level];
        ndesiredFeaturesPerScale *= factor;		//第一层的特征点数最多,越往上越少
    }
	
    nfeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);
    // Make sure we forget about what is too close to the boundary
    //edge_threshold_ = std::max(edge_threshold_, patch_size_/2 + kKernelWidth / 2 + 2);

    // pre-compute the end of a row in a circular patch
    int halfPatchSize = patchSize / 2;
    std::vector<int> umax(halfPatchSize + 2);

    int v, v0, vmax = cvFloor(halfPatchSize * std::sqrt(2.f) / 2 + 1);
    int vmin = cvCeil(halfPatchSize * std::sqrt(2.f) / 2);
    for (v = 0; v <= vmax; ++v)
        umax[v] = cvRound(std::sqrt((double)halfPatchSize * halfPatchSize - v * v));

    // Make sure we are symmetric
    for (v = halfPatchSize, v0 = 0; v >= vmin; --v){
        while (umax[v0] == umax[v0 + 1])
            ++v0;
        umax[v] = v0;
        ++v0;
    }

    allKeypoints.clear();
    std::vector<KeyPoint> keypoints;
    std::vector<int> counters(nlevels);
    keypoints.reserve(nfeaturesPerLevel[0]*2);

    for( level = 0; level < nlevels; level++ )
    {
        int featuresNum = nfeaturesPerLevel[level];
        Mat img = imagePyramid(layerInfo[level]);
        Mat mask = maskPyramid.empty() ? Mat() : maskPyramid(layerInfo[level]);

        // Detect FAST features, 20 is a good threshold
        {使用fast进行角点检测
			Ptr<FastFeatureDetector> fd = FastFeatureDetector::create(fastThreshold, true);
			fd->detect(img, keypoints, mask);
        }

        // 移除边界附近的角点特征
        KeyPointsFilter::runByImageBorder(keypoints, img.size(), edgeThreshold);
        // Keep more points than necessary as FAST does not give amazing corners
        KeyPointsFilter::retainBest(keypoints, scoreType == ORB_Impl::HARRIS_SCORE ? 2 * featuresNum : featuresNum);

        nkeypoints = (int)keypoints.size();
        counters[level] = nkeypoints;

        float sf = layerScale[level];
        for( i = 0; i < nkeypoints; i++ ){
            keypoints[i].octave = level;
            keypoints[i].size = patchSize*sf;
        }

        std::copy(keypoints.begin(), keypoints.end(), std::back_inserter(allKeypoints));
    }

    std::vector<Vec3i> ukeypoints_buf;
    nkeypoints = (int)allKeypoints.size();
    if(nkeypoints == 0){
        return;
    }
	
    Mat responses;
    // 使用Harris筛选FAST检测到的特征点
    if( scoreType == ORB_Impl::HARRIS_SCORE ) {
        HarrisResponses(imagePyramid, layerInfo, allKeypoints, 7, HARRIS_K);

        std::vector<KeyPoint> newAllKeypoints;
        newAllKeypoints.reserve(nfeaturesPerLevel[0]*nlevels);
        int offset = 0;
        for( level = 0; level < nlevels; level++ ) {
            int featuresNum = nfeaturesPerLevel[level];
            nkeypoints = counters[level];
            keypoints.resize(nkeypoints);
            std::copy(allKeypoints.begin() + offset,
                      allKeypoints.begin() + offset + nkeypoints,
                      keypoints.begin());
            offset += nkeypoints;

            //cull to the final desired level, using the new Harris scores.
            KeyPointsFilter::retainBest(keypoints, featuresNum);

            std::copy(keypoints.begin(), keypoints.end(), std::back_inserter(newAllKeypoints));
        }
        std::swap(allKeypoints, newAllKeypoints);
    }

    nkeypoints = (int)allKeypoints.size();
    {	//通过质心计算当前特征点的角度fastAtan2((float)m_01, (float)m_10);
        ICAngles(imagePyramid, layerInfo, allKeypoints, umax, halfPatchSize);
    }

    for( i = 0; i < nkeypoints; i++ ) {			//将每一层经过缩放过的角点还原到原图上
        float scale = layerScale[allKeypoints[i].octave];
        allKeypoints[i].pt *= scale;
    }
}

计算特征点的描述子:

static void
computeOrbDescriptors( const Mat& imagePyramid, const std::vector<Rect>& layerInfo,
                       const std::vector<float>& layerScale, std::vector<KeyPoint>& keypoints,
                       Mat& descriptors, const std::vector<Point>& _pattern, int dsize, int wta_k )
{
    int step = (int)imagePyramid.step;
    int j, i, nkeypoints = (int)keypoints.size();
    for( j = 0; j < nkeypoints; j++ ){
        const KeyPoint& kpt = keypoints[j];
        const Rect& layer = layerInfo[kpt.octave];
        float scale = 1.f/layerScale[kpt.octave];
        float angle = kpt.angle;
        angle *= (float)(CV_PI/180.f);
        float a = (float)cos(angle), b = (float)sin(angle);
        const uchar* center = &imagePyramid.at<uchar>(cvRound(kpt.pt.y*scale) + layer.y, cvRound(kpt.pt.x*scale) + layer.x);
        float x, y;
        int ix, iy;
        const Point* pattern = &_pattern[0];
        uchar* desc = descriptors.ptr<uchar>(j);

    #if 1//这个宏就是将点根据pattern旋转对应的角度得到新的xy
        #define GET_VALUE(idx) \
               (x = pattern[idx].x*a - pattern[idx].y*b, \
                y = pattern[idx].x*b + pattern[idx].y*a, \
                ix = cvRound(x), \
                iy = cvRound(y), \
                *(center + iy*step + ix) )
    #else
	//省略
    #endif

        if( wta_k == 2 ){
            for (i = 0; i < dsize; ++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;
            }
        }
        else if( wta_k == 3 ){//下面wta_k=3和4同理
            for (i = 0; i < dsize; ++i, pattern += 12){
                int t0, t1, t2, val;
                t0 = GET_VALUE(0); t1 = GET_VALUE(1); t2 = GET_VALUE(2);
                val = t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0);

                t0 = GET_VALUE(3); t1 = GET_VALUE(4); t2 = GET_VALUE(5);
                val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 2;

                t0 = GET_VALUE(6); t1 = GET_VALUE(7); t2 = GET_VALUE(8);
                val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 4;

                t0 = GET_VALUE(9); t1 = GET_VALUE(10); t2 = GET_VALUE(11);
                val |= (t2 > t1 ? (t2 > t0 ? 2 : 0) : (t1 > t0)) << 6;

                desc[i] = (uchar)val;
            }
        }
        else if( wta_k == 4 ){
            for (i = 0; i < dsize; ++i, pattern += 16){
                int t0, t1, t2, t3, u, v, k, val;
                t0 = GET_VALUE(0); t1 = GET_VALUE(1);
                t2 = GET_VALUE(2); t3 = GET_VALUE(3);
                u = 0, v = 2;
                if( t1 > t0 ) t0 = t1, u = 1;
                if( t3 > t2 ) t2 = t3, v = 3;
                k = t0 > t2 ? u : v;
                val = k;

                t0 = GET_VALUE(4); t1 = GET_VALUE(5);
                t2 = GET_VALUE(6); t3 = GET_VALUE(7);
                u = 0, v = 2;
                if( t1 > t0 ) t0 = t1, u = 1;
                if( t3 > t2 ) t2 = t3, v = 3;
                k = t0 > t2 ? u : v;
                val |= k << 2;

                t0 = GET_VALUE(8); t1 = GET_VALUE(9);
                t2 = GET_VALUE(10); t3 = GET_VALUE(11);
                u = 0, v = 2;
                if( t1 > t0 ) t0 = t1, u = 1;
                if( t3 > t2 ) t2 = t3, v = 3;
                k = t0 > t2 ? u : v;
                val |= k << 4;

                t0 = GET_VALUE(12); t1 = GET_VALUE(13);
                t2 = GET_VALUE(14); t3 = GET_VALUE(15);
                u = 0, v = 2;
                if( t1 > t0 ) t0 = t1, u = 1;
                if( t3 > t2 ) t2 = t3, v = 3;
                k = t0 > t2 ? u : v;
                val |= k << 6;

                desc[i] = (uchar)val;
            }
        }
        else
            CV_Error( Error::StsBadSize, "Wrong wta_k. It can be only 2, 3 or 4." );
        #undef GET_VALUE
    }
}

4.算法优缺点

优点:计算速度很快,比SIFT、SURF快

缺点:对噪声敏感,对光照变化敏感

二、SIFT算法原理

	SIFT,全称是Scale-Invariant Feature Transform,是与缩放无关的特征检测,也就是具有尺度不变性。SIFT算法可以说是传统CV领域中达到巅峰的一个算法,这个算法强大到即使对图片进行放缩、变形、模糊、明暗变化、光照变化、添加噪声,甚至是使用不同的相机拍摄不同角度的照片的情况下,SIFT都能检测到稳定的特征点,并建立对应关系。

1.特征点提取

  • 高斯函数尺度处理
    ​ 所谓尺度可以理解为图像模糊程度,使用不同的sigma值( σ {\sigma} σ​)的高斯核对图像进行卷积,达到对图像的尺度处理。
    G ( x , y , σ ) G(x,y,\sigma) G(x,y,σ)是高斯核函数, L ( x , y , σ ) L(x,y,\sigma) L(x,y,σ)是对应 σ \sigma σ值尺度处理下的尺度图像, I ( x , y ) I(x,y) I(x,y)​为原图像。以下公式可求出尺度图像:
    G ( x , y , σ ) = ( 1 2 π σ 2 ) e − ( x 2 + y 2 ) 2 σ 2 \Huge G(x,y,\sigma)=(\frac{1}{2\pi\sigma^2})^{e^\frac{-(x^2+y^2)}{2\sigma^2}} G(x,y,σ)=(2πσ21)e2σ2(x2+y2) L ( x , y , σ ) = G ( x , y , σ ) ∗ I ( x , y ) \Huge L(x,y,\sigma)=G(x,y,\sigma) *I(x,y) L(x,y,σ)=G(x,y,σ)I(x,y)
    ​ 所求的尺度图中, σ \sigma σ 越大图像越模糊

  • 图像金字塔
    ​ 相邻不同层级的 σ \sigma σ​ 值之间的高斯图像做差分(图像大小相同,模糊程度不同),此过程达到保留原图像重要信息的目的
    请添加图片描述

  • 构造DoG空间
    ​ 然后进行图像进行缩放,每一个缩放的高斯模糊金字塔差分结果称为DoG

请添加图片描述

  • 极值检测
    关键点由DoG空间的极值点组成,极值点从正数第二层到倒数第二层中寻找,某个点跟上一层、本层、下一层周围的26个点进行比较,符合极大值或极小值,此点便为极值点。
    请添加图片描述
  • 对离散空间进行插值,找到精确特征点
    ​ 在离散空间中寻找到的极值点并不一定是真正的极值点。我们用离散值插值的方式,将离散空间转换为连续空间,得到更加准确的极值点。同时去除低对比度的关键点和不稳定的边缘响应点。
    请添加图片描述

2.特征点描述

  • 关键点梯度求解
  • 利用关键点领域所有点的梯度方向,当作极值点方向

请添加图片描述
请添加图片描述

  • 对关键点周围像素进行方向统计、高斯加权
    取特征点周围8*8的像素进行梯度方向统计和高斯加权(蓝色圆圈代表高斯加权范围)。每4 * 4窗口生成8个方向,这样就生成了2 * 2 * 8的向量作为特征点的数学描述。
    SIFT算法采用4 * 4 * 8共128维向量作为特征点的描述子。最后通过描述子的欧式距离进行特征点匹配。

请添加图片描述

3.算法优缺点

优点:特征稳定,对旋转、尺度变换、亮度保持不变性,对视角变换、噪声也有一定程度的稳定性;

缺点:实时性不高,对于边缘光滑目标的特征点提取能力较弱

三、SURF算法原理

	SURF全称 Speeded Up Robust Features(加速稳健特征),是对SIFT算法的一种改进。

1.特征点提取

  • 使用方型滤波器取代SIFT算法中的高斯滤波器,借此提高运算

2.特征点描述

  • 使用哈尔小波转换的概念,利用积分图简化描述子的计算

3.算法优缺点

优点:相较SIFT算法快几倍,比SIFT更加稳健

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值