Opencv[三]——图像噪点消除
本文介绍了常见的图像滤波方法及其应用。主要内容包括:1)噪声类型(高斯噪声、椒盐噪声)和滤波器概念;2)5种滤波方法:均值滤波(简单平均)、方框滤波(可调节归一化)、高斯滤波(加权平均保留细节)、中值滤波(去除椒盐噪声)和双边滤波(保留边缘);3)方法对比:高斯滤波适合一般场景,中值滤波针对椒盐噪声,双边滤波能保留边缘但计算复杂;4)OpenCV实现示例。文章为图像去噪处理提供了实用的方法选择和参
首先介绍一些概念:
噪声:指图像中的一些干扰因素,通常是由图像采集设备、传输信道等因素造成的,表现为图像中随机的亮度,也可以理解为有那么一些点的像素值与周围的像素值格格不入。常见的噪声类型包括高斯噪声和椒盐噪声。高斯噪声是一种分布符合正态分布的噪声,会使图像变得模糊或有噪点。椒盐噪声则是一些黑白色的像素值分布在原图像中。

滤波器:也可以叫做卷积核,与自适应二值化中的核一样,本身是一个小的区域,有着特定的核值,并且工作原理也是在原图上进行滑动并计算中心像素点的像素值。滤波器可分为线性滤波和非线性滤波,线性滤波对邻域中的像素进行线性运算,如在核的范围内进行加权求和,常见的线性滤波器有均值滤波、高斯滤波等。非线性滤波则是利用原始图像与模板之间的一种逻辑关系得到结果,常见的非线性滤波器中有中值滤波器、双边滤波器等。
滤波与模糊联系与区别:
-
它们都属于卷积,不同滤波方法之间只是卷积核不同(对线性滤波而言)
-
低通滤波器是模糊,高通滤波器是锐化
-
低通滤波器就是允许低频信号通过,在图像中边缘和噪点都相当于高频部分,所以低通滤波器用于去除噪点、平滑和模糊图像。高通滤波器则反之,用来增强图像边缘,进行锐化处理。
注意:椒盐噪声可以理解为斑点,随机出现在图像中的黑点或白点;高斯噪声可以理解为拍摄图片时由于光照等原因造成的噪声。
本实验中共提供了五种滤波的方式,下面进行一一介绍。
1. 均值滤波
均值滤波是一种最简单的滤波处理,它取的是卷积核区域内元素的均值,如3×3的卷积核:
$$
k e r n e l={\frac{1}{9}}{\Bigg[}\begin{array}{l l l}{1}&{1}&{1}\\{1}&{1}&{1}\\{1}&{1}&{1}\end{array}{\Bigg]}
$$
在滤波算法组件中,当参数filtering_method选为均值滤波,参数component_param为ksize,代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
比如有一张4*4的图片,现在使用一个3*3的卷积核进行均值滤波时,其过程如下所示:

对于边界的像素点,则会进行边界填充,以确保卷积核的中心能够对准边界的像素点进行滤波操作。在OpenCV中,默认的是使用BORDER_REFLECT_101的方式进行填充,下面的滤波方法中除了中值滤波使用的是BORDER_REPLICATE进行填充之外,其他默认也是使用这个方式进行填充,因此下面就不再赘述。通过卷积核在原图上从左上角滑动计算到右下角,从而得到新的4*4的图像的像素值。

import cv2
if __name__ == "__main__":
path = "./lvbo2.png"
image_np = cv2.imread(path)
# no_noise_image = cv2.medianBlur(image_np, 3) # 中值滤波
# no_noise_image = cv2.boxFilter(image_np, -1, (3, 3), normalize=True) # 方框滤波
# no_noise_image = cv2.bilateralFilter(image_np, 9, 75, 75) # 双边滤波
# no_noise_image = cv2.GaussianBlur(image_np, (3, 3), 1) # 高斯滤波
no_noise_image = cv2.blur(image_np, (3, 3)) # 均值滤波
# 返回处理正确后的内容
cv2.imshow("image_np", image_np)
cv2.imshow("no_noise_image", no_noise_image)
cv2.waitKey(0)
2. 方框滤波
方框滤波跟均值滤波很像,如3×3的滤波核如下:
$$
k e r n e l={a}{\Bigg[}\begin{array}{l l l}{1}&{1}&{1}\\{1}&{1}&{1}\\{1}&{1}&{1}\end{array}{\Bigg]}
$$
在滤波算法组件中,当参数filtering_method选为方框滤波时,参数component_param为ksize,ddepth,normalize。下面讲解这3个参数的含义:

ksize:代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
ddepth:输出图像的深度,-1代表使用原图像的深度。
normalize:当normalize为True的时候,方框滤波就是均值滤波,上式中的a就等于1/9;normalize为False的时候,a=1,相当于求区域内的像素和。
其滤波的过程与均值滤波一模一样,都采用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值可能会不同。

