3、特征点检测(上):Harris角点检测原理、数学推导与代码实现

各位同学,欢迎来到特征点检测的第一讲。

图像融合要做出全景大片的效果,第一步是什么?

你得先知道两张图里哪些地方是“同一个位置”。

比如你拍了两张风景照,左边那张的右上角有一座塔,右边那张的左上角也有同一座塔。你得让程序认出这个塔,然后才能把两张图拼起来。这个“认出”的过程,靠的就是特征点检测。

今天咱们聊的,是特征点检测里最经典、最基础的方法——Harris角点检测。我个人觉得,搞懂Harris,后面再学SIFT、SURF这些高级货,会轻松很多。

3.1 什么是角点?为什么它这么重要?

先问个问题:什么样的点适合做特征点?

你想想看,如果我在一张纯白墙上点一个像素点,换个角度拍,这个点还在吗?大概率找不到了,因为周围全是白的,没有任何区分度。

但如果我拍的是窗户的角,或者桌子的棱,换个角度,这个角依然很明显。这种点,就是我们要找的角点

角点说白了,就是图像里两个不同方向都有明显变化的位置。比如一个“L”形的拐角,你往左挪一点,灰度值变了;往上挪一点,灰度值也变了。这种点在图像里非常稳定,适合用来做匹配。

核心观点: 好的特征点,应该具备“可重复性”。换个角度、换个光照,它还在那里。

3.2 Harris角点检测的直觉理解

Harris角点检测的思路其实很朴素。

想象一下,你拿一个小窗口在图像上滑动。这个小窗口可能是3x3或者5x5的像素块。

  • 平坦区域:窗口往哪滑,里面的像素值都差不多。灰度变化很小。
  • 边缘区域:窗口沿着边缘方向滑,灰度变化不大;但垂直于边缘滑,变化就很大。
  • 角点区域:窗口往任何方向滑,灰度变化都很大。

Harris要做的,就是用一个数学公式,把第三种情况给揪出来。

嗯,这里要注意:它不关心你窗口滑了多远,它只关心微小的移动带来的变化。这个“微小”很重要,因为后面我们要用泰勒展开来近似计算。

3.3 数学推导:从直觉到公式

好,咱们来点硬核的。别怕,我会一步步拆开讲。

假设图像灰度函数为 I(x, y)。我们把窗口在 (x, y) 处移动一个微小的量 (u, v),那么窗口内的灰度变化量 E(u, v) 可以写成:

E(u, v) = Σ w(x, y) * [I(x+u, y+v) - I(x, y)]²

这里的 w(x, y) 是窗口函数,通常用高斯权重,让窗口中心的像素贡献更大。

接下来是关键一步。I(x+u, y+v) 怎么算?用泰勒展开!

I(x+u, y+v) ≈ I(x, y) + u * Ix + v * Iy

其中 Ix 和 Iy 是图像在 x 和 y 方向的梯度。把这个近似代回 E(u, v) 的公式里:

E(u, v) ≈ Σ w(x, y) * [u * Ix + v * Iy]²
        = Σ w(x, y) * (u² * Ix² + 2uv * IxIy + v² * Iy²)

写成矩阵形式会更优雅:

E(u, v) ≈ [u, v] * M * [u, v]ᵀ

其中 M 是一个 2x2 的矩阵,叫做结构张量(Structure Tensor):

M = Σ w(x, y) * [ Ix²   IxIy ]
                [ IxIy  Iy²  ]

这个 M 矩阵,就是Harris角点检测的核心。它描述了窗口内梯度的分布情况。

我的经验: 在实际项目中,Ix 和 Iy 通常用 Sobel 算子来计算。别小看这一步,梯度的精度直接影响角点检测的效果。我早期做项目时,为了省事用了简单的差分,结果角点检测出来一堆假点,后来换成 Sobel 才稳定下来。

3.4 角点响应函数:如何判断是不是角点?

有了 M 矩阵,我们怎么判断一个点是不是角点呢?

Harris 提出了一个巧妙的办法:看 M 矩阵的两个特征值 λ₁ 和 λ₂。

  • 两个特征值都很小:平坦区域,不是角点。
  • 一个特征值大,一个特征值小:边缘区域,不是角点。
  • 两个特征值都很大:角点!

但直接算特征值有点慢。Harris 给出了一个近似公式,叫做角点响应函数 R

R = det(M) - k * (trace(M))²

