Harris角点检测算法(也称Harris & Stephens角点检测器)是一个极为简单的角点检测算法。该算法的主要思想是,如果像素周围显示存在多于一个方向的边,则认为该点为兴趣点,该点也就是称为角点。角点就是极值点,即在某方面属性特别突出的点,是在某些属性上强度最大或者最小的孤立点、线段的终点。
在图像域中点x上的对称半正定矩阵可以定义为:
其中▽I为包含导数Ix和Iy的图像梯度。
选择权重矩阵W(通常为高斯滤波器G),可以得到卷积:
该卷积的目的是得到M1在周围像素上的局部平均。计算出的矩阵称为Harris矩阵。W的宽度决定了在像素x周围的感兴趣区域。像这样在区域附近对Harris矩阵取平均的原因是特征值会依赖于局部图像特性而变化。如果图像的梯度在该区域变化,那么Harris的第二个特征值将不再是0。如果图像的梯度没有变化,Harris的特征值也不会变化。
取决于该区域▽I的值,Harris矩阵的特征值有三种情况:
在不需要实际计算特征值的情况下,引入指示函数:
为了去除加权常数k,通常使用商数:
作为指示器。
算法的核心是利用局部窗口在图像上进行移动,判断灰度是否发生较大的变化。如果窗口内的灰度值(在梯度图上)都有较大的变化,那么这个窗口所在区域就存在角点。
这样就可以将 Harris 角点检测算法分为以下三步:
在这里需要使用到scipy.ndimage.filters模块中的高斯导数滤波器来计算导数,因为需要在角点检测过程中抑制噪声强度。
首先将角点响应函数添加到harris.py文件中,该函数使用高斯导数实现。同样地,参数σ定义了使用的高斯滤波器的尺度大小。
from scipy.ndimage import filters
def compute_harris_response(im, sigma = 3):
imx = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (0, 1), imx)
imy = zeros(im.shape)
filters.gaussian_filter(im, (sigma, sigma), (1, 0), imy)
Wxx = filters.gaussian_filter(imx * imx, sigma)
Wxy = filters.gaussian_filter(imx * imy, sigma)
Wyy = filters.gaussian_filter(imy * imy, sigma)
Wdet = Wxx * Wyy - Wxy ** 2
Wtr = Wxx + Wyy
return Wdet / Wtr
上面的函数返回像素值为Harris响应函数值的一幅图像。然后需要从中挑选出像素值高于阈值的所有图像点,再加上额外的限制,即角点之间的间隔必须大于设定的最小距离。这种方法会产生很好的角点检测结果。
为了实现该算法,我们获取所有的候选像素点,以角点响应值递减的顺序排序,然后将距离已标记为角点位置过近的区域从候选像素点中删除。将下面的函数添加到harris.py中:
def get_harris_points(harrisim, min_dist = 10, threshold = 0.1):
corner_threshold = harrisim.max() * threshold
harrisim_t = (harrisim > corner_threshold) * 1
coords = array(harrisim_t.nonzero()).T
candidate_values = [harrisim[c[0], c[1]] for c in coords]
index = argsort(candidate_values)
allowed_locations = zeros(harrisim.shape)
allowed_locations[min_dist: -min_dist, min_dist: -min_dist] = 1
filtered_coords = []
for i in index:
if allowed_locations[coords[i, 0], coords[i, 1]] == 1:
filtered_coords.append(coords[i])
allowed_locations[(coords[i, 0] - min_dist) : (coords[i, 0] + min_dist), (coords[i, 1] - min_dist) : (coords[i, 1] + min_dist)] = 0
return filtered_coords
为了显示图像中的角点,可以使用Matplotlib模块绘制函数,并将其添加到harris.py文件中:
def plot_harris_points(image, filtered_coords):
figure()
gray()
imshow(image)
plot([p[1] for p in filtered_coords], [p[0] for p in filtered_coords], '*')
axis('off')
show()
这样准备工作就已经完成,接下来运行下边这段代码,打开灰度图,计算响应函数并基于响应值选择角点,最后在原始图像中覆盖绘制检测出的角点。
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters
import harris
im = array(Image.open('jimei_grey.jpg'))
harrisim = harris.compute_harris_response(im)
filtered_coords = harris.get_harris_points(harrisim, 6)
harris.plot_harris_points(im, filtered_coords)
Harris角点检测器仅仅能够检测出图像中的兴趣点,但是没有给出通过比较图像间的兴趣点来寻找匹配角点的方法。所以就需要在每个点添加一个描述子并给出一个比较这些描述子的方法。
兴趣点描述子是分配给兴趣点的一个向量,描述该点附近的图像的表观信息。描述子越好,寻找到的对应点越好。我们用对应点或者点的对应来描述相同物体和场景点在不同图像上形成的像素点。
Harris角点的描述子通常是由周围图像像素块的灰度值,以及用于比较的归一化互相关矩阵构成的。图像的像素块由以该像素点为中心的周围矩形部分图像构成。
为了获取图像像素块,并使用归一化的互相关矩阵来比较它们,需要将以下两个函数添加到harris.py中:
def get_descriptors(image,filtered_coords,wid=5):
desc = []
for coords in filtered_coords:
patch = image[coords[0]-wid:coords[0]+wid+1,
coords[1]-wid:coords[1]+wid+1].flatten()
desc.append(patch)
return desc
def match(desc1,desc2,threshold=0.5):
n=len(desc1[0])
d = -ones((len(desc1),len(desc2)))
for i in range(len(desc1)):
for j in range(len(desc2)):
d1 = (desc1[i] - mean(desc1[i])) / std(desc1[i])
d2 = (desc2[j] - mean(desc2[j])) / std(desc2[j])
ncc_value = sum(d1*d2)/(n-1)
if ncc_value>threshold:
d[i,j] = ncc_value
ndx = argsort(-d)
matchscores = ndx[:,0]
return matchscores
第一个函数的参数为奇数大小长度的方形灰度图像块,该图像块的中心为处理的像素点。该函数将图像块像素值压平成一个向量,然后添加到描述子列表中。第二个函数使用归一化的互相关矩阵,将每个描述子匹配到另一个图像中的最优的候选点。由于数值较高的距离代表两个点能够更好地匹配,所以在排序之前,对距离取相反数。为了获得更稳定的匹配,我们从第二幅图像向第一幅图像匹配,然后过滤掉在两种方法中不都是最好的匹配。用下边的函数实现:
def match_twosided(desc1,desc2,threshold=0.5):
matches_12 = match(desc1,desc2,threshold)
matches_21 = match(desc2,desc1,threshold)
ndx_12 = where(matches_12 >= 0)[0]
for n in ndx_12:
if matches_21[matches_12[n]] != n:
matches_12[n] = -1
return matches_12
这些匹配可以通过在两边分别绘制出图像,使用线段连接匹配的像素点来直观地可视化。可视化过程由以下代码实现:
def appendimages(im1,im2):
rows1 = im1.shape[0]
rows2 = im2.shape[0]
if rows1 < rows2:
im1 = concatenate((im1,zeros((rows2-rows1,im1,shape[1]))),axis=0)
elif rows1>rows2:
im2 = concatenate((im2,zeros((rows1-rows2,im2.shape[1]))),axis=0)
return concatenate((im1,im2),axis=1)
def plot_matches(im1,im2,locs1,locs2,matchscores,show_below=True):
im3 = appendimages(im1,im2)
if show_below:
im3 = vstack((im3,im3))
imshow(im3)
cols1 = im1.shape[1]
for i,m in enumerate(matchscores):
if m>0:
plot([locs1[i][1],locs2[m][1]+cols1],[locs1[i][0],locs2[m][0]],'c')
axis('off')
使用归一化互相关矩阵寻找对应点的实例:
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters
import harris
im1 = array(Image.open('jimei.jpg').convert('L'))
im2 = array(Image.open('jimei2.jpg').convert('L'))
wid = 5
harrisim = harris.compute_harris_response(im1, 5)
filtered_coords1 = harris.get_harris_points(harrisim,wid+1)
d1 = harris.get_descriptors(im1,filtered_coords1,wid)
harrisim = harris.compute_harris_response(im2,5)
filtered_coords2 = harris.get_harris_points(harrisim,wid+1)
d2 = harris.get_descriptors(im2,filtered_coords2,wid)
print('staring matching')
matches = harris.match_twosided(d1,d2)
figure()
gray()
harris.plot_matches(im1,im2,filtered_coords1,filtered_coords2,matches)
show()
从上图可以看出,算法的结果存在一些不正确匹配,因为图像像素块的互相关矩阵具有较弱的描述性,而且描述符不具有尺度不变性和旋转不变性,算法中像素块的大小也会影响匹配的结果。
角点检测是计算机视觉系统中用来获得图像特征的一种重要方法,也称为特征点检测。如果某一点在任意方向的一个微小变动都会引起灰度很大的变化,那么我们就把这个点称为图像的一个角点。更形象一点的话,我们可以把角点理解为平面的交汇处或者边的交点,导致交点的局部区域具有多个不同区域的不同方向的边界。角点在保留图像图形重要特征的同时,可以有效地减少信息的数据量,有效地提高了计算的速度,有利于图像的可靠匹配,该方法也是特征检测与匹配的基础。harris角点检测是一种直接基于灰度图像的角点提取算法。
总结来说,harris角点检测就是:像素点梯度坐标分布较散、梯度变化程度较大、对应矩阵M 的特征值都较大时,窗口中含有角点;像素点的梯度在某一个方向上变化较大、另一个方向上变化较小,相应M 的特征值一个较大一个较小时,窗口中含有边缘;像素点的梯度坐标分布集中在原点附近、梯度变化幅度非常小、对应M 的特征值都比较小时,窗口处于平坦区域。且具有旋转不变性、对亮度和对比度的变化不灵敏以及不具有尺度不变性等性质。
从实验可以看出,对亮度和对比度的仿射变换并不改变Harris响应的极值点出现的位置,但是,由于阈值的选择,可能会影响角点检测的数量。特征点的提取过程可以减少噪声的影响,对灰度变化、图像形变以及遮挡等都有较好的适应能力。位置也会影响角点数量,特征点的匹配度量值相对位置变化比较敏感,可以提高匹配的精度。最后阀值对角点数量的影响也很大,阀值越大,角点数量就会越少。