import cv2
if __name__ == "__main__":
path = "./lvbo2.png"
image_np = cv2.imread(path)
# no_noise_image = cv2.medianBlur(image_np, 3) # 中值滤波
no_noise_image = cv2.boxFilter(image_np, -1, (3, 3), normalize=True) # 方框滤波
# no_noise_image = cv2.bilateralFilter(image_np, 9, 75, 75) # 双边滤波
# no_noise_image = cv2.GaussianBlur(image_np, (3, 3), 1) # 高斯滤波
# no_noise_image = cv2.blur(image_np, (3, 3)) # 均值滤波
# 返回处理正确后的内容
cv2.imshow("image_np", image_np)
cv2.imshow("no_noise_image", no_noise_image)
cv2.waitKey(0)
3. 高斯滤波
前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。还记得我们在自适应二值化里是怎么生成高斯核的吗?这里跟自适应二值化里生成高斯核的步骤是一样的,都是以核的中心位置为坐标原点,然后计算周围点的坐标,然后带入下面的高斯公式中。
$$
g(x,y)=\frac{1}{2\pi\sigma^{2}}e^{-\frac{(x^{2}+y^{2})}{2\sigma^{2}}}
$$
其中的值也是与自适应二值化里的一样,当时会取固定的系数,当kernel大于7并且没有设置时,会使用固定的公式进行计算$\sigma$的值:
$$
\sigma=0.3*\left((k s i z e-1)*0.5-1\right)+0.8
$$
我们还是以3*3的卷积核为例,其核值如下所示:
$$
\ k e r n e l=\left[\begin{array}{c}{{0.0625~~~~0.125~~~~0.0625}}\\{{0.125~~~~0.25~~~~0.125}}\\{{0.0625~~~~0.125~~~~0.0625}} \end{array}\right]=\left[\begin{array}{c c c}{\frac{1}{16}~~~\frac{1}{8}~~~\frac{1}{16}}\\{\frac{1}{8}~~~\frac{1}{4}~~~\frac{1}{8}}\\{\frac{1}{16}~~~\frac{1}{8}~~~\frac{1}{16}}\end{array}\right]
$$
得到了卷积核的核值之后,其滤波过程与上面两种滤波方式的滤波过程一样,都是用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值不同。
在滤波算法组件中,当参数filtering_method选为高斯滤波,参数component_param为ksize,sigmaX。下面讲解这2个参数的含义:

ksize:代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
sigmaX:就是高斯函数里的值,σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。均值滤波与高斯滤波的对比结果如下(均值滤波丢失的细节更多):

import cv2
if __name__ == "__main__":
path = "./lvbo2.png"
image_np = cv2.imread(path)
# no_noise_image = cv2.medianBlur(image_np, 3) # 中值滤波
# no_noise_image = cv2.boxFilter(image_np, -1, (3, 3), normalize=True) # 方框滤波
# no_noise_image = cv2.bilateralFilter(image_np, 9, 75, 75) # 双边滤波
no_noise_image = cv2.GaussianBlur(image_np, (3, 3), 1) # 高斯滤波
# no_noise_image = cv2.blur(image_np, (3, 3)) # 均值滤波
# 返回处理正确后的内容
cv2.imshow("image_np", image_np)
cv2.imshow("no_noise_image", no_noise_image)
cv2.waitKey(0)
4. 中值滤波
中值又叫中位数,是所有数排序后取中间的值。中值滤波没有核值,而是在原图中从左上角开始,将卷积核区域内的像素值进行排序,并选取中值作为卷积核的中点的像素值,其过程如下所示:

中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。
比如下面这张斑点噪声图,用中值滤波显然更好:

在滤波算法组件中,当参数filtering_method选为中值滤波,参数component_param为ksize,代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
import cv2
if __name__ == "__main__":
path = "./lvbo3.png"
image_np = cv2.imread(path)
no_noise_image = cv2.medianBlur(image_np, 3) # 中值滤波
# no_noise_image = cv2.boxFilter(image_np, -1, (3, 3), normalize=True) # 方框滤波
# no_noise_image = cv2.bilateralFilter(image_np, 9, 75, 75) # 双边滤波
# no_noise_image = cv2.GaussianBlur(image_np, (3, 3), 1) # 高斯滤波
# no_noise_image = cv2.blur(image_np, (3, 3)) # 均值滤波
# 返回处理正确后的内容
cv2.imshow("image_np", image_np)
cv2.imshow("no_noise_image", no_noise_image)
cv2.waitKey(0)
5. 双边滤波
模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。

