角检测(Corner detection)是指检测图像中具有代表性的(我们感兴趣的)角点,一般讲为形状或边缘的拐角处,这些点可以大略标记对象在图像中的轮廓和位置,如果从一个图像序列中检测每个图像的角点,就可以找出图像之间存在的相关和相对应的角点,这对比如全景拼接(多张图片拼接成一张全景图片)很有用。
角检测还可以用在运动检测、物体识别等方面。

Harris角检测

Harris角检测(也叫Harris & Stephens角检测)是目前可用的最简单的角检测算法。它的基本思路是这样的:对于图像中的一个点,如果它周围存在1个以上不同方向的边缘,这个点所在处就是角。

下面需要粗略地介绍一下其中的数学原理,以便理解Harris滤波器函数参数的作用。

我们之前学习边缘检测的时候知道,边缘上的点,水平和垂直两个方向的梯度幅度(一阶导数)较周围高,如果要检测点所在处是否具有1个方向以上的边缘,就必须要综合它周围点的梯度一起考虑,那么问题就变成了需要计算周围区域,像素两两之间的梯度关系,我们在学习PCA算法的时候知道协方差矩阵能体现这种关系,设Ix为点x在它周围一小块区域内的水平方向梯度,同样,设Iy为点y垂直方向梯度,组成一个协方差矩阵:
图片描述

在点(x,y)附近一小块区域内,离(x,y)越近,关系越大,这就需要考虑加权计算,设加权算子为W(典型值使用高斯核,之前笔记介绍过),得到:
A = W * M

A被称为Harris矩阵,它两个特征值λ1和λ2,如果:

  • λ1和λ2都为较大的正数,表示对应的(x,y)点处是角

  • 若λ1较大,而且λ2约等于0,表示所在点只有一条边,非角

  • 若λ1和λ2都约等于0,表示所在处没有边角

求Harris矩阵的特征值计算量较大,Harris给出了一个方程:
205bb6f246c54d741267eec95c898d92.png

上式只要计算矩阵的行列式(det)和迹(trace)即可,计算方便,得到的结果可作为角的检测,其中系数k是一个经验值,它的设置跟边缘的粗细有关。我们暂且把这种方法称为k方法

为使得计算更方便,Noble角测量(Noble’s corner measure)给出了去除k系数的方法,只要计算:
8b58eaf1ec3c177c06c1ce0b759b3666.png

eps(或ϵ)为一个很小的正的常量,我们暂且称此为eps方法

Harris代码实现
根据以上所介绍的eps方法,下面实现一个Harris角检测函数:

def harris_eps(im, sigma=3):
    imx = np.zeros(im.shape)
    filters.gaussian_filter(im, (sigma,sigma), (0,1), imx)
    imy = np.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
    #按eps公式计算
    return Wdet * 2 / (Wtr + 1e-06)

注意:书上并没有严格按照公式计算返回值,经测试,对某些图片会出现无法除的情况,所以上面的代码进行了改正

确定坐标
Harris返回的结果是一个与原图像大小相同的矩阵,要判断是否是角点,还需要做如下的工作:

  • 设定一个阈值,只考虑高于阈值的点,这样可以过滤掉无用的或不感兴趣的点

  • 一个角处一般会有多个点,在标记角坐标的时候,应该设定一个最小距离,在此距离内只需要一个点进行标记即可

这个判断函数可以使用skimage库中的corner_peaks函数,其中参数min_distance指上述的最小距离,threshold_rel则为阈值,函数原型:

skimage.feature.corner_peaks(harrisim, min_distance=10, threshold_abs=0, threshold_rel=0.1, ...)

函数默认返回由所有角点在原图像中的坐标组成的数组。

skimage库的Harris函数

skimage库也提供了Harris角检测函数:

skimage.feature.corner_harris(image, method='k', k=0.05, eps=1e-06, sigma=1)

method: 'k'或'eps',对应上述的两种计算方法
k: k方法中的k系数,取值区间为[0, 0.2],k的值越小,表示将检测越锐利的角
eps: eps方法中的系数,默认即可
sigma: 高斯核的标准差

简单示例:

import numpy as np
from skimage.feature import corner_harris, corner_peaks

square = np.zeros([10, 10])
square[2:8, 2:8] = 1
square.astype(int)

print square
>>[[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  1.  1.  1.  1.  1.  1.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]]

harris_result = corner_harris(square) 
print corner_peaks(harris_result, min_distance=1) #此函数能够从harris结果中检测角的坐标位置
>>[[2 2]
 [2 7]
 [7 2]
 [7 7]]

上面harris_result如图,观察一下角处的值与周围的不同:
图片描述

对比示例

我分别用我们自己实现的harris_eps函数,跟skimage中的corner_harris函数进行效果对比,发现两者存在差异,有使用了两张图像进行了测试,一张是内容比较简单的矢量图,一张是写实图,效果如下:
图片描述

可以看到,使用简单的房子的图像时,通过微调参数,三种方法都可以达到比较接近的效果。但使用写实的图像(第二列)时,三者差异较大,skiamge库的版本检测出的角结果不是我们期望的。而且我通过调整参数也很难达到效果。原因还不清楚,有空再回头分析一下corner_harris函数的源代码。

以上示例的代码:

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from skimage.feature import corner_harris, corner_peaks
from scipy.ndimage import filters

#harris_eps函数此处省略,见上文

im1 = np.array(Image.open('house.jpg').convert('L'))
im2 = np.array(Image.open('tower-left.jpg').convert('L'))

my_coords1 = corner_peaks(harris_eps(im1, sigma=1), min_distance=12, threshold_rel=0)
eps_coords1 = corner_peaks(corner_harris(im1, method='eps', sigma=1), min_distance=20, threshold_rel=0)
k_coords1 = corner_peaks(corner_harris(im1, method='k', sigma=1), min_distance=20, threshold_rel=0)

my_coords2 = corner_peaks(harris_eps(im2, sigma=1), min_distance=5, threshold_rel=0.01)
eps_coords2 = corner_peaks(corner_harris(im2, method='eps', sigma=1), min_distance=5, threshold_rel=0.01)
k_coords2 = corner_peaks(corner_harris(im2, method='k', sigma=1), min_distance=5, threshold_rel=0.01)

def plot_coords(index, title, im, coords):
    plt.subplot(index)
    plt.imshow(im)
    plt.plot(coords[:, 1], coords[:, 0], '+r', markersize=5)
    plt.title(title)
    plt.axis('off')

plt.gray()
index = 321
plot_coords(index, 'my', im1, my_coords1)
plot_coords(index + 1, 'my', im2, my_coords2)
plot_coords(index + 2, 'skimage-eps', im1, eps_coords1)
plot_coords(index + 3, 'skimage-eps', im2, eps_coords2)
plot_coords(index + 4, 'skimage-k', im1, k_coords1)
plot_coords(index + 5, 'skimage-k', im2, k_coords2)
plt.tight_layout(w_pad=0)
plt.show()

小结

下一笔记学习如何从图像间找出相关的对应点。
你还可以查看我的其它笔记

参考资料

wiki: Corner detection
skimage corner example


jk_v1
1.8k 声望198 粉丝

Linux爱好者,技术积累主要在Linux、Qt、Android,后以Android开发为主,从上层(kotlin,java)到底层(jni,linux)有一定的工作经验和理解,擅长快速学习和知识关联梳理,整合不同技术资源为客户提供合适的解决方案。