当前位置:   article > 正文

OpenCv学习笔记2--模糊滤波,边缘检测_opencv 过滤边缘

opencv 过滤边缘

opencv系列博客只是为了记录小编对<<opencv3计算机视觉-pyhton语言实现>>的学习笔记,所有代码在我的github主页https://github.com/RenDong3/OpenCV_Notes.

欢迎star,不定时更新...

一 不同色彩空间的转换

OpenCV中有数百种关于在不同色彩空间之间转换的方法。当前,在计算机中有三种常用的色彩空间:灰度,BGR以及HSV(Hue,Saturation,Value)

  • 灰度色彩空间是通过去除色彩信息来将其转换成灰阶,灰度色彩空间对中间处理特别有效,比如人脸检测。
  • BGR,即蓝-绿-红色彩空间,每一个像素点都由一个三元数组来表示,分别代表蓝、绿、红三种颜色。网页开发者可能熟悉另一个与之相似的色彩空间:RGB,他们只是在颜色顺序上不同。
  • HSV,H(Hue)是色调,S(Saturation)是饱和度,V(Value)表示黑暗的程度(或光谱另一端的命令程度)。

在第一次处理BGR色彩空间的时候,可以不要其中的一个色彩分量,比如像素值[0 255 255](没有蓝色,绿色分量取最大值,红色分量取最大值)表示黄色。如果读者有艺术背景,会发现绿色和红色混合产生浑浊的褐色,这是因为计算所使用的颜色模型具有可加性并且处理的是光照,而绘画不是这样的(它遵从减色模型 subtractive color model)。计算机使用显示器发光来做颜色的媒介,因此运行在计算机上的软件所使用的色彩模型是加色模型。

  1. #-*- coding:utf-8 -*-
  2. import numpy as np
  3. import cv2
  4. '''
  5. 使用numpy索引指定像素值,改变BGR的颜色通道值
  6. '''
  7. img = cv2.imread('1.jpg')
  8. #0, 1, 2 = B, G, R
  9. img[:,:,0] = 0
  10. cv2.imshow('img',img)
  11. cv2.waitKey(0)

左边为原始图像,右边为改变通道数值后的处理图片 

二 傅里叶变换

在OpenCV中,对图像和视频的大多数处理或多或少都会涉及到傅里叶变换的概念。Joseph Fourier(约瑟夫.傅里叶)是以为18世纪的法国数学家,他发现并推广了很多数学概念,主要研究热学规律,在数学上,他认为一切都可以用波形来描述。具体而言,他观察到所有的波形都是由一系列简单且频率不同的正弦曲线叠加得到

也就是说人们看到的波形都是由其他波形叠加得到的。这个概念对操作图像非常有帮助,因为这样我们可以区分图像哪些区域的信号变化特别强,哪些区域的信号变化不那么强,从而可以任意地标记噪声区域,感兴趣区域,前景和背景等。原始图像由许多频率组成,人们能够分离这些频率来处理图像和提取感兴趣的数据。

下面通过傅里叶变换来介绍图像的幅度谱(magnitude specturm)。图像的幅度谱是另一种图像,幅度谱图像呈现了原始图像在变化方面的一种表示:把一张图像中最明亮的像素放到图像中央,然后逐渐变暗,在边缘上像素最暗。这样可以发现图像中有多少亮的像素和暗的像素,以及它们分布的比例。

傅里叶变换的概念是许多常见的图像处理操作的基础,比如边缘检测或线段和形状检测

下面介绍两个概念:高通滤波器和低通滤波器。

1.高通滤波器

高通滤波器(HPF)是检测图像的某个区域,然后根据像素与周围像素的亮度差值来提升该像素的亮度的滤波器。

 以如下的核(kernel),即滤波器矩阵为例:

注:核是指一组权重的集合,它会应用在源图像的一个区域,并由此生成目标图像的一个像素。比如,大小为7的核意味着每49(7x7)个源图像的像素会产生目标图像的一个像素。
可把核看做一块覆盖在源图像上可移动的毛玻璃片,玻璃片覆盖区域的光线会按某种方式进行扩散混合后透过去。

 在计算完中央像素与周围邻近像素的亮度差值之和以后,如果亮度变化很大,中央像素的亮度会增加,反之则不会。换句话说,如果一个像素比它周围的像素更突出,就会提升它的亮度。

这在边缘检测上尤为有效,它采用一种称为高频提升滤波器(high boost filter)的高通滤波器。

高通和低通滤波器都有一个半径(radius)的属性,它决定了多大面积的临近像素参与滤波运算。

