一起学opencv-python十(给图像加噪声,模糊处理和图像锐化)

版权声明: https://blog.csdn.net/qq_41740705/article/details/82827968

参考了https://www.bilibili.com/video/av24998616/?p=9

https://www.bilibili.com/video/av24998616/?p=10和

https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html

给图像加噪声

首先要知道有哪些常见的噪声。参考了https://www.cnblogs.com/lytwajue/p/7381202.html

和https://blog.csdn.net/zh_jessica/article/details/77967650#%E5%AE%89%E8%A3%85skimage%E5%BA%93

 

这个也就是黑点或者白点随机出现,一个点就是占一个像素咯。

 

这个其实很容易写出来的。用两个循环和一个random模块中的一个函数就可以写出来。

扫描二维码关注公众号,回复: 3388157 查看本文章

 

注意是包含边界的。

 

我是加了10%的噪声,看到用了0.86,时间还是挺长的。

原图:

 

加完椒盐噪声的图片:

 

这里要说的是我原来的1.jpg并没有加上椒盐噪声,因为我们并没有把a写入1.jpg,只是读进来的位图数据a被改变了,但是a并不是和1.jpg关联的。

 

然后是高斯噪声:

 

功率谱是信号处理相关的一个概念,和傅里叶变换有关,简单来说就是各种频率余弦信号所含能量的一个谱。

 

 

 

这是我自己写出来的一个高斯噪声的程序:

这里先给大家倒个歉,以前好像说过cv2.add没有广播机制。

 

这里这么报错了,说明是可以广播的。

 

这是我写的高斯噪声的程序:

 

用了0.5s,这是我把像素点减少到274*200个的结果,不然的话可能时间会比较长了,下面是出来的图:

 

看到里面是有一些噪声的。

 

sigma是标准差。然后我们必须重新认识一下np.where函数,它的用处可不止前面学的那么简单:

 

可以说有了x,y参数的where才是完整的。x参数的意思就是如果前面的条件满足,那么对应的像素替换为x,否则就替换为y。

        r1=np.where((g+a[i,j])>255,255,(g+a[i,j]))

        r2=np.where(r1<0,0,r1)

        a[i,j]=np.round(r2)

上面三行操作是为了保证亮度是0-255之间的整数。

 

 

我要提醒你们一点的是,网上有很多加高斯噪声的代码都是错的。比如:https://blog.csdn.net/kaikai______/article/details/53535909

 

首先这个代码是单通道的,这个其实博主也承认了,第二点,它的代码的判断其实写的没有一点意义,因为NoiseImg的dtype,也就是src的dtype,也就是我们默认用cv2.imread读进来的图片的dtype是uint8,那么本来就是0-255,这里NoiseImg[i,j]=NoiseImg[i,j]+random.gauss(means,sigma)本身就会自动溢出,也就是256会被认为是0,这个我们前面的文章里面已经见识过溢出和饱和的巨大差别,图像处理我们需要的是饱和而不是溢出。我来演示一下:

 

看到下面的灾难性后果了嘛?颜色完全变了哎,这就是溢出的结果。

 

上面的[187 244 253]为什么变成了[193 250 3]呢?看起来不就是每个元素都加了6吗?253+6=259-256=3。这已经溢出了,在溢出后面加判断又有什么用处呢?所以我的处理是不要让a[i,j]去接受a[i,j]+r.gauss(0,1)的值,不然就会溢出。

 

所以诸君,慎重对待网上的一些代码,自己要动动脑子。还有一点我们需要注意:

 

 

 

当然其实也不难理解,这就是python的机制嘛,如果让a=a+5,那么a指向的地址空间就变了,因为改变的不是某一个子数组,也就是不是内容,这个改变的是地址指向。如果是改变内容的话,地址指向不变,改变的是地址里面存的内容了,还有一点,[:]改变的也是内容,这也可以解释,毕竟它指代的是所有元素,而不是地址,其实按照c语言的中数组指针来类比,a就是a列表的首地址。参看下图。

 

