模糊操作是图像处理中最简单和常用的操作之一,使用该操作的原因之一就为了给图像预处理时减低噪声。
模糊操作基于离散卷积计算:
g
(
i
,
j
)
=
∑
k
,
l
f
(
i
+
k
,
j
+
l
)
∗
h
(
k
,
l
)
g(i,j) = \sum_{k,l}f(i+k,j+l)*h(k,l)
g(i,j)=k,l∑f(i+k,j+l)∗h(k,l)
其中权重核h(k,l)为“滤波系数”。上面的式子可以简记为:
g
=
f
⨂
h
g = f\bigotimes h
g=f⨂h
通常这些卷积算子可以是线性操作,所以又叫线性滤波。不同卷积核得到不同的卷积效果,模糊是卷积的一种表象。(关于边界问题:有几种填充方法:补零、边界复制、块复制、镜像复制等方法)。必须是奇数卷积核,就是计算这些范围内的值来确定中心位置的大小
1,均值滤波(归一化滤波)
均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围8个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
用 3×3 大小模板进行均值滤波。
由于图像边框上的像素无法被模板覆盖,所以不做处理,这当然造成了图像边缘的缺失
以(2,2)像素点为例,卷积核如下:
则滤波后的结果为:
g(2,2)=int[(1+2+1+1+10+2+5+2+6)/9=3
滤波后(2,2)像素点的值由 10 变为 3
最终结果:
2,中值滤波
仍以(2,2)像素点为例,对模板中的 9 个数进行从小到大排序:1,1,1,2,2,2,5,6,10。中间值为 2。所以,中值滤波后(2,2)位置的值10变为 2. 同理对其他像素点。处理结果:
均值模糊、中值模糊、自定义模糊(锐化)源代码:
import cv2 as cv
import numpy as np
def blur_demo(image):
dst = cv.blur(image, (5, 5))
cv.imshow("blur_demo", dst)
"""
均值模糊函数blur():
定义:blur(src,ksize,dst=None, anchor=None, borderType=None)
定义是有5个参数,但最后三个均为none,所以也就2个参数
src:要处理的原图像
ksize: 必须是奇数卷积核,周围关联的像素的范围:代码中(5,5)就是5*5的大小,就是计算这些范围内的均值来确定中心位置的大小
"""
def median_blur_demo(image):
dst = cv.medianBlur(image, 5)
cv.imshow("median_blur_demo", dst)
"""
定义:medianBlur(src, ksize, dst=None)
ksize与blur()函数不同,不是矩阵,而是一个数字,例如为5,就表示了5*5的方阵
中值滤波对于白点噪声/椒盐噪声的去除是非常的好的。
"""
def custom_blur_demo(image):
#kernel = np.ones([5, 5], np.float32)/25 除以25防止溢出
kernel = np.array([[0, -1, 0],[-1, 5, -1],[0, -1, 0]], np.float32) #锐化算子(奇数,总和为1或者0)
dst = cv.filter2D(image, -1, kernel=kernel)
cv.imshow("custom_blur_demo", dst)
"""
filter2D(src,ddepth,kernel):
滤波函数的使用需要一个核模板,对图像的滤波操作过程为:将和模板放在图像的一个像素A上,求与之对应的图像上的每个像素点的和,
核不同,得到的结果不同,而滤波的使用核心也是对于这个核模板的使用,需要注意的是,该滤波函数是单通道运算的,
也就是说对于彩色图像的滤波,需要将彩色图像的各个通道提取出来,对各个通道分别滤波才行。
ddepth:深度,输入值为-1时,目标图像和原图像深度保持一致
kernel: 卷积核(或者是相关核),一个单通道浮点型矩阵
"""
src = cv.imread("F:/images/lenanoise.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
blur_demo(src)
median_blur_demo(src)
custom_blur_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果:
3,高斯滤波
高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
高斯分布的概率密度函数:
如果选取3*3卷积模板,分布值为0.2,0.4,0.2;由于浮点数不易计算,所以按比例转换为1:2: 1进行计算。
源代码:
import cv2 as cv
import numpy as np
def clamp(pv): #防止溢出
if pv > 255:
return 255
if pv < 0:
return 0
else:
return pv
def gaussian_noise(image): # 获取有高斯噪声的图片
h, w, c = image.shape
for row in range(h):
for col in range(w):
s = np.random.normal(0, 20, 3) #产生随机数
b = image[row, col, 0] # blue
g = image[row, col, 1] # green
r = image[row, col, 2] # red
image[row, col, 0] = clamp(b + s[0]) #产生有高斯噪声的图片
image[row, col, 1] = clamp(g + s[1])
image[row, col, 2] = clamp(r + s[2])
cv.imshow("noise image", image)
src = cv.imread("F:/images/lena.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
t1 = cv.getTickCount()
gaussian_noise(src)
t2 = cv.getTickCount()
time = (t2 - t1)/cv.getTickFrequency()
print("time consume : %s"%(time*1000))
dst = cv.GaussianBlur(src, (0, 0), 15) #(0,0),然后根据sigmaX=15自动计算ksize,高斯模糊对高斯噪声有抑制作用
"""
GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
src,输入图像,即源图像,填Mat类的对象即可。图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
ksize,高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数(并不能理解)。或者,它们可以是零的,它们都是由sigma计算而来。
sigmaX,表示高斯核函数在X方向的的标准偏差。 根据这个可以获取sigmaY,若是sigmaX和sigmaY都没有则根据ksize获取
sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
"""
cv.imshow("Gaussian Blur", dst)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果:
4,双边滤波
OpenCV经典的两种实现EPF方法:高斯双边和均值迁移
高斯模糊只考虑了权重,只考虑了像素空间的分布,没有考虑像素值和另一个像素值之间差异的问题,如果像素间差异较大的情况下(比如图像的边缘),高斯模糊会进行处理,但是我们不需要处理边缘,要进行的操作就叫做边缘保留滤波(EPF)。
(1) 均值模糊无法克服边缘像素信息丢失缺陷。原因是均值模糊是基于平均权重。
(2) 高斯模糊部分克服了该缺陷,但是无法完全避免,因为没考虑到像素值的不同。
(3) 双边滤波是保留边缘的滤波方法,避免了边缘信息的丢失,保留了图像轮廓不变。
只模糊上半部分,下半部分差异太大,没有模糊差异越大,越会完整保留。
源代码(双边模糊、均值迁移)
import cv2 as cv
import numpy as np
def bi_demo(image):
dst = cv.bilateralFilter(image, 0, 100, 15)
cv.imshow("bi_demo", dst)
"""
定义:bilateralFilter(src, d, sigmaColor, sigmaSpace, dst=None, borderType=None)
值域和空域的两个方差sigma可以简单的设置为相等,小于10,无太大效果,大于150效果太强,像卡通片似的。
滤波器尺寸d:大于5将较慢(5 forreal-time),d=9,for off-lineapplications,d 是像素邻域“直径”。
计算的半径,半径之内的像数都会被纳入计算,如果提供-1或者0,会从后面的参数sigmaSpace中自动计算。
Sigma_color(颜色标准差):颜色空间过滤器的sigma值,这个参数的值越大,表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
Sigma_space(空间标准差):坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。
当d>0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
双边滤波的内在想法是:在图像的值域(range)上做传统滤波器在空域(domain)上做的工作。空域滤波对空间上邻近的点进行加权平均,加权系数随着距离的增加而减少;
值域滤波则是对像素值相近的点进行加权平均,加权系数随着值差的增大而减少
"""
def shift_demo(image):
dst = cv.pyrMeanShiftFiltering(image, 10, 50)
cv.imshow("shift_demo", dst)
"""
meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,
求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
可以利用均值偏移算法的这个特性,实现彩色图像分割,Opencv中对应的函数是pyrMeanShiftFiltering。
这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,
所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。
pyrMeanShiftFiltering(src, sp, sr, dst=None, maxLevel=None, termcrit=None):
第一个参数src,输入图像,8位,三通道的彩色图像,并不要求必须是RGB格式,HSV、YUV等Opencv中的彩色图像格式均可;
第二个参数sp,定义的漂移物理空间半径大小; #越大,细节丢失越多
第三个参数sr,定义的漂移色彩空间半径大小;
第四个参数dst,输出图像,跟输入src有同样的大小和数据格式;
第五个参数maxLevel,定义金字塔的最大层数;
第六个参数termcrit,定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合
"""
src = cv.imread("F:/images/example.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
bi_demo(src)
shift_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果: