前言
接到考核任务。
在学OpenCV模糊处理这方面,顾名思义,模糊处理是使图片变的模糊,在学习过程中发现有很多种模糊且有一堆听着十分有逼格的名字,公式也让初学的我看着头大,硬着头皮学下去。在网上查阅了很多相关资料,大部分都是一上来就十分高大上,门槛十分高,让我望而却步,只能自己一步步查,从底层开始了解,并理解好通俗的表达出来,致力于让每一个像我一样的零基础学习爱好者能保持热爱。同时也能督促我自己能好好理解。
学习模糊之前,一开始完全看不懂长篇大论的讲解,但发现主要的核心关键点都落在“卷积”上,所以在学模糊处理之前,最起码我们要有个“卷积”的概念,所以加入了“卷积”的理解。
1、写在模糊理解前
1.1什么是卷积
百度百科中的解释:
看了=没看
在知乎看了排名前三的高赞回答,算是对卷积有所了解。
把链接放这
如何通俗易懂的解释“卷积”
了解卷积之后
二维卷积的操作时从卷积核开始,这是一个小的权值矩阵。这个卷积核在 二维输入数据上「滑动」,对当前输入的部分元素进行矩阵乘法,然后将结果汇为单个输出像素。如下图所示:
将以上的运算规则运用到如下图便可验证
一个标准的卷积
卷积核重复这个过程知道遍历了整张图片,将一个二维矩阵转换为另一个二维矩阵。输出特征实质上是在输入数据相同位置上的加权和(权值是卷积核本身的值)
输入数据是否落入这个「大致相似区域」,直接决定了数据经过卷积核后的输出。这意味着卷积核的尺寸直接决定了生成新的特征时汇合了多少(或几个)输入特征。
这与全连接层完全相反。在上面的例子中,我们的输入特征为 55=25,输出数据为 33=9. 如果我们使用标准的全连接层,就会产生一个25*9=225 个参数的权值矩阵,每个输出都是所有输入数据的加权求和。卷积操作允许我们只用 9个参数来实现这个变换,每个输出特性不用「查看」每个输入特征,而是只是「查看」来自大致相同位置的输入特征。
一些常用的技术
在我们继续介绍卷积神经网络之前,介绍两种卷积层中常用的技术:Padding 和 Strides
Padding:如果你看到上面的动画,那么会注意到在卷积核滑动的过程中,边缘基本会被「裁剪」掉,将 55 特征矩阵转换为 33的特征矩阵。边缘上的像素永远不在卷积核的中心,因为内核没有任何东西可以扩展到边缘之外。这并不理想,因为我们经常希望输出的尺寸等于输入。
一些 padding 操作
Padding 做了一些非常机智的办法来解决这个问题:用额外的「假」像素(通常值为0,因此经常使用的术语「零填充」)填充边缘。这样,在滑动时的卷积核可以允许原始边缘像素位于其中心,同时延伸到边缘之外的假像素,从而产生与输入相同大小的输出。Striding:运行卷积层时,我们通常希望输出的尺寸是比输入更低。这在卷积神经网络中是常见的,在增加信道数量的同时空间尺寸减小。其中一种方法是使用池化层(例如,取每
2×2 网格的平均值/最大值将空间维度减半)。还有一种方法是使用 Striding:
一个步长为 2 的卷积操作
Stride 的想法是改变卷积核的移动步长跳过一些像素。Stride 是 1 表示卷积核滑过每一个相距是 1的像素,是最基本的单步滑动,作为标准卷积模式。Stride 是 2 表示卷积核的移动步长是 2,跳过相邻像素,图像缩小为原来的1/2。Stride 是 3 表示卷积核的移动步长是 3,跳过 2 个相邻像素,图像缩小为原来的 1/3。 越来越多的新网络结构,比如 ResNet,已经完全抛弃了池化层。当需要对图像进行缩小时会采用 Stride 方法。
1.2滤波器
滤波器分为:高通滤波器(HPF)和低通滤波器(LPF)。
- 高通滤波器:根据像素与周围的像素的亮度差值来提升改像素的亮度。
主要作用:锐化。 - 低通滤波器:在像素与周围像素的亮度差值小于一个特定值时,平滑改像素的亮度。
主要作用:去噪和模糊化。
以下我们讨论到的均值模糊、中值模糊、高斯模糊都属于低通滤波器
2、模糊方式
2.1均值模糊(滤波)
均值滤波从字面意思上就是取平均值,也就是小方格上的系数全是1,与覆盖下的像素值相乘,再去除以9(卷积和大小为3*3),得到平均值,赋值给中心像素。用卷积框覆盖区域所有像素的平均值来代替中心元素,很显然,它的准确值是降低了,也就不能很好的保留图像细节,因为全用均值代替了。
均值模糊函数blur():
定义:blur(src,ksize,dst=None, anchor=None, borderType=None)
定义是有5个参数,但最后三个均为none,所以也就2个参数
src:要处理的原图像
ksize: 必须是奇数卷积核,周围关联的像素的范围:代码中(5,5)就是5*5的大小,就是计算这些范围内的均值来确定中心位置的大小
下面用代码直接展示,直观感受
(考核中要求代码方面注释详细[叹气])
import cv2 as cv #加载opencv环境
import numpy as np #加载Numpy包
def blur_demo(image): #均值模糊(去噪)
dst = cv.blur(image, (5, 5)) #卷积层 5行5列
cv.imshow("blur_demo", dst) #生成目标
print("--------- HL均值模糊的测试 ---------")
src = cv.imread('pic.jpg')
cv.namedWindow("image", cv.WINDOW_AUTOSIZE)#autosize窗口自适应大小
cv.imshow("org",src)#展示图片
blur_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
是不是有了一层模糊美~
把卷积层改为
def blur_demo(image): #均值模糊(去噪)
dst = cv.blur(image, (10, 10)) #卷积层 10行10列
cv.imshow("blur_demo", dst)
效果:
可以看到,卷积值越大,模糊程度越明显。
以上只是直观的感受卷积层的魅力,具体的计算还需要我慢慢去琢磨
2.2中值模糊
字面意思就是取中间的值来代替中心像素。和均值滤波类似,但均值滤波是取平均值,而中值滤波取中间的那个值。
中值滤波可以有效的去除椒盐噪声
这里需要解释一下噪声:
图像噪声:
图像噪声是图像在获取或者传输过程中受到随机信号干扰,妨碍人们对图像理解及分析处理的信号。很多时候将图像看作随机过程,因而描述噪声的方法完全可以借用随机过程的描述,即使用其概率分布函数和概率密度分布函数。图像噪声的产生来自图像获取中的环境条件和传感元器件自身的质量,图像在传输过程中产生图像噪声的主要因素是所用的传输信道受到噪声污染。
椒盐噪声:
椒盐噪声是数字图像中的常见噪声,一般是图像传感器、传输信道及解码处理等产生的黑白相间的亮暗点噪声,椒盐噪声常由图像切割产生。椒盐噪声是指两种噪声:盐噪声和椒噪声。盐噪声一般是白色噪声,椒噪声一般为黑色噪声。前者属于高灰度噪声,或者属于低灰度噪声,一般两种噪声同时出现,呈现黑白杂点。去除椒盐噪声常用的方法是中值滤波。
我简单将图片处理了一下加上了椒盐噪声
我把代码放下面,感兴趣可以试试
import cv2 as cv
import numpy as np
def saltpepper(img,n):
m=int((img.shape[0]*img.shape[1])*n)
for a in range(m):
i=int(np.random.random()*img.shape[1])
j=int(np.random.random()*img.shape[0])
if img.ndim==2:
img[j,i]=255
elif img.ndim==3:
img[j,i,0]=255
img[j,i,1]=255
img[j,i,2]=255
for b in range(m):
i=int(np.random.random()*img.shape[1])
j=int(np.random.random()*img.shape[0])
if img.ndim==2:
img[j,i]=0
elif img.ndim==3:
img[j,i,0]=0
img[j,i,1]=0
img[j,i,2]=0
return img
img=cv.imread('pic.jpg')
saltImage=saltpepper(img,0.02)
cv.imshow('saltImage',saltImage)
cv.imwrite('saltImage.jpg',saltImage)
cv.waitKey(0)
cv.destroyAllWindows()
我们接下来用一串中值模糊的代码来验证是否真的能有效去除椒盐噪声
中值模糊代码如下:
import cv2 as cv
import numpy as np
def med_blur_demo(image): #中值模糊(去噪:椒盐噪声)
dst = cv.medianBlur(image, 5)#5为方框的尺寸
cv.imshow("med_blur_demo", dst)
print("--------- Hello HL's test ! ---------")
src = cv.imread('saltImage.jpg')
cv.namedWindow("image", cv.WINDOW_AUTOSIZE)
med_blur_demo(src)
cv.imshow("org",src)
cv.waitKey(0)
cv.destroyAllWindows()
嘶~确实给力啊
那么问题来了,为什么它可以去除椒盐噪声呢?
这是因为椒盐噪声像素值要么很小为0,要么很大为255,而取中间值话,就会用替代这些,从而给图像去噪点。从上图中我们也可以发现,虽然没有噪点了,但也牺牲了图像的质量,变的模糊了。
本着质疑的精神,为均值模糊打抱不平,想要试试我均值大哥能不能做到,直接操作,实践出真知。
啊这…
值得注意的是:中值滤波虽然可以克服线性滤波器所带来的图像细节模糊,但是在线、尖顶等细节多的图像不宜用中值滤波
2.3高斯模糊
使用opencv高斯模糊
高斯平滑函数GaussianBlur()
定义:GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
1. src:输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
2. ksize:高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数(并不能理解)。或者,它们可以是零的,它们都是由sigma计算而来。
3. sigmaX:表示高斯核函数在X方向的的标准偏差。根据这个可以获取sigmaY,若是sigmaX和sigmaY都没有则根据ksize获取
4. dst:目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
5. sigmaY:表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
6. borderType:用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
基于此,我用一串代码给图片加上了高斯噪声
import cv2 as cv
import numpy as np
# 定义一个各通道值 0-255范围 超出按截断处理
def ext(pv):
if pv > 255:
return 255
if pv < 0:
return 0
else:
return pv
# 高斯噪点的生成
def gauss_noise(image):
h, w, ch = image.shape
for row in range(h):
for col in range(w):
s = np.random.normal(0, 10, 3)#生成高斯分布的概率密度随机数
# 去除每一个像素的三个通道值
b = image[row, col, 0]
g = image[row, col, 1]
r = image[row, col, 2]
# 在每一个像素的三个通道值上加上高斯噪声
image[row, col, 0] = ext(b + s[0])
image[row, col, 1] = ext(g + s[1])
image[row, col, 2] = ext(r + s[2])
cv.imshow("gauss_noise", image)
print("--------- HL添加高斯噪声测试 ---------")
img = cv.imread("pic.jpg")
cv.imshow("org", img)
t1 = cv.getTickCount()
gauss_noise(img)
cv.waitKey(0)
cv.destroyAllWindows()
代码讲解(毕竟考核要求我注释详细…)
核心代码:s = np.random.normal(0, 10, 3)
原型:numpy.random.normal(loc, scale, size)
- loc:float代表生成的高斯分布的随机数的均值
- scale:float 代表这个分布的方差
- size:int or tuple of ints 输出的shape,默认为None,只输出一个值
- 当指定整数时,输出整数个值,也可以输出(a, b)→ a 行 b 列
将该添加了高斯噪声的图片,分别用均值模糊,中值模糊,高斯模糊,让我们拭目以待。
import cv2 as cv
import numpy as np
def blur_demo(image): #均值模糊(去噪)
dst = cv.blur(image, (10, 10)) #卷积层 10行10列
cv.imshow("blur_average", dst)
def med_blur_demo(image): #中值模糊(去噪:椒盐噪声)
dst = cv.medianBlur(image, 5)
cv.imshow("blur_med", dst)
print("--------- HL的各种模糊处理降噪测试 ---------")
img = cv.imread('pic.jpg')
cv.imshow('yuantu',img)
src = cv.imread(r"gauss_noise.jpg")
cv.namedWindow("image", cv.WINDOW_AUTOSIZE)
med_blur_demo(src)
blur_demo(src)
cv.imshow("gauss_noise",src)
gauss_blur = cv.GaussianBlur(src,(0,0),0.9)
cv.imshow("gauss_blur", gauss_blur)
cv.waitKey(0)
cv.destroyAllWindows()
从结果来看,面对高斯噪声的干扰,均值模糊已经完败,中值模糊还能看得过去,但高斯模糊面对“家人”就显得易如反掌了,与原图相比,很好的去除了高斯噪声的干扰,虽然还是牺牲了一些清晰度,但整体来说,已经很好啦~
想再尝试的我,用加了椒盐噪声的图片让高斯模糊去处理,想看看效果
好吧,中值胜出…
总结
以上三种模糊操作,为了方便比较,我也均采用同一张图片进行处理,也在实践中体会了各种模糊的优劣,在此在此总结:
- 中值模糊在去除脉冲噪声、斑点噪声(speckle noise)、椒盐噪声(salt-and-pepper noise)、图像扫描噪声的同时又能保留凸图像边缘细节,噪声成分很难选上,所以几乎不会影响到输出,但中值滤波花费的时间是均值滤波的5倍以上。
- 均值模糊中,由于噪声成分被放入平均计算中,所以输出受到了噪声的影响。
- 高斯模糊可以应对绝大部分的高斯噪声的图片,但对于椒盐噪声这样的也难以抵挡,至于后续,还需要鄙人的深入学习了。