那么有没有提供给我们现成的模块的呢?也是有的。

 

 

显示让我们安装scikit-image,那么我们就安装这个。

 

 

mode是模式,输入是字符串,,默认是'gaussian'就是高斯噪声,'s&p'是椒盐噪声。image将要被转化为浮点型,这个下面我们会看到。

 

seed是起始点,可以不要。clip是True的话会对模式是高斯,泊松和斑点分布应用在图像上之后进行修正,反之不会,默认就是真。mean就是分布的均值是浮点数,用在高斯和斑点分布中,默认是0。var是方差,默认为0.01。有些我们没有用到的就先不介绍了。amount是噪声的比例,在椒盐,盐和胡椒噪声中用到。salt_vs_pepper用于s&p,是盐(白色)和胡椒(黑色)的比例,默认是相等的0.5。

 

来加一个椒盐试一试。

 

速度挺快的,但是颜色不知道为什么不是黑白的。而且类型是float64,出来的都是在[0,1]之间。0是白色,1是黑色。然后是高斯分布,用默认的均值0和方差0.01:

 

最后一行忽略掉,那个是我后来测试其它东西打上去的。

均值滤波

 

这里面是有默认的卷积核的,blur用的就是均值滤波的卷积核。

 

ksize是卷积核大小。anchor是锚,也就是卷积结果放置的位置,(-1,-1)是默认的,代表核中心。bordertype是边界补充类型,参考了https://blog.csdn.net/qianqing13579/article/details/42323397

 

 

 

没错在copyMakeBorder里面我们就见过这几个。

 

 

可以比较明显的看出来,均值滤波对于高斯噪声的效果比较好,但是对于椒盐噪声就不太行了。这是因为我们加的高斯噪声的方差0.01很小,而且均值为0,很大概率高斯噪声造成像素点的颜色变化很小,一取平均的话就可以消除。

 

如果我们增大方差到0.1的话,可以看到明显消除噪声的效果也是非常的差。

中值滤波

 

中值滤波是比较难写出对应的卷积核,不过算法还是比较好写的,就是取对应矩形内元素的中值作为卷积的结果。

 

这个ksize只需要填一个大于1的奇数就可以。而blur需要填的是元组。

 

看到了错误原因是is not a tuple。下面我们试一试中值滤波:

 

这个原因可能是加完噪声之后数组元素变成小数造成的。那么我们就用我们上面自己写的代码呗。首先我觉得既然加噪声比较常用,我们就把它封装成一个模块。需要注意的是模块的路径一定要在sys.path里面。sys.path.append可以添加路径。

 

 

好,然后我们测试中值滤波:

 

我们本来设想的应该是a不变的啊,这为什么a有噪声呢?这是因为

 

 

没错,a是个数组,在函数里面对某一个元素赋值时会牵连的。所以我们需要改几个地方:

 

我选择改imnoise模块是因为这样时一劳永逸。

 

为什么3图加高斯噪声的基本看不出来效果呢,是因为标准差太小了。我设的默认值是1。

 

改成20可以看到明显的效果。100的效果更明显。

 

如果means是正的,会增加图片亮度。100的效果是很显著的。

 

从上面我们可以看出:中值滤波对于椒盐噪声和sigma较小,mean=0的高斯噪声可以很好的处理,这是因为中值滤波卷积的结果是要取中值的,而椒盐噪声是0或者255,基本不可能被取到,除非有很多0或者255,也就是有很多黑色和白色,但是这样加进去的噪声要么是相当于没有加进去,黑里加黑或者白里加白,要么是黑里加白或者白里加黑,这种是很容易过滤掉的。

对于sigma比较大或者means比较大的,中值滤波就比较乏力了,因为噪声影响到的像素比较多,取一个区域中值的时候,如果有太多像素点的亮度偏离原来的数值太多,这个中值也会偏离比较大。

