特征点检测:图像配准的“地基”
图像配准这事儿,说白了就是找两张图之间的对应关系。怎么找?你得先找到那些“有特征”的点。这就好比你要把两张拼图拼在一起,得先找到拼图块边缘的独特花纹。特征点,就是图像里的那些“独特花纹”。
这一章,我们重点聊聊几种最常用的特征点检测方法。我个人在项目里用得最多的就是它们,几乎涵盖了从简单到复杂的所有场景。
角点检测入门:Harris 与 Shi-Tomasi
角点,就是图像中那些两条边交汇的地方。比如说,一张桌子的四个角,或者一个窗户的拐角。为什么它们重要?因为角点在图像发生平移、旋转甚至光照变化时,依然能保持稳定。我在做卫星图像配准时遇到过,有些区域纹理很弱,但角点总能提供可靠的锚点。
Harris 角点检测
Harris 角点检测的核心思想,说白了就是判断一个像素在周围小窗口里,往哪个方向移动,灰度变化都很大。如果灰度变化各向同性,那它就是角点。
它通过一个数学上的协方差矩阵(结构张量)来计算。矩阵的两个特征值,一个代表沿着边缘方向的梯度变化,另一个代表垂直方向的变化。如果两个特征值都大,就是角点。
Shi-Tomasi 改进
Shi-Tomasi 可以看成是 Harris 的一个小变种。它发现,直接用后面那个公式里的 R 值来衡量角点,有时候不准。不如直接用两个特征值中的较小值。如果这个最小值大于某个阈值,那它就是个好角点。
这个改动看似简单,实际效果却好不少。OpenCV 里的 goodFeaturesToTrack 函数,用的就是 Shi-Tomasi 方法。我建议你在项目里可以优先试试它。
尺度不变特征:SIFT 与 SURF
刚才提到,角点检测怕尺度变化。如果你的配准场景里,两张图拍摄距离不一样,那角点就基本废了。这时候就需要尺度不变特征了。SIFT 就是这方面的祖师爷。
SIFT(Scale-Invariant Feature Transform)
SIFT 的全称是“尺度不变特征变换”。它首先构建一个图像尺度空间。怎么构建?通过对图像进行不同尺度的高斯模糊,然后差分。在差分尺度空间里,找那些在多个尺度下都能稳定存在的极值点。这些点就是候选关键点。
然后对这些候选点进行精确定位,去除低对比度的点和边缘响应点。最后给每个关键点分配一个主方向,并生成一个 128 维的特征描述子。这个描述子对旋转、缩放、光照变化都有很强的鲁棒性。
SIFT 关键点生成步骤(简版):
- 尺度空间极值检测(DoG 金字塔)
- 关键点定位(去掉低对比度和边缘点)
- 方向分配
- 描述子生成
SIFT 精度很高,但速度慢。而且它是有专利的,商用要小心。不过 OpenCV 里已经内置了,你用的时候只需要知道原理就行。
SURF(Speeded Up Robust Features)
SURF 可以看作是 SIFT 的加速版。它用盒式滤波器代替高斯滤波,用积分图加速计算。特征描述子也从 128 维降到了 64 维。速度大概是 SIFT 的 3-7 倍,但精度略有下降。
说真的,在现在这个时代,如果你追求速度,ORB 可能更好;如果追求精度,SIFT 仍然是标杆。SURF 处于一个比较尴尬的位置。不过在一些老项目或特定场景里,它还是有价值的。我个人十分推荐在表格对比里保留它。
实时利器:ORB 特征
ORB 是 OpenCV Lab 搞出来的,全称是 Oriented FAST and Rotated BRIEF。它把两种已知算法 FAST 和 BRIEF 结合了起来,还做了旋转不变性改进。计算速度非常快,完全免费,没有专利问题。
ORB 首先用 FAST 算法快速检测角点,然后为每个特征点计算一个方向,再用改进后的 BRIEF 描述子(叫做 rBRIEF)来生成二进制描述子。这个描述子是二进制的,所以在匹配时可以用汉明距离,计算量小,速度快。
下面这张表格,是我在实际项目里常用的对比,大家可以参考下:
| 方法 | 尺度不变 | 旋转不变 | 速度 | 推荐场景 |
|---|---|---|---|---|
| Harris | 否 | 是 | 快 | 简单场景、教学实验 |
| Shi-Tomasi | 否 | 是 | 快 | 与 Harris 类似,更好用 |
| SIFT | 是 | 是 | 慢 | 高精度、遥感、医学图像 |
| SURF | 是 | 是 | 中等 | 需要加速的 SIFT 场景 |
| ORB | 否 | 是 | 极快 | 实时系统、嵌入式、手机端 |
代码实战:如何在 OpenCV 里调用
说了这么多理论,咱们还是得上手。在 OpenCV 里,调用这些特征检测器很简单。我给大家贴两个最常用的示例:一个是用 SIFT,一个是用 ORB。
import cv2
# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# SIFT 检测
sift = cv2.SIFT_create()
keypoints_sift, descriptors_sift = sift.detectAndCompute(img, None)
# ORB 检测
orb = cv2.ORB_create()
keypoints_orb, descriptors_orb = orb.detectAndCompute(img, None)
# 画出来看看
img_sift = cv2.drawKeypoints(img, keypoints_sift, None, color=(0, 255, 0))
img_orb = cv2.drawKeypoints(img, keypoints_orb, None, color=(0, 255, 0))
cv2.imshow('SIFT Keypoints', img_sift)
cv2.imshow('ORB Keypoints', img_orb)
cv2.waitKey(0)
代码很简洁。关键就是这个 detectAndCompute 函数,一步到位,既检测了关键点位置,又生成了描述子。我个人习惯上会先打印出关键点的数量和描述子的维度,确认一下数据对不对。
特征点检测是图像配准的第一步,也是最重要的一步。点选得好,后面的匹配和变换就稳了;点选得不好,后面再怎么调也没用。所以,花点时间理解这些算法的特点,并根据你的实际场景做选择,非常值得。