python高斯噪声怎么去除_OpenCV 常用总结(Python)

最近一直在用cv2,记录一下常用的一些操作和代码吧。

首先放OpenCV 的python官方文档链接:

Welcome to OpenCV-Python Tutorials’s documentation!

OpenCV 教程 - OpenCV 2.3.2 documentation

主要用的模块大概分为以下几类:

  1. 图片读写,2. 图像滤波,3.图像增强,4.阈值分割,5.形态学操作,当然还有其他。。。

绪论:

工作环境Linux Ubuntu 16.04, Python 3.6, OpenCV 4.0。因为OpenCV的数据结构是基于numpy的,所以也要对numpy有大概的了解。

首先导入必要的库:

import 

由于Ubuntu下cv2的imshow展示图片功能会有bug,因此使用matplotlib来展示图片。

一,图片读写:

Basic

图片读入:

image = cv2.imread('test.jpg', -1)

没什么好说的,第一个参数文件路径,第二个参数是通道数,-1代表原图与原图保持一致,0代表转换为灰度图,1代表使用BGR通道读入。

需要注意的就是读入的图片是以np.ndarray类型存储的,且彩色图像的通道为BGR与常见的RGB不同。

图片展示:

cv2.imshow('image', image)
cv2.WaitKey(0)
cv2.destroyAllWindows()

imshow第一个参数是窗口名,string类。第二个是np.ndarray的数组。注意在下面要使用WaitKey,不然展示窗口就会闪一下,程序就继续运行了,参数代表停多长时间,0的话是除非干扰不然一直展示。

在Ubuntu下cv2的imshow会出bug, 使程序崩溃,因此使用pyplot展示图片,但需要注意pyplot的彩色图片是RGB的格式,所以展示图像时需要先转换通道。

对于灰度图像:

plt.imshow(image, cmap='gray')
plt.show()

对于彩色图像:

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()

图片写出:

cv2.imwrite('image.jpg', image)

这边需要注意的是第一个参数文件名要加格式,比如.jpg .png 之类的。

More

多的话只对文件读入加一些。

读取某个文件夹下所有jpg或其他图片可以使用glob,但无法读取子文件夹下的。

import glob
PATH = ''
for file in glob.glob(PATH + '*.jpg'):
    img = cv2.imread(file, -1)

也可以把jpg改为png等等。

那么如何读取一个文件夹内包括其子文件夹下的所有图片呢?这个时候要用os和fnmatch了。

import os
import fnmatch


def find_files(directory, pattern):
    """
    Method to find target files in one directory, including subdirectory
    :param directory: path
    :param pattern: filter pattern
    :return: target file path
    """
    for root, dirs, files in os.walk(directory):
        for basename in files:
            if fnmatch.fnmatch(basename, pattern):
                filename = os.path.join(root, basename)
                yield filename

for filename in find_files('Home/Leon', '*.jpg'):
    img = cv2.imread(filename, -1)

这个看一下应该就知道怎么回事了把?find_files引用了一个网上的代码我忘了哪个了,,,

然后对读入的图片色彩空间转换,常用cvtColor

img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

第二个参数很多种,常用的色彩空间也就是把BGR转成RGB或者Gray,偶尔会用HSV,因为HSV不会有很多颜色的参数,对于图片的纹理啊这些特征的提取和处理很有用。HSV三个通道分别是:色调(H),饱和度(S),明度(V)。具体释义可以看维基百科的

https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4

实话讲对HSV我理解的不多,反正就是H跟颜色有关,是一种对于RGB的非线性变换,饱和度的话就是我们有时候会说有些颜色好艳啊之类的,我会用来提取纹理,V明度我一直就认为是类似灰度值了。印象中有专门的矩阵来乘可以完成两个色彩空间的变换。

二,图像滤波

图像降噪:

降噪第一步是估计噪声,只有知道是什么样子的噪声类型才可以找到好的降噪方法。估计噪声的话一般先观察直方图,也就是Histogram。一般有两种方法,一种是opencv自带的,还有一个matplotlib的,我个人倾向第二种,因为简单。

plt.hist(img.ravel(), 256, [0, 256])
plt.show()

对于如何估计噪声不说了,主要是写应用的。

