欲速则不达,哈里斯角点检测是一个不简单也不复杂的概念方法,但是需要了解很多前置概念,一步步来。但是想要一步到位,也可以直接跳过第一部分的前置概念理解部分。
什么是数字图像概念?
这是我最喜欢的SUV,沃尔沃XC90,在我们眼中,我们都看出这是一个非常漂亮的汽车的图片,但是在计算机的识别后,是什么?
在计算机图像中,该图像是一个数组,其中包含元素数量为 192010803,其中3为三层通道Channel;而通过对图像属性的对比,发现图像的分辨率也为1920*1080。说明经过数字化处理,图像的每个像素在计算机中都被转化为一个值。
什么是角点?
角点说白了就是物体边缘的拐点。通过对角点的识别,我们可以实现很多功能,比如:
实现图片的拼接:
三维重建:
等等…
通过对比下述三张图,理解哈里斯对于角点的定义。
首先,我们选择一个合适大小的像素框,如途中金黄色区域。我们试图通过对框中像素值的变化去探究是否为角点。
第一个 flat,我们在上下左右以及对角线移动像素框时,会发现框中的值几乎没有变化,全部为黑色值0;
第二个 edge,我们在上下移动时,发现像素框中值没有变化,但是左右移动时以及对角线移动时会发生变化;
第三个 corner,我们不论上下移动还是左右移动还是对角线移动,都会发生变化,所以其为角点。
将上述哈里斯角点原理化为公式来看:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v)=\sum _{x,y}w(x,y) [I(x+u,y+v)-I(x,y)]^2 E(u,v)=x,y∑w(x,y)[I(x+u,y+v)−I(x,y)]2
其中, u , v u,v u,v 分别代表着在竖直和水平方向上的偏移, w ( x , y ) w(x,y) w(x,y) 为像素框的中心; I ( x + u , y + v ) I(x+u,y+v) I(x+u,y+v) 为像素框位移之后坐标加偏移 ( x + u , y + v ) (x+u,y+v) (x+u,y+v) 的灰度值, I ( x , y ) I(x,y) I(x,y) 为位移之前坐标位置 ( x , y ) (x,y) (x,y) 的灰度值。
其结果:E代表的就是 “平移前选定的红框中每个像素” 与 “平移后选定的绿框的每个像素” 对应位置的差的平方和。
泰勒展开将一些复杂的函数逼近近似的表示为简单的多项式函数。
f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ( x − x 0 ) 2 + . . . + f ( n ) ( x 0 ) n ! ( x − x 0 ) n + o [ ( x − x 0 ) n ] f(x)=f(x_0)+f'(x_0)(x-x_0)+\frac {f''(x_0)} 2(x-x_0)^2+...+\frac {f^{(n)}(x_0)} {n!}(x-x_0)^n+o[(x-x_0)^n] f(x)=f(x0)+f′(x0)(x−x0)+2f′′(x0)(x−x0)2+...+n!f(n)(x0)(x−x0)n+o[(x−x0)n]
上述泰勒公式使用的余项是皮亚诺余项。
通过上图理解,通过不断的进行泰勒展开,不断逼近原函数曲线;我们可以通过泰勒公式来获取函数的信息。
通过一阶泰勒公式 f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) f(x)=f(x_0)+f'(x_0)(x-x_0) f(x)=f(x0)+f′(x0)(x−x0),我们可以将哈里斯角点检测灰度值函数 I ( x + u , y + v ) I(x+u,y+v) I(x+u,y+v) 公式化为:
I ( x + u , y + v ) ≈ I ( x , y ) + I x u + I y v I(x+u,y+v)≈I(x,y)+I_xu+I_yv I(x+u,y+v)≈I(x,y)+Ixu+Iyv
其中
I x u = d I d x u ; I y v = d I d y v I_xu=\frac {dI} {dx} u;I_yv=\frac {dI} {dy} v Ixu=dxdIu;Iyv=dydIv
所以有:
E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u,v)=\sum _{x,y} w(x,y)[I(x+u,y+v)-I(x,y)]^2 E(u,v)=∑x,yw(x,y)[I(x+u,y+v)−I(x,y)]2
≈ w ( x , y ) ∑ x , y [ I ( x , y ) + I x u + I y v − I ( x , y ) ] 2 ≈w(x,y)\sum _{x,y} [I(x,y)+I_xu+I_yv-I(x,y)]^2 ≈w(x,y)∑x,y[I(x,y)+Ixu+Iyv−I(x,y)]2
= w ( x , y ) ∑ x , y [ I x u + I y v ] 2 =w(x,y)\sum _{x,y} [I_xu+I_yv]^2 =w(x,y)∑x,y[Ixu+Iyv]2
= w ( x , y ) ∑ x , y ( I x 2 u 2 + 2 I x I y u v + I y 2 v 2 ) =w(x,y)\sum _{x,y} (I_x^2u^2+2I_xI_yuv+I_y^2v^2) =w(x,y)∑x,y(Ix2u2+2IxIyuv+Iy2v2)
而又因为 w ( x , y ) w(x,y) w(x,y) 代表的就是像素框中心点值,不是我们主要研究的部分,可以暂时从 E ( u , v ) E(u,v) E(u,v) 的计算中提出来,所以可以将 E ( u , v ) E(u,v) E(u,v) 表示为:
E ( u , v ) = [ u 2 ∑ x , y I x 2 + 2 u v ∑ x , y I x I y + v 2 ∑ x , y I y 2 ] E(u,v)=[u^2 \sum_{x,y} I_x^2 + 2uv\sum_{x,y} I_xI_y +v^2\sum_{x,y} I_y^2] E(u,v)=[u2∑x,yIx2+2uv∑x,yIxIy+v2∑x,yIy2]
转换成矩阵形式:
推导如下:
下一步,我们需要对 I x I_x Ix 与 I y I_y Iy 的意义进行探究。
首先,上述已经说明, I x = d I d x I_x=\frac {dI} {dx} Ix=dxdI, I y = d I d y I_y=\frac {dI} {dy} Iy=dydI, I ( x , y ) I(x,y) I(x,y) 代表着 ( x , y ) (x, y) (x,y) 点的像素值。
其次,我们有求 平移前后对应像素差平方值的函数 E ( u , v ) E(u,v) E(u,v)
我们可以将该函数表示为:
即:
所以我们下面将对矩阵 M M M 进行研究:
对于每一个边缘上的点而言,当 d I d x \frac {dI} {dx} dxdI 有意义时, d I d y \frac {dI} {dy} dydI 值很小约为0;当 d I d y \frac {dI} {dy} dydI 有意义时, d I d x \frac {dI} {dx} dxdI 值很小约为0。当然除了角点。如图所示:
所以一共存在三种情况的点:
如果向完全理解 d I d x \frac {dI} {dx} dxdI 与 d I d y \frac {dI} {dy} dydI,需要结合 索贝尔 (Sobel) 算子 进行理解。
因为本节博客主要介绍哈里斯角点检测,所以这里只简单介绍一下索贝尔算子。
首先,明确索贝尔算子的作用为 边缘检测。
索贝尔有两个算子,一个是检测水平边缘;另一个是检测垂直边缘;
e . g . e.g. e.g. 假设我们想要检测原始图像 A A A 的横向边缘以及纵向边缘, G x G_x Gx 为横向索贝尔算子, G y G_y Gy 为纵向索贝尔算子。
e . g . e.g. e.g. 案例:对比下面三个原始图片的索贝尔算子结果:
代码:
# 索贝尔算子
import cv2
cv2.namedWindow('video', cv2.WINDOW_NORMAL)
cv2.resizeWindow('video', 640, 480)
img = cv2.imread("Sobel_0.png")
sobel_x = cv2.Sobel(img[:, :, 0], cv2.CV_16S, 1, 0)
sobel_y = cv2.Sobel(img[:, :, 0], cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(sobel_x)
absY = cv2.convertScaleAbs(sobel_y)
# # 横向边缘检测
# dst = cv2.addWeighted(absX, 1, absY, 0, 0)
# # 纵向边缘检测
# dst = cv2.addWeighted(absX, 0, absY, 1, 0)
# 横向+纵向边缘检测
dst = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
cv2.imshow("video", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
原始图片: Sobel_0.png;Sobel_1.png;Sobel_2.png
对三个图片分别做横向边缘检测:
对三个图片分别做横向+纵向边缘检测:
以上便是Sobel算子的基本实现。
梯度统计图: I x I_x Ix 为水平方向的梯度值, I y I_y Iy 为竖直方向的梯度值。
很明显,平坦点几乎没有什么梯度变化,周围没有什么像素的变化,值趋近于0;
而边缘点,只有一个方向有梯度变化,上上图中区域2的变化是在水平移动有像素值有变化,竖直方向移动,像素值几乎没有变化;
而角点,两个方向的移动都会有梯度的变化,都有像素值的大量改变。
梯度图:
平坦点的移动几乎没有任何像素值的变化;
边缘点的移动会在一个方向有大量像素值的变化,而另一个方向如同平坦点一样;
角点会在两个方向都有大量像素值的变化。
通过上述加强了对:角点与边缘和平坦的理解,下面将回归公式,从对角点响应大小判定的方案去判定是否为角点。
角点响应公式: R = d e t ( M ) − k ( t r a c e ( M ) ) 2 R=det(M)-k(trace(M))^2 R=det(M)−k(trace(M))2
其中:
d e t ( M ) det(M) det(M) 为求矩阵M的行列式的值, d e t ( M ) = I x 2 ∗ I y 2 − I x I y ∗ I x I y det(M)=I_x^2*I_y^2-I_xI_y*I_xI_y det(M)=Ix2∗Iy2−IxIy∗IxIy;
k k k 称为经验值,一般为 0.04 0.04 0.04~ 0.06 0.06 0.06;
t r a c e ( M ) trace(M) trace(M) 为矩阵对角线的和, t r a c e ( M ) = I x 2 + I y 2 trace(M)=I_x^2+I_y^2 trace(M)=Ix2+Iy2
通过 R R R 的值,来判断是角点的强度,也可以说来判断是角点的真实性。
前置知识到此结束,下面将通过代码案例实际进行一张图片的角点检测。
1、彩色图像转化为灰阶图像;
2、计算空间微分(泰勒展开);
3、建构结构张量(Sobel算子);
4、计算哈里斯响应(角点响应);
5、非极大值抑制(筛选点)。
# harris detector
import cv2
import numpy as np
'''
image: 源图片;
blocksize:窗口大小;
ksize:索贝尔梯度计算的Kernel大小;
k:角点响应R的经验值系数。
'''
def cornerHarris(image, blocksize=2, ksize=3, k=0.04):
def _clacHarris(cov,k):
result = np.zeros([cov.shape[0], cov.shape[1]], dtype=np.float32)
for i in range(cov.shape[0]):
for j in range(cov.shape[1]):
a = cov[i, j, 0]
b = cov[i, j, 1]
c = cov[i, j, 2]
result[i, j] = a * c - b * b - k * (a + c) * (a + c)
return result
# Sobel
sobel_x = cv2.Sobel(image, cv2.CV_32F, 1, 0, ksize=ksize)
sobel_y = cv2.Sobel(image, cv2.CV_32F, 0, 1, ksize=ksize)
# 建立存储R值矩阵
cov = np.zeros([image.shape[0], image.shape[1], 3], dtype=np.float32)
# 计算Ix^2,Iy^2与Ix*Iy
for i in range(image.shape[0]):
for j in range(image.shape[1]):
cov[i,j,0] = sobel_x[i,j] * sobel_x[i,j]
cov[i,j,1] = sobel_x[i,j] * sobel_y[i,j]
cov[i,j,2] = sobel_y[i,j] * sobel_y[i,j]
# 计算梯度和
cov = cv2.boxFilter(cov, -1, (blocksize, blocksize), normalize=False)
return _clacHarris(cov,k)
if __name__ == '__main__':
img = cv2.imread("harris_detector.jpg")
# 将图片转化为灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 哈里斯角点检测
result = cornerHarris(gray_img, 2, 3, 0.04)
# 筛选
pos = cv2.goodFeaturesToTrack(result, 0, 0.01, 10)
for i in range(len(pos)):
cv2.circle(img, (int(pos[i][0][0]), int(pos[i][0][1])), 1, [255,0,0], thickness=2)
cv2.imshow('harris',img)
cv2.waitKey(0)
哈里斯角点检测,主要用于用图像中找出代表角点的特征点。
角点是图像中最重要的特征,基本上角点的特性不会受到旋转、平移以及图像亮度的影响。所以虽然角点只是一张图像中很小的一部分,但是通常却代表着一张图像中最重要的特征。
2022年11月1日
HK理工大学 包玉刚图书馆