下面是一个高通滤波器的例子,代码如下:

  1. #-*- coding:utf-8 -*-
  2. import cv2
  3. import numpy as np
  4. from scipy import ndimage
  5. '''
  6. 使用高斯滤波器进行模糊图像,对比自设kernel
  7. 高通滤波器常用于边缘检测
  8. '''
  9. kernel_3x3 = np.array([[-1, -1, -1],
  10. [-1, 8, -1],
  11. [-1, -1, -1]])
  12. kernel_5x5 = np.array([[-1, -1, -1, -1, -1],
  13. [-1, 1, 2, 1, -1],
  14. [-1, 2, 4, 2, -1],
  15. [-1, 1, 2, 1, -1],
  16. [-1, -1, -1, -1, -1]])
  17. #读取图像并灰度化
  18. img = cv2.imread("lena.jpg", 0)
  19. # img = cv2.resize(img, (640,480))
  20. #卷积运算
  21. k3 = ndimage.convolve(img, kernel_3x3)
  22. k5 = ndimage.convolve(img, kernel_5x5)
  23. #高斯模糊
  24. blurred = cv2.GaussianBlur(img, (11, 11), 0)
  25. #求差
  26. g_hpf = img - blurred
  27. #显示
  28. cv2.imshow('3x3', k3)
  29. cv2.imshow('5x5', k5)
  30. cv2.imshow('g_hpf', g_hpf)
  31. cv2.waitKey()
  32. cv2.destroyAllWindows()

 

导入 模块后,我们定义了一个3x3和一个5x5的核,然后将读入的图像转换成灰度格式。通常大多数图像处理都会用Numpy模块来完成,但是这里的情况比较特殊,因为需要用一个给定核与图像进行'卷积',但是Numpy碰巧只接受一维数组。

但并不是说不能用Numpy完成多维数组的卷积运算,只是有些复杂,而ndimage的convolv()函数可以解决这个问题.

scipy.ndimage.convolve(input, weights, output=None, mode='reflect', cval=0.0, origin=0)[source]

The array is convolved with the given kernel.

Parameters:

input : array_like

Input array to filter.

weights : array_like

Array of weights, same number of dimensions as input

output : ndarray, optional

The output parameter passes an array in which to store the filter output.

mode : {‘reflect’,’constant’,’nearest’,’mirror’, ‘wrap’}, optional

the mode parameter determines how the array borders are handled. For ‘constant’ mode, values beyond borders are set to be cval. Default is ‘reflect’.

cval : scalar, optional

Value to fill past edges of input if mode is ‘constant’. Default is 0.0

origin : array_like, optional

The origin parameter controls the placement of the filter, relative to the centre of the current element of the input. Default of 0 is equivalent to (0,)*input.ndim.

Returns:

result : ndarray

The result of convolution of input with weights.

 

上面代码用了两个自定义卷积核来实现两个高通滤波器。最后又用一种不同的方法来实现高通滤波器:通过对图像应用低通滤波器之后,与原始图像计算差值。这样得到的效果会更好。

下图是opencv中对GaussianBlur()函数即高斯滤波函数的解释

 cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) → dst

 

参数:
  • src - 输入图像; 图像可以具有任何数量的信道,其独立地处理的,但深度应CV_8UCV_16UCV_16SCV_32FCV_64F
  • dst - 输出与大小和类型相同的图像src
  • ksize - 高斯内核大小。 ksize.width 并且 ksize.height 可以有所不同,但它们都必须是积极的和奇怪的。或者,它们可以为零,然后从中计算 sigma*
  • sigmaX - X方向的高斯核标准差。
  • sigmaY - Y方向的高斯核标准偏差; 如果 sigmaY 为零,则将其设置为等于 sigmaX,如果两个sigma均为零,则它们分别从ksize.width 和 计算得到 ksize.height(参见 getGaussianKernel()详细信息); 完全控制的结果,无论这一切的语义未来可能的修改,建议指定所有的ksizesigmaXsigmaY
  • borderType - 像素外推法(borderInterpolate()详情请参阅 参考资料)。

该函数将源图像与指定的高斯内核进行卷积。支持就地过滤。

