特征检测的不变性是一个重要概念,虽然方向不变性(即使图像旋转也能检测到相同特征点)能够被简单特征点检测器(例如 FAST 特征检测器等)处理,但难以实现在图像尺度改变时特征保持不变。为了解决这一问题,在计算机视觉中引入了尺度不变特征的概念。
无论对象是在哪个比例下拍摄的,不仅要对关键点进行一致的检测,而且还要计算与每个检测到的特征点相关联的尺度因子。理想情况下,对于在两个不同图像上以不同尺度表征的同一对象点,计算出的尺度因子的比率等于它们各自尺度的比率。已经提出了多种尺度不变的特征,本节将介绍 SURF
(Speeded Up Robust Features
) 特征,它不仅具有尺度不变性,而且还具有很高的计算效率。
SURF
特征检测器在 OpenCV
中使用 cv::SURF
函数实现。
(1) 可以通过 cv::FeatureDetector
使用 SURF
特征检测器:
cv::Ptr<cv::xfeatures2d::SurfFeatureDetector> ptrSURF = new cv::xfeatures2d::SurfFeatureDetector(2000.0);
ptrSURF->detect(image, keypoints);
(2) 为了绘制这些特征,使用带有 DRAW_RICH_KEYPOINTS
标志的 cv::drawKeypoints
函数,以便可视化相关的尺度因子:
cv::drawKeypoints(image, keypoints, featureImage, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
检测到的特征图像如下所示:
使用 DRAW_RICH_KEYPOINTS
标志绘制的关键点圆的大小与每个特征的计算尺度成正比。SURF
算法还为每个特征关联了一个方向,以保证尺度不变性,该方向由每个圆内的径向线表示。
检测在不同尺度下,拍摄的同一物体照片,特征检测结果如下:
通过观察两幅图像上检测到的关键点,可以看出对应圆圈大小的变化往往与尺度的变化成正比。例如,在这两个图像中人物面部均检测到 SURF
特征,并且两个对应的圆圈(不同大小)包含相同的视觉元素,当然,并非所有特征都是如此。
可以使用高斯滤波器估计图像的导数,这些滤波器使用 σ σ σ 参数定义内核, σ σ σ 参数用于构造滤波器的高斯函数的方差,且隐式地定义了评估导数的尺度,具有较大 σ σ σ 值的滤波器可以平滑图像的精细细节。
例如,如果我们使用不同尺度的高斯滤波器计算给定图像点的拉普拉斯算子,则会获得不同的值。观察不同尺度因子下滤波器响应的变化,我们可以得到一条在 σ σ σ 值处达到最大值的曲线。如果我们为以两个不同尺度拍摄的同一物体的两幅图像提取此最大值,则这两个最大值的比率将对应于拍摄图像的尺度比率,这是尺度不变特征提取过程的核心。也就是说,尺度不变特征应该被检测为图像和尺度空间(在不同尺度上应用导数滤波器获得)中的局部最大值。
SURF
通过如下操作实现:首先,为了检测特征,在每个像素处计算 Hessian
矩阵,该矩阵测量函数的局部曲率:
H ( x , y ) = [ ∂ 2 I ∂ x 2 ∂ 2 I ∂ x ∂ y ∂ 2 I ∂ x ∂ y ∂ 2 I ∂ y 2 ] H(x,y)=\left[ \begin{array}{ccc} \frac {\partial^2I} {\partial x^2}& \frac {\partial^2I} {\partial x\partial y}\\ \frac {\partial^2I} {\partial x\partial y}& \frac {\partial^2I} {\partial y^2} \\\end{array}\right] H(x,y)=[∂x2∂2I∂x∂y∂2I∂x∂y∂2I∂y2∂2I]
该矩阵的行列式提供了曲率的强度,因此,我们将角点定义为具有高局部曲率(即在多个方向上的变化较高)的图像点。由于矩阵由二阶导数组成,因此可以使用不同尺度的高斯核的拉普拉斯算子来计算该矩阵,则 Hessian
将变为三个变量的函数 H ( x , y , σ ) H(x,y,σ) H(x,y,σ)。因此,当 Hessian
的行列式在空间和尺度空间都达到局部最大值(需要进行 3x3x3
非极大值抑制)时,就构成了一个尺度不变特征。为了被视为有效点,该行列式必须大于 cv::SURF
类的构造函数中的第一个参数指定的最小值。
然而,在不同尺度上计算这些导数的计算成本很高,SURF
算法的目标是使该过程尽可能高效。其通过使用仅涉及几个整数加法的近似高斯核来实现的,它们具有以下结构:
左侧的核用于估计混合二阶导数,而右侧的核用于估计垂直方向的二阶导数,旋转第二个核可用于估计了水平方向的二阶导数。最小的核大小为 9x9
像素,对应于 σ ≈ 1.2 σ≈1.2 σ≈1.2,为了获得尺度空间表示,需要不断增加核大小,应用的滤波器的确切数量可以由 SURF
类的附加参数指定。默认情况下,使用 12
种不同大小的内核(最大为 99x99
),使用积分图像保证了每个滤波器运算可以通过仅使用三个与滤波器大小无关的加法来计算。
一旦确定了局部最大值,通过在尺度和图像空间中的插值获得每个检测到的兴趣点的精确位置,最后获得一组以亚像素精度定位的特征点,并且包含与之相关联的尺度值。
SURF
算法是另一种尺度不变特征检测器 SIFT
(Scale-Invariant Feature Transform
) 的变体。SIFT
同样将特征描述为图像和尺度空间中的局部最大值,但其使用拉普拉斯滤波器响应而非 Hessian
行列式,拉普拉斯算子是使用高斯滤波器的差异在不同尺度(即增加 σ σ σ 的值)计算的。为了提高效率, σ σ σ 的值每增加一倍,图像的大小就减少两倍,每个金字塔级别对应一个 octave
,每个尺度是一个层,通常每个 octave
含有四层。下图说明了具有两个 octave
的金字塔,其中第一个 octave
的四个高斯滤波图像产生三个 DoG
层:
OpenCV
中包含有检测这些特征的类,调用方式与 SURF
类似:
// 构建 SIFT 特征检测器对象
detector = new cv::SIFT();
// 检测 SIFT 特征
detector->detect(image,keypoints);
在以上代码中,我们使用默认参数构建检测器,但我们也可以指定所需 SIFT
点的数量、每个 octave
的层数以及 σ σ σ 的初始值。调用结果与使用 SURF
获得的结果类似:
由于特征点的计算基于浮点核,SIFT
通常被认为在空间和尺度上的特征定位更准确,但它的计算成本也更高。
头文件 (harrisDetector.h
) 完整代码参考 Harris 特征点检测一节,主函数文件 (surfCorners.cpp
) 完整代码如下所示:
#include
#include
#include
#include
#include
#include
#include "harrisDetector.h"
int main() {
// Harris
cv::Mat image = cv::imread("3.png", 0);
if (!image.data) return 0;
std::vector<cv::KeyPoint> keypoints;
// SURF
keypoints.clear();
cv::Ptr<cv::xfeatures2d::SurfFeatureDetector> ptrSURF = cv::xfeatures2d::SurfFeatureDetector::create(2000.0);
ptrSURF->detect(image, keypoints);
cv::Mat featureImage;
cv::drawKeypoints(image, keypoints, featureImage, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("SURF");
cv::imshow("SURF", featureImage);
std::cout << "Number of SURF keypoints: " << keypoints.size() << std::endl;
// 加载另一张图像
image = cv::imread("4.png", 0);
ptrSURF->detect(image, keypoints);
cv::drawKeypoints(image, keypoints,featureImage, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("SURF (2)");
cv::imshow("SURF (2)", featureImage);
// SIFT
image = cv::imread("3.png", 0);
keypoints.clear();
cv::Ptr<cv::xfeatures2d::SiftFeatureDetector> ptrSIFT = cv::xfeatures2d::SiftFeatureDetector::create();
ptrSIFT->detect(image, keypoints);
cv::drawKeypoints(image, keypoints, featureImage, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("SIFT");
cv::imshow("SIFT",featureImage);
std::cout << "Number of SIFT keypoints: " << keypoints.size() << std::endl;
cv::waitKey();
return 0;
}
OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解