Advertisement

【图像特征】【OPENCV】SIFT,SURF,ORB,Harris,FAST,量化匹配度

阅读量:

图像特征

本章旨在介绍特征点理论及相关OpenCV的应用方法。如存在不足或遗漏之处,请随时指正。如能对你有所帮助,请不吝赐教。xue.2018.4.9

通过深入理解OpenCVTutorial>>feature2d module.2D Features framework中的内容, 可以掌握OpenCV实现特征点检测的核心原理. 2. [OpenCV-Python教程]中的《特征检测与描述》章节详细介绍了这一技术.

目录:

图像关键特征
1.0 SIFT(Scale-invariant feature transform)算法作为一种高效的尺度不变特征提取方法
1.2 引用文献中详细阐述了多分辨率尺度空间理论基础
1.3 极值点检测方法作为SIFT算法的核心模块之一
1.4 精确定位过程()实现了关键点位置的高精度计算
1.5 方向赋值过程为各关键点赋予稳定的方向特性
1.6 特征点描述器(Keypoint Descriptor)用于表征关键区域的局部特征信息
1.7 特征匹配算法建立了不同尺度下的对应关系
1.8 OpenCV源码库提供了标准化实现版本供工程应用参考

  • 2.0 Harris算法
  • 3.0 快速实现方案
    • 3.1 算法原理:

    • 3.2 基于机器学习的角点检测机制 Machine Learning-based Corner Detection Mechanism

    • 3.3 基于非最大值抑制的特征消除 Non-maximal Suppression-based Feature Elimination

    • 3.4 OpenCV源代码 OpenCV Source Code

      • 4.0 ORB(留坑待填)
      • 5.0 SURF(留坑代填)
      • 6.0 量化匹配度
        • 6.1 量化思路
        • 6.2 代码:

环境:
Win10.x64
VS2015
Opencv3.4

该参考文档由Opencv-contrib提供,并附有详细的代码示例说明。


1.0 SIFT(Scale-invariant feature transform)

图像的尺度变换(缩放)、旋转变换以及明暗(亮度)变化均保持一致性。

应用范围包括以下几个方面:
首先是物体识别技术;
其次是机器人路径规划与环境建模;
然后是图像拼接方法;
接着是三维建模算法;
此外还有手势识别系统;
最后涉及影像追踪技术和行为分析模块。

1.1 参考文献

原理参考:
1,SIFT算法详解
2,SIFT特征提取分析
3. SIFT原理与源码分析:DoG尺度空间构造
4.Introduction to SIFT (Scale-Invariant Feature Transform)
5.sift

1.2 尺度空间和多分辨率

尺度空间:

  1. 分析图像在不同尺度下的特征表示。
  2. 无论在哪个尺度下均能检测到相同的关键点特征。
  3. 其主要作用即是整合并反映图像在各个尺度下的特征信息。

尺度空间表达 采用高斯核

  1. 高斯核仅构建多尺度空间的唯一可能。
  2. 高斯卷积仅作为表现尺度空间的一种形式存在。

多分辨率金字塔表示
构建图像金字塔通常涉及两个主要步骤:

  1. 使用低通滤波操作生成平滑后的图像
  2. 对生成的平滑图像进行降采样处理以实现尺寸缩减

尺度空间表达是通过不同高斯核的平滑卷积处理,在各个尺度层次中均保持相同的分辨率
金字塔式的多分辨率结构中各层级的分辨率依次按固定比例递减

1.3 Scale-space Extrema Detection

在应用Laplace算子于高斯金字塔时产生的Log(Laplace of Gaussian)金字塔计算开销较大;为了提高效率,SIFT采用了类似于Difference of Gaussian的高斯差分来近似计算LOG特征。

这里写图片描述

注: 每个octave含有相同分辨率,不同高斯核\sigmak\sigma

这里写图片描述

建立完DOG之后,在上图中标记为黑X的那个像素(或称为象素),会与其周围的八个相邻象素进行比较,并与上下不同尺度下的九个象素进行对比。如果该区域是一个局部极值,则认为它是当前尺度空间中的特征点

这里写图片描述

不同的高斯尺度组

根据经验建议:octaves =4,number of scale levels =5, \sigma =1.6,k=\sqrt(2)

1.4Keypoint Localization(精确化)

 1 去除较弱的极值点:在获得特征点之后,在该区域内提取更为精炼的信息(这些极值点均来自离散空间搜索结果)。采用泰勒展开的方法解析该区域,并去除数值低于contrastThresholdValue的所有候选区域。