# 6.2 补充

上面只是看直方图的,但在程序中如果想使用直方图的信息怎么办呢?建议还是使用cv2自带的方法,这个速度很快。(我自己写的计算直方图方法速度的10倍,也可能是因为我自己写的太辣鸡了)

代码如下所示:

hist = cv2.calcHist([img], [0], None, [256], [0, 256])

返回的是一个length=256的列表,分别是像素值为0--255的统计个数。

# 补充完毕

对于降噪常用的方法有均值滤波,高斯滤波,中值滤波,双边滤波等,当然你也可以定义自己的滤波函数。

blur = cv2.blur(img,(5,5))
gau_blur = cv2.GaussianBlur(img,(5,5),0)
median = cv2.medianBlur(img,5)
bi_blur = cv2.bilateralFilter(img,9,75,75)

从上到下分别是均值滤波,高斯滤波,中值滤波,双边滤波。下面说一下应用场景:

  1. 均值滤波我反正不怎么用,了解滤波概念学习下就好了。
  2. 高斯滤波一般都会有一些效果,当我实在找不到最好的滤波方法时我一般使用高斯滤波,不保证效果但不会出问题。主要滤高斯噪声的,就是直方图看着正态分布那样。但现实图片中并没有高斯噪声,高斯只是比较适合去拟合现实中的一些噪声。(不确定欢迎指正)
  3. 均值滤波我用的比较多,对于椒盐噪声有很好的效果,就是直方图上会有一些孤立的高点那样的。当然滤出椒盐噪声的话维纳滤波也会有很好效果,但较为麻烦所以不怎么用。
  4. 双边滤波相当于把大图片分小再一块一块滤波,很多时候效果也不错,网上看说美颜会经常用。他比较好的一点是可以保留原始图像的纹理边缘等细节。

对于函数具体参数可以参看

Smoothing Images

除此之外还有局部均值滤波等,non-local-means,我个人感觉和bilateralFilter比较相似,用于一张大图上有一些相似部分的滤波,速度很慢而且占用内存也挺大,不建议用。

Image Denoising - OpenCV-Python Tutorials 1 documentation

边缘检测:

实际应用中我用的比较多的是找边界,findContours。

contours, _ = cv2.findContours(img_close, 1, 2)

其中contours保存了图像中所有的contour信息,是一个列表。

这里注意下opencv版本更新,老版finContours是返回三个参数,现在是两个参数。还有Contour都是闭合的。但很多时候Contour会有大有小,我们可以设置阈值来滤出一些我们不想要的边界,最常用的是根据包围的面积大小来看,偶尔也会根据周长。在滤出contour后经常会话bounding box, 因为Contour通常是不规则的。

for contour in contours:
    if area_thresh_max > cv2.contourArea(contour) > area_thresh_min:
        x, y, w, h = cv2.boundingRect(contour)
        cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 10)

boundingRect返回的分别是Contour左上角坐标x,y和矩形框的宽高w,h。

另外对于Contour还可以根据其area或len排个序,取前几位这样。

contours = sorted(contours, key=cv2.contourArea, reverse=True)[:1]

此外边缘检测还经常用一些算子,Canny, Sobel, Laplacian等。其中Canny对边缘检测效果最好,但是很多时候也会受到噪音的干扰。Laplacian经常用来做边缘增强。

我建议大家可以根据实际情况自己定义kernel算子,然后使用filter2D来做。

比如求上下的边缘就可以定义:

KERNEL_HORIZON = np.asarray([[1, 2, 1],
                            [0, 0, 0],
                            [-1, -2, -1]], dtype=np.int)

然后使用:

img = cv2.filter2D(img, -1, KERNEL_HORIZON)

左右边缘的话可以:

KERNEL_VERTICAL = np.asarray([[1, 0, -1],
                             [2, 0, -2],
                             [1, 0, -1]], dtype=np.int)

反正算子随便定,你3X3,5X5,数值也随意,反正多试试哪个好用哪个。

不过需要注意一点是矫正,你比如我的KERNEL_HORIZON就有可能让边缘向上或向下偏一个像素值,虽然很小,但如果是做cutting的话就不是那么精确了。