高斯滤波是一种线性平滑滤波,对于除去高斯噪声有很好的效果。在其官方文档中形容高斯滤波为”Probably the most useful filter”,同时也指出高斯滤波并不是效率最高的滤波算法。高斯算法在官方文档给出的解释是高斯滤波是通过对输入数组的每个点与输入的高斯滤波核执行卷积计算然后将这些结果一块组成了滤波后的输出数组,通俗的讲就是高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个核(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。 
在图像处理中高斯滤波一般有两种实现方式:一种是用离散化窗口滑窗卷积,另一种是通过傅里叶变换。最常见的就是第一种滑窗实现,只有当离散化的窗口非常大,用滑窗计算量非常大的情况下会考虑基于傅里叶变换的方法。 
我们在参考其他文章的时候可能会出现高斯模糊和高斯滤波两种说法,其实这两种说法是有一定区别的。我们知道滤波器分为高通、低通、带通等类型,高斯滤波和高斯模糊就是依据滤波器是低通滤波器还是高通滤波器来区分的。比如低通滤波器,像素能量低的通过,而对于像素能量高的部分将会采取加权平均的方法重新计算像素的值,将能量像素的值编程能量较低的值,我们知道对于图像而言其高频部分展现图像细节,所以经过低通滤波器之后整幅图像变成低频造成图像模糊,这就被称为高斯模糊;相反高通滤波是允许高频通过而过滤掉低频,这样将低频像素进行锐化操作,图像变的更加清晰,被称为高斯滤波。说白了很简单就是:高斯滤波是指用高斯函数作为滤波函数的滤波操作而高斯模糊是用高斯低通滤波器。 
高斯滤波在图像处理中常用来对图像进行预处理操作,虽然耗时但是数字图像用于后期应用但是其噪声是最大的问题,噪声会造成很大的误差而误差在不同的处理操作中会累积传递,为了能够得到较好的图像,对图像进行预处理去除噪声也是针对数字图像处理的无奈之举。 
高斯滤波器是一类根据高斯函数的形状来选择权值的线性平滑滤波器,高斯滤波器对于服从正太分布的噪声非常有效,一维高斯函数如下:

二维高斯函数如下:

 

  • src: 输入图像,图像深度为cv2.CV_8U、cv2.CV_16U、cv2.CV_16S、cv2.CV_32F、cv2.CV_64F。
  • dst: 输出图像,与输入图像有相同的类型和尺寸。
  • ksize: 高斯内核大小,元组类型
  • sigmaX: 高斯核函数在X方向上的标准偏差
  • sigmaY: 高斯核函数在Y方向上的标准偏差,如果sigmaY是0,则函数会自动将sigmaY的值设置为与sigmaX相同的值,如果sigmaX和sigmaY都是0,这两个值将由ksize[0]和ksize[1]计算而来。具体可以参考getGaussianKernel()函数查看具体细节。建议将size、sigmaX和sigmaY都指定出来。
  • borderType: 推断图像外部像素的某种便捷模式,有默认值cv2.BORDER_DEFAULT,如果没有特殊需要不用更改,具体可以参考borderInterpolate()函数。

 这里注意有一点需要注意:使用卷积进行运算,并不能保证的每个像素值都在0~255之间。对于在区间外的像素点会导致灰度图无法显示,所以还需要做归一化,然后每个值乘以255,再将所有的值映射到这个区间内。归一化算法:x=(x-Min)(Max-Min),这样x的范围就在[0,1]之间了。我们在上面调用的函数ndimage.convolve()在内部已经做了这些处理,所以我们就不需要自己写归一化的处理过程了。

2 低通滤波器

高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。低通滤波器(LPF)则是在像素与周围像素的亮度差值小于一定特征值,平滑该像素的亮度。它主要用于去噪和模糊化,比如说,高斯模糊是最常用的模糊滤波器(平滑滤波器)之一,它是削弱高频信号强度的低通滤波器。实质就是去除图像中的高频成分(比如:噪声,边界)

三 边缘检测

边缘在人类视觉和计算机视觉中均起着重要的作用。人类能够仅凭一张背景剪影或一个草图就能识别出物体的类型和姿态。

Open CV提供了许多边缘检测滤波函数,包括以下:

  1. Laplacian() #作为边缘检测函数,他会产生明显的边缘线条,灰度图像更是如此。
  2. Sobel()
  3. Scharr()

这些滤波函数都会将非边缘区域转换为黑色,边缘区域转换成白色或其他饱和的颜色。但是这些函数都容易将噪声错误的识别为边缘。缓解这个问题的方法就是在找到边缘之前对图像进行模糊处理,去除噪声。

Open CV也提供了需要模糊滤波函数,包括以下:

  1. blur()
  2. medianBlur() #它对去除数字化的视频噪声特别有效,特别是去除彩色图像的噪声
  3. GaussianBlur()

边缘检测和模糊滤波的函数的参数有很多,但总会有一个ksize参数,它是一个奇数,表示滤波核的宽和高(以像素为单位)。

  1. #-*- coding: utf-8 -*-
  2. import numpy as np
  3. import cv2
  4. '''
  5. 自定义函数进行边缘检测
  6. 1 使用medianBlur()作为模糊函数,去除彩色图像的噪声
  7. 2 灰度化
  8. 3 使用Laplacian()作为边缘检测函数,产生边缘线条
  9. 4 归一化 并进行边缘和背景的黑白转换(之前是背景黑,边缘白,乘以原图像将边缘变黑)
  10. '''
  11. def strokeEdge(src, blurKsize = 7, edgeKsize = 5):
  12. '''
  13. :param src: 原图像 BGR彩色空间
  14. :param blurKsize: 模糊滤波器kernel size k<3 不进行模糊操作
  15. :param edgeKsize: 边缘检测kernel size
  16. :return: dst
  17. '''
  18. if blurKsize >= 3:
  19. ##中值模糊
  20. blurredSrc = cv2.medianBlur(src, blurKsize)
  21. #灰度化
  22. gray = cv2.cvtColor(blurredSrc, cv2.COLOR_BGR2GRAY)
  23. else:
  24. #灰度化
  25. gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
  26. cv2.imshow('gray',gray)
  27. ###边缘检测
  28. laplacian = cv2.Laplacian(gray, cv2.CV_8U, gray, ksize= edgeKsize)
  29. cv2.imshow('Laplacian',laplacian)
  30. ##归一化 转换背景
  31. normalizeInverse = (1.0/255)*(255- gray)
  32. cv2.imshow('normalizeInverse', normalizeInverse)
  33. ##分离通道
  34. channels = cv2.split(src)
  35. ##计算后的结果与每个通道相乘
  36. for channel in channels:
  37. ##这里是点乘 即对应原素相乘
  38. channel[:] = channel * normalizeInverse
  39. cv2.imshow('B',channels[0])
  40. #合并通道
  41. return cv2.merge(channels)
  42. img = cv2.imread('1.jpg',cv2.IMREAD_COLOR)
  43. dst = strokeEdge(img)
  44. cv2.imshow('dst',dst)
  45. cv2.waitKey()
  46. cv2.destroyAllWindows()

这里对一些常用的函数进行了官方文档的查阅,下面给出中文的解释

中值滤波器模糊图像

拉普拉斯算子计算二阶导数 

四 用定制内核做卷积

Open CV预定的许多滤波器(滤波函数)都会使用核。其实核是一组权重,它决定了如何通过临近像素点来计算新的像素点。核也称作卷积矩阵,它对一个区域的像素做调和或卷积运算。通常基于核的滤波器被称为卷积滤波器。

Open CV提供了一个通用的filter2D()函数,它运用由用户指定的任意核或卷积矩阵

卷积矩阵是一个二维数组,有奇数行,奇数列,中心的元素对应于感兴趣的像素,其它的元素对应于这个像素周围的邻近像素,每个像素都有一个整数或浮点数的值,这些值就是应用在像素上的权重。如下:

  1. kernel = np.array([[-1,-1,-1],
  2. [-1, 9,-1],
  3. [-1,-1,-1]])

上面感兴趣的像素权重为9,其余邻近像素上的权重为-1。对于感兴趣的像素来说,新像素值使用当前像素值乘以9,然后减去8个邻近像素值。如果感兴趣的像素和邻近像素有一点差别,那么这个差别会增加。这样会使图片锐化,因为该像素与邻近像素值之间的差距拉大了。

接下来的例子在源图像和目标图像上分别使用卷积核:

cv2.filter2D(src,ddepth,kernel[,dst[,anchor[,delta[,borderType]]]])函图卷积运算函数。

该函数使用于任意线性滤波器的图像,支持就地操作。当其中心移动到图像外,函数可以根据指定的边界模式进行插值运算。

  • src: 输入图像。
  • ddepth: 目标图像深度,如果没写将生成与原图像深度相同的图像。当ddepth输入值为-1时,目标图像和原图像深度保持一致。输入和输出对应关系:
  • kernel: 卷积核(或者是相关核),一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。
  • dst: 输出图像,和输入图像具有相同的尺寸和通道数量
  • anchor: 内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。
  • delta: 在储存目标图像前可选的添加到像素的值,默认值为0
  • borderType: 像素向外逼近的方法,默认值是cv2.BORDER_DEFAULT,即对全部边界进行计算。

接着我们向filter.py文件中添加几个类,第一个类为VconvolutionFilter,它表示一般的卷积滤波器;第二个子类是SharpenFilter,它表示特定的锐化滤波器。第三个子类是FindEdgesFilter,是一个特定的边缘检测器。第四个子类是BlurFilter,是一个模糊滤波器。最后一个EmbossFilter子类。

  1. #-*- coding: utf-8 -*-
  2. import cv2
  3. import numpy as np
  4. '''
  5. 定义卷积滤波器的object类,然后分别定义锐化 边缘 模糊 滤波器的子类
  6. '''
  7. class VConvolutionFilter(object):
  8. def __init__(self,kernel):
  9. self._kernel = kernel
  10. def apply(self,src, dst):
  11. cv2.filter2D(src, -1, self._kernel, dst)
  12. ###锐化滤波器 权重和为1 如果不想改变图像亮度就该这样,
  13. # 如果使他的权重加起来为0,就会得到一个边缘检测核,把边缘转为白色,把非边缘变为黑色
  14. class SharpenFilter(VConvolutionFilter):
  15. def __init__(self):
  16. kernel = np.array([[-1, -1, -1],
  17. [-1, 9, -1],
  18. [-1, -1, -1]])
  19. VConvolutionFilter.__init__(self, kernel)
  20. #边缘检测器 权重和为0
  21. class FindEdgeFilter(VConvolutionFilter):
  22. def __init__(self):
  23. kernel = np.array([[-1, -1, -1],
  24. [-1, 8, -1],
  25. [-1, -1, -1]])
  26. VConvolutionFilter.__init__(self, kernel)
  27. ##模糊滤波器 权重和为1 但是权值都为正值
  28. class BLurFilter(VConvolutionFilter):
  29. def __init__(self):
  30. kernel = np.array([[0.04, 0.04, 0.04, 0.04, 0.04],
  31. [0.04, 0.04, 0.04, 0.04, 0.04],
  32. [0.04, 0.04, 0.04, 0.04, 0.04],
  33. [0.04, 0.04, 0.04, 0.04, 0.04],
  34. [0.04, 0.04, 0.04, 0.04, 0.04],])
  35. VConvolutionFilter.__init__(self, kernel)
  36. class EmbossFilter(VConvolutionFilter):
  37. def __init__(self):
  38. kernel = np.array([[-1, -1, 0],
  39. [-1, 1, 1],
  40. [0, 1, 2]])
  41. VConvolutionFilter.__init__(self, kernel)
  42. if __name__ == "__main__":
  43. img = cv2.imread('lena.jpg',0)
  44. cv2.imshow('orign',img)
  45. demo = SharpenFilter()
  46. demo.apply(img,img)
  47. cv2.imshow('img',img)
  48. cv2.waitKey()
  49. cv2.destroyAllWindows()

可以对滤波器进行选择从而实现不同效果,这里选择的是锐化滤波器.

五 Canny边缘检测

Open CV还提供了一个非常方便的Canny()函数,该算法非常流行,不仅是因为它的效果,还因为在Open CV程序中实现时非常简单:

  1. #-*- coding:utf-8 -*-
  2. import cv2
  3. import numpy as np
  4. '''
  5. Canny边缘检测
  6. 1 使用高斯滤波器进行去除图像噪声
  7. 2 计算图像梯度
  8. 3 非极大值抑制
  9. 4 滞后阈值即在检测边缘上使用双阈值去除假阳性
  10. 5 分析所有边缘与其之间的连接,以保留真正的边缘消除不明显的边缘
  11. '''
  12. img = cv2.imread('lena.jpg',0)
  13. cv2.imshow('origin',img)
  14. canny = cv2.Canny(img, 100, 200)
  15. cv2.imshow('canny',canny)
  16. cv2.waitKey()
  17. cv2.destroyAllWindows()

 

Candy边缘检测算法算法非常复杂,但是也很有趣,它有5个步骤,即使用高斯滤波器对图像进行去噪,计算梯度,在边缘上使用非最大抑制,在检测到的边缘上使用阈值去除假阳性,最后还会分析所有的边缘及其之间的连接,以保留真正的边缘并消除不明显的边缘。

cv2.Candy函数部分参数如下:

  • 第一个参数是需要处理的原图像,该图像必须是单通道的灰度图;
  • 第二个参数是阈值1。
  • 第三个参数是阈值2.

其中较大的阈值2用于检测图像中明显的边缘,但是一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候较小的第一个阈值用于将这些断续的边缘连接起来。

 

 

 

参考博客: https://www.cnblogs.com/zyly/p/8893579.html

个人github主页:https://github.com/RenDong3/OpenCV_Notes

opencv官方文档:https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html?highlight=cv2.laplacian#cv2.Laplacian

持续打卡中.......

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/86709
推荐阅读
相关标签
  

闽ICP备14008679号