笔记:常用的图像滤波方法以及python实现,包括:均值滤波、高斯滤波、最大值滤波、最小值滤波。
之前写过一篇图像增强的文章,但是最后得到的增强结果包含很多噪声点(某师兄指出的)。所以今天来用滤波算法去除噪声。
图像的噪声来源有很多,比如成像设备的电子器件老化,或者拍摄环境中有外界因素干扰。一种简单的从图像去除噪声的方式就是进行“滤波”!
opencv已经提供了封装好的滤波方式,但是为了自己更好的理解各种算法,还是应该自己动手实现一次。所以笔记一方面对滤波算法的原理进行分析,另一方面给出了python实现代码(代码没有优化过,还是普通的双层循环,这样更直接)。
笔记分为以下几个部分:
1、均值滤波及其python实现
2、高斯滤波及其python实现
3、最大值滤波与最小值滤波及其python实现
第1节:均值滤波及其python实现
“均值”这个词表示对一些数求和,那么对于图像来说,“数”就是亮度值,“一些数”就是“一块小区域”(比如足球场中的一个足球)。这篇笔记不涉及什么是卷积核了,相信读者已经知道了这种基本知识。下面给出了均值滤波的卷积核内容:
![ca94643404655c247d3ed73da53fd982.png](https://i-blog.csdnimg.cn/blog_migrate/a734c97083dfde6e6c61c3b65b4f26a7.png)
假设这个卷积核与一个3x3的区域相乘,实际上就是实现了求和再均值功能。假设一个图片中的足球正好需要3x3=9个像素进行表示,那么通过与kernel卷积就导致这9个像素都趋于平滑,也就是说某个像素点将会被周围8个像素点“平均”。
对于图像来说,只需要遍历所有的像素点(或称为卷积核大小的区域),然后分别和卷积核kernel进行乘法加权就可以了。
from
上述代码读取了dark图片,然后进行伽马变换(前面的笔记),通过均值滤波得到了下面的结果:
![57e838117bb9e0cafd1a099aac47cbd1.png](https://i-blog.csdnimg.cn/blog_migrate/3c97c9ee8f8b5242ec2fd40381e4c93d.jpeg)
可以看出,第一张dark图片经过伽马变换得到的第二张图片显然存在噪声点(不是椒盐噪声,尽管看起来黑色像素点有点像椒噪声),经过均值滤波以后明显噪声变少了。
为什么呢?我个人理解,噪声点就是“孤立点”,比如噪声周围的像素值都是76,但是噪声值为10,也就是说这个噪声点是黑色的,那么怎么让它接近76呢?使用周围8个76的点对它进行平均就可以了。平均的意思就是重新赋值:value(noise)=(8*76+10)/9=68.7,这样噪声点就不“孤立”了!它和周围的像素点值很接近了,以至于肉眼已经无法观察到它。
第2节:高斯滤波及其python实现
高斯这个名字真的是伴随我们走过好多年啊!
均值滤波导致一个像素会被周围的8个像素“平均”,比如上一节的公式value(noise)=(8*76+10)/9=68.7。仔细想一下,这个点如果不是噪声点,而是一个边缘,那么是不是就造成了它被“过度”平均了!所以说,需要进行“加权”,让中心像素的权重大一些,周围像素的权重小一些。由于这个核的数值比较多,我就直接从代码里面截图了:
![387db1de020ba777a74df0698c860830.png](https://i-blog.csdnimg.cn/blog_migrate/fd1cf65a4d8e442e8a275a36131c1f9e.png)
没错,这个就是服从高斯分布的卷积核,可以想象一下高斯分布的形状,就是中间高两边低。这个卷积核最后要除以273(卷积核所有的数值相加等于273),但是我没写,因为最后计算均值的时候除以就行了。
show me code !!!
import cv2
import numpy as np
star_rgb = cv2.imread("lena.jpg")
star_gray = cv2.cvtColor(star_rgb, cv2.COLOR_BGR2GRAY)
def gaussian(image):
"""
Gaussian filter for a RGB image
Args:
image:RGB image
Returns:
filtered image
"""
gaussian_image = np.zeros(shape=image.shape, dtype=np.float32)
gaussian_kernel = np.array([[1, 4, 7, 4, 1],
[4, 16, 26, 16, 4],
[7, 26, 41, 26, 7],
[4, 16, 26, 16, 4],
[1, 4, 7, 4, 1]], dtype=np.float32)
gaussian_kernel = gaussian_kernel / gaussian_kernel.sum()
for i in range(image.shape[0]-5):
for j in range(image.shape[1]-5):
gaussian_image[i+2, j+2] = np.sum(image[i:i+5, j:j+5]*gaussian_kernel)
return gaussian_image.astype(np.uint8)
std_gaussian_star = cv2.GaussianBlur(src=star_gray, ksize=(5, 5), sigmaX=0)
gaussian_star = gaussian(star_gray)
cv2.imshow("original star", star_gray)
cv2.imshow("my gaussian star", gaussian_star)
cv2.imshow("cv2 gaussian star", std_gaussian_star)
cv2.waitKey(0)
为了比较一下自己的实现和opencv的标准实现的差异,调用了GaussianBlur函数进行对比,没使用自己的图是因为自己那张图的噪声不明显,结果如下:
![f32c3c30ca11db2100f151e2e6b2aa1e.png](https://i-blog.csdnimg.cn/blog_migrate/14c0cbe25c716960a59b557f9fcf422d.jpeg)
第一张是原始噪声图,第二张是自己的滤波结果,第三张是cv2标准函数的结果,视觉上效果差不多,说明实现没问题。
第3节:最大值滤波与最小值滤波及其python实现
前面两个都是线性滤波器,所谓线性就是“线性求和”,用求和的均值来代替原始像素值。那么,有没有非线性的形式呢?最大值和最小值滤波就是这个思路,不进行求和操作。
还是以足球为例,比如3x3的足球中心像素点是噪声,能不能不通过和周围像素值进行平均的方式去掉它?最值滤波(最大值和最小值的统称)提出使用最值来代替中心值。如果采用最大值代替中心值,那么就会导致中心值更亮;如果采用最小值代替中心值,就会导致中心值偏暗。
按照我的理解:如果噪声是椒噪声(黑色,亮度低)就要使用最大值代替中心值;如果噪声是盐噪声(白色,亮度高)就要使用最小值代替中心值。
最大值滤波的代码如下,最小值滤波只需要把nm.max()换成np.min()就好了:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
dark_image = np.array(Image.open("dark.jpg").resize((500,700),Image.ANTIALIAS))
gama_image = (dark_image.astype(np.float32)/255)**0.25
def max_box(image):
mean_image = np.zeros(shape=image.shape, dtype=np.float32)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
for k in range(image.shape[2]):
mean_image[i][j][k] = np.max(image[i:i+3,j:j+3,k])
return mean_image
plt.figure(figsize=(10,10))
plt.subplot(1,3,1)
plt.imshow(dark_image/255)
plt.subplot(1,3,2)
plt.imshow(gama_image)
mean_image=max_box(gama_image)
plt.subplot(1,3,3)
plt.imshow(mean_image)
plt.show())
还是使用之前的照片为例,第一张是原图,第二张是伽马变换以后的结果,第三张是去噪以后的结果显然效果好些了,噪声点变少了。(噪声主要集中在左下方):
![daa755a55ccdee373a41f2301ae0dd66.png](https://i-blog.csdnimg.cn/blog_migrate/c0af8501c722c8c24827c71b6e11b54b.jpeg)
总结一下:需要知道噪声类型才能确定使用何种滤波。