1 理论部分
1.1 特征
在数据挖掘、计算机视觉等领域经常会提起“特征”一词,那么究竟什么才是特征?数据挖掘中我们可以称被分析数据集的某一属性(一行为一个样本,一列为一个属性)为特征,因为它反映了该数据集的某一种特性,是该数据集的一种固有标识。在计算机视觉中,一张图像的特征其实也有类似的概念,这种特征也能够描述这张图像的全部(或局部)内容或含义。
图像特征可以是①有意义的图像区域,该区域具有独特的特征和良好的识别性;也可以是②对图像信息的一种数据抽取和表示。
- 针对①可以是图像中的角点、边缘、斑点、直线、曲线及高密度区域等。大多数特征检测算法都会涉及图像的角点、边和斑点的识别,也有一些涉及脊向的概念,可以认为脊向是细长物体的对称轴,例如识别图像中的一条路。角点和边都好理解,那什么是斑点呢?斑点通常是指与周围有着颜色和灰度差别的区域。在实际地图中,往往存在着大量这样的斑点,如一颗树是一个斑点,一块草地是一个斑点,一栋房子也可以是一个斑点。由于斑点代表的是一个区域,相比单纯的角点,它的稳定性要好,抗噪声能力要强,所以它在图像配准上扮演了很重要的角色。
- 对于②,则是采用直方图向量、投影向量等方法提取出新形式的数据特征,使得所提取出来的特征具有一些较好的特性,比如旋转不变性、光照不变性等,使得模型能更好地检测或识别出图像的特征。
图像的特征应用很广泛,我们通过图像特征能过够将实现图像的分类、目标检测、图像拼接等。而特征就需要特征检测来提出,最终达到的目标是:通过特征检测我们实现的就是能够让计算机“读懂”图像,将人类看到的视觉信息转化成计算机能够识别和处理的定量形式。
1.2 角点
角点是常见的点特征,它具有旋转不变形,光照不变性,视角不变性等特性,可以在目标匹配、目标跟踪、三维重建等应用中使用。常见的点特征提取算法主要有Harris角点检测算法、FAST特征检测算法、SIFT特征检测算法、SURF特征检测算法等,本文主要介绍Harris算法。
用一个滑动的窗口在图像内移动,分别会出现以下三种情况(如图1所示):
- 平坦区域(左图):当窗口向任意方向移动时,窗口内的像素值不会发生太大变化;
- 边缘(中图):当窗口沿着平行于边缘的方向移动时,窗口内的像素值不会发生太大变化;
- 角点(右图):不管窗口向着哪个方向移动,窗口内的像素值都会发生很大的改变。
图1. 角点的识别
1.3 图像梯度
图像的梯度,指的是图像灰度值的变化率。由于图像灰度变化并不连续,因此采用差分法来求梯度。如点 ( x , y ) (x,y) (x,y)处在 x x x方向和 y y y方向的梯度按sobel算子计算,具体见Sobel算子简介。
1.4 Harris角点检测算法思想
该算法可以分为三个主要的步骤:
- 建立数学模型描述窗口灰度变化
当窗口在 x x x和 y y y方向分别移动了 u u u和 v v v该窗口的灰度值变化为 I ( x + u , y + v ) − I ( x , y ) I(x+u,y+v)-I(x,y) I(x+u,y+v)−I(x,y)。
设 w ( x , y ) w(x,y) w(x,y)为窗口函数,即窗口内各点之间的权重,表示该点对窗口灰度变化的贡献大小。当各点权重都为1时,此窗口函数也称为一个均值滤波核:
[ 1 1 1 1 1 1 1 1 1 ] \left[ \begin{matrix} 1&1&1\\1&1&1\\1&1&1 \end{matrix} \right] ⎣⎡111111111⎦⎤当窗口函数为以窗口中心为原点并呈高斯分布时,此窗口函数也称为高斯滤波核,如:
1 16 [ 1 2 1 2 4 2 1 2 1 ] \frac 1 {16} \left[ \begin{matrix} 1&2&1\\2&4&2\\1&2&1 \end{matrix} \right] 161⎣⎡121242121⎦⎤高斯滤波实质是一种加权平均滤波,为了实现平均,核还带有一个系数,例如上面的十六分之一,这些系数等于矩阵中所有数值之和的倒数。因为高斯滤波是以空间距离来确定权重大小的,但并没有考虑用颜色距离来确定权重,这样就导致了高斯滤波在去除噪声的同时,也一定程度上模糊了边界。关于高斯滤波可参考图像平滑去噪之高斯滤波器。
图2. 移动窗口导致的灰度变化
将图2中公式进行简化,由泰勒公式得:
I ( x + u , y + v ) = I ( x , y ) + u I x + v I y I(x+u,y+v)=I(x,y)+uI_x+vI_y I(x+u,y+v)=I(x,y)+uIx+vIy I ( x + u , y + v ) − I ( x , y ) = u I x + v I y I(x+u,y+v)-I(x,y)=uI_x+vI_y I(x+u,y+v)−I(x,y)=uIx+vIy
窗口的灰度变化 E ( u , v ) E(u,v) E(u,v):
E ( u , v ) = Σ ( x , y ) w ( x , y ) × ( u 2 I x 2 + v 2 I y 2 + 2 u v I x I y ) E(u,v)=\Sigma_{(x,y)} w(x,y)\times(u^2I_x^2+v^2I_y^2+2uvI_xI_y) E(u,v)=Σ(x,y)w(x,y)×(u2Ix2+v2Iy2+2uvIxIy)
将上式写成矩阵形式:
E ( u , v ) = ∑ ( x , y ) w ( x , y ) × [ u v ] [ I x 2 I x I y I x I y I y 2 ] [ u v ] E(u,v)=\sum_{(x,y)} w(x,y)\times\left[ \begin{matrix} u & v \end{matrix} \right] \left[ \begin{matrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{matrix} \right] \left[ \begin{matrix} u \\ v \end{matrix} \right] E(u,v)=(x,y)∑w(x,y)×[uv][Ix2IxIyIxIyIy2][uv] = [ u v ] M [ u v ] =\left[ \begin{matrix} u & v \end{matrix} \right] \boldsymbol{M} \left[ \begin{matrix} u \\ v \end{matrix} \right] =[uv]M[uv] M \boldsymbol{M} M矩阵为一个是实对称矩阵,将其对角化后,可得:
M = ∑ ( x , y ) w ( x , y ) × [ I x 2 I x I y I x I y I y 2 ] = R [ λ 1 0 0 λ 2 ] R − 1 \boldsymbol{M}=\sum_{(x,y)} w(x,y)\times\left[ \begin{matrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{matrix} \right]=R\left[ \begin{matrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{matrix} \right]R^{-1} M=(x,y)∑w(x,y)×[Ix2IxIyIxIyIy2]=R[λ100λ2]R−1其中, R R R为正交矩阵, R T = R − 1 R^T=R^{-1} RT=R−1,因此 E ( u , v ) = [ u v ] R [ λ 1 0 0 λ 2 ] R T [ u v ] T E(u,v)=\left[ \begin{matrix} u & v \end{matrix} \right] R \left[ \begin{matrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{matrix} \right] R^{T} \left[ \begin{matrix} u & v \end{matrix} \right]^T E(u,v)=[uv]R[λ100λ2]RT[uv]T = [ u ′ v ′ ] [ λ 1 0 0 λ 2 ] [ u ′ v ′ ] =\left[ \begin{matrix} u' & v' \end{matrix} \right] \left[ \begin{matrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{matrix} \right] \left[ \begin{matrix} u' \\ v' \end{matrix} \right] =[u′v′][λ100λ2][u′v′] = λ 1 ( u ′ ) 2 + λ 2 ( v ′ ) 2 = ( u ′ ) 2 1 λ 1 + ( v ′ ) 2 1 λ 2 =\lambda_1 ({u'})^2+\lambda_2 ({v'})^2=\frac {({u'})^2} {\frac 1 {\lambda_1}} + \frac {({v'})^2} {\frac 1 {\lambda_2}} =λ1(u′)2+λ2(v′)2=λ11(u′)2+λ21(v′)2我们得到 E ( u , v ) E(u,v) E(u,v)就是一个椭圆,椭圆的长轴为 λ 2 − 0.5 \lambda_2^{-0.5} λ2−0.5,短轴为 λ 1 − 0.5 \lambda_1^{-0.5} λ1−0.5(假设 λ 1 > λ 2 \lambda_1>\lambda_2 λ1>λ2)。因为 u u u和 v v v为每次窗口移动的位移,要使 E ( u , v ) E(u,v) E(u,v)最大,则 λ 1 \lambda_1 λ1和 λ 2 \lambda_2 λ2必须同时最大。
我们还可以从另一个角度来理解:矩阵代表着运动,且当矩阵维度没有发生改变时,一个矩阵就包含了两个运动:旋转和拉伸。其中,对角化得到的正交矩阵 R R R可看做旋转因子,对角矩阵包含的两个特征值 λ 1 λ_1 λ1和 λ 2 λ_2 λ2可看做缩放因子,也即是两个正交方向上的变化分量。椭圆的大小与旋转因子无关,只与缩放因子(即特征值)有关。
上述旋转和缩放过程可以直观地放在椭圆上观察。下图中,对角矩阵 [ 3 0 0 1 ] \left[ \begin{matrix} 3 & 0\\ 0 & 1 \end{matrix} \right] [3001]为包含两个特征值3和1,决定了红色椭圆短轴和长轴的长度。若在对角矩阵的基础上与正交矩阵相乘,即乘以旋转因子,则得 M = [ 2 − 1 − 1 2 ] M=\left[ \begin{matrix} 2 & -1 \\ -1 & 2 \end{matrix} \right] M=[2−1−12],红色椭圆也随之旋转为蓝色椭圆。
图3.椭圆的旋转
- 构造角点响应函数 R R R
窗口灰度的变化可由 E ( u , v ) E(u,v) E(u,v)表示,而 E ( u , v ) E(u,v) E(u,v)的大小主要由矩阵 M \boldsymbol{M} M决定。上面提到,要使 E ( u , v ) E(u,v) E(u,v)最大,则矩阵 M \boldsymbol{M} M的特征值必须同时最大。
为了在实际中更好地应用于编程,我们构造角点响应函数 R R R:
R = det ( M ) − k ( trace ( M ) ) 2 ( 1 ) R=\text{det}(\boldsymbol{M})-k(\text{trace}(\boldsymbol{M}))^2 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (1) R=det(M)−k(trace(M))2 (1)其中, det ( M ) = λ 1 λ 2 \text{det}(\boldsymbol{M})=λ_1λ_2 det(M)=λ1λ2为矩阵的行列式, trace ( M ) = λ 1 + λ 2 \text{trace}(\boldsymbol{M})=λ_1+λ_2 trace(M)=λ1+λ2为矩阵的迹, k k k为一个经验常数,通常取在0.04~0.06之间。
我们可以看出,当特征值同时取最大时, R R R才取最大,所以可以用 R R R来代替 E ( u , v ) E(u,v) E(u,v)。
另外还有其他形式的角点响应函数 R R R:
R = λ m i n R=\lambda_min R=λmin R = λ 1 − k λ 2 , k = 0.05 R = \lambda_1-k\lambda_2,\ \ \ k=0.05 R=λ1−kλ2, k=0.05 R = d e t ( M ) t r a c e ( M ) = λ 1 λ 2 λ 1 + λ 2 R=\frac {det(M)} {trace(M)}=\frac {\lambda_1\lambda_2} {\lambda_1+\lambda_2} R=trace(M)det(M)=λ1+λ2λ1λ2关于这些角点函数,本文不做过多的讨论,下文中 R R R所指均为式(1)。
- 角点的判定
根据 R R R值的大小,可以将窗口所在的区域划分为平面、边缘和角点(图3):
a. 平面:由于灰度值变化较小,所以 R R R值很小,并且沿任意方向的灰度变化都很小,所以 I x I_x Ix和 I y I_y Iy都较小,所以 λ 1 λ_1 λ1和 λ 2 λ_2 λ2也都较小;
b. 边缘: R R R为负数,仅在垂直于边缘的方向上有较大的变化量, I x I_x Ix和 I y I_y Iy中只有一个变化较大,因此 λ 1 ≫ λ 2 λ_1\ggλ_2 λ1≫λ2或者 λ 1 ≪ λ 2 λ_1\llλ_2 λ1≪λ2;另外,对于斜边缘, I x I_x Ix和 I y I_y Iy可能都比较大,但 I x I_x Ix和 I y I_y Iy成一定的比例,同样对应 λ 1 ≫ λ 2 λ_1\ggλ_2 λ1≫λ2或者 λ 1 ≪ λ 2 λ_1\llλ_2 λ1≪λ2;
c. 角点: R R R值很大,并且 I x I_x Ix和 I y I_y Iy都很大,所以 λ 1 λ_1 λ1和 λ 2 λ_2 λ2也都较大。
更详细的解释可见2.2.3 矩阵MM的理解。
图4. 角点的判定
1.5 Harris算法的特性
-
参数 k k k对对角点检测结果的影响
设矩阵 M \boldsymbol{M} M的特征值 λ 1 ≥ λ 2 ≥ 0 \lambda_1\ge\lambda_2\ge0 λ1≥λ2≥0,令 λ 2 = n λ 1 , 0 ≤ n ≤ 1 \lambda_2=n\lambda_1,0\le n\le 1 λ2=nλ1,0≤n≤1,则角点响应为:
R = λ 1 λ 2 − k ( λ 2 + λ 2 ) 2 = λ 1 2 ( n − k ( 1 + n ) 2 ) R=\lambda_1\lambda_2-k(\lambda_2+\lambda_2)^2=\lambda_1^2(n-k(1+n)^2) R=λ1λ2−k(λ2+λ2)2=λ12(n−k(1+n)2)假设 R ≥ 0 R\ge0 R≥0,则有:
0 ≤ k ≤ n ( 1 + n ) 2 ≤ 0.25 0\le k\le\frac{n}{(1+n)^2}\le0.25 0≤k≤(1+n)2n≤0.25对于较小的 n n n值, R ≈ λ 1 2 ( n − k ) , k < n R\approx\lambda_1^2(n-k),kR≈λ12(n−k),k<n。
所以,可以得出这样的结论:
增大 k k k的值,将减小角点响应值 R R R,降低角点检测的灵性,减少被检测角点的数量;
减小 k k k值,将增大角点响应值 R R R,增加角点检测的灵敏性,增加被检测角点的数量。
-
对亮度和对比度的变化不敏感
Harris算法采用图像梯度或微分来进行运算,而梯度或微分对图像密度的拉升或收缩,以及对亮度的升高或降低不明感。也就是说,对亮度和对比度的仿射变换并不改变极值点出现的位置。但是阈值选择的不同会影响检测角点的数量。
图5. 部分不变性
-
旋转不变性
Harris角点检测算子使用的是角点附近的区域灰度二阶矩矩阵。而二阶矩矩阵可以表示成一个椭圆,椭圆的长短轴正是二阶矩矩阵特征值平方根的倒数。当特征椭圆转动时,特征值并不发生变化,所以判断角点响应值 R R R也不发生变化,由此说明Harris角点检测算子具有旋转不变性。
图6. 用椭圆表示二阶矩矩阵
-
不具有尺度不变性
当图像被缩小时,所检测出的角点可能是完全不一样的。
图7. 不具有尺度不变性
2 实践部分
OpenCV中cornerHarris函数
OpenCV中有可直接调用的Harris角点检测的函数:
cv2.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])
参数解释:
- src – 输入的灰度图像,float32类型;
- blockSize - 用于角点检测的邻域大小,就是上面提到的窗口的尺寸;
- ksize - 用于计算梯度的Sobel算子的尺寸;
- k - 用于计算角点响应函数的参数 k k k,取值在0.04~0.06之间;
- dst - 存储着Harris角点响应的图像矩阵,大小与输入图像大小相同,是一个浮点型矩阵;
- borderType - 边界处理的类型。
下面基于python进行Harris角点检测:
import cv2 as cv
from matplotlib import pyplot as plt
import numpy as np
block_size = 3
sobel_size = 3
k = 0.06
image = cv.imread('Building.jpg')
print(image.shape)
height = image.shape[0]
width = image.shape[1]
channels = image.shape[2]
print("width: %s height: %s channels: %s" % (width, height, channels))
gray_img = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
gray_img = np.float32(gray_img)
corners_img = cv.cornerHarris(gray_img, block_size, sobel_size, k)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
dst = cv.dilate(corners_img, kernel)
for r in range(height):
for c in range(width):
pix = dst[r, c]
if pix > 0.05 * dst.max():
cv.circle(image, (c, r), 5, (0, 0, 255), 0)
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
图8. 角点检测结果
2.2 基于python实现Harris算法
def harris_det(img, block_size=3, ksize=3, k=0.04, threshold = 0.01, WITH_NMS = False):
'''
params:
img:单通道灰度图片
block_size:权重滑动窗口
ksize:Sobel算子窗口大小
k:响应函数参数k
threshold:设定阈值
WITH_NMS:非极大值抑制
return:
corner:角点位置图,与源图像一样大小,角点处像素值设置为255
'''
h, w = img.shape[:2]
gray = cv2.GaussianBlur(img, ksize=(ksize, ksize), sigmaX=2)
grad = np.zeros((h,w,2),dtype=np.float32)
grad[:,:,0] = cv2.Sobel(gray,cv2.CV_16S,1,0,ksize=3)
grad[:,:,1] = cv2.Sobel(gray,cv2.CV_16S,0,1,ksize=3)
m = np.zeros((h,w,3),dtype=np.float32)
m[:,:,0] = grad[:,:,0]**2
m[:,:,1] = grad[:,:,1]**2
m[:,:,2] = grad[:,:,0]*grad[:,:,1]
m = [np.array([[m[i,j,0],m[i,j,2]],[m[i,j,2],m[i,j,1]]]) for i in range(h) for j in range(w)]
D,T = list(map(np.linalg.det,m)),list(map(np.trace,m))
R = np.array([d-k*t**2 for d,t in zip(D,T)])
R_max = np.max(R)
R = R.reshape(h,w)
corner = np.zeros_like(R,dtype=np.uint8)
for i in range(h):
for j in range(w):
if WITH_NMS:
if R[i,j] > R_max*threshold and R[i,j] == np.max(R[max(0,i-1):min(i+2,h-1),max(0,j-1):min(j+2,w-1)]):
corner[i,j] = 255
else:
if R[i,j] > R_max*threshold :
corner[i,j] = 255
return corner
if __name__ == "__main__":
image = cv2.imread('./timg.jpg')
height, width, channel = image.shape
print('image shape --> h:%d w:%d c:%d' % (height, width, channel))
cv2.imshow('mount', image)
cv2.waitKey(2000)
cv2.destroyAllWindows()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
dst = harris_detect(gray)
image_dst = image[:, :, :]
image_dst[dst > 0.01 * dst.max()] = [0, 0, 255]
cv2.imwrite('./dsti8_1.jpg', image_dst)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
3 参考资料
- https://www.cnblogs.com/ronny/p/4009425.html
- https://github.com/datawhalechina/team-learning/tree/master/03%20%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89
- https://blog.csdn.net/m0_38052500/article/details/106936075
- https://blog.csdn.net/my_kun/article/details/106918857
- https://www.cnblogs.com/recoverableTi/p/13184038.html