角点检测基础:Harris与Shi-Tomasi
说到SLAM前端的特征提取,角点检测是绕不开的基础。我刚开始做视觉SLAM时,总觉得角点检测不就是找几个像素点嘛,有啥难的?后来在实际项目中踩了不少坑,才明白这里面门道很深。
今天咱们就聊聊两种最经典的角点检测方法:Harris和Shi-Tomasi。它们虽然年代久远,但至今仍是很多SLAM系统的标配。
什么是角点?
先想一个问题:图像里什么样的点才算"角点"?
说白了,角点就是图像中那些在两个方向上都有明显灰度变化的点。比如棋盘格的交叉点、窗户的边角、书本的尖角。
你想想看,如果在一个平坦的墙面上,你往哪个方向移动窗口,灰度变化都不大——这种地方不适合做特征点。如果是一条边缘,沿着边缘方向移动灰度不变,垂直方向变化大——这种点也不够稳定。只有角点,往任何方向移动一点点,灰度都会明显变化。
这就是角点检测的核心思想:寻找那些"移动一下就会变"的点。
Harris角点检测原理
Harris角点检测是1988年提出的,算是个老前辈了。但它的思路非常优雅,至今仍被广泛使用。
核心思想
Harris用一个数学公式来衡量"角点程度":
E(u,v) = Σ w(x,y) [I(x+u, y+v) - I(x,y)]²
这个公式看着复杂,其实意思很简单:计算窗口移动前后,像素值变化的加权和。
为了简化计算,Harris用了泰勒展开,最终得到一个2×2的矩阵M:
M = Σ w(x,y) [ Ix² IxIy ]
[ IxIy Iy² ]
其中Ix和Iy是图像在x和y方向的梯度。
角点响应函数
有了矩阵M,Harris定义了一个响应函数R:
R = det(M) - k * trace(M)²
其中det(M)是行列式,trace(M)是迹,k是经验常数(通常取0.04-0.06)。
判断规则很简单:
- R > 0 且较大 → 角点
- R < 0 → 边缘
- |R| 很小 → 平坦区域
关键点:Harris角点检测对旋转和光照变化具有不变性,但对尺度变化敏感。也就是说,你把图片旋转一下,角点还在;但如果你把图片放大缩小,角点可能就找不到了。
代码实现
OpenCV里实现Harris角点检测非常简单:
import cv2
import numpy as np
img = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Harris角点检测
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
# 阈值筛选
img[dst > 0.01 * dst.max()] = [0, 0, 255]
这里blockSize是窗口大小,ksize是Sobel算子的孔径大小,k就是上面说的经验常数。
我的经验:k值的选择很关键。k太大,会漏掉很多角点;k太小,又会检测出大量伪角点。我个人习惯在0.04-0.06之间调参,具体看场景。纹理丰富的场景用大一点的值,纹理稀疏的场景用小一点的值。
Shi-Tomasi角点检测
Shi-Tomasi是Harris的改进版本,由Jianbo Shi和Carlo Tomasi在1994年提出。它改进了角点响应函数的计算方式。
改进在哪里?
Harris用det(M) - k*trace(M)²来判断角点,而Shi-Tomasi发现,直接用矩阵M的最小特征值来判断效果更好。
为什么?
矩阵M的两个特征值λ₁和λ₂,分别代表两个方向上的灰度变化强度。如果两个特征值都很大,说明两个方向变化都剧烈——这就是角点。如果一个大一个小,说明是边缘。如果两个都小,说明是平坦区域。
Shi-Tomasi的角点响应函数就是:
R = min(λ₁, λ₂)
当R大于某个阈值时,就认为是角点。
核心区别:Harris用行列式和迹的组合,Shi-Tomasi直接用最小特征值。后者更直观,而且在实际应用中往往效果更好。
代码实现
OpenCV里用goodFeaturesToTrack函数实现Shi-Tomasi:
import cv2
import numpy as np
img = cv2.imread('chessboard.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Shi-Tomasi角点检测
corners = cv2.goodFeaturesToTrack(
gray,
maxCorners=100, # 最多检测100个角点
qualityLevel=0.01, # 质量等级
minDistance=10 # 角点之间的最小距离
)
# 绘制角点
for corner in corners:
x, y = corner.ravel()
cv2.circle(img, (int(x), int(y)), 3, (0, 255, 0), -1)
避坑指南:我曾经在纹理稀疏的场景里用Shi-Tomasi,发现检测到的角点太少,导致SLAM系统频繁丢失。后来我把qualityLevel从0.01降到0.001,角点数量才够用。但降得太低又会引入噪声,需要根据实际场景调参。
两种方法的对比
| 特性 | Harris | Shi-Tomasi |
|---|---|---|
| 响应函数 | det(M) - k·trace(M)² | min(λ₁, λ₂) |
| 计算复杂度 | 较低 | 稍高(需计算特征值) |
| 角点质量 | 一般 | 更好 |
| 参数调优 | 需要调k值 | 需要调qualityLevel |
| 适用场景 | 通用场景 | 需要高质量角点的场景 |
知识体系结构图
下面这张图展示了角点检测的核心逻辑:
实际应用中的选择
在实际SLAM项目中,我一般这样选择:
- 计算资源有限(比如嵌入式设备):用Harris,计算量小一些
- 需要高质量角点(比如视觉里程计):用Shi-Tomasi,角点更稳定
- 纹理丰富场景:两种都可以,Harris的k值调大一点
- 纹理稀疏场景:Shi-Tomasi的qualityLevel调低一点
我的建议:刚开始做SLAM时,先用Shi-Tomasi,因为它参数少、效果好。等你对角点检测有了感觉,再尝试Harris,理解它的数学原理。这样循序渐进,学得更扎实。
嗯,角点检测的基础就聊到这里。这两种方法虽然简单,但它们是很多高级特征提取算法的基础。理解了它们,后面学SIFT、SURF、ORB就会轻松很多。
公众号:蓝海资料掘金营,微信deep3321