特征检测是计算机视觉领域的一个重要分支,其核心在于利用特定工具识别图像中的关键区域。大多数特征检测算法的一个显著特点是,它们并不依赖于机器学习技术,这使得其结果更具可解释性,在某些情况下甚至能实现更快的处理速度。
在本系列的前两篇文章中,探讨了用于检测图像边缘的最常用算子:Sobel、Scharr、Laplacian以及用于图像平滑的高斯算子。这些算子在不同程度上都利用了图像导数或梯度,并通过卷积核来实现。
除了边缘,图像分析中另一类常被关注的局部区域是角点。角点在图像中出现的频率低于边缘,通常标志着物体边界方向的改变,或是一个物体的结束与另一个物体的开始。因其更为稀有,角点往往能提供更具价值的信息。

一个直观的例子
想象一下在拼一幅二维拼图。大多数人开始时,会先寻找带有物体边界(边缘)图案的拼图块。这是因为,拥有相似物体边缘的拼图块数量较少,从而更容易找到相邻的拼图块。
更进一步,可以专注于挑选那些包含角点而非单纯边缘的拼图块——即物体边缘方向发生变化的区域。这类拼图块比仅含边缘的更为稀有,其独特的形状使得寻找其他相邻碎片变得更为容易。
例如,在下方的拼图中,共有6个边缘(B2, B3, B4, D2, D3, 和 D4),但只有1个角点(C5)。从一开始就挑选角点,由于其稀有性,能更轻松地定位其位置。

本文的目标是理解如何检测角点。为此,将深入剖析哈里斯角点检测算法的细节——这是一种于1988年提出的、最简单且流行的角点检测方法之一。
核心思想
考虑三种类型的图像区域:平坦区域、边缘区域和角点区域。上文已展示了这些区域的结构。接下来的目标是理解这三种情况下图像梯度的分布情况。
在分析过程中,还将构建一个包含大部分梯度点的椭圆。正如我们将看到的,该椭圆的形状将有力地揭示所处理区域的类型。
平坦区域
平坦区域是最简单的情况。通常,整个图像区域的强度值几乎相同,导致在X和Y轴方向上的梯度值都很小,并集中在零点附近。
通过获取平坦区域示例中的梯度点(Gₓ, Gᵧ),可以绘制其分布图,如下图所示:

现在,可以围绕这些点构建一个中心在(0, 0)的椭圆,并识别其两个主轴:
- 长轴:椭圆沿此方向被最大程度拉伸。
- 短轴:椭圆沿此方向达到最小延伸。
在平坦区域的情况下,由于椭圆往往呈圆形,可能难以从视觉上区分长轴和短轴,正如本例所示。
尽管如此,可以为两个主轴计算椭圆的半径λ₁和λ₂。如上图所示,这两个值几乎相等,且绝对值都很小。
边缘区域
对于边缘区域,强度变化仅发生在边缘带。在边缘之外,强度几乎保持不变。因此,大多数梯度点仍然集中在(0, 0)附近。
然而,在边缘带附近的一小部分区域,梯度值可能在两个方向上发生剧烈变化。从上面的图像示例看,边缘是斜向的,可以看到两个方向的变化。因此,梯度分布会沿对角线方向倾斜,如下图所示:

对于边缘区域,绘制的椭圆通常向一个方向倾斜,其半径λ₁和λ₂的差异非常显著。
角点区域
对于角点,角点之外的大部分强度值保持不变;因此,大多数点的分布仍位于中心(0, 0)附近。
观察角点的结构,可以粗略地将其视为两个具有不同方向的边缘的交汇处。对于边缘,前文已讨论过其分布会沿相同方向延伸,无论是X方向、Y方向,还是两者兼有。
由于角点包含两条边缘,最终会得到两个不同的点群,它们从中心向两个不同的方向延伸。示例如下图所示。

最后,如果围绕该分布构建椭圆,会发现它比平坦和边缘情况下的椭圆更大。可以通过测量λ₁和λ₂来区分这一结果,在此场景下,这两个值会大得多。
可视化总结
刚刚观察了λ值在不同场景下的变化。为了更好地可视化结果,可以构建如下示意图:

该图展示了λ值与区域类型之间的关系。
判别公式
为了能够将区域分类到上述三种类型之一,通常使用以下公式来估算R系数:
R = λ₁ ⋅ λ₂ – k ⋅ (λ₁ + λ₂)² ,其中 0.04 ≤ k ≤ 0.06
根据R值,可以对图像区域进行分类:
- R < 0 – 边缘区域
- R ≈ 0 – 平坦区域
- R > 0 – 角点区域
OpenCV 实践
在OpenCV中可以轻松实现哈里斯角点检测,使用cv2.cornerHarris函数。以下示例展示了具体步骤。
这是将要处理的输入图像:

输入图像
首先,导入必要的库。
import numpy as np
import cv2
import matplotlib.pyplot as plt
接下来,将输入图像转换为灰度格式,因为哈里斯检测器处理的是像素强度。同时需要将图像格式转换为float32,因为计算出的像素相关值可能超出[0, 255]的范围。
path = 'data/input/shapes.png'
image = cv2.imread(path)
grayscale_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
grayscale_image = np.float32(grayscale_image)
现在可以应用哈里斯滤波器。cv2.cornerHarris函数接受四个参数:
grayscale_image– 输入灰度图像,格式为float32。blockSize (= 2)– 定义用于角点检测的邻域像素块的尺寸。ksize (= 3)– 用于计算导数的Sobel滤波器的尺寸。k (= 0.04)– 用于计算R值的公式中的系数。
R = cv2.cornerHarris(grayscale_image, 2, 3, 0.04)
R = cv2.dilate(R, None)
cv2.cornerHarris函数返回一个矩阵,其维度与原始灰度图像完全相同。该矩阵的值可能远超出正常范围[0, 255]。对于每个像素,该矩阵包含了上文讨论的R系数值。
cv2.dilate是一种形态学算子,可在检测后立即使用,以更好地在视觉上聚合局部角点。
一种常见的技术是定义一个阈值,低于该阈值的像素被视为角点。例如,可以将所有R值大于全局最大R值1%的图像像素视为角点。在本例中,将这些像素标记为红色(0, 0, 255)。
为了可视化图像,需要将其转换为RGB格式。
image[R > 0.01 * R.max()] = [0, 0, 255]
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
最后,使用matplotlib显示输出图像。
plt.figure(figsize=(10, 8))
plt.imshow(image_rgb)
plt.title('Harris Corner Detection')
plt.axis('off')
plt.tight_layout()
plt.show()
结果如下:

输出图像。红色标识出检测到的角点。
结论
本文探讨了一种判断图像区域是否为角点的稳健方法。所介绍的用于估算R系数的公式在绝大多数情况下都表现良好。
在实际应用中,通常需要对整幅图像运行角点分类器。每次都围绕梯度点构建椭圆并估算R系数是资源密集型的,因此会采用更先进的优化技术来加速这一过程。尽管如此,这些技术很大程度上都基于本文所研究的核心思想。
