OpenCV: Smoothing Images
一、模糊操作的原理类似数学上的一维卷积操作,深度学习中的多维卷积运算。
二、均值滤波——blur()均值滤波用来模糊和平滑图像,但是图像失真比较大。
官方API文档:OpenCV: Image Filtering-blur()
语法格式如下:
cv.blur(src, ksize[, dst[, anchor[, borderType]]]) ->dst
- src是图像数据,可以是任何通道的图像,图像的深度必须是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F中的一种
- ksize是卷积核的尺寸,不一定得是宽高相同,比如ksize=(1, 5),也不一定是奇数比如ksize=(4, 4)
- anchor是卷积核的锚点,默认值是(-1, -1),表示锚点是卷积核的中心
- borderType是边界处理方式,详见borderType官方文档。
该API的卷积核的任何元素都是1,而卷积核权值就是元素个数的倒数,也即这是一个归一化的卷积核。
因此,这个API名正言顺地不愧为“均值滤波”的实现API之一。
二、方框滤波——boxFilter()官方API文档:OpenCV: Image Filtering-boxFilter()
语法格式如下:
cv.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) ->dst
- src是原图像数据,虽然官方文档没做限制,但本人估计和blur()的条件一样。
- ddepth是输出图像的位深度(不懂先转向,位深决定色深),如果不设置就是取默认值-1,代表输出图像dst的位深度和原图像src相同。其次还可以用其他位深度,详见官方文档Depth combinations。如下图所示如果原图是CV_8U,那么输出图像可是CV_16S。但是也要注意如果前后位深度差别太大,可能会出现类似【iCCP: known incorrect sRGB profile】的错误,或者输出图像显示上的问题,这个需要看ICCP是否支持。如果用OpenCV的位深度宏参数,需要在前面加模块名,比如ddepth=cv.CV_16S。
- ksize是卷积核的尺寸,不一定得是宽高相同,比如ksize=(1, 5),也不一定是奇数比如ksize=(4, 4)。
- anchor是卷积核的锚点,默认值是(-1, -1),表示锚点是卷积核的中心。
- normalize是归一化方式;默认是归一化,此时方框滤波的卷积核外围权值就为核元素求和的倒数,这时方框滤波等同于均值滤波blur();如果不想要归一化,需要设置normalize=False。
- borderType是边界处理方式,如果不设置,默认是cv.BORDER_DEFAULT,详见borderType官方文档。
该API的卷积核的任何元素都是1,而卷积核权值默认是求和的倒数;只有normalize设置为False时才是一个非归一化的卷积核。
三、中值滤波——medianBlur()中值滤波一般用来去除椒盐噪声(salt-and-pepper noise),去噪效果显著。
官方API文档:OpenCV: Image Filtering-medianBlur()
语法格式如下。
cv.medianBlur(src, ksize[, dst]) ->dst
- src是原图像变量,可以是单通道、三通道乃至四通道;位深度上,如果ksize是3或5,原图像位深度就只能是CV_8U, CV_16U, or CV_32F,如果ksize更大,位深度就只能是CV_8U。
- ksize是滤波孔隙尺寸,必须是大于1的奇数,比如3579;中值滤波不需要写成长度为2的序列形式,也即ksize=3才是对的。
中值滤波最有意思的一点,就是不涉及到卷积运算,仅仅是比较运算得到孔隙内中值;所以没有卷积核的概念,ksize是滤波孔隙尺寸。
四、高斯滤波——GaussianBlur()高斯滤波相比均值滤波,不仅去噪效果好,而且图像失真较少。
官方API文档:OpenCV: Image Filtering-GaussianBlur()
高斯滤波的API-GaussianBlur()的语法如下。
cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) ->dst
- src是图像变量;通道数不限制,位深度必须是CV_8U, CV_16U, CV_16S, CV_32F or CV_64F其中之一。
- ksize是卷积核大小;卷积核的宽高可以不一样,但是都必须是大于0的奇数,或者都等于0;如果都等于0 ,卷积核的大小由sigma计算。
- sigmaX代表X方向的高斯核标准差,本人猜测应该是宽度方向的分布标准差
- sigmaY代表Y方向的高斯核标准差,本人猜测应该是高度方向的分布标准差
- borderType是边界处理方法,默认是cv.BORDER_DEFAULT,除了cv.BORDER_WRAP不支持,其他的方法详见官档。
高斯滤波的卷积核不像均值滤波和方框滤波那样元素全部相同,顾名思义——卷积核各元素共同组成高斯分布;如下图所示,就属于一个3*3的高斯核。
五、双边滤波——bilateralFilter()双边滤波器可以有效去除噪声,也叫边缘保留滤波(ERF),同时较好地保留边缘锐度。但是,其速度比绝大多数滤波器都慢。
API中文参考文档:opencv之bilateralFilter()函数_bilateralfilter函数
语法格式如下。
cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) ->dst
- src是原图像数据,可以是单通道或三通道,可以是八位整型或浮点型。
- d是像素领域直径,过大的滤波器会导致效率低(但是性能好);对于实时应用,建议取d=5;对于需要过滤严重噪声的离线应用,可取d=9;当d小于等于0时,自动由sigmaSpace的值确定(d与sigmaSpace成正比)。
sigma参数
- 简单起见,可以将下面两个sigma参数设置成同一值;如果他们很小(特别是小于10),就会导致双边滤波效果大打折扣,而如果他们很大(特别是大于150),就会导致滤波效果很好,但是也会使图像看起来“卡通化”。
- sigmaColor是颜色空间的sigma参数,表示允许的色差范围;换句话说,越大就会导致在像素领域内的色差越大的像素的颜色被混合,从而导致更大区域的半等色。
- sigmaSpace是坐标空间的sigma参数,表示允许的距离范围;越大就会导致在sigmaColor色差范围内的像素即使很远也会被包括到。
- 一个d参数和两个sigma参数结合起来,意思就是说在像素领域内、并且也在色差范围和距离范围之类的点就会被滤波处理。
- borderType是边界处理方法,默认是cv.BORDER_DEFAULT,其他的方法详见官档。
双边滤波不支持原地处理图像,也即不支持传入src和dst同样的变量。
六、图像导数/梯度求解——sobel()和scharr() 6.1 Sobel()算子参见Sobel算子的API文档。
sobel算子结合了高斯平滑和微分计算,主要用于边缘检测,其次还有去除噪点、平滑图像。但是它检测边缘的位置精度不是很高。
语法格式如下。
cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) ->dst
- src是原图像。
- ddepth是输出图像位深度,同上述讲解。
- dx、dy分别是x方向、y方向的微分次数;当dx=1、dy=0时表示求x方向的一阶导数,当dx=0、dy=1时表示求y方向的一阶导数(如果同时为1,通常得不到想要的结果)。
- ksize表示sobel算子的大小,必须是1、3、5或者7,默认为3;如果为-1(或者用宏参数,ksize=cv2.FILTER_SCHARR),表示采用更加精确的scharr算子。
- scale表示将梯度计算得到的数值放大的比例系数,效果通常使梯度图更亮,默认为1。
- delta表示将目标图像存储进多维数组前,将每个像素值增加delta,默认为0。
- borderType表示边界处理方式,同上述讲解。
Sobel算子的3*3卷积核,见本文9.2小节。
为了避免负梯度时小于0被截断为0(来自利用cv2.Sobel()计算图像梯度的细节讲解),最好将ddepth设置为CV_32F或CV_64F;sobel梯度计算完后,用一个convertScaleAbs将负像素点转成正数并且深度转成CV_8U,最后才用imshow()显示;
或者,直接在读取图像后,对图像对应的多维数组直接除以255,除完后数组的数据类型自动变为CV_64F,范围在0-1之间,也即归一化;在进行Sobel梯度运算(ddepth参数设置为-1即可)后再来一个取绝对值(不是用convertScaleAbs,而是用Python内置abs、Numpy中的abs或absolute),最后用imshow()显示图像,就可以规避问题;
前后两种方法类似点在于都需在计算梯度前将图像位深度转成浮点型,避免负数梯度被截断。
6.2 Scharr()算子Scharr算子可以称得上是对Sobel算子的优化;Scharr算子在处理边缘时比Sobel精度高一些。
两种算子的区别就是他们的卷积核不同;它们的计算耗时和算法复杂度相同。
官方API文档:OpenCV: Image Filtering-Scharr()
语法格式如下
cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) ->dst
- src是原图像,同Sobel。
- ddepth是图像位深度,同Sobel。
- dx和dy分别是宽度方向梯度导数阶数、高度方向梯度导数阶数,同Sobel。
- scale是比例系数,同Sobel。
- delta是偏差系数,同Sobel。
- borderType是边界处理类型,同Sobel。
可以看出!
七、自定义图像分离滤波——filter2D()cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])
和
cv.Sobel(src, ddepth, dx, dy[, dst[, ksize=cv2.FILTER_SCHARR[, scale[, delta[, borderType]]]]]) ->dst
完全相同!
官方API文档:OpenCV: Image Filtering-filter2D()
语法如下:
cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) ->dst
- src是待处理图像,不限制通道数和位深度;支持原地操作图像,也即支持传入src和dst同样的变量。
- ddepth是输出图像的位深度,默认是-1,和原图相同。其他信息详见本文第二部分方框滤波API的参数ddepth的讲解。
- kernel是卷积核或者相关区域孔隙;只能是单通道浮点矩阵,因此如果你想要给多通道图像的不同通道以不同的核进行运算,你可以先用split()将通道分离,然后分别传入不同核的filter2D()进行运算。如果Kernel很小,函数使用的是直接简单的算法,而如果kernel是11*11或更大,就是用复杂的DFT算法。kernel是需要程序员手动传入的,可以是逐个元素一一开列的ndarray,也可是用np.ones()定义出来的全为1的矩阵,也可是用一些生成卷积核的API,比如getStructuringElement、getGaussianKernel。
- anchor用来表示卷积核锚点相对于卷积核的坐标,默认是(-1, -1),表示锚点位于卷积核的中心。
- delta是图像偏移值,默认为0,也即不偏移;含义,卷积运算或相关运算后再给每个像素点加上一个delta偏移值;也即dst=src × kernel + delta。
- borderType是边界处理方法,默认是cv.BORDER_DEFAULT,其他的方法详见官档。
第三方参考资料:OpenCV每日函数 图像过滤模块 (20) sepFilter2D函数
可分离滤波的API官方文档:OpenCV: Image Filtering-sepFilter2D()
语法格式如下:
cv.sepFilter2D(src, ddepth, kernelX, kernelY[, dst[, anchor[, delta[, borderType]]]]) ->dst
- src是原图像。
- ddepth是输出图像dst的位深度;具体规则详见本文第二部分方框滤波API的参数ddepth的讲解。
- kernelX是横向单维卷积核,需要人为传入具体到每个元素取值的卷积核;比如kernelX=np.array[-1, 3, -1]。
- kernelY是纵向单维卷积核,需要人为传入具体到每个元素取值的卷积核;;比如kernelY=np.array[-1, 3, -1]。
- dst是输出图像,宽高和通道数与原图相同。
- anchor是卷积核锚点,与上同。
- delta是偏差值,卷积后全部元素与此值相加。
- borderType是边界处理方式,与上同;cv.BORDER_WRAP不支持。
可分离滤波器算法的运算机制:先用kernelX进行卷积运算,然后再将第一次卷积运算的结果用kernelY进行卷积,最后每个元素加上delta偏差值。
可分离滤波器算法的优点:假设图像面积为A,按照filter2D()来卷积,卷积核是K*K大小,那么最后运算量与A*K*K成正比;而如果同一图像按照sepFilter2D()来卷积,横、纵卷积核长度都是K,最后运算量与2*A*K成正比;当K大于等于3时(普遍情况正如此),明显前者运算量更大;因此可分离滤波器的运算速度很快!
九、不同应用场景下的卷积核(Convolutional Kernel) 9.1 模糊/平滑(1)均值滤波的卷积核
(2)高斯模糊的卷积核
9.2 梯度(1)垂直梯度-bottom sobel
(2)垂直梯度-top sobel
(3)水平梯度-left sobel
(4)水平梯度-right sobel
9.3 浮雕(Emboss)浮雕本质就是在给定方向上的梯度,比如下面这个kernel是135°上的梯度。
9.4 轮廓(Outline)如果想要检测轮廓,可以用下面的核;中间是8,邻近的所有像素点都是-1;表示如果中间像素与周围像素点的值相近,最后得到的取值很小,偏向于黑色。
9.5 锐化(Sharpen)锐化是指加大相邻像素点之间的色差;下图即是一个锐化卷积核,假设中央是x,而-1的位置对应y,那么最后得到的取值是5x-4y;与相邻像素点的差值由原来的x-y变成了5(x-y),显然差的绝对值更大了。
9.6 拉普拉斯算子(Laplacian Operator)一般用来检测边缘,当然也可用来检测图像中的模糊。
9.7 分身(Identity)当卷积核的锚点元素为1,而其他元素都为0时,代表卷积得到的结果图与原图别无二致。
十、生成卷积核(Convolutional Kernel)或相关区域孔隙(Correlation Aperture)的方法 10.1 结构元素——getStructuringElement()不仅可以用来生成卷积核,也可用来生成供腐蚀(erode或morphologyEx + cv.MORPH_ERODE)、膨胀(dilate或morphologyEx + cv.MORPH_DILATE)、开运算(morphologyEx + cv.MORPH_OPEN)、闭运算(morphologyEx + cv.MORPH_CLOSE)、形态学梯度(morphologyEx + cv.MORPH_GRADIENT)、顶帽运算(morphologyEx + cv.MORPH_TOPHAT)、黑帽运算(morphologyEx + cv.MORPH_BLACKHAT)所需的结构元素。
cv.getStructuringElement(shape, ksize[, anchor]) ->retval
- shape是结构元素的形状,一共可以有矩形(宽高可不同)、椭圆、交叉十字三种;分别对应的OpenCV宏参数是cv.MORPH_RECT、cv.MORPH_ELLIPSE、cv.MORPH_CROSS。
- ksize是结构元素的最小外切矩形(也称结构元素的矩形背景)宽高;如果结构元素本身就是矩形,也就是结构元素的宽高;如果结构元素是椭圆或交叉十字,那么是椭圆或交叉十字形状的最小外切矩形的宽高。
- anchor是结构元素的中心点,默认值是(-1, -1),处于正中央;传入anchor的坐标显然应该是相对于结构元素的相对坐标;如果形状是矩形,不管传入什么anchor最后结果都是全1矩形;如果形状是椭圆,anchor指椭圆中心点相对于结构元素矩形背景的相对坐标。
- retval就是结构元素了。
下面给代码例程,辅助大家对该API的理解;我们可以看到所有的点取值都是1,只不过形状构成不同罢了!
import cv2 as cv
def filter_custom():
rect_ker = cv.getStructuringElement(cv.MORPH_RECT, (7, 7))
elli_ker = cv.getStructuringElement(cv.MORPH_ELLIPSE, (7, 7))
cross_ker = cv.getStructuringElement(cv.MORPH_CROSS, (7, 7))
print(rect_ker.dtype)
print(elli_ker.dtype)
print(cross_ker.dtype)
print(rect_ker)
print(elli_ker)
print(cross_ker)
filter_custom()
cv.waitKey(0)
cv.destroyAllWindows()
输出如下。
uint8 uint8 uint8 [[1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1]] [[0 0 0 1 0 0 0] [0 1 1 1 1 1 0] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [1 1 1 1 1 1 1] [0 1 1 1 1 1 0] [0 0 0 1 0 0 0]] [[0 0 0 1 0 0 0] [0 0 0 1 0 0 0] [0 0 0 1 0 0 0] [1 1 1 1 1 1 1] [0 0 0 1 0 0 0] [0 0 0 1 0 0 0] [0 0 0 1 0 0 0]]10.2 高斯卷积核——getGaussianKernel()
语法格式如下。
cv.getGaussianKernel(ksize, sigma[, ktype]) ->retval
- ksize是高斯卷积核的长度,需要是大于0的奇数。
- sigma是高斯函数的标准差σ参数,越大数值越分散。如果sigma取值小于等于0,就由ksize的大小推算,公式为sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8。
- ktype是高斯卷积核的数据类型,可以是CV_32F或CV_64F。
这个生成高斯核的方式,和GaussianBlur()生成的卷积核不同,这种方法得到的卷积核只能是ksize * 1的形状。
但是,这两种方式得到的卷积核的权重值求和都是1,因为如果不为1,往往是错误的。
10.3 导数卷积核——getDerivKernels()该函数可以用来生成用于计算图像导数的滤波器系数,
语法格式如下。
cv.getDerivKernels(dx, dy, ksize[, kx[, ky[, normalize[, ktype]]]]) ->kx, ky
- dx和dy表示x、y轴方向上的导数阶数;同Sobel
- ksize表示生成的两个一维卷积核的长度,因此ksize只用传一个整数即可;ksize可以是-1(FILTER_SCHARR)、1、3、5、7。如果ksize设置成-1或FILTER_SCHARR,那么kx和ky从scharr算子上的第一列、第一行拆分;反之,从sobel算子上的第一列、第一行拆分。
- normalize表示是否归一化标志,默认取值是False,也即不归一化。如果设置为True,那么所有权值都要除以2^(ksize∗2−dx−dy−2)。一般来说,如果用来滤波的图像的位深度是浮点型,那么就需要归一化;如果用来滤波的图像是8位无符号整型,就需要将结果存储在 16 位图像中,以保留所有小数位,不用归一化。
- ktype是卷积核的数据类型,可以是CV_32F(默认)或CV_64F。
- kx和ky分别表示x、y轴方向上的从sobel算子、scharr算子上第一列、第一行拆分的一维卷积核。返回的结果,可以传入sepFilter2D();或者传递给createSeparableLinearFilter()创建线性可分的过滤器。
比如说因为3*3的sobel算子的如下
取出第一列作为dx,那么dx=[[-1], [0], [1]];取出第一行作为dy,也即dy=[[-1], [2], [-1]]。
10.4 特征卷积核——getGaborKernel这个卷积核,一般用来提取特征;特别是人脸特征识别,经常用到该函数。
该函数由两部分组成——高斯函数(对应标准差参数sigma和均值参数theta)和正余弦函数,
参考自Gabor滤波简介与Opencv中的实现及参数变化实验
语法格式如下。
cv.getGaborKernel(ksize, sigma, theta, lambd, gamma[, psi[, ktype]]) ->retval
- ksize是卷积核的大小。
- sigma是高斯核函数的标准查,希腊字母是σ。
- theta是高斯核函数的均值,希腊字母是θ。
- lambd是正余弦函数波长,希腊字母是λ。
- gamma是长宽比,决定这Gabor核函数图像的椭圆率,希腊字母是γ。
- psi是正余弦函数的相位偏移量,取值范围是-180~180,希腊字母是ψ。
- ktype是卷积核的数据类型,可以是CV_32F(默认)或CV_64F。
- retval是输出的卷积核。



