在这一章当中,
- 我们将了解 Harris 角点检测背后的概念。
- 我们将看到函数: cv.cornerHarris() , cv.cornerSubPix()
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_x和I_y分别是 x 和 y 方向的图像导数。(可以使用 cv.Sobel() 计算得到)。
然后是主要部分。在此之后,他们建立了一个等式来评分,从而判断窗口中是否含有角点:
R = det(M)-k(trace(M))^ 2
其中
- $$
det(M)=\lambda_1 \lambda_2
$$ - $$
trace(M)= \lambda_1+\lambda_2
$$ - $$
λ1和λ2是 M 的特征值
$$
根据这些特征值可以判断一个区域是角点,边缘还是平面。
- 当λ1和λ2都很小时,|R|也很小,该区域是平面。
- 当λ1>>λ2 或者R<0时,R该区域是边缘。
- 当 λ1 和λ2大且λ1∼λ2时,R很大,该区域是一个角点。
上述结论可以用下图表示:
因此,Harris 角点检测的结果是具有这些分数的灰度图像。选取适当的阈值即可筛选出图像中的角点。我们将用一个简单的图像来进行演示。
OpenCV 中的 Harris 角点检测器
OpenCV 中的 cv.cornerHarris() 函数用来实现 Harris 角点检测。它的参数是:
- img – 输入图像,应为 float32 类型的灰度图。
- blockSize – 角点检测所考虑的邻域大小。
- ksize – Sobel 导数的内核大小。
- k – Harris 检测器方程中的自由参数。
请参阅以下示例:
import numpy as np
import cv2 as cv
filename = 'chessboard.png'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)
#result is dilated for marking the corners, not important
dst = cv.dilate(dst,None)
# Threshold for an optimal value, it may vary depending on the image.
img[dst>0.01*dst.max()]=[0,0,255]
cv.imshow('dst',img)
if cv.waitKey(0) & 0xff == 27:
cv.destroyAllWindows()
以下是三个结果:
具有亚像素级精度的角点
有时,您可能需要以最高精度找到角点。OpenCV 带有一个函数 cv.cornerSubPix() ,它进一步细化了角点检测,以达到亚像素级精度。以下是一个例子。像往常一样,我们需要先找到 Harris 角点。然后将这些角的质心(角点处可能有一堆像素,我们采用它们的质心)传递给该函数来细化它们。Harris 角点以红色像素标记,细化后的角点以绿色像素标记。对于此函数,我们必须定义迭代停止的条件。我们在经过指定次数的迭代或达到一定精度后停止它,以先发生者为准。我们还需要定义进行角点搜索的邻域大小。
import numpy as np
import cv2 as cv
filename = 'chessboard2.jpg'
img = cv.imread(filename)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# find Harris corners
gray = np.float32(gray)
dst = cv.cornerHarris(gray,2,3,0.04)
dst = cv.dilate(dst,None)
ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)
# find centroids
ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
# define the criteria to stop and refine the corners
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)
# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]
cv.imwrite('subpixel5.png',img)
下面是结果,其中的一些重要位置进行了缩放: