opencv 图像噪点消除 图像梯度处理_opencv 去除噪点
目录
一、图像噪点消除
1、均值滤波 cv2.blur(img, ksize= )
2、方框滤波 cv2.boxFilter(img, ksize= , ddepth= , normalize= )
3、高斯滤波 cv2.GaussianBlur(img, ksize= , sigmaX= )
4、中值滤波 cv2.medianBlur(img, ksize= )
5、双边滤波 cv2.bilateralFilter(img, ksize= , sigmaColor= , sigmaSpace= )
小结
二、图像梯度处理
1、垂直边缘、水平边缘提取
2、边缘检测算子
a、Sobel算子 cv2.Sobel(img, ddepth= , dx= , dy= , ksize= )
b、Laplacian算子 cv2.Laplacian(img, ddepth)
一、图像噪点消除
首先,噪点是图像中随机出现的亮度或颜色异常像素(如“雪花点”“颗粒感”),主要由传感器噪声、低光照、压缩失真等引起,表现为图像中随机的亮度,也可以理解为有那么一些点的像素值与周围的像素值格格不入,常见的噪声类型包括高斯噪声和椒盐噪声。高斯噪声是一种分布符合正态分布的噪声,会使图像变得模糊或有噪点。椒盐噪声则是一些黑白色的像素值分布在原图像中。
那么滤波就是用来消除这些噪声点的方法,相当于美颜软件中的磨皮功能,而滤波也分了好几种类别,下面介绍几种常用的(以下案例中用到的两张图片 lvbo2.png 和 lvbo3.png 分别代表高斯噪点和椒盐噪点):
1、均值滤波 cv2.blur(img, ksize= )
均值滤波是一种最简单的滤波处理,它取的是滤波核区域内元素的均值,即每个像素的值变成周围像素的平均值。
API为 cv2.blur(img, ksize= ), img是需要操作的图,ksize是滤波核的大小,通常是填一个元组
具体代码示例如下:
import cv2import numpy as npdef test1(): img1 = cv2.imread(\'./src/lvbo3.png\') img2 = cv2.blur(img1, ksize=(3,3)) 用3x3的卷积核进行均值滤波 cv2.imshow(\"img1\", img1) cv2.imshow(\"img2\", img2) cv2.waitKey(0)if __name__==\"__main__\": test1()
运行结果:
可以看到,比起原图还是模糊了很多噪声点的,而且均值滤波是适用于椒盐噪点的
2、方框滤波 cv2.boxFilter(img, ksize= , ddepth= , normalize= )
方框滤波其实跟均值滤波很像,滤波核为;
当a为1/9时其实就是均值滤波,这是在参数 normalize=True 的时候,当 normalize=False时,a=1,即核中心点的像素值为区域内的像素和
API 为 cv2.boxFilter(img, ksize= , ddepth= , normalize= ),前两个就不介绍了,ddepth代表输出图像的深度,一般设为 -1;normalize 刚刚说过为 True 时就是均值滤波,为 False 就相当于求区域内的像素和。
具体代码示例如下:
def test2(): \'\'\'方框滤波\'\'\' img1 = cv2.imread(\'./src/lvbo2.png\') img2 = cv2.boxFilter(img1, ddepth=-1, ksize=(3,3),normalize=False) # img2 = cv2.boxFilter(img1, ddepth=-1, ksize=(3,3),normalize=True) 相当于均值滤波 cv2.imshow(\"img1\", img1) cv2.imshow(\"img2\", img2) cv2.waitKey(0)
运行结果:
可以看到图片变得很白,这是因为当 normalize=False 时相当于求区域内的像素和,其值稍不注意就会达到255,所以图像的很多区域都会变白,所以一般情况下还是都用 normalize=True
3、高斯滤波 cv2.GaussianBlur(img, ksize= , sigmaX= )
前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。而高斯滤波的卷积核权重并不相同:中间像素点的权重最高,越远离中心的像素点权重越小。
通常来说,当滤波核大小为3x3时,其核值如下所示:
API 为 cv2.GaussianBlur(img, ksize= , σx= ),sigmaX 就是高斯函数里的值,sigmaX 值越大,模糊效果越明显.
具体代码示例如下:
def test3(): \'\'\'高斯滤波\'\'\' img1 = cv2.imread(\'./src/lvbo2.png\') img2 = cv2.GaussianBlur(img1, (3,3), sigmaX=80) cv2.imshow(\"img1\", img1) cv2.imshow(\"img2\", img2) cv2.waitKey(0)
运行结果如下:
可以看到比起有高斯噪声的原图来说,新图像还是平滑了很多,而且很明显高斯滤波适用于高斯噪点
4、中值滤波 cv2.medianBlur(img, ksize= )
中值滤波就是用区域内的中值来代替中心像素值,所以那种孤立的斑点,如0或255很容易消除掉,它适用于去除椒盐噪声和斑点噪声。但是效率很慢,因为要对区域内的像素值排序,而排序这件事对计算机来说很费时间。
API 为 cv2.medianBlur(img, ksize= ),这两个参数就不用过多介绍了,但是这里 ksize 的值是一个整数不是元组。
具体代码案例如下:
def test4(): \'\'\'中值滤波\'\'\' img1 = cv2.imread(\'./src/lvbo3.png\') img2 = cv2.medianBlur(img1, ksize=3) cv2.imshow(\"img1\", img1) cv2.imshow(\"img2\", img2) cv2.waitKey(0)
运行结果如下:
可以看到,中值滤波对椒盐噪点的消除效果是比较好的
5、双边滤波 cv2.bilateralFilter(img, ksize= , sigmaColor= , sigmaSpace= )
因为边缘也算噪点的一种,所以如何在去除噪点的同时尽可能地保留边缘信息是很多人都在思考的问题,所以产生了双边滤波。
原理就是将区域内每个点的位置信息和像素值与中心像素点的进行比对,再分别计算距离和像素值的权重,两个权重相乘就是区域内每个点的最终权重,再加权求和得到中心像素点的最终值.
API为 cv2.bilateralFilter(img, ksize= , σcolor= , σspace= ),这里的 ksize 也是填一个整数;sigmaColor 代表计算像素值权重时所用的高斯函数的值,sigmaSpace 代表计算距离权重时所用的高斯函数的值,这俩值越大代表能勾搭更远的像素以及能包容差别更大的颜色。
具体代码示例如下:
def test5(): \'\'\'双边滤波\'\'\' img1 = cv2.imread(\'./src/lvbo3.png\') img2 = cv2.bilateralFilter(img1, 5, sigmaColor=150, sigmaSpace=150) cv2.imshow(\"img1\", img1) cv2.imshow(\"img2\", img2) cv2.waitKey(0)
运行结果如下:
小结
几种滤波方式就介绍完了,总结来说在不知道用什么滤波器好的时候,优先高斯滤波,然后均值滤波,斑点和椒盐噪声优先使用中值滤波,如果要在去除噪点的同时尽可能保留更多的边缘信息那就用双边滤波。
同时,由于均值滤波、方框滤波、高斯滤波都是线性滤波,速度都比较快,而中值和双边滤波因为是非线性滤波涉及到排序,速度相对较慢
这里强调一下,滤波并不代表模糊噪点,滤波是一种技术,不同的滤波会产生不同的效果,比如低通滤波器是模糊,高通滤波器是锐化,所以模糊只是滤波技术产生的一种效果。
二、图像梯度处理
首先要了解什么是图像梯度,图像梯度其实就是像素点周围色彩的变化量,它能反应这个点是否处于边缘,而且它不止有大小还有方向,当然这里的梯度指的是总梯度,总梯度有两种计算方式,一种是先分别计算出水平梯度 Gx 和垂直梯度 Gy,再计算总梯度,另一种是直接得出,具体我们往下看:
1、垂直边缘、水平边缘提取
首先先看垂直边缘提取,关键是用卷积,所用到的卷积核是这个:
这个核就是用来提取垂直边缘的,举个实际例子看是怎么做到的;
假设现在有一张原图:
现在用上面那个 k1 卷积核对红色方框进行卷积,可以得到中心像素点的值最终为-290,越界取0;接着对绿色方框的区域进行卷积,得到中心像素点的值为930,越界取255,所以得到边缘:
同理,将 k1 转置一下变成 k2 就可以实现水平边缘提取:
API为 cv2.filter2D(img, ddepth, kernel),img为要进行操作的原图;ddepth 为输出图像的深度,常用 -1 表示输出与输入图像具有相同的深度;kernel 为卷积核,这里的 kernel 要自己设定。
具体代码示例如下:
def test6(): \'\'\'垂直边缘 水平边缘提取\'\'\' img1 = cv2.imread(\'./src/shudu.png\') kernel = np.array([[-1,0,1], [-2,0,2], [-1,0,1]],dtype=np.float32) #手动设定卷积核 img2 = cv2.filter2D(img1, ddepth=-1, kernel=kernel) #垂直边缘提取 img3 = cv2.filter2D(img1, ddepth=-1, kernel=kernel.T) #水平边缘提取 卷积核转置 cv2.imshow(\'img1\', img1) cv2.imshow(\'img2\', img2) cv2.imshow(\'img3\', img3) cv2.waitKey(0)
运行结果如下:
可以看到成功捕捉了垂直与水平边缘
2、边缘检测算子
为了更高效地计算图像梯度,我们常常使用设定好的卷积核,也称为算子,下面介绍两种常用的算子:
a、Sobel算子 cv2.Sobel(img, ddepth= , dx= , dy= , ksize= )
在一开始我们提出可以先分别计算水平梯度和垂直梯度再计算总梯度,Sobel算子就可以实现这种方法,像刚刚提取垂直和水平边缘所用到的 k1、k2就是 Sobel算子,只不过方向不同。
第二列为0的 k1 就是用来计算水平梯度 Gx 的,但是刚刚不是拿 k1来提取垂直边缘的吗,怎么又来计算水平梯度了,其实是因为边缘就是像素值突变的地方,对于垂直边缘来说,水平方向上的像素值在剧烈变化,反而是垂直方向上的像素值几乎不变,所以水平梯度可以检测垂直边缘(就像用水平尺子量垂直墙是否歪了),同理,垂直梯度也是来检测水平边缘的,所以第二行为0的 k2 是来计算垂直梯度 Gy 的,一句话总结:边缘方向永远与梯度方向垂直。
API为 cv2.Sobel(img, ddepth, dx, dy, ksize),img为输入的灰度图;ddepth 为输出图像的深度,常用 -1 表示输出与输入图像具有相同的深度;当 dx=1,dy=0时表示求水平梯度,dx=0,dy=1时求垂直梯度;ksize 表示Sobel算子的大小,可选3、5、7等整数,默认为3。
具体代码示例如下:
def test7(): \'\'\'Sobel算子\'\'\' img1 = cv2.imread(\'./src/tiqubianyuan.png\') img2 = cv2.Sobel(img1, ddepth=-1, dx=1, dy=0, ksize=3) #水平梯度 img3 = cv2.Sobel(img1, -1, 0, 1, 3) #垂直梯度 cv2.imshow(\'img1\', img1) cv2.imshow(\'img2\', img2) cv2.imshow(\'img3\', img3) cv2.waitKey(0)
运行结果如下:
虽然还是提取的水平或垂直边缘,但实际上opencv已经独立计算出了 Gx 和 Gy,只是没有显示,因为我们并不是要这两个值,而是根据这两个值来提取边缘。
至于为什么 Sobel算子不直接输出总梯度呢,时因为有时候我们只需要水平或垂直边缘,如果需要总梯度的话可以手动计算,比如 grad_img = np.sqrt(Gx**2 + Gy**2)
而且,Sobel算子的这几个值事实上并没有规定,也可以使用自己的,比如最初只利用邻域间的原始差值来检测边缘的Prewitt算子:
b、Laplacian算子 cv2.Laplacian(img, ddepth)
当需要提取精细边缘时可以用 Laplacian算子,它直接找出边缘的极值位置,相当于直接定位“像素值变化最快的地方”,尤其擅长捕捉像素值的快速变化点(如边缘、角点、噪声),它的卷积核长这样:
API为 cv2.Laplacian(img, ddepth),这两个参数相信不用介绍了,直接看具体代码示例:
def test8(): \'\'\'Laplacian算子\'\'\' img1 = cv2.imread(\'./src/tiqubianyuan.png\') img2 = cv2.Laplacian(img1, ddepth=-1) cv2.imshow(\'img1\', img1) cv2.imshow(\'img2\', img2) cv2.waitKey(0)
运行结果如下:
下面是两种算子的一些对比:
这篇就到此为止,下一篇也是重点的图像边缘检测 (๑•̀ㅂ•́)و✧
以上有问题可以指出