还有线检测圆检测等,使用Hough方法,该方法原理比较简单,很容易懂,建议大家看看原理。但在Hough前要先将图像二值化,即非黑即白。好像是后面我要说的,可以跳着看。

edges = cv2.Canny(gray_img,50,150,apertureSize = 3)
lines = cv2.HoughLines(edges,1,np.pi/180,200)

cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,
                            param1=50,param2=30,minRadius=0,maxRadius=0)

在实际应用中碰到的问题往往与纯粹的直线会有些偏差,所以我一般会自己仿照着Hough的原理写一下针对性的函数。所以还是看看原理吧兄弟们。

三,图像增强

图像增强,其实也就是对比度增强,还是先看一眼直方图然后决定怎么增强吧。最简单的无外乎线性变换,比如你原图的直方图灰度分布0-128就到顶了,那就可以img = img*2,对比度就增强了两倍。这也是全局化的增强,叫直方图均衡化(Histogram Equalization)。

OpenCV里有直接的函数

img_equ = cv2.equalizeHist(img)

还有局部自适应直方图均衡化,叫CLAHE(Contrast Limited Adaptive Histogram Equalization),这也是我用的比较多的。

clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
img = clahe.apply(img)

第一个参数是对比度的limited,第二个参数是局部的窗口大小。官方文档也写得很清晰了:

Histograms - 2: Histogram Equalization

增强这一部分其实很多类,上面说的都是对比度的增强,当然还有边缘增强,就是锐化,这种一般先使用边缘检测提取出边缘的特征图,再拿边缘特征图与原图相加就可以是边缘部分得到增强。常用的是高斯边缘检测。

四,阈值分割

阈值分割其实也就是二值化,首先我觉得大家都知道现在几乎所有图片都是256个灰度级吧,二值化就是变成非黑即白,就是只有两个灰度级。

最简单的阈值分割就是,比如127这个灰度吧,你让大于127的变成255,小于等于127的变成0就好了,这个可以直接用numpy的数据结构操作的,不需要用什么OpenCV的函数,反正建议大家尽量不要过于依赖OpenCV已有的函数,要活学活用是吧。

img[img > 127] = 255
img[img < 128] = 0

可以看出numpy是很方便的,需要说明下这里最好不要用两个for循环来遍历图片,比大小,然后赋值0或255,这样速度太慢了。numpy里是矩阵运算的,速度会快很多。

上面这个其实就是OpenCV里的THRESH_BINARY,里面还有inverse就是黑白换一下,这玩意直接拿255减原图不就妥了嘛,反正简单的二值化建议直接用numpy做。

OpenCV里还有Adaptive Threshold,有两个参数分别是mean和gaussian,反正我理解的就是,先做个均值滤波或者高斯滤波完了再简单的Threshold,所以我也不怎么用。不过还是放一下文档链接吧,随缘看。

Image Thresholding

OK,上面的都不怎么用那我阈值分割平常用什么啊?我觉得利器就是OTSU,这种阈值分割是自适应的阈值,会根据Histogram计算出最小方差差的灰度值,然后用那个灰度值来做简单的阈值分割。这种对于直方图有个波浪起伏那种特别好用,就是下面第三个。因为很多时候我们很难去确定到底用哪个阈值,比如上面说127是随意说的,对不同图片就阈值不一样。

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第1张图片

也不一定是完全要求这样非常明显的分布啦,比如第二个的直方图,只要后半部分有那么一点点上升的趋势,就可以很好的分出来了。用起来也很好用:

ret,th = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

ret我记得是算出来的那个阈值,th就是ndarray的图片了,一般我只关心图片,所以就直接

_, th = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

就很舒服。同样的,对于这是针对全局Histogram只有一个凹槽的OTSU,那么如果Histogram波浪起伏呢?这个时候就可以分段来提取了,比如我在0-127做一遍OTSU,阈值下0阈值上我给127, 然后再对剩下的继续OTSU,就这样。前提是需要提前观察好图像的特征,所有方法都不是死搬硬套的,要针对具体问题来改变。

五,形态学操作

