OpenCV  4.10.0
开源计算机视觉
正在加载...
正在搜索...
无匹配项
Harris 角点检测

目标

在本章中,

理论

在上章中,我们看到图像中的角点是强度在所有方向上出现大幅变化的区域。由**Chris Harris 与 Mike Stephens**在其 1988 年的论文**综合角点与边缘检测器**中对这些角点进行的早期尝试,这就是如今所谓的 Harris 角点检测器。他将这个简单的想法转化成了数学形式。它基本在所有方向上计算相对于偏移量\((u,v)\)的强度差异。以下是对其的表达

\[E(u,v) = \sum_{x,y} \underbrace{w(x,y)}_\text{窗口函数} \, [\underbrace{I(x+u,y+v)}_\text{偏移强度}-\underbrace{I(x,y)}_\text{强度}]^2\]

窗口函数要么是矩形窗口,要么是为其下方像素赋予权重的 Gaussian 窗口。

我们必须最大化此函数 \(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(\operatorname{trace}(M))^2\]

其中

  • \(\det(M) = \lambda_1 \lambda_2\)
  • \(\operatorname{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\)和\(\lambda_2\)很大并且\(\lambda_1 \sim \lambda_2\)时发生)时,该区域是角点。

它可以用以下漂亮的图片表示

图片

因此 Harris 角点检测的结果是一个具有这些分数的灰度图像。对合适的评分进行阈值处理会给你图像中的角点。我们用一张简单的图片来实现它。

OpenCV 中的 Harris 角点检测器

OpenCV 中有一个函数 cv.cornerHarris() 用于此目的。它的参数是

  • 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)
# 结果经过扩张来标记角点,这不重要
dst = cv.dilate(dst,None)
# 对最优值进行阈值处理,它可能会因图像而异。
img[dst>0.01*dst.max()]=[0,0,255]
cv.imshow('dst',img)
if cv.waitKey(0) & 0xff == 27
void imshow(const String &winname, InputArray mat)
在指定窗口中显示图像。
int waitKey(int delay=0)
等待按键按下。
void destroyAllWindows()
销毁所有 HighGUI 窗口。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
将图像从一种颜色空间转换为另一种颜色空间。
void cornerHarris(InputArray src, OutputArray dst, int blockSize, int ksize, double k, int borderType=BORDER_DEFAULT)
Harris 角点检测器。
void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar &borderValue=morphologyDefaultBorderValue())
使用特定结构元素膨胀图像。

以下是三个结果

图片

具有亚像素精度的角点

在某些情况下,您可能需要找到最大精度的角点。OpenCV 提供了一个函数 cv.cornerSubPix(),该函数利用子像素精度进一步精修已检测到的角点。下面是一个示例。和往常一样,我们首先需要找到哈里斯角点。然后将这些角点的质心( CORNER 可能有许多像素,我们取其质心)传递给它们以进行精修。哈里斯角点用红色像素标记,而精修后的角点则用绿色像素标记。对于此函数,我们必须定义迭代停止的条件。我们将在指定次数的迭代后或在达到特定精度后停止迭代,以先发生者为准。此外,我们还需要定义搜索角点的邻域大小。

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)
# 定义停止并精修角点的条件
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)
# 现在绘制它们
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)
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定的文件。
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
精修角点位置。
double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
对每个数组元素应用固定级别阈值。
int connectedComponentsWithStats(InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity, int ltype, int ccltype)
根据布尔图像计算连通组件标记图像,并且还会生成统计输出...

下面是结果,其中一些重要位置显示在放大窗口中以实现可视化

图片

其他资源

练习