可以看到,双边滤波明显保留了更多边缘信息,下面来介绍一下双边滤波。
双边滤波的基本思路是同时考虑将要被滤波的像素点的空域信息(周围像素点的位置的权重)和值域信息(周围像素点的像素值的权重)。为什么要添加值域信息呢?是因为假设图像在空间中是缓慢变化的话,那么临近的像素点会更相近,但是这个假设在图像的边缘处会不成立,因为图像的边缘处的像素点必不会相近。因此在边缘处如果只是使用空域信息来进行滤波的话,得到的结果必然是边缘被模糊了,这样我们就丢掉了边缘信息,因此添加了值域信息。
双边滤波采用了两个高斯滤波的结合,一个负责计算空间邻近度的权值(也就是空域信息),也就是上面的高斯滤波器,另一个负责计算像素值相似度的权值(也就是值域信息),也是一个高斯滤波器。其公式如下所示:
$$
g(i,j)=\frac{\sum_{(k,l)\in S(i,j)}f(k,l)\omega(i,j,k,l)}{\Sigma_{(k,l)\in S(i,j)}\omega(i,j,k,l)}
$$
其中,
$S(i,j)$:指以(i,j)为中心的邻域的范围
$f(k,l)$:输入的点的像素值
$\omega(i,j,k,l)$:代表经过两个高斯函数计算出的值
$g(i,j)$:表示中心点(i,j)的像素值
上述公式我们进行转化,假设公式中$\omega(i,j,k,l)$为m,则有
$$
g(i,j)=\frac{f_{1}\cdot m_{1}+f_{2}\cdot m_{2}+\dots+f_{n}\cdot m_{n}}{m_{1}+m_{2}+\dots+m_{n}}
$$
设$m{1}+m{2}+\cdots+m_{n}=M$,则有
$$
g(i,j)=f_{1}\cdot{\frac{m_{1}}{M}}+f_{2}\cdot{\frac{m_{2}}{M}}+\cdots+f_{n}\cdot{\frac{m_{n}}{M}}
$$
此时可以看到,这与上面的滤波中计算过程已经一模一样了,$\frac{m_{1}}{M}$就代表了第一个点的权重。接下来我们看看$\omega(i,j,k,l)$是怎么来的,令
$$
\omega(i,j,k,l)=w_{s}*w_{r}
$$
而
$$
\omega_{s}=e^{-{\frac{(i-k)^{2}+(j-l)^{2}}{2\sigma_{s}{}^{2}}}}
$$
$$
\omega_{r}=e^{-{\frac{\|f(i,j)-f(k,l)\|^{2}}{2\sigma_{r}{}^{2}}}}
$$
可以看到,对于$\omega{S}$来说,这就是普通的高斯滤波函数,其带入的坐标是坐标值,${\boldsymbol{\sigma}}{s}$是程序输入值,该函数是在空间临近度上计算的。而$\omega_{r}$是计算像素值相似度,也是高斯函数带入坐标值,然后得到对应点的像素值,进行两个点像素值插值的绝对值的平方。也就是说,双边滤波的核值不再是一个固定值,而是随着滤波的过程在不断发生变化的。
在本实验中的滤波算法组件中,当参数filtering_method选为双边滤波,参数component_param为ksize,d,sigmaColor,sigmaSpace,下面讲解这4个参数的含义:
-
ksize:卷积核的大小
-
d:过滤时周围每个像素领域的直径
-
sigmaColor:在color space中过滤sigma。参数越大,临近像素将会在越远的地方mix。
-
sigmaSpace:在coordinate space中过滤sigma。参数越大,那些颜色足够相近的的颜色的影响越大。
关于2个sigma参数:
简单起见,可以令2个sigma的值相等;
如果他们很小(小于10),那么滤波器几乎没有什么效果;
如果他们很大(大于150),那么滤波器的效果会很强,使图像显得非常卡通化。
关于参数d:
过大的滤波器(d>5)执行效率低。
对于实时应用,建议取d=5;
对于需要过滤严重噪声的离线应用,可取d=9;
import cv2
if __name__ == "__main__":
path = "./lvbo3.png"
image_np = cv2.imread(path)
# no_noise_image = cv2.medianBlur(image_np, 3) # 中值滤波
# no_noise_image = cv2.boxFilter(image_np, -1, (3, 3), normalize=True) # 方框滤波
no_noise_image = cv2.bilateralFilter(image_np, 9, 75, 75) # 双边滤波
# no_noise_image = cv2.GaussianBlur(image_np, (3, 3), 1) # 高斯滤波
# no_noise_image = cv2.blur(image_np, (3, 3)) # 均值滤波
# 返回处理正确后的内容
cv2.imshow("image_np", image_np)
cv2.imshow("no_noise_image", no_noise_image)
cv2.waitKey(0)
6. 小结
在不知道用什么滤波器好的时候,优先高斯滤波,然后均值滤波。
斑点和椒盐噪声优先使用中值滤波。
要去除噪点的同时尽可能保留更多的边缘信息,使用双边滤波。
线性滤波方式:均值滤波、方框滤波、高斯滤波(速度相对快)。
非线性滤波方式:中值滤波、双边滤波(速度相对慢)。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)