opencv特征匹配算法原理

这篇具有很好参考价值的文章主要介绍了opencv特征匹配算法原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、ORB算法原理

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

算法步骤:

1.特征点提取

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

opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能
( 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

opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能
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 σ​ 值之间的高斯图像做差分(图像大小相同,模糊程度不同),此过程达到保留原图像重要信息的目的
    opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能

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

opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能

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

2.特征点描述

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

opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能
opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能

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

opencv特征匹配算法原理,计算机视觉,opencv,算法,人工智能

3.算法优缺点

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

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

三、SURF算法原理

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

1.特征点提取

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

2.特征点描述

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

3.算法优缺点

优点:相较SIFT算法快几倍,比SIFT更加稳健文章来源地址https://www.toymoban.com/news/detail-777584.html

到了这里,关于opencv特征匹配算法原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入探究计算机视觉库OpenCV:开源视觉算法与应用详解

    计算机视觉作为人工智能领域的重要分支,为我们提供了丰富的工具和技术,帮助我们处理图像和视频数据。而OpenCV(Open Source Computer Vision Library)作为最受欢迎的开源计算机视觉库之一,为开发人员提供了丰富的功能和工具,用于处理图像和视频数据。本文将深入探讨Open

    2024年03月13日
    浏览(52)
  • 计算机毕设 python opencv 机器视觉图像拼接算法

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月07日
    浏览(59)
  • 多目标跟踪算法 实时检测 - opencv 深度学习 机器视觉 计算机竞赛

    🔥 优质竞赛项目系列,今天要分享的是 🚩 深度学习多目标跟踪 实时检测 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分 工作量:3分 创新点:4分 🧿 更多资料, 项目分享: https://gitee.com/dancheng-sen

    2024年02月05日
    浏览(66)
  • OpenCV处理图像和计算机视觉任务时常见的算法和功能

    当涉及到OpenCV处理图像和计算机视觉任务时,有许多常见的具体算法和功能。以下是一些更具体的细分: 图像处理算法: 图像去噪 :包括均值去噪、高斯去噪、中值滤波等,用于减少图像中的噪声。 直方图均衡化 :用于增强图像的对比度,特别适用于低对比度图像。 边缘

    2024年02月11日
    浏览(40)
  • 《计算机视觉中的深度学习》之目标检测算法原理

    参考:《计算机视觉中的深度学习》 目标检测的挑战: 减少目标定位的准确度 减少背景干扰 提高目标定位的准确度 目标检测系统常用评价指标:检测速度和精度 提高精度:有效排除背景,光照和噪声的影响 提高检测速度:精简检测流程,简化图像处理算法 算法概述:传

    2024年03月27日
    浏览(60)
  • 计算机设计大赛 深度学习人脸表情识别算法 - opencv python 机器视觉

    🔥 优质竞赛项目系列,今天要分享的是 🚩 深度学习人脸表情识别系统 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分 工作量:3分 创新点:4分 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/

    2024年02月21日
    浏览(161)
  • 从算法到应用:美颜SDK背后的计算机视觉原理解析

    美颜SDK能够通过一系列的算法让用户在拍摄自拍照或视频时可以轻松地获得更加美丽的效果。接下来,小编将深入探讨美颜SDK背后的计算机视觉原理,揭秘其实现的关键算法及其应用。 1.人脸检测与定位 人脸检测算法通常基于深度学习技术,如卷积神经网络(CNN)。通过训练

    2024年04月11日
    浏览(37)
  • 计算机视觉——飞桨深度学习实战-图像分类算法原理与实战

    图像分类是深度学习在视觉领域第一个取得突破性成果的任务。本章首先介绍了图像分类任务的发展历程与评价指标。然后分为三个角度分别介绍了在图像分类领域具有重要地位的三种模型。第一种是基于残差网络的模型,本章重点介绍了ResNet、DenseNet和DPN。第二种是基于T

    2024年02月02日
    浏览(54)
  • 计算机视觉:暗通道去雾算法的原理及python实现

    暗通道先验去雾算法是何恺明2009年发表在CVPR上的一篇论文,还获得了当年的CVPR最佳论文 原文链接:IEEE Xplore Full-Text PDF: 本篇论文提出了一种简单但是有效的图像先验条件——暗通道先验法从一幅输入图像中去雾。暗通道先验是一种对于大量户外有雾图像的统计结果,**它最

    2024年02月07日
    浏览(34)
  • 【计算机视觉 | 目标检测】术语理解7:二值匹配(Binary Matching),DETR中的Object query的理解,匈牙利算法,DETR中的二分图匹配

    当涉及到计算机视觉中的二值匹配(Binary Matching),它是一种用于比较和匹配二值图像的技术。二值图像由黑色和白色像素组成,每个像素只有两种可能的取值。二值匹配的目标是确定两个二值图像之间的相似度或匹配度。 以下是几种常见的二值匹配方法: 汉明距离:通过

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包