角点检测基础: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
适用场景 通用场景 需要高质量角点的场景

知识体系结构图

下面这张图展示了角点检测的核心逻辑:

角点检测核心逻辑 输入图像 计算梯度 Ix, Iy 构建矩阵 M Harris: R = det - k·tr² Shi-Tomasi: R = min(λ₁, λ₂) 角点/边缘/平坦 角点/边缘/平坦

实际应用中的选择

在实际SLAM项目中,我一般这样选择:

  • 计算资源有限(比如嵌入式设备):用Harris,计算量小一些
  • 需要高质量角点(比如视觉里程计):用Shi-Tomasi,角点更稳定
  • 纹理丰富场景:两种都可以,Harris的k值调大一点
  • 纹理稀疏场景:Shi-Tomasi的qualityLevel调低一点

我的建议:刚开始做SLAM时,先用Shi-Tomasi,因为它参数少、效果好。等你对角点检测有了感觉,再尝试Harris,理解它的数学原理。这样循序渐进,学得更扎实。

嗯,角点检测的基础就聊到这里。这两种方法虽然简单,但它们是很多高级特征提取算法的基础。理解了它们,后面学SIFT、SURF、ORB就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321