目录
一、Harris角点检测器
1.角点检测算法
(1)角点(cornerpoints):
- 局部窗口沿各方向移动,均产生明显变化的点
- 图像局部曲率突变的点
典型的角点检测算法:
- Harris角点检测
- CSS角点检测
Harris角点检测算法(也称Harris & Stephens角点检测器)是一个极为简单的角点检测算法。该算法的主要思想是:如果像素周围显示存在多于一个方向的边,我们认为改点为兴趣点。该点就称为角点。
(2)一个好的角点检测算法需要具备以下几点:
- 检测出图像中“真实的”角点
- 准确的定位性能
- 很高的稳定性
- 具有对噪声的鲁棒性
- 具有较高的鲁棒性
- 具有较高的计算效率
(3)角点检测算法的基本原理:
算法的基本思想是使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
(4)Harris 角点检测算法分为以下三步:
- 当窗口(局部区域)同时向 x (水平)和 y(垂直) 两个方向移动时,计算窗口内部的像素值变化量 E(x,y) ;
- 对于每个窗口,都计算其对应的一个角点响应函数 R;
- 然后对该函数进行阈值处理,如果 R>threshold,表示该窗口对应一个角点特征。
2.Harris检测的数学模型
建立数学模型,确定哪些窗口会引起较大的灰度值变化。 让一个窗口的中心位于灰度图像的一个位置(x, y),这个位置的像素灰度值为 I(x, y) ,如果这个窗口分别向 x 和 y 方向移动一个小的位移 u 和 v ,到一个新的位置 (x+u, y+v) ,这个位置的像素灰度值就是 I(x+u, y+v)。
则有:
就是窗口移动引起的灰度值的变化值。
设 w(x, y) 为位置 (x, y) 处的窗口函数,表示窗口内各像素的权重,最简单的就是把窗口内所有像素的权重都设为1,即一个均值滤波核。
当然,也可以把 w(x, y) 设定为以窗口中心为原点的高斯分布,即一个高斯核。如果窗口中心点像素是角点,那么窗口移动前后,中心点的灰度值变化非常强烈,所以该点权重系数应该设大一点,表示该点对灰度变化的贡献较大;而离窗口中心(角点)较远的点,这些点的灰度变化比较小,于是将权重系数设小一点,表示该点对灰度变化的贡献较小。
则窗口在各个方向上移动[公式] 所造成的像素灰度值的变化量公式如下:
将图像窗口平移[u, v]产生灰度变化E(u, v):
若窗口内是一个角点,则E(u,v)的计算结果将会很大。
为了提高计算效率,对上述公式进行简化,利用泰勒级数展开来得到这个公式的近似形式:
3.Harris角点求解
(1)通过特征值
如果特征值在两个方向都比较大就是角点;如果特征值在一个方向比较大是边;如果特征值都很小就是平面。
可以把R看成旋转因子,其不影响两个正交方向的变化分量。经对角化处理后,将两个正交方向的变化分量提取出来,就是 λ1 和 λ2(特征值)。
(2)通过R响应
因为特征值计算和判断起来比较麻烦,所以提出了和特征值对应的R相应进行简便的计算。
现在我们已经得到 E(u,v)的最终形式,别忘了我们的目的是要找到会引起较大的灰度值变化的那些窗口。
灰度值变化的大小则取决于矩阵M,M为梯度的协方差矩阵。在实际应用中为了能够应用更好的编程,所以定义了角点响应函数R,通过判定R大小来判断像素是否为角点。
计算每个窗口对应的得分(角点响应函数R定义):
其中 det M = λ1λ2 是矩阵的行列式, trace M = λ1 + λ2 是矩阵的迹。
λ1 和 λ2 是矩阵 M 的特征值, k 是一个经验常数,在范围 (0.04, 0.06) 之间。
R的值取决于M的特征值,对于角点 |R| 很大,平坦的区域 |R| 很小,边缘的 R 为负值。
因为特征值 λ1 和 λ2 决定了 R 的值,所以我们可以用特征值来决定一个窗口是平面、边缘还是角点:
- 平面:该窗口在平坦区域上滑动,窗口内的灰度值基本不会发生变化,所以 |R| 值非常小,在水平和竖直方向的变化量均较小,即 Ix和 Iy都较小,那么 λ1 和 λ2 都较小;
- 边缘:|R|值为负数,仅在水平或竖直方向有较大的变化量,即 Ix和 Iy只有一个较大,也就是 λ1>>λ2 或 λ2>>λ1;
- 角点:|R| 值很大,在水平、竖直两个方向上变化均较大的点,即 Ix和 Iy 都较大,也就是 λ1 和 λ2 都很大。
Harris 角点检测的结果是带有这些分数 R 的灰度图像,设定一个阈值,分数大于这个阈值的像素就对应角点。
如下图所示:
4.代码实现
在opencv中有提供实现 Harris 角点检测的函数 cv2.cornerHarris,我们直接调用的就可以,非常方便。
函数原型:
cv2.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])
对于每一个像素 (x,y),在 (blockSize x blockSize) 邻域内,计算梯度图的协方差矩阵 M(x,y),然后通过上面第二步中的角点响应函数得到结果图。图像中的角点可以为该结果图的局部最大值。
即可以得到输出图中的局部最大值,这些值就对应图像中的角点。
参数解释:
- src - 输入灰度图像,float32类型
- blockSize - 用于角点检测的邻域大小,就是上面提到的窗口的尺寸
- ksize - 用于计算梯度图的Sobel算子的尺寸
- k - 用于计算角点响应函数的参数k,取值范围常在0.04~0.06之间
import cv2
from matplotlib import pyplot as plt
import numpy as np
# detector parameters
block_size = 3
sobel_size = 3
k = 0.06
image = cv2.imread('Figure\harris_corner.png')
print(image.shape)
height = image.shape[0]
width = image.shape[1]
channels = image.shape[2]
print("width: %s height: %s channels: %s" % (width, height, channels))
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# modify the data type setting to 32-bit floating point
gray_img = np.float32(gray_img)
# detect the corners with appropriate values as input parameters
corners_img = cv2.cornerHarris(gray_img, block_size, sobel_size, k)
# result is dilated for marking the corners, not necessary
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# dst = cv2.dilate(corners_img, kernel)
dst = corners_img
# Threshold for an optimal value, marking the corners in Green
# image[corners_img>0.01*corners_img.max()] = [0,0,255]
for r in range(height):
for c in range(width):
pix = dst[r, c]
if pix > 0.05 * dst.max():
cv2.circle(image, (c, r), 5, (0, 0, 255), 0)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
5.结果分析
Harris角点检测器特性:
- 阈值决定角点的数量
- 光照不变性:Harris角点检测算子对亮度和对比度的变化不敏感(光照不变性): 在进行Harris角点检测时,使用了微分算子对图像进行微分运算,而微分运算对图像密度的拉升或收缩和对亮度的抬高或下降不敏感。换言之,对亮度和对比度的仿射变换并不改变Harris响应的极值点出现的位置,但是,由于阈值的选择,可能会影响角点检测的数量。
- 旋转不变性:Harris角点检测算子具有旋转不变性 : Harris角点检测算子使用的是角点附近的区域灰度二阶矩矩阵。而二阶矩矩阵可以表示成一个椭圆,椭圆的长短轴正是二阶矩矩阵特征值平方根的倒数。当特征椭圆转动时,特征值并不发生变化,所以判断角点响应值也不发生变化,由此说明Harris角点检测算子具有旋转不变性。
- 不具有尺度不变性:Harris角点检测算子不具有尺度不变性 尺度的变化会将角点变为边缘,或者边缘变为角点,Harris的理论基础并不具有尺度不变性。
二、SIFT(尺度不变特征变换)
1.SIFT特征简介
SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。
SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。
SIFT算法可以解决的问题:
- 目标的旋转、缩放、平移(RST)
- 图像仿射/投影变换(视点viewpoint)
- 弱光照影响(illumination)
- 部分目标遮挡(occlusion)
- 杂物场景(clutter)
- 噪声
2.SIFT算法实现步骤
特征检测:
(1)尺度空间极值检测: 搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。
(2)关键点定位: 在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
(3)方向确定: 基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
(4)关键点描述: 在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。
特征匹配:
(1)SIFT特征的生成: 即从多幅图像中提取对尺度缩放、旋转、亮度变化无关的特征向量。
(2)SIFT特征向量的匹配。
SIFT特征的生成一般包括以下几个步骤:
(1)构建尺度空间,检测极值点,获得尺度不变性。
(2)特征点过滤并进行精确定位。
(3)为特征点分配方向值。
关键点主方向:极值点周围区域梯度直方图的主峰值也是特征点方向。
关键点辅方向:在梯度方向直方图中,当存在另一个相当于主峰值80%能量的峰值时,则将这个方向认为是该关键点的辅方向。这可以增强匹配的鲁棒性,Lowe的论文指出大概有15%关键点具有多方向,但这些点对匹配的稳定性至为关键。
(4)生成特征描述子。
下图是一个SIFT描述子事例。其中描述子由2×2×8维向量表征,也即是2×2个8方向的方向直方图组成。左图的种子点由8×8单元组成。每一个小格都代表了特征点邻域所在的尺度空间的一个像素,箭头方向代表了像素梯度方向,箭头长度代表该像素的幅值。然后在4×4的窗口内计算8个方向的梯度方向直方图。绘制每个梯度方向的累加可形成一个种子点,如右图所示:一个特征点由4个种子点的信息所组成。
当两幅图像的SIFT特征向量生成以后,下一步就可以采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图1的某个关键点,通过遍历找到图像2中的距离最近的两个关键点。在这两个关键点中,如果最近距离除以次近距离小于某个阈值,则判定为一对匹配点。
- 穷举匹配:
- KD树:
采用kd树的数据结构来完成搜索。搜索的内容是以目标图像的关键点为基准,搜索与目标图像的特征点最邻近的原图像特征点和次邻近的原图像特征点。Kd树是一个平衡二叉树。
3.关键点检测的相关概念
(1)哪些点是SIFT中要查找的关键点(特征点)?
这些点是一些十分突出的点,不会因光照、尺度、旋转等因素的改变而消失,比如角点、边缘点、暗区域的亮点以及亮区域的暗点。假定两幅图像中有相同的景物,那么使用某种算法分别提取各自的特征点,这些点之间会有相互对应的匹配关系。
根据归纳,我们可以看出SIFT特征点希望选出具有下述不变性的点:
(2)什么是尺度空间(scalespace)?
- 尺度空间理论最早于1962年提出,其主要思想是通过对原始图像进行尺度变换,获得图像多尺度下的空间表示。从而实现边缘、角点检测和不同分辨率上的特征提取,以满足特征点的尺度不变性。
- 尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。
- 尺度越大图像越模糊。
尺度不变性:
旋转不变性:
高斯核可以产生多尺度空间的核,一个图像的尺度空间,L(x,y,σ),定义为原始图像I(x,y)与一个可变尺度的2维高斯函数G(x,y,σ)卷积运算。
(3)高斯模糊
高斯模糊是在AdobePhotoshop等图像处理软件中广泛使用的处理效果,通常用它来减小图像噪声以及降低细节层次。这种模糊技术生成的图像的视觉效果是好像经过一个半透明的屏幕观察图像。
(4)高斯金字塔
高斯金字塔的构建过程可分为两步: - 对图像做高斯平滑;
- 对图像做降采样。
为了让尺度体现其连续性,在简单下采样的基础上加上了高斯滤波。一幅图像可以产生几组(octave)图像,一组图像包括几层(interval)图像。
最后可将组内和组间尺度归为:
i —— 金字塔组数
n —— 每一组的层数
4.关键点检测——DOG
(1)DoG(DifferenceofGaussian)函数:
注:DoG在计算上只需相邻高斯平滑后图像相减,因此简化了计算。
DoG高斯差分金字塔对应DOG算子,需构建DOG金字塔可以通过高斯差分图像看出图像上的像素值变化情况。(如果没有变化,也就没有特征。特征必须是变化尽可能多的点。)DOG图像描绘的是目标的轮廓。
(2)DOG局部极值检测
DoG的局部极值点:
特征点是由DOG空间的局部极值点组成的。为了寻找DoG函数的极值点,每一个像素点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。
中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。
去除边缘响应:
由于DoG函数在图像边缘有较强的边缘响应,因此需要排除边缘响应。DoG函数的峰值点在边缘方向有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率可以通过计算在该点位置尺度的2×2的Hessian矩阵得到,导数由采样点相邻差来估计:
5.代码实现
from PIL import Image
from pylab import *
import sys
from PCV.localdescriptors import sift
if len(sys.argv) >= 3:
im1f, im2f = sys.argv[1], sys.argv[2]
else:
# im1f = '../data/sf_view1.jpg'
# im2f = '../data/sf_view2.jpg'
im1f = './images/sift13.jpg'
im2f = './images/sift14.jpg'
# im1f = '../data/climbing_1_small.jpg'
# im2f = '../data/climbing_2_small.jpg'
im1 = array(Image.open(im1f))
im2 = array(Image.open(im2f))
sift.process_image(im1f, 'out_sift_1.txt')
l1, d1 = sift.read_features_from_file('out_sift_1.txt')
figure()
gray()
subplot(121)
sift.plot_features(im1, l1, circle=False)
sift.process_image(im2f, 'out_sift_2.txt')
l2, d2 = sift.read_features_from_file('out_sift_2.txt')
subplot(122)
sift.plot_features(im2, l2, circle=False)
#matches = sift.match(d1, d2)
matches = sift.match_twosided(d1, d2)
print('{} matches'.format(len(matches.nonzero()[0])))
figure()
gray()
sift.plot_matches(im1, im2, l1, l2, matches, show_below=True)
show()
三、匹配地理标记图像
1.安装配置Graphviz和pydot
Graphviz下载地址:https://graphviz.gitlab.io/_pages/Download/windows/graphviz-2.38.msi
安装后添加环境变量:
命令行输入:dot -version 查看是否安装完成。
在命令行使用pip安装相应工具包:
pip install graphviz
pip install pydot
注:若遇到报错:“dot” not found in path;打开python文件夹找到pydot.py 文件用记事本打开,查找字段 self.prog = ‘dot’ 修改为self.prog = ‘dot.exe’。
2.图像库
3.代码实现
# -*- coding: utf-8 -*-
from pylab import *
from PIL import Image
from PCV.localdescriptors import sift
from PCV.tools import imtools
import pydot
""" This is the example graph illustration of matching images from Figure 2-10.
To download the images, see ch2_download_panoramio.py."""
#download_path = "panoimages" # set this to the path where you downloaded the panoramio images
#path = "/FULLPATH/panoimages/" # path to save thumbnails (pydot needs the full system path)
download_path = "F:/pycharmCode/computerVision/images/" # set this to the path where you downloaded the panoramio images
path = "F:/pycharmCode/computerVision/out_img/" # path to save thumbnails (pydot needs the full system path)
# list of downloaded filenames
imlist = imtools.get_imlist(download_path)
nbr_images = len(imlist)
# extract features
featlist = [imname[:-3] + 'sift' for imname in imlist]
for i, imname in enumerate(imlist):
sift.process_image(imname, featlist[i])
matchscores = zeros((nbr_images, nbr_images))
for i in range(nbr_images):
for j in range(i, nbr_images): # only compute upper triangle
print('comparing ', imlist[i], imlist[j])
l1, d1 = sift.read_features_from_file(featlist[i])
l2, d2 = sift.read_features_from_file(featlist[j])
matches = sift.match_twosided(d1, d2)
nbr_matches = sum(matches > 0)
print('number of matches = ', nbr_matches)
matchscores[i, j] = nbr_matches
print("The match scores is: \n", matchscores)
# copy values
for i in range(nbr_images):
for j in range(i + 1, nbr_images): # no need to copy diagonal
matchscores[j, i] = matchscores[i, j]
#可视化
threshold = 2 # min number of matches needed to create link
g = pydot.Dot(graph_type='graph') # don't want the default directed graph
for i in range(nbr_images):
for j in range(i + 1, nbr_images):
if matchscores[i, j] > threshold:
# first image in pair
im = Image.open(imlist[i])
im.thumbnail((100, 100))
filename = path + str(i) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(i), fontcolor='transparent', shape='rectangle', image=filename))
# second image in pair
im = Image.open(imlist[j])
im.thumbnail((100, 100))
filename = path + str(j) + '.png'
im.save(filename) # need temporary files of the right size
g.add_node(pydot.Node(str(j), fontcolor='transparent', shape='rectangle', image=filename))
g.add_edge(pydot.Edge(str(i), str(j)))
g.write_png('whitehouse.png')