形态学操作也是我用的比较多的。讲出这个概念,大概第一反应就要想到膨胀和腐蚀了,在OpenCV里函数直接就是dilate和erode。除了膨胀腐蚀还有开运算和闭运算,说白了就是开运算:先腐蚀后膨胀,闭运算:先膨胀后腐蚀。关于膨胀腐蚀的概念和源码可以看看:

腐蚀与膨胀(Eroding and Dilating)

我当时学的时候到这里就很疑惑,为什么有了膨胀腐蚀还要开运算闭运算呢?其实开闭运算最重要的一点就是,可以保持物体原有大小。然后一个是消除物体内部孔洞的另一个是增强物体之间连接点的,当然要根据背景是黑是白来确定用那个,用0还是1的kernel等等。

kernel = np.ones((10, 10), dtype=np.uint8)
img = cv2.erode(img, kernel=kernel, iterations=1)
img = cv2.dilate(img, kernel=kernel, iterations=1)

很多人知道filter2D的kernel必须要奇数,但是这里形态学并不是。首先明确形态学操作也是对二值图进行的,简单来讲就是你图上这一块和我的kernel都一样我就置为255,不一样我就置为0,当然erode和dilate是相反的,其实跟背景也有很多关系,所以有时候我会:

img = cv2.erode(255-img, kernel=kernel, iterations=1)

就不用想太多,show一下看看不行了再调回去。然后iteration就是指迭代几次,比如iterations=2下面两行是等价的:

img = cv2.erode(img, kernel=kernel, iterations=2)
img = cv2.erode(cv2.erode(img, kernel=kernel, iterations=1), kernel=kernel, iterations=1)

然后开闭运算就是:

img = cv2.dilate(cv2.erode(img, kernel=kernel, iterations=1), kernel=kernel, iterations=1)
img = cv2.erode(cv2.dilate(img, kernel=kernel, iterations=1), kernel=kernel, iterations=1)


是不是很简单?其实OpenCV里还有个专门的morphology来进行开运算闭运算,据说有一些改变,效果会好一些(一个大佬建议我尽量用morphology的),反正我是一直觉得差不多。

opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

除此之外open和close还有一点用就是也可以作为边界的提取。就是拿Open的图片减去close的图片就好了,morphology里也有专门的参数,反正我还是觉得下面两个等价的。

edges = opening - closing
edges = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

有时候我也会设置一些奇葩的kernel比如不全1啊等等,还有可以用不同大小的kernel来分别erode和dilate,我就用过10X1的kernel先膨胀再用1X10的腐蚀回来,当时是为了消除一些水平线我记得。

六,其他:

反正这个就是想到什么说什么呗。先说下大概要说啥,今天累了。

  1. 聚类cluster,K-means算法 (OK)2. Region Growing,分水岭算法watershed 3. Super pixel,这个算是为机器学习做个铺垫吧。

先说一些其他的常用函数,然后讲下k-means。

其他常用函数:

首先经常用的resize函数,用来调整图片大小的,一般在调整时候尽量保持长宽比不要变吧,不然很容易丢失原始图像的细节。

img= cv2.resize(img, (256, 256))

聚类(Cluster)-->K-means

这个算法也是用于图像分割的,先了解下K的意思大概是类别数,means就是均值。算法大概的意思就是:

首先,在一张图里随机生成K个小点,作为K个核,然后以这些核为中心。

然后,对于每个像素点,计算他与这K个核的距离,类别归为距离最小的核那个。

此时,对于一个图片来讲,就分成了K个区域。

再然后,对这K个区域分别计算均值,这里敲黑板,这个均值不是像素的灰度值的均值,而是像素坐标点的均值!!!然后把计算得到的那个均值(也就是那个像素坐标点)作为新的核,此时就重新产生了K个核。

再重新计算每个像素点与K个核的距离,然后再更新K个核,反复进行,直到所有的核的位置不再变,也就是计算均值得到的新核与过往的旧核重合,此时结束。

就是如下图这个意思(引【十大经典数据挖掘算法】k-means - Treant - 博客园)

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第2张图片

当然K-means最大的缺点就是,太依赖于最开始随机产生的核,所以很容易产生局部最优解而不是全局最优解,理解一下就是下面这样的。

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第3张图片

