目标
在本章中,
理论
在上一章中,我们看到角点是图像中所有方向上的强度变化都很大的区域。Chris Harris & Mike Stephens 在他们 1988 年发表的论文 A Combined Corner and Edge Detector 中,对寻找这些角点进行了早期的尝试,因此现在被称为哈里斯角点检测器。他将这个简单的想法转化为了数学形式。它基本上是找到所有方向上位移 \((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\) 时,该区域是角点。
可以用一张漂亮的图片表示如下
image
因此,哈里斯角点检测的结果是一个具有这些分数的灰度图像。对一个合适的分数进行阈值处理可以得到图像中的角点。我们将用一个简单的图像来完成它。
OpenCV 中的哈里斯角点检测器
OpenCV 具有函数 cv.cornerHarris() 用于此目的。它的参数是
- img - 输入图像。它应该是灰度和 float32 类型。
- blockSize - 这是用于角点检测的邻域大小
- ksize - 所使用的 Sobel 导数的孔径参数。
- k - 方程中哈里斯检测器的自由参数。
请参见下面的示例
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 corner detector.
void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar &borderValue=morphologyDefaultBorderValue())
使用特定的结构元素对图像进行膨胀。
以下是三个结果
image
亚像素精度的角点
有时,您可能需要以最大精度找到角点。OpenCV 提供了一个函数 cv.cornerSubPix(),它可以进一步细化检测到的角点,达到亚像素精度。下面是一个例子。像往常一样,我们需要先找到哈里斯角点。然后我们将这些角点的质心(一个角点可能有很多像素,我们取它们的质心)传递给它来细化它们。哈里斯角点用红色像素标记,细化的角点用绿色像素标记。对于这个函数,我们必须定义何时停止迭代的标准。我们在指定的迭代次数后或达到一定的精度后停止它,以先发生者为准。我们还需要定义它搜索角点的邻域大小。
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)
computes the connected components labeled image of boolean image and also produces a statistics outpu...
下面是结果,其中一些重要的位置显示在缩放的窗口中以进行可视化
image
附加资源
练习