原文中contrastThresholdValue =0.04

  1. 去除边缘效应的影响:DOG算法表现出对边界区域较强的响应特性;这些位于边界上的点并不构成稳定的特征点;该算法利用2\times 2 Hessian矩阵来计算主要轮廓。

其中定义α为λ的最大值, β为λ的最小值;
Tr(H)=Dxx+Dyy=α + β
Det(H) = DxxDyy−(Dyy)²=αβ
定义γ=α/β, 则有:
((Tr(H))²)/Det(H)=( (α+β) )²/( αβ )=((γ+1))²/γ
通过((γ+1)^2)/γ的比值超过某个阈值来筛选特征点.

原文中edgeThresholdValue =10

1.5 Orientation Assignment(方向赋值)

特定区域内图像像素在特定方向上的影响由关键点决定。 为了对这一区域内的图像特征进行提取, 将该范围内的图像进行\alpha尺度下的高斯平滑处理, 并通过以下公式计算梯度幅值m(x,y)和角度\theta(x,y):
m(x,y)=\sqrt{(L(x+1,y)-L(x-1,y))^2+(L(x,y+1)-L(x,y-1))^2}
\theta(x,y)=arctan\left(\frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)}-\frac{L(x-1,y)}}\right)
随后, 将计算出的角度结果划分为8个子区域, 并对每个子区域的幅值进行叠加操作, 如下图所示

这里写图片描述

峰值:主方向
峰值80%:辅方向,一个监测点可以检测到多个方向。

Lowe论文指出15%的关键点具有多方向
此时的关键点信息有(x,y,\sigma,\theta)

1.6 Keypoint Descriptor特征点描述

I have established the keypoint descriptor by analyzing an area centered on the keypoint that encompasses a 16 \times 16 grid. This area has been partitioned into sixteen 4 \times 4 sub-regions. For each sub-region, an orientation histogram with eight bins has been generated. These features are compiled into a vector representation known as the keypoint descriptor, which results in a total of 128 distinct feature descriptors. To enhance the method's resilience against variations in lighting conditions and rotational transformations, additional preprocessing steps have been incorporated.

这里写图片描述

1.7Keypoint Matching

对比分析两幅图像特征点及其邻域像素分布情况;然而,在某些情况下(即有时),第二近邻与第一近邻之间的差异并不显著;因此,在这种情形下(即当满足一定条件时),我们可以通过计算两组最近距离之比来判断匹配的有效性;若比值超过0.8,则判定为无效匹配;这样处理后能够有效减少误配率(约5%),而正确识别率则能达到95%。

1.8 OPencv源码

复制代码
    #include "stdafx.h"
    #include <opencv2\xfeatures2d\nonfree.hpp>
    #include <iostream> 
    #include<vector>
    #include"opencv2\core\core.hpp"
    #include"opencv2\xfeatures2d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    using namespace cv;
    using namespace std;
    
    int main()
    {
    Mat src0 = imread("C:\ titleTemp.bmp",IMREAD_GRAYSCALE);
    Mat src1 = imread("C:\ test1.jpg",IMREAD_GRAYSCALE);
    
    if (src0.data == NULL || src1.data == NULL)
    {
        cout << "image no exist!" << endl;
        return -1;
    }
    
    Ptr<Feature2D> sift = xfeatures2d::SIFT::create();
    vector<KeyPoint> keypoint0,keypoint1,keypointImg;
    
    Mat descriptor1, descriptor0;
    
    sift->detect(src0, keypoint0);
    sift->detect(src1, keypoint1);
    sift->compute(src0, keypoint0, descriptor0);
    sift->compute(src1, keypoint1, descriptor1);
    
    BFMatcher matcher;
    matcher.BRUTEFORCE_SL2;
    vector<DMatch> matches;
    
    matcher.match(descriptor0, descriptor1, matches);
    //排序
    sort(matches.begin(), matches.end());
    
    vector<KeyPoint> imgPt1, imgPt2;
    vector<DMatch> matchesVoted;
    
    for (int idx=0;idx <10;idx++)
    {
        DMatch dmatch;
        dmatch.queryIdx = idx;
        dmatch.trainIdx = idx;
    
        matchesVoted.push_back(dmatch);
        imgPt1.push_back(keypoint0[matches[idx].queryIdx]);
        imgPt2.push_back(keypoint1[matches[idx].trainIdx]);
    }
    
    Mat img_matches;
    std::vector< DMatch > emptyVec;
    drawMatches(src0, imgPt1, src1, imgPt2, matchesVoted, img_matches, DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imshow("SIFT_Match_Image", img_matches);
    waitKey();
    return 0;
    }

2.0 Harris

原理参考: [1]OpenCV Tutorials

代码出自OpenCV Tutorials:

复制代码
    Mat src_gray = imread("C:\titleTemp.bmp", IMREAD_GRAYSCALE);
    
    Mat dst, dst_norm, dst_norm_scaled;
    dst = Mat::zeros(src_gray.size(), CV_32FC1);
    
    /// Detector parameters
    int blockSize = 2;
    int apertureSize = 3;
    double k = 0.04;
    
    /// Detecting corners
    cornerHarris(src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT);
    
    /// Normalizing
    normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
    convertScaleAbs(dst_norm, dst_norm_scaled);
    
    /// Drawing a circle around corners 
    for (int j = 0; j < dst_norm.rows; j++)
    {
        for (int i = 0; i < dst_norm.cols; i++)
        {
            if ((int)dst_norm.at<float>(j, i) > 128)
            {
                circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0);
            }
        }
    }
    /// Showing the result
    namedWindow("Harris", CV_WINDOW_AUTOSIZE);
    imshow("Harris", dst_norm_scaled);
    waitKey();
    
    return 0;