其中:

  • det(M) = λ₁ * λ₂ = Ix² * Iy² - (IxIy)²
  • trace(M) = λ₁ + λ₂ = Ix² + Iy²
  • k 是一个经验常数,通常在 0.04 到 0.06 之间。

判断规则很简单:

  • R > 0 且较大:角点。
  • R < 0:边缘。
  • |R| 很小:平坦区域。
避坑指南: 我曾经在调参时,把 k 值设成了 0.1,结果角点检测出来全是边缘点。后来查资料才发现,k 值太大或太小都会影响判断。建议从 0.04 开始试,效果不对再微调。

3.5 算法流程总结

咱们把整个流程捋一遍,我习惯用下面这张图来理解:

Harris角点检测算法流程 1. 计算梯度 2. 构建M矩阵 3. 计算R值 4. 阈值筛选 5. 非极大值抑制 输出角点坐标 Sobel算子 计算Ix, Iy 高斯加权 构建结构张量

这张图把整个流程串起来了。从计算梯度开始,到最终输出角点坐标,每一步都有它的道理。

3.6 代码实现:从理论到实践

光说不练假把式。咱们用 Python 和 OpenCV 来实现一下。

import cv2
import numpy as np

def harris_corner_detection(image_path, k=0.04, threshold=0.01):
    """
    Harris角点检测实现
    
    参数:
        image_path: 图像路径
        k: Harris角点响应函数中的经验常数
        threshold: 角点筛选阈值(相对于最大R值的比例)
    
    返回:
        标记了角点的图像
    """
    # 1. 读取图像并转为灰度图
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = np.float32(gray)
    
    # 2. 计算梯度(Sobel算子)
    Ix = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
    Iy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
    
    # 3. 计算Ix², Iy², IxIy
    Ix2 = Ix * Ix
    Iy2 = Iy * Iy
    Ixy = Ix * Iy
    
    # 4. 高斯加权(构建M矩阵的每个元素)
    sigma = 2.0
    ksize = (5, 5)
    Sxx = cv2.GaussianBlur(Ix2, ksize, sigma)
    Syy = cv2.GaussianBlur(Iy2, ksize, sigma)
    Sxy = cv2.GaussianBlur(Ixy, ksize, sigma)
    
    # 5. 计算角点响应函数R
    det = Sxx * Syy - Sxy * Sxy
    trace = Sxx + Syy
    R = det - k * (trace ** 2)
    
    # 6. 阈值筛选
    R_max = np.max(R)
    if R_max == 0:
        R_max = 1  # 防止除零
    R_normalized = R / R_max
    corners = np.where(R_normalized > threshold)
    
    # 7. 在原图上标记角点
    result_img = img.copy()
    for y, x in zip(corners[0], corners[1]):
        cv2.circle(result_img, (x, y), 3, (0, 0, 255), -1)
    
    return result_img

# 使用示例
# result = harris_corner_detector('building.jpg')
# cv2.imshow('Harris Corners', result)
# cv2.waitKey(0)
我的建议: 代码里的 threshold 参数很关键。设得太小,会检测出大量假角点;设得太大,又会漏掉真正的角点。我一般会先设成 0.01,然后根据结果上下调整。另外,高斯滤波的 sigma 值也值得调一调,sigma 越大,角点越少但越稳定。

3.7 实际项目中的注意事项

最后,分享几个我在项目中踩过的坑:

  • 图像噪声问题: Harris 对噪声比较敏感。如果图像质量不好,建议先做一次高斯滤波去噪。
  • 尺度问题: Harris 检测到的角点对尺度变化不鲁棒。如果你要拼接的两张图缩放比例不同,Harris 可能找不到对应的点。这时候可以考虑用 SIFT 或 SURF。
  • 计算效率: 对于大尺寸图像,Harris 的计算量不小。我一般会先缩小图像,检测到角点后再映射回原图。
曾经踩过的坑: 有一次我在做无人机航拍图像的拼接,直接用 Harris 检测角点,结果发现很多角点都在树冠上。树冠随风摆动,导致匹配失败。后来我加了一个掩膜,只在地面建筑物上检测角点,效果就好了很多。所以,特征点检测不是万能的,要根据场景做针对性处理

好了,Harris角点检测的原理、推导和实现就讲到这里。它虽然简单,但思想非常经典。后面的课程里,我们会基于它继续深入,看看更高级的特征点检测方法是怎么演进的。


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