就是有三类吧,最优解当然是导数为零的地方,然后开始的K的点(红色)全跑到中间那一类里面了,就只能找到一类了,就很难受是吧,也可能两类。

所以我用K-means的时候一般会设置个flag条件,达不到一直循环K-means,这样的后果就是费cpu还占时间,所以我一般不用啊。建议个人学习玩玩,如果真工作千万不要用,主要一点就是不稳定。

你说你设个flag循环个千八百次总有一次初始随机点比较正常满足要求的,但万一呢?万一你就要被老板骂一顿了。讲道理K-means我用的是sklearn的,所以就不再贴代码了。opencv我不确定有没有。。。

那么K-means的意义在哪里呢?其实这就是一个机器学习的例子,是无监督的学习,学学原理对将来是有好处的,多学无害嘛毕竟,而且K-means的代码实现起来也不难,可以自己写写K-means的算法,提升提升自己。OK,K-means就这样了,有问题的欢迎来讨论哈~~

区域生长(Region Growing)-->Watershed

这也是用于图像分割的非监督学习算法,通用的解释都是把这个算法等效成高低起伏的山山上还有些小坑那种。

你想啊,好多个连着的山,一个挨着一个,这个时候采用传统阈值分割,就很难把山和山分开(采用聚类的K-means应该还行),但是要注意有山峰还有山谷啊,还有那些坑对吧。假想有一场一直下着的雨,那么水往低处流,水填充的地方就可以很好的把山和山给分开,甚至于山上的一些小坑也能分的出来。

这里山的陡峭程度就是图像的梯度,就是图像变化特别剧烈的部分。唉口语怎么感觉说不清呢?直接引一下百度百科吧:

在该算法中,分水岭计算分两个步骤,一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高排序,然后在从低到高实现淹没过程中,对每一个局部极小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。

就是,想象一个二维的图,你把它想成三维的,xy轴然后每个坐标点的灰度值当z轴,有画面了吧,一个高低起伏的三维立体图。然后你就想象拿一壶水网这图里倒,就行了。水位线以下一点距离再往下的给个label -1(背景), 水位线以上一点距离再往上给个label 1(前景),水位线附近的给个label 0(边界)。然后吧label等于某一值的赋值就好了。类似这样:

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第4张图片
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]

注意一点不要把读入的图片直接拿来用,最好先预处理下,腐蚀膨胀下之类的,效果会好很多。

Super Pixel

这个概念其实是很类似于机器学习里的pooling操作的,主要目的是为了在保留图像大致信息的情况下缩小图像的尺寸,其实也不绝对。反正看看下面这两个图片大概就明白了吧。

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第5张图片

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第6张图片

上面那个图可以抽象为下面这个图,而且绝大部分的信息(包括边界,形状,颜色等等)都被保留了,同时还可以相当于缩小了大小(把那些看着块状的区域直接替代下就行了)。有感觉 了吧,superpixel其实在传统cv里没什么用(我感觉),在CNN等机器学习网络里常用pooling也就是池化层来实现,比如maxpooling,averagepooling,网上有很多相关的资料。下图放一个maxpooling的吧。

python高斯噪声怎么去除_OpenCV 常用总结(Python)_第7张图片

这个是步长为二的最大值池化,就是2*2里面选最大的像素点的灰度值来代替这四个像素点的坐标值。以此类推。pooling步长的选择一般都是2,太大的话会丢失很多信息,再小,,1有个p用啊,,,所以就2了一般。


我觉得我的第一篇图像处理相关的基本该说的也都说了,更的过程中也收了一些点赞,但没feedback啊建议啊之类的,欢迎大家一起探讨啊~

最后,网上有很多资源,有很多大佬写的教程啊什么的,我之前从中受益良多,学会了很多东西,这也是我加入自己理解后分享出来的原因吧。

大家一起互帮互助走向人生巅峰啊~

其他有什么需要的提醒我下,会的话我也加上。

小白一个,初次更,也没画什么图啊什么的,有些可能讲的也不一定清楚,也不一定有人看。。。。反正如果你看了麻烦给个建议啊feedback啊什么的,实在没啥讲的就点个赞吧。

你可能感兴趣的:(python高斯噪声怎么去除,通过hsv筛选颜色,python)