本章将学习如下内容:
- 深入讲解Harris角点检测的原理
- 介绍两个关键函数:
cv2.cornerHarris()
和cv2.cornerSubPix()
的实现方法。
理论基础
在前一章我们了解到,角点是图像中所有方向都存在显著灰度变化的特殊区域。 Chris Harris和Mike Stephens在1988年发表的论文《组合式角点与边缘检测器》中首次系统性地提出了检测这类角点的方法, 因此该算法被命名为Harris角点检测器。 他们将这一直观概念转化为严谨的数学表达,其核心思想是通过计算图像在各个方向微小位移时的灰度变化差异来识别角点特征, 具体的数学表达式如下所示。
$$E(u,v) = \sum_{x,y} \underbrace{w(x,y)}_\text{window function} \, [\underbrace{I(x+u,y+v)}_\text{shifted intensity}-\underbrace{I(x,y)}_\text{intensity}]^2$$
在角点检测中,我们使用矩形窗口或高斯窗口作为加权函数,对区域内像素赋予不同权重。 要实现角点检测,需要最大化函数 $E(u,v)$,这等同于最大化其中的第二项。 通过对原方程进行泰勒展开并经过数学推导(完整推导过程请参阅相关标准教材),最终可获得如下表达式:
$$\begin{aligned} E(u,v) \approx \begin{bmatrix} u & v \end{bmatrix} M \begin{bmatrix} u \\ v \end{bmatrix} \end{aligned}$$
其中:
$$\begin{aligned} M = \sum_{x,y} w(x,y) \begin{bmatrix}I_x I_x & I_x I_y \\ I_x I_y & I_y I_y \end{bmatrix} \end{aligned}$$
其中 $I_x$ 和 $I_y$ 分别表示图像在x和y方向的导数(可通过cv2.Sobel()
函数轻松求得)。
接下来的核心环节是:研究者们构建了一个评分方程,这个关键公式将判定当前窗口区域是否包含角点特征。
$$R = det(M) - k(trace(M))^2$$
其中:
- $det(M) = \lambda_1 \lambda_2$
- $trace(M) = \lambda_1 + \lambda_2$
- $\lambda_1$ 和 $\lambda_2$ 表示矩阵M的特征值
这些特征值的大小决定了区域类型(角点/边缘/平坦区):
- 当 $|R|$ 值较小( $\lambda_1$ 和 $\lambda_2$ 都较小时),判定为平坦区域
- 当 $R<0$ 远大于 $\lambda_1 >> \lambda_2$ 或相反情况时),判定为边缘区域
- 当 $R$ 值较大( $\lambda_1$ and $\lambda_2$ 均较大且数值接近时),判定为角点区域
可通过下图直观表示:
Harris角点检测的输出结果是包含这些评分值的灰度图像,通过设定合适阈值即可提取图像中的角点。我们将通过简单图像进行演示。
OpenCV 中的 Harris 角点检测器实现
OpenCV提供 cv2.cornerHarris()
函数,参数说明:
img
:输入图像(需为float32类型的灰度图)blockSize
:角点检测邻域窗口尺寸ksize
:Sobel算子孔径参数k
:Harris检测器方程中的自由参数
具体实现参见以下示例:
%matplotlib inline
import cv2
import numpy as np
filename = '/data/cvdata/chessboard.jpg'
img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
#result is dilated for marking the corners, not important
dst = cv2.dilate(dst,None)
# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]
# cv2.imshow('dst',img)
# if cv2.waitKey(0) & 0xff == 27:
# cv2.destroyAllWindows()
import matplotlib.pyplot as plt
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f5dfb75d400>
plt.imshow(dst)
<matplotlib.image.AxesImage at 0x7f5dfb33c590>
import cv2
import numpy as np
filename = '/data/cvdata/chessboard.jpg'
img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# find Harris corners
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
dst = cv2.dilate(dst,None)
ret, dst = cv2.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)
# find centroids
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
# define the criteria to stop and refine the corners
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)
# Now draw them
res = np.hstack((centroids,corners))
res = np.int_(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]
cv2.imwrite('xx_subpixel5.png',img)
True
以下是结果展示,其中通过放大窗口呈现了部分关键位置的细节以便观察: