目标
本章中,
理论
在上一章中,我们看到角点是图像中在所有方向上强度变化都很大的区域。早期尝试寻找这些角点的方法是由Chris Harris & Mike Stephens在他们1988年的论文A Combined Corner and Edge Detector中提出的,因此现在被称为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\]
窗口函数可以是矩形窗口或高斯窗口,它为下面的像素赋予权重。
为了进行角点检测,我们必须最大化此函数\(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'
gray = np.float32(gray)
img[dst>0.01*dst.max()]=[0,0,255]
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_BGR)
从文件中加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一种颜色空间转换为另一种颜色空间。
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()**,它可以进一步细化检测到的角点,达到亚像素精度。下面是一个示例。像往常一样,我们首先需要找到 Harris 角点。然后,我们将这些角点的质心(角点处可能有一堆像素,我们取它们的质心)传递给函数以对其进行细化。Harris 角点用红色像素标记,细化后的角点用绿色像素标记。对于此函数,我们必须定义迭代停止的标准。在达到指定的迭代次数或达到一定的精度后,无论哪个先发生,迭代都将停止。我们还需要定义它搜索角点的邻域大小。
import numpy as np
import cv2 as cv
filename = 'chessboard2.jpg'
gray = np.float32(gray)
dst = np.uint8(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_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=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)
计算布尔图像的连通分量标记图像,并产生统计输出……
以下是结果,其中一些重要位置在缩放窗口中显示以进行可视化
图像
附加资源
练习