3.0 Fast

3.1 原理:

这里写图片描述

Step 1:如上图所示,在图中标注的任意一点p处(记作I(p)),其对应的颜色值为该区域内的平均颜色值;以该中心点p为中心、半径为3画圆圈区域,在此区域内共有16个像素。
Step 2:如果这连续排列的16个像素中包含至少连续排列着12个相邻且具有相同颜色值(即I(p)-t)的情况,则该中心区域即被判定为一个角点。
Step 3:为了提高计算效率的一种快速尝试方法是通过排除大部分非角点多边形区域中的非候选区域(例如排除掉那些不含足够数量特征明显的角落区域),这里仅考虑四个关键位置(编号分别为1,9,5,13):当且仅当这两个位置上的颜色值均大于或小于Step 2中的阈值时才检查第三个和第四个位置;如果上述四个位置中有三个满足条件(大于或小于),则将其标记为候选角点;对于这些候选角点,则需进一步测试圆周上所有相关的位置。
上述算法运行效率较高,在实际应用中表现良好:
第①条优点:能够有效地识别出图像中的关键拐折处;
第②条优点:能够较为准确地定位出不同物体间的接触面;
第③条优点:能够在有限的空间内获取足够的代表性样本;
然而也存在一些不足之处:
第一条缺点:由于该方法对图像进行排序后才进行角点判断;因此在某些特殊情况下可能会导致误判;
第二条缺点:当图像中有多个特征时(如多个边缘交界处),可能会出现检测结果过于密集的情况;
第三条缺点:当检测到的有效特征数少于设定阈值时(通常设为5~7),可能会导致误报的发生;
第四条缺点:由于该算法对图像进行排序后才进行角点判断;因此可能导致某些边缘处的实际角度信息被忽略;

3.2 机器学习一个角点探测器Machine Learning a Corner Detector

Step 1: 选择一系列图像,并采用fast计算器提取特征点(feature points)。
Step 2: 对于每一个特征点, 将其周围的16个关键点存储在一个向量P中。
Step 3: 考虑一个特定的特征点x, 其周围的16个相邻区域包括以下三种状态:

这里写图片描述

Step4中,则向量P由三个子集Pd、Ps、Pb构成,并设定一个布尔变量Kp,true作为corner。
Step5中应用决策树ID3来判断向量p。

3.3 非极大值抑制Non-maximal Suppression

在多个相邻角点中选择最优的一组角点属于另一个问题,在该问题中可采用极大值抑制方法来实现;

  1. 定义一个得分函数(score function)S_V;其中V代表在圆周上与给定点p对应的像素差绝对值之和;
  2. 比较两个相邻特征点对应的V值大小;当某一点处的V值较低时,则将其标记为待排除节点;

3.4 OPencv源码

