数字图像处理(五)空域高通滤波器

高通滤波器经常用来增强图像和提取图像的边缘信息,在日常的图像处理和图像识别中都有着很广泛的应用。这里要说明的高通滤波器主要有如下几种:非锐化掩膜,索贝尔算子,拉普拉斯算子,canny算法。

非锐化掩膜

该算法经常用于图像的增强,在图像边缘的提取上用的不多。实现的方法如下:

  • 对于原始图像\(f(x,y)\),对其进行高斯模糊得到平滑之后的图像\(\overline{f(x,y)}\)
  • 原始图像和平滑后的图像做差得到掩膜\(g_{mask}(x,y)=f(x,y)-\overline{f(x,y)}\)
  • 将原始图像与k倍的掩膜相叠加得到增强后的图像\(g(x,y)=f(x,y)+k*g_{mask}(x,y)\)
  • 一般将k取值为1,为图像增强,k大于1则为高提升图像。

该算法得到的图像处理效果如下:

从左致右依次为:原始图像,得到的掩膜(规整到0-255),增强后的图像

由上图的结果可以看出,掩膜图像在边缘处的亮度很高,在其余地方的亮度较低,在和原始图像进行叠加之后,这里采用的取整方式是将叠加后图像进行整体归一化之后再乘255之后取整。因为原图像边缘在和掩膜叠加之后,图像边缘的亮度很大,在进行归一化之后原图像中的亮部有一定程度的变暗。如果采用另一种取整方式即:如果叠加超过255则将其视为255,小于0则统一视为0。可以很有效的解决这个问题,但图像的边缘就没有上一方法这么明显。具体使用哪一种方法根据实际情况进行选择。

索贝尔算子

sobel算子在图像边缘的提取中经常用到,它采用的是利用图像的一阶导数来进行边缘的提取。即\(\nabla f\)

我们知道,二元函数的梯度由x方向和y方向两个方向的方向导数构成,那么sobel算子也一样,有两个,分别为x方向和y方向,两个算子分别如下: \[ \begin{bmatrix}-1&0&1\\-1&0&2\\-1&0&1\end{bmatrix} ~~~~~~~~~~\begin{bmatrix}-1&-2&-1\\0&0&0\\1&2&1\end{bmatrix} \]

  • 将图像分别与上述两个算子相卷积得到x方向和y方向的边缘\(G_x\)\(G_y\)
  • 通过\(G_x\)\(G_y\)即可以算出图像的一阶导\(G=\sqrt{G_x^2+G_y^2}\),有时为了快速计算也会取\(G=|G_x|+|G_y|\)\(G=max\{|G_x|,|G_y|\}\)
  • 将G规整到0-255即可得到图像的sobel算子边缘

依次为:原图像,x方向边缘,y方向边缘,sobel边缘

sobel算子在提取图像的明显边缘的表现很好,在提取图像的细节边缘方面也有这很好的表现,用领一幅图的处理结果来说明这一点

可以看出,即使是烟囱上的细小边缘轮廓,sobel算子提取出来的边缘有着很好的体现。

拉普拉斯算子

\[ [f(x+1)-f(x)]-[f(x)-f(x-1)]=f(x+1)+f(x-1)-2f(x) \] 由此导出拉普拉斯算子为: \[ \begin{bmatrix}0&1&0\\1&-4&1\\0&1&0\end{bmatrix} \] 有时也在其中加入对角线方向的二阶导,则算子为: \[ \begin{bmatrix}1&1&1\\1&-8&1\\1&1&1\end{bmatrix} \] 在实践中也经常使用如下这两个算子: \[ \begin{bmatrix}0&-1&0\\-1&4&-1\\0&-1&0\end{bmatrix} \begin{bmatrix}-1&-1&-1\\-1&8&-1\\-1&-1&-1\end{bmatrix} \]

假设提取出的图像边缘为\(L(x,y)\),那么图像增强的算法为\(g(x,y)=f(x,y)+c*L(x,y)\)

如果使用最上方两个算子提取出的边缘,则c=-1,使用下面的两个算子,c=1

使用拉普拉斯算子提取出的图像边缘信息如下:

依次为:原图像,无对角线的laplace边缘,有对角线的laplace边缘

拉普拉斯算子提取出的图像边缘在每个边缘处会有两条边缘线,这是由其二阶导的性质所决定的。相较于sobel算子提取出的边缘结果,laplace边缘更加细致,得到了“边缘的边缘”。由下图的对比可以更加明显的看出:

左边为sobel算子提取结果,右边为laplace