中间插播一个小知识:参考了http://lib.csdn.net/article/python/64659

 

 

 

高斯模糊

其实上面我们也看到了,滤波处理是有模糊的功能的,因为边界看起来真的模糊多了,这主要还是因为卷积核的选取的原因,上一讲也有可以让边界更清晰的卷积核,这个下面会有。

下面的图中有一个5阶的高斯卷积核,这个卷积核是根据半径和sigma=1计算出来的。

 

上面还有快速计算高斯卷积的一种方法,就是用先后用两个一维的去卷积,为什么一维的是1,2,1呢?其实这都是默认sigma=1,然后对照下面左边的图,1对应的函数值大概就是0.2,最中间是0.4,所以就是1,2,1。而其实[]1,2,1]*[1;2;1]就是三阶的核,只不过没有除以16。

 

这个可以分开的原因是两个维度是垂直的,也就是是独立的,也就是参数ρ等于0。

 

那么

 

加入ρ=0,二维的边缘概率密度就等于两个一维的概率密度之积。这里稍微涉及了一点概率论的知识。

 

 

ksize参数有两种选择,就是宽和高都是正奇数或者它们都给0,根据sigma来计算它们?这里我怀疑是用3σ原理。

 

如果σx和σy都是0,那么就按照kszie来计算,我怀疑是直接用的σ=1。不过上面有说到getGaussianKernel是给了我们一个公式的:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8。

 

这个公式其实还就是根据3σ来的:参考https://blog.csdn.net/kuaile20/article/details/17606235

 

那么理论就先到这里,实战开始了。

 

这是我们加了sigma是50的高斯模糊,再进行高斯过滤,似乎效果不是太好,那么我们加大高斯卷积核的大小到11。

 

效果明显更好,虽然图片的轮廓也更模糊了,为什么图片的轮廓更模糊了呢?因为我们用了更大的卷积核,考虑了更多边缘周围像素点的情况,那么最后的结果就是边缘的过渡更平滑,边缘也就更加模糊了。增大卷积核还更加细分了权值,可以想象,是会更能消除高斯噪声的。

但是对于椒盐噪声的效果其实不太理想,这是因为椒盐噪声非0即255嘛,但是增加核数效果还是会好,这是因为椒盐噪声被“人海淹没了”,虽然它们很’“偏激’,但是如果周围很多正常人,一加权平均,就显得整个团体没有那么”偏激“。

图像锐化

由于opencv似乎没有直接提供图像锐化的API,我们需要用自定义卷积核来自己实现锐化。

 

用的就是filter2D函数来自定义卷积核。ddepth是数据深度,我们就照着填-1吧,kernel就是我们的卷积核,如果想各个通道的卷积核不一样,哪那么就先把图像用cv2.split分为三通道,然后每个通道分别用。delta是在输出之前,卷积之后要整体加的一个数。

 

好的,那么我们回顾一下之前的锐化的核是什么。

 

5阶的,上面已经基本上说明了原理了,如果不是边缘,这个值一般和原来差别不大,因为周围像素点的颜色差别不大,差别很大说明是边缘;如果是边缘,那么它周围的像素点的颜色肯定差别比较大了,一边是和边缘点的颜色相近的,还有一边是差别较大的,那么我觉得边缘出来的效果就有可能是亮度比较高或者比较低。

 

效果还是比较明显的,有噪声的话,噪声也会更加明显。

我们最后来学一下什么叫做depth,-1又是什么意思?参考了https://blog.csdn.net/sz76211822/article/details/47278185

 

还有https://blog.csdn.net/yiyuehuan/article/details/43703067

 

我想-1应该是跟随其它输入的意思。上面我们填-1也就是跟随a的dtype了,

 

改一下就会跟着变。

 

我们先到这里。

猜你喜欢

转载自blog.csdn.net/qq_41740705/article/details/82827968