复制代码
    Mat src0 = imread("C:\ Users\ Xue.Bicycle\ Desktop\ titleTemp.bmp", IMREAD_GRAYSCALE);
    Mat src1 = imread("C:\ Users\ Xue.Bicycle\ Desktop\ test1.jpg", IMREAD_GRAYSCALE);
    
    if (src0.data == NULL || src1.data == NULL)
    {
        cout << "image no exist!" << endl;
        return -1;
    }
    //fast
    Ptr<FastFeatureDetector> fast = FastFeatureDetector::create(0);
    vector<KeyPoint> keypoint0, keypoint1;
    fast->detect(src0, keypoint0);
    fast->detect(src1, keypoint1);
    
    Ptr<xfeatures2d::SiftDescriptorExtractor> extractor = xfeatures2d::SiftDescriptorExtractor::create();
    Mat descriptor1, descriptor0;
    extractor->compute(src0, keypoint0, descriptor0);
    extractor->compute(src1, keypoint1, descriptor1);
    
    BFMatcher matcher;
    matcher.BRUTEFORCE_SL2;
    vector<DMatch> matches;
    
    matcher.match(descriptor0, descriptor1, matches);
    //排序
    sort(matches.begin(), matches.end());
    
    vector<KeyPoint> imgPt1, imgPt2;
    vector<DMatch> matchesVoted;
    
    if (0 == matches.size())
        return 0;
    
    for (int idx = 0; idx <10; idx++)
    {
        DMatch dmatch;
        dmatch.queryIdx = idx;
        dmatch.trainIdx = idx;
    
        matchesVoted.push_back(dmatch);
        imgPt1.push_back(keypoint0[matches[idx].queryIdx]);
        imgPt2.push_back(keypoint1[matches[idx].trainIdx]);
    }
    
    Mat img_matches;
    std::vector< DMatch > emptyVec;
    drawMatches(src0, imgPt1, src1, imgPt2, matchesVoted, img_matches, DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    imshow("SIFT_Match_Image", img_matches);
    waitKey();
    return 0;

4.0 ORB(留坑待填)

5.0 SURF(留坑代填)

6.0 量化匹配度

量化分析的基础:利用特征识别技术完成图像分类任务。收集并整理一批单据文件,并记录其名称以便后续处理。如若发现任何问题,请指导在下!衷心感谢!

6.1 量化思路

Step1: 通过配准局部图像ImgTitle来实现整幅图像Img的整体配准效果。由于单凭匹配点数量难以充分反映配准质量,在实际应用中发现若两幅图像相互配准,则其特征点在x,y方向上的差值变化具有一定的规律性特性即理想状态下应接近于零;
Step2: 在相同分辨率条件下对相邻特征点之间的差值Difx、Dify与配准后的特征点进行归一化处理;
Step3: 在相同分辨率条件下对各向异性的差值Difx、Dify以及图像与其配准图之间的差异进行分析并量化相对分布情况。

6.2 代码:

复制代码
    //keypointOri,keypoint分别为Img,ImgTitle的特征点。matches为暴力匹配法的结果。(接上述代码)
    vector<int> vScore, vScore1, vScoreOri, vScoreOri1;
    
    if (matches.size() <=1)
        return -1;
    
    int reNScore = 0;
    for (auto itr = matches.begin();itr != matches.end() - matches.size()/2;itr++)
    {
        Point2f p2f0 = keypointOri[itr->trainIdx].pt;
        Point2f p2f1 = keypointOri[(itr+1)->trainIdx].pt;
    
        vScoreOri.push_back(p2f0.x - p2f1.x);
        vScoreOri1.push_back(p2f0.y - p2f1.y);
    
        p2f0 = keypoint[itr->queryIdx].pt;
        p2f1 = keypoint[(itr + 1)->queryIdx].pt;
    
        vScore.push_back(p2f0.x - p2f1.x);
        vScore1.push_back(p2f0.y - p2f1.y);
    }
    float nScore = 0;
    float nScore_ = 0;
    
    for (int idx = 0; idx < std::min((int)10,(int)vScore.size()/10); idx++)
    {
        nScore += abs(vScore[idx]);
        nScore_ += abs(vScoreOri[idx]);
    }
    
    float fR;
    if (nScore == 0 || nScore_ == 0)
        return -1;
    else
    {
        fR = nScore / nScore_;
    }
    
    nScore = nScore_ = 0;
    for (int idx = 0; idx < vScore.size()/2; idx++)
    {           
        nScore += abs(vScoreOri[idx]*fR - vScore[idx]);
        nScore_ += abs(vScoreOri1[idx]*fR - vScore1[idx]);
    
    }
    return (nScore*0.8 + nScore_*0.2)/matches.size();

全部评论 (0)

还没有任何评论哟~