对于这种稍微复杂的图像来说,laplace算子提取的边缘过于细致,反而导致很多地方难以看清,这给肉眼的分辨造成了一些困难。但在一些图像识别领域,例如根据卫星图片去识别地面的车辆,很多时候地面的车辆就是一个个小的色块,用laplace算子就可以很好的勾勒出这些车辆的边缘轮廓以及内部的一些细节。而sobel算子对于内部的细节就显得有些无能为力。同时 ,“边缘的边缘”这项很好的性质也使得laplace算子提取的边缘可以很好的用于图像增强。

canny算法

canny算法是在sobel边缘提取上优化而来。sobel算子不论边缘强弱,都会在最终的图像中有所表征,这就导致有很多无效的边缘被提取出来显示在结果图像上。canny算法的基本思路就是对这些边缘信息进行筛选,只留下最可能是边缘的像素点。实现方法如下:

  • 和sobel一样,首先算出\(G_x\)\(G_y\)
  • 根据\(G_x\)\(G_y\)算出每个像素点的\(weight=\sqrt{G_x^2+G_y^2}\)以及\(angle=atan\frac{G_y}{G_x}\)
  • \(angle\)离散化到\(45^o\)的倍数角度。
  • 对于图像中的每个像素点\((x,y)\),将其权重\(weight(x,y)\)与其\(angle(x,y)\)方向和\(-angle(x,y)\)方向的两个像素点的\(weight\)进行比较,如果该点的\(weight\)不是最大的一个,则将其置为0
  • 双阈值检测:设置边缘的亮度上阈值和下阈值,对图像的边缘进行再一次的筛选,最后将边缘信息规整到0-255,。这里的阈值可以自行手动设置。

依次为:原图像,无双阈值检测,下阈值为150,下阈值为200

附录

参考文献

[1]数字图像处理[M]:第三版/(美)拉斐尔·C·冈萨雷斯(Rafael C.Gonzalez),(美)理查德·E·伍兹(Richard E. Woods)著;阮秋琦等译,—北京:电子工业出版社,2017.5

[2]Brook_icv.图像处理基础(4):高斯滤波器详解[G/OL].博客园: 2017-02-16 [2020-03-23].https://www.cnblogs.com/wangguchangqing/p/6407717.html#autoid-4-1-0

[4]顽皮的石头7788121. 图像边缘检测:Canny算子、Prewitt算子和sobel算子[G/OL].简书 2018-11-06 [2020-03-26]. https://www.jianshu.com/p/bed4ffe996a1

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import cv2 as cv
import numpy as np
import math

sigma=1.5 #高斯滤波器的参数

def add_zeros(img,edge): #图像边缘补零
shape=img.shape
temp=np.zeros((shape[0]+2*edge,shape[1]+2*edge))
for i in range(shape[0]):
for j in range(shape[1]):
temp[i+edge][j+edge]=img[i][j][0]
return temp

def f(x,y): #定义二维正态分布函数
return 1/(math.pi*sigma**2)*math.exp(-(x**2+y**2)/(2*sigma**2))

def gauss(n): #生成n*n的高斯滤波器
mid=n//2
filt=np.zeros((n,n))
for i in range(n):
for j in range(n):
filt[i][j]=f(i-mid,j-mid)/f(-mid,-mid)
return filt.astype(np.uint8)

