Harris 角点检测

目标

在这一章当中,

理论

在上一章中,我们看到角点是图像中的区域,在所有方向上的强度变化都很大。找到这些角点的一个早期尝试出现在Chris Harris 和 Mike Stephens1988 年的论文 A Combined Corner and Edge Detector中,因此现在它被称为 Harris 角点探测器。他把这个简单的想法变成了数学形式。基本原理是将窗口向各个方向上位移(u,v),然后计算滑动前后强度差异的总和。表达式如下:

E(u,v) = \sum {x,y} \underbrace{w(x,y)} {window function} [\underbrace{I(x+u,y+v)} {shifted intensity}-\underbrace{I(x,y)} {intensity}] ^ 2

窗口函数可以是矩形窗口,也可以是对每个像素赋予不同权重的高斯窗口。

我们必须使E(u,v)的值最大化以进行角点检测。这意味着,必须最大化方程右侧的第二项。将泰勒展开应用于上述方程并进行一些数学转换(请参考您喜欢的任何标准教科书进行完全推导),我们得到最终的等式:

E(u,v) \approx \begin {bmatrix} u&v \end {bmatrix} M \begin {bmatrix} u \ v \end {bmatrix}

其中

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}

这里I_xI_y 分别是 x 和 y 方向的图像导数。(可以使用 cv.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 的特征值

根据这些特征值可以判断一个区域是角点,边缘还是平面。

  • 当$$\lambda_1$$和$$\lambda_2$$都很小时,$$| R |$$也很小,该区域是平面。
  • 当$$\lambda_1\gt\gt\lambda_2$$或者$$\lambda_2\gt\gt\lambda_1$$时,$$R\lt0$$,该区域是边缘。
  • 当$$\lambda_1$$和$$\lambda_2$$大且$$\lambda_1 \sim \lambda_2$$时,$$R$$很大,该区域是一个角点。

上述结论可以用下图表示:

harris_region.jpg

因此,Harris 角点检测的结果是具有这些分数的灰度图像。选取适当的阈值即可筛选出图像中的角点。我们将用一个简单的图像来进行演示。

OpenCV 中的 Harris 角点检测器

OpenCV 中的 cv.cornerHarris() 函数用来实现 Harris 角点检测。它的参数是:

  • img - 输入图像,应为 float32 类型的灰度图。
  • blockSize - 角点检测所考虑的邻域大小。
  • ksize - Sobel 导数的内核大小。
  • k - Harris 检测器方程中的自由参数。

请参阅以下示例:

  1. import numpy as np
  2. import cv2 as cv
  3. filename = 'chessboard.png'
  4. img = cv.imread(filename)
  5. gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  6. gray = np.float32(gray)
  7. dst = cv.cornerHarris(gray,2,3,0.04)
  8. #result is dilated for marking the corners, not important
  9. dst = cv.dilate(dst,None)
  10. # Threshold for an optimal value, it may vary depending on the image.
  11. img[dst>0.01*dst.max()]=[0,0,255]
  12. cv.imshow('dst',img)
  13. if cv.waitKey(0) & 0xff == 27:
  14. cv.destroyAllWindows()

以下是三个结果:

harris_result.jpg

具有亚像素级精度的角点

有时,您可能需要以最高精度找到角点。OpenCV 带有一个函数 cv.cornerSubPix() ,它进一步细化了角点检测,以达到亚像素级精度。以下是一个例子。像往常一样,我们需要先找到 Harris 角点。然后将这些角的质心(角点处可能有一堆像素,我们采用它们的质心)传递给该函数来细化它们。Harris 角点以红色像素标记,细化后的角点以绿色像素标记。对于此函数,我们必须定义迭代停止的条件。我们在经过指定次数的迭代或达到一定精度后停止它,以先发生者为准。我们还需要定义进行角点搜索的邻域大小。

  1. import numpy as np
  2. import cv2 as cv
  3. filename = 'chessboard2.jpg'
  4. img = cv.imread(filename)
  5. gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  6. # find Harris corners
  7. gray = np.float32(gray)
  8. dst = cv.cornerHarris(gray,2,3,0.04)
  9. dst = cv.dilate(dst,None)
  10. ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
  11. dst = np.uint8(dst)
  12. # find centroids
  13. ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
  14. # define the criteria to stop and refine the corners
  15. criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
  16. corners = cv.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)
  17. # Now draw them
  18. res = np.hstack((centroids,corners))
  19. res = np.int0(res)
  20. img[res[:,1],res[:,0]]=[0,0,255]
  21. img[res[:,3],res[:,2]] = [0,255,0]
  22. cv.imwrite('subpixel5.png',img)

下面是结果,其中的一些重要位置进行了缩放:

subpixel3.png

其他资源

练习