def gauss_filter(img,n): #对图像进行n*n卷积块的高斯滤波
filt=gauss(n)
con=1/np.sum(filt)
shape=img.shape
temp=add_zeros(img,n//2)
result=np.zeros((shape[0],shape[1],1))
for i in range(shape[0]):
for j in range(shape[1]):
tmp=0
for k in range(n):
for l in range(n):
tmp+=filt[k][l]*temp[i+k][j+l]
result[i][j][0]=con*tmp
return result.astype(np.uint8)

def unsharp_mask(img,n,is_mask=0): #用n*n高斯模糊后非锐化掩膜,is_mask为0返回叠加后图像,为1返回掩膜
shape=img.shape
new_img=np.zeros((shape[0],shape[1],1))
for i in range(shape[0]):
for j in range(shape[1]):
new_img[i][j][0]=img[i][j][0]
mask=new_img-gauss_filter(img,n)
for i in range(shape[0]):
for j in range(shape[1]):
if i==0 or j==0 or i==shape[0]-1 or j==shape[1]-1:
mask[i][j][0]=0
result=new_img+mask
result=result-np.min(result)
result=result/np.max(result)*255
mask=mask-np.min(mask)
mask=mask/np.max(mask)*255
if is_mask:
return mask.astype(np.uint8) #返回掩膜
return result.astype(np.uint8) #返回结果

sobelx=[[-1,0,1],[-2,0,2],[-1,0,1]]
sobely=[[-1,-2,-1],[0,0,0],[1,2,1]]
laplace4=[[0,-1,0],[-1,4,-1],[0,-1,0]]
laplace8=[[-1,-1,-1],[-1,8,-1],[-1,-1,-1]]

def filt_3(img,filt): #任意3*3滤波器(图像,算子)
shape=img.shape
temp=add_zeros(img,1)
result=np.zeros((shape[0],shape[1],1))
for i in range(shape[0]):
for j in range(shape[1]):
tmp=0
for k in range(3):
for l in range(3):
tmp+=filt[k][l]*temp[i+k][j+l]
result[i][j][0]=tmp
#result=np.abs(result) #分别输出x方向和y方向的边缘信息
#result=result/np.max(result)*255
#cv.imwrite("tmp.bmp",result.astype(np.uint8))
return result

def laplace_edge(img,filt): #规整后的拉普拉斯算子边缘
tmp=filt_3(img,filt)
tmp=tmp-np.min(tmp)
shape=tmp.shape
for i in range(shape[0]):
for j in range (shape[1]):
if i==0 or j==0 or i==shape[0]-1 or j==shape[1]-1:
tmp[i][j][0]=0
tmp=tmp/np.max(tmp)*255
return tmp.astype(np.uint8)

def laplace(img,filt): #原图像与拉普拉斯边缘叠加的结果图像
tmp=filt_3(img,filt)
shape=img.shape
result=np.zeros((shape[0],shape[1]))
for i in range(shape[0]):
for j in range(shape[1]):
result[i][j]=tmp[i][j][0]+img[i][j][0]
if i==0 or j==0 or i==shape[0]-1 or j==shape[1]-1:
result[i][j]=0
result-=np.min(result)
result=result/np.max(result)*255
return result.astype(np.uint8)

def sobel(img): #提取图像的sobel边缘
shape=img.shape
sobx=filt_3(img,sobelx)
soby=filt_3(img,sobely)
result=np.zeros((shape[0],shape[1]))
for i in range(shape[0]):
for j in range(shape[1]):
if i==0 or j==0 or i==shape[0]-1 or j==shape[1]-1:
result[i][j]=0
else:
result[i][j]=math.sqrt(sobx[i][j][0]**2+soby[i][j][0]**2)
result=result/np.max(result)*255
return result.astype(np.uint8)

def canny(img,n=3): #使用canny算法提取图像边缘(使用n*n的高斯滤波器进行模糊操作)
de=[[1,0,-1,0],[1,1,-1,-1],[0,1,0,-1],[-1,1,1,-1]]
shape=img.shape
tmp=gauss_filter(img,n)
sobx=filt_3(tmp,sobelx)
soby=filt_3(tmp,sobely)
weight,angle,result=np.zeros((shape[0],shape[1])),np.zeros((shape[0],shape[1])),np.zeros((shape[0],shape[1]))
angle=angle.astype(np.int)
for i in range(shape[0]):
for j in range(shape[1]):
weight[i][j]=math.sqrt(sobx[i][j][0]**2+soby[i][j][0]**2)
if sobx[i][j][0]:
angle[i][j]=round((math.atan(soby[i][j][0]/sobx[i][j][0])/(math.pi/4)-0.5))%4
for i in range(shape[0]-2):
for j in range(shape[1]-2):
tmp_i,tmp_j=i+1,j+1
if weight[tmp_i][tmp_j]<=weight[tmp_i+de[angle[tmp_i][tmp_j]][0]][tmp_j+de[angle[tmp_i][tmp_j]][1]] and weight[tmp_i][tmp_j]<=weight[tmp_i+de[angle[tmp_i][tmp_j]][2]][tmp_j+de[angle[tmp_i][tmp_j]][3]]:
result[tmp_i][tmp_j]=0
else:
result[tmp_i][tmp_j]=weight[tmp_i][tmp_j]
result=result/np.max(result)*255
mean=np.mean(img)
for i in range(shape[0]):
for j in range(shape[1]):
if result[i][j]<100:
result[i][j]=0
return result.astype(np.uint8)


filename=["test3_corrupt.pgm","test4.tif"]
for i in filename:
img=cv.imread(i)
cv.imwrite(i+"_mask.bmp",unsharp_mask(img,3,1))
cv.imwrite(i+"_unsharp_mask.bmp",unsharp_mask(img,3))
cv.imwrite(i+"_sobel.bmp",sobel(img))
cv.imwrite(i+"_canny.bmp",canny(img,3))
cv.imwrite(i+"laplace4_edge.bmp",laplace_edge(img,laplace4))
cv.imwrite(i+"laplace8_edge.bmp",laplace_edge(img,laplace8))