目录
1. 插值方法
在图像处理和计算机图形学中,插值(Interpolation)是一种通过已知数据点之间的推断或估计来获取新数据点的方法。它在图像处理中常用于处理图像的放大、缩小、旋转、变形等操作,以及处理图像中的像素值。
图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。
1.1 最近邻插值
CV2.INTER_NEAREST new_img1=cv.warpAffine(img,M,(w,h),flags=cv.INTER_NEAREST)
首先给出目标点与原图像点之间坐标的计算公式:
dstX:目标图像中某点的x坐标,
dstY:目标图像中某点的y坐标,
srcWidth:原图的宽度,
dstWidth:目标图像的宽度;
srcHeight:原图的高度,
dstHeight:目标图像的高度。
而srcX和srcY:目标图像中的某点对应的原图中的点的x和y的坐标。
通俗的讲,该公式就是让目标图像中的每个像素值都能找到对应的原图中的像素值,这样才能根据不同的插值方法来获取新的像素值。根据该公式,我们就可以得到每一个目标点所对应的原图像的点,比如一个2*2的图像放大到4*4,如下图所示,其中红色的为每个像素点的坐标,黑色的则表示该像素点的像素值。
那么根据公式我们就可以计算出放大后的图像(0,0)点对应的原图像中的坐标为:
也就是原图中的(0,0)点,而最近邻插值的原则是:目标像素点的像素值与经过该公式计算出来的对应的像素点的像素值相同,如出现小数部分需要进行取整。那么放大后图像的(0,0)坐标处的像素值就是原图像中(0,0)坐标处的像素值,也就是10。接下来就是计算放大后图像(1,0)点对应的原图像的坐标,还是带入公式:
也就是原图中的(0.5,0)点,因此需要对计算出来的坐标值进行取整,取整后的结果为(0,0),也就是说放大后的图像中的(1,0)坐标处对应的像素值就是原图中(0,0)坐标处的像素值,其他像素点计算规则与此相同。
思考:
一张图的第一行像素点的值分别为: 10 10 20 30 40 放大一倍后 新图像的第一行的第3个像素点的值是多少?
点坐标:(2,0)
(1,0)==>10
4x2的图像放大到8x4,放大一
1.2 双线性插值
-
CV2.INTER_LINEAR
双线性插值是一种图像缩放、旋转或平移时进行像素值估计的插值方法。当需要对图像进行变换时,特别是尺寸变化时,原始图像的某些像素坐标可能不再是新图像中的整数位置,这时就需要使用插值算法来确定这些非整数坐标的像素值。
双线性插值的工作原理是这样的:
假设要查找目标图像上坐标为
(x', y')
的像素值,在原图像上对应的浮点坐标为(x, y)
。在原图像上找到四个最接近
(x, y)
的像素点,通常记作P00(x0, y0)
,P01(x0, y1)
,P10(x1, y0)
,P11(x1, y1)
,它们构成一个2x2的邻域矩阵。分别在水平方向和垂直方向上做线性插值:
水平方向:根据
x
与x0
和x1
的关系计算出P00
和P10
、P01
和P11
之间的插值结果。垂直方向:将第一步的结果与
y
与y0
和y1
的关系结合,再在垂直方向上做一次线性插值。综合上述两次线性插值的结果,得到最终位于
(x', y')
处的新像素的估计值。
总结: 4乘4的图像 变成6乘6的图像 那么目标图像的(3,3)点的像素是原图中(1.8333,1.8333)的像素颜色,但是坐标必须是整数 它周围有四个像素点 该取谁呢? 按照到各自的距离比例 来分配颜色值
首先要了解线性插值,而双线性插值本质上就是在两个方向上做线性插值。还是给出目标点与原图像中点的计算公式
比如我们根据上述公式计算出了新图像中的某点所对应的原图像的点P,其周围的点分别为Q12、Q22、Q11、Q21, 要插值的P点不在其周围点的连线上,这时候就需要用到双线性插值了。首先延申P点得到P和Q11、Q21的交点R1与P和Q12、Q22的交点R2,如下图所示:
然后根据Q11、Q21得到R1的插值,根据Q12、Q22得到R2的插值,然后根据R1、R2得到P的插值即可,这就是双线性插值。以下是计算过程:
首先计算R1和R2的插值:
然后根据R1和R2计算P的插值:
这样就得到了P点的插值。注意此处如果先在y方向插值、再在x方向插值,其结果与按照上述顺序双线性插值的结果是一样的。
双线性插值的对应关系看似比较清晰,但还是有2个问题。首先是根据坐标系的不同,产生的结果不同,这张图是左上角为坐标系原点的情况,我们可以发现最左边x=0的点都会有概率直接复制到目标图像中(至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值(带入到权重公式中会发现结果)。
下面这张图是右上角为坐标系原点的情况,我们可以发现最右面的点都会有概率直接复制到目标图像中(至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值。那么当我们采用不同的坐标系时产生的结果是不一样的,而且无论我们采用什么坐标系,最左侧和最右侧(最上侧和最下侧)的点是“不公平的”,这是第一个问题。
第二个问题时整体的图像相对位置会发生变化。如下图所示,左侧是原图像(3,3),右侧是目标图像(5,5),原图像的几何中心点是(1,1),目标图像的几何中心点是(2,2),根据对应关系,目标图像的几何中心点对应的原图像的位置是(1.2,1.2),那么问题来了,目标图像的原点(0,0)和原始图像的原点是重合的,但是目标图像的几何中心点相对于原始图像的几何中心点偏右下,那么整体图像的位置会发生偏移,所以参与计算的点相对都往右下偏移会产生相对的位置信息损失。这是第二个问题。
因此,在OpenCV中,为了解决这两个问题,将公式进行了优化,如下所示:
使用该公式计算出原图中的对应坐标后再进行插值计算,就不会出现上面的情况了。
1.3 像素区域插值
-
cv2.INTER_AREA
像素区域插值主要分两种情况,缩小图像和放大图像的工作原理并不相同。
当使用像素区域插值方法进行缩小图像时,它就会变成一个均值滤波器(滤波器其实就是一个核,这里只做简单了解,后面实验中会介绍),其工作原理可以理解为对一个区域内的像素值取平均值。
当使用像素区域插值方法进行放大图像时
如果图像放大的比例是整数倍,那么其工作原理与最近邻插值类似;
如果放大的比例不是整数倍,那么就会调用双线性插值进行放大。
其中目标像素点与原图像的像素点的对应公式如下所示:
1.4 双三次插值
-
cv2.INTER_CUBIC
与双线性插值法相同,该方法也是通过映射,在映射点的邻域内通过加权来得到放大图像中的像素值。不同的是,双三次插值法需要原图像中近邻的16个点来加权,也就是4x4的网格。
目标像素点与原图像的像素点的对应公式如下所示:
下面我们举例说明,假设原图像A大小为m*n,缩放后的目标图像B的大小为M*N。其中A的每一个像素点是已知的,B是未知的,我们想要求出目标图像B中每一个像素点(X,Y)的值,必须先找出像素(X,Y)在原图像A中对应的像素(x,y),再根据原图像A距离像素(x,y)最近的16个像素点作为计算目标图像B(X,Y)处像素值的参数,利用BiCubic基函数求出16个像素点的权重,图B像素(x,y)的值就等于16个像素点的加权叠加。
BiCubic基函数也就是双三次插值的权重函数,它决定了如何根据距离对周围像素进行加权平均。
假如下图中的P点就是目标图像B在(X,Y)处根据上述公式计算出的对应于原图像A中的位置,P的坐标位置会出现小数部分,所以我们假设P点的坐标为(x+u,y+v),其中x、y表示整数部分,u、v表示小数部分,那么我们就可以得到其周围的最近的16个像素的位置,我们用a(i,j)(i,j=0,1,2,3)来表示,如下图所示。
然后给出BiCubic函数:
a
一般取-0.5或-0.75,用于控制插值函数的形状。
d
代表的是目标像素点与某个像素点之间的相对距离,d_h、d_w
我们要做的就是将上面的16个点相较于p点的位置距离算出来,获取16像素所对应的权重W(d)。然而BiCubic函数是一维的,所以我们需要将像素点的行与列分开计算,比如a00这个点,我们需要将d_x带入BiCubic函数中,计算a00点对于P点的x方向的权重,然后将d_y带入BiCubic函数中,计算a00点对于P点的y方向的权重,其他像素点也是这样的计算过程,最终我们就可以得到P所对应的目标图像B在(X,Y)处的像素值为:
依此办法我们就可以得到目标图像中所有的像素点的像素值。
刚刚我们说拿到了目标点的坐标为 (x+u,y+v) ,其中 x、y 表示整数部分,u、v表示小数部分。那么我们取坐标的整数部分作为参考点,也就是(x,y),小数部分表示目标像素相对于参考点的偏移量。
比如说目标点的坐标为(3.7,2.4),那么我们的参考点就是(3,2)。
1.5 Lanczos插值
cv2.INTER_LANCZOS4
Lanczos插值方法与双三次插值的思想是一样的,不同的就是其需要的原图像周围的像素点的范围变成了8*8,并且不再使用BiCubic函数来计算权重,而是换了一个公式计算权重。
首先还是目标像素点与原图像的像素点的对应公式如下所示:
下面我们举例说明,假设原图像A大小为m*n,缩放后的目标图像B的大小为M*N。其中A的每一个像素点是已知的,B是未知的,我们想要求出目标图像B中每一个像素点(X,Y)的值,必须先找出像素(X,Y)在原图像A中对应的像素(x,y),再根据原图像A距离像素(x,y)最近的64个像素点作为计算目标图像B(X,Y)处像素值的参数,利用权重函数求出64个像素点的权重,图B像素(x,y)的值就等于64个像素点的加权叠加。
假如下图中的P点就是目标图像B在(X,Y)处根据上述公式计算出的对应于原图像A中的位置,P的坐标位置会出现小数部分,所以我们假设P点的坐标为(x+u,y+v),其中x、y表示整数部分,u、v表示小数部分,那么我们就可以得到其周围的最近的64个像素的位置,我们用a(i,j)(i,j=0,1,2,3,4,5,6,7)来表示,如下图所示。
1.6 小结
最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差。双线性插值的计算速度慢一点,但效果有了大幅度的提高,适用于大多数场景。双三次插值、Lanczos插值的计算速度都很慢,但是效果都很好。
在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分需求。
综合代码:
import cv2 as cv
#读图
cat=cv.imread("../images/images/cat1.png")
w=cat.shape[1]//2
h=cat.shape[0]//2
# 旋转
# 获取旋转矩阵
M=cv.getRotationMatrix2D((w,h),45,0.5)
# 仿射变换
dst=cv.warpAffine(cat,M,(w,h))
cv.imshow("dst",dst)
# 最近邻插值 + 边界复制
dst1=cv.warpAffine(cat,M,(w,h),cv.INTER_NEAREST)
cv.imshow("dst1",dst1)
#双线性插值 单线性插值插两次:水平、 垂直
dst2=cv.warpAffine(cat,M,(w,h),cv.INTER_LINEAR)
cv.imshow( "dst2",dst2)
# 像素区域插值 缩小:均值滤波 放大:整数 最近邻 不是整数 双线性 4点 2x2
dst3=cv.warpAffine(cat,M,(w,h),cv.INTER_AREA)
cv.imshow( "dst3",dst3)
# 双三次插值 16个 4x4
dst4=cv.warpAffine(cat,M,(w,h),cv.INTER_CUBIC)
cv.imshow( "dst4",dst4)
# Lanczos插值 64个 8x8
dst5=cv.warpAffine(cat,M,(w,h),cv.INTER_LANCZOS4)
cv.imshow( "dst5",dst5)
cv.waitKey(0)
cv.destroyAllWindows()
2. 图像掩膜
2.1 制作掩膜
掩膜(Mask)是一种在图像处理中常见的操作,它用于选择性地遮挡图像的某些部分,以实现特定任务的目标。掩膜通常是一个二值化图像,并且与原图像的大小相同,其中目标区域被设置为1(或白色),而其他区域被设置为0(或黑色),并且目标区域可以根据HSV的颜色范围进行修改,如下图就是制作红色掩膜的过程:
通过这个掩膜,我们就可以对掩膜中的白色区域所对应的原图中的区域进行处理与操作。
mask=cv.inRange(img,color_low,color_high)
cv2.inRange用于进行多通道图像(尤其是彩色图像)的阈值操作。
2.2 与运算
我们在高中时学过逻辑运算中的“与”运算,其规则是当两个命题都是真时,其结果才为真。而在图像处理中,“与”运算被用来对图像的像素值进行操作。具体来说,就是将两个图像中所有的对应像素值一一进行“与”运算,从而得到新的图像。从上面的图片我们可以看出,掩膜中有很多地方是黑色的,其像素值为0,那么在与原图像进行“与”运算的时候,得到的新图像的对应位置也是黑色的,如下图所示:
通过掩膜与原图的与运算,我们就可以提取出图像中被掩膜覆盖的区域(扣图)。
cv2.bitwise_and(src1,src2[,mask])
src1
:第一个输入数组。通常是输入的原始图像。
src2
:第二个输入数组。它可以是另一个图像、一个常数值或者与src1
相同的图像。
当应用掩膜时,这个参数经常就是
src1
本身;即对同一个图像进行操作。如果对两个不同的图像执行按位与操作(例如,将两张图片的某些部分组合在一起),可以分别将它们作为
src1
和src2
输入到cv2.bitwise_and()
函数中,创建复杂的图像效果或进行图像合成。
mask
:掩膜(可选)。输入数组元素只有在该掩膜非零时才被处理。是一个8位单通道的数组,尺寸必须与src1
和src2
相同。返回值:输出数组,应用掩膜后的图像,与输入数组大小和类型相同。
2.3 颜色替换
前一个实验中,我们已经能够识别到图像中的某一种颜色,那么我们就可以对识别到的颜色进行一个操作,比如将其替换成别的颜色,其原理就是在得到原图的掩膜之后,对掩膜中的白色区域所对应的原图中的区域进行一个像素值的修改即可。
2.3.1 制作掩膜
掩膜(Mask)是一种在图像处理中常见的操作,它用于选择性地遮挡图像的某些部分,以实现特定任务的目标。掩膜通常是一个二值化图像,并且与原图像的大小相同,其中目标区域被设置为1(或白色),而其他区域被设置为0(或黑色),并且目标区域则可以根据HSV的颜色范围进行修改,如下图所示,可以选择制作不同颜色的掩膜:
通过这个掩膜,我们就可以对掩膜中的白色区域所对应的原图中的区域(也就是原图中的红色区域)进行像素值的修改,从而完成颜色替换的功能。
2.3.2 颜色替换
由于掩膜与原图的大小相同,并且像素位置一一对应,那么我们就可以得到掩膜中白色(也就是像素值为255)区域的坐标,并将其带入到原图像中,即可得到原图中的红色区域的坐标,然后就可以修改像素值了,这样就完成了颜色的替换,如下图所示:
mask_image_np == 255
: 这一部分实际上是在生成一个布尔数组,其形状与mask_image_np
相同。
image_np[...] = (0, 255, 0)
: 这里使用了NumPy的高级索引功能。
2.4 图像掩膜代码
import cv2 as cv
import numpy as np
#读图
demo=cv.imread("../images/images/demo.png")
demo=cv.resize(demo,(640,640))
#转为 hsv 颜色空间
hsv=cv.cvtColor(demo,cv.COLOR_BGR2HSV)
#定义颜色范围
low=np.array([0,43,46])
high=np.array([10,255,255])
#创建掩膜 cv.inRange(img,low,high)
mask=cv.inRange(hsv,low,high)
# 颜色提取cv2.bitwise_and(src1,src2[,mask])传的是原图
dst=cv.bitwise_and(demo,demo,mask=mask)
cv.imshow("old",demo)
cv.imshow("mask",mask)
cv.imshow("dst",dst)
print(demo.shape)
print(mask.shape)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果:
颜色替换:
import cv2 as cv
import numpy as np
# 读图
demo=cv.imread("../images/images/demo.png")
demo=cv.resize(demo,(640,640))
# 转为hsv颜色空间
hsv=cv.cvtColor(demo,cv.COLOR_BGR2HSV)
# 定义颜色范围
low=np.array([0,43,46])
high=np.array([10,255,255])
#创建掩膜cv.inRange(img,low,high)传hsv颜色空间下的图像
mask=cv.inRange(hsv,low,high)
# 颜色替换
arr=mask==255
# print(arr)
demo[arr]=(0,255,0)
# demo[mask==255]=(0,255,0)
cv.imshow("old",demo)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果:
3. 图像添加水印
本实验中添加水印的概念其实可以理解为将一张图片中的某个物体或者图案提取出来,然后叠加到另一张图片上。具体的操作思想是通过将原始图片转换成灰度图,并进行二值化处理,去除背景部分,得到一个类似掩膜的图像。然后将这个二值化图像与另一张图片中要添加水印的区域进行“与”运算,使得目标物体的形状出现在要添加水印的区域。最后,将得到的目标物体图像与要添加水印的区域进行相加,就完成了添加水印的操作。这样可以实现将一个图像中的某个物体或图案叠加到另一个图像上,从而实现添加水印的效果。就本实验而言,会用到两个新的组件,一个是模板输入,一个是图像融合。
3.1 模板输入
其实,该组件起到的就是图片输入的功能,只不过使用模板输入所输入的图片其实是作为要添加的水印,有了水印的彩色图之后,我们需要用它来制作一个掩模,这就用到了灰度化和二值化,即先灰度化后二值化,这就得到了带有水印图案的掩模。
3.2 与运算
有了模板的掩膜之后(也就是二值化图),我们就可以在要添加水印的图像中,根据掩膜的大小切割出一个ROI区域,也就是我们要添加水印的区域,之后让其与模板的掩膜进行与运算,我们知道,与运算的过程中,只要有黑色像素,那么得到的结果图中的对应位置也会是黑色像素。由于模板的掩膜中目标物体的像素值为黑色,所以经过与运算后,就会在ROI区域中得到模板图的形状。
3.3 图像融合(图像位与操作)
将模板的形状添加到水印区域之后,就可以将该图像与原始的模板图进行图像融合。该组件的目的就是将图像对应的数组中的对应元素进行相加(一定要注意这里的两个数组是规格相同的,也就是说要么都是灰度图,要么都是彩图),其过程如下图所示。
因此就可以让原始的模板图与添加模板图形状的ROI区域进行图像融合,得到添加水印的ROI区域。
3.4 综合代码
import cv2 as cv
# 读图
bg=cv.imread("../images/images/bg.png")
logo=cv.imread("../images/images/logohq.png")
h,w=logo.shape[0:2]
roi=bg[:h,:w]
# 转灰度
gray=cv.cvtColor(logo,cv.COLOR_BGR2GRAY)
# 二值化
# 黑logo白底 与上logo
_,mask1=cv.threshold(gray,200,255,cv.THRESH_BINARY)
bg1=cv.bitwise_and(roi,roi,mask=mask1)
cv.imshow("mask1",mask1)
cv.imshow("bg1",bg1)
# 白logo黑底 与上logo
_,mask2=cv.threshold(gray,200,255,cv.THRESH_BINARY_INV)
logo1=cv.bitwise_and(logo,logo,mask=mask2)
cv.imshow("mask2",mask2)
cv.imshow("logo1",logo1)
# 融合
# roi[:]=cv.add(bg1,logo1)
dst=cv.add(logo1,bg1)
roi[:]=dst
cv.imshow("roi",roi)
cv.imshow("bg",bg)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果:
4.图像噪点消除
首先介绍一些概念:
噪声:指图像中的一些干扰因素,通常是由图像采集设备、传输信道等因素造成的,表现为图像中随机的亮度,也可以理解为有那么一些点的像素值与周围的像素值格格不入。常见的噪声类型包括高斯噪声和椒盐噪声。高斯噪声是一种分布符合正态分布的噪声,会使图像变得模糊或有噪点。椒盐噪声则是一些黑白色的像素值分布在原图像中。
滤波器:也可以叫做卷积核,与自适应二值化中的核一样,本身是一个小的区域,有着特定的核值,并且工作原理也是在原图上进行滑动并计算中心像素点的像素值。滤波器可分为线性滤波和非线性滤波,线性滤波对邻域中的像素进行线性运算,如在核的范围内进行加权求和,常见的线性滤波器有均值滤波、高斯滤波等。非线性滤波则是利用原始图像与模板之间的一种逻辑关系得到结果,常见的非线性滤波器中有中值滤波器、双边滤波器等。
滤波与模糊联系与区别:
它们都属于卷积,不同滤波方法之间只是卷积核不同(对线性滤波而言)
低通滤波器是模糊,高通滤波器是锐化
低通滤波器就是允许低频信号通过,在图像中边缘和噪点都相当于高频部分,所以低通滤波器用于去除噪点、平滑和模糊图像。高通滤波器则反之,用来增强图像边缘,进行锐化处理。
注意:椒盐噪声可以理解为斑点,随机出现在图像中的黑点或白点;高斯噪声可以理解为拍摄图片时由于光照等原因造成的噪声。
4.1 均值滤波
均值滤波是一种最简单的滤波处理,它取的是卷积核区域内元素的均值,如3×3的卷积核:
在滤波算法组件中,当参数filtering_method选为均值滤波,参数component_param为ksize,代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
比如有一张4*4的图片,现在使用一个3*3的卷积核进行均值滤波时,其过程如下所示:
对于边界的像素点,则会进行边界填充,以确保卷积核的中心能够对准边界的像素点进行滤波操作。在OpenCV中,默认的是使用BORDER_REFLECT_101的方式进行填充,下面的滤波方法中除了中值滤波使用的是BORDER_REPLICATE进行填充之外,其他默认也是使用这个方式进行填充,因此下面就不再赘述。通过卷积核在原图上从左上角滑动计算到右下角,从而得到新的4*4的图像的像素值。
4.2 方框滤波
方框滤波跟均值滤波很像,如3×3的滤波核如下:
在滤波算法组件中,当参数filtering_method选为方框滤波时,参数component_param为ksize,ddepth,normalize。下面讲解这3个参数的含义:
ksize:代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
ddepth:输出图像的深度,-1代表使用原图像的深度。
图像深度是指在数字图像处理和计算机视觉领域中,每个像素点所使用的位数(bit depth),也就是用来表示图像中每一个像素点的颜色信息所需的二进制位数。图像深度决定了图像能够表达的颜色数量或灰度级。
normalize:当normalize为True的时候,方框滤波就是均值滤波,上式中的a就等于1/9;normalize为False的时候,a=1,相当于求区域内的像素和。
其滤波的过程与均值滤波一模一样,都采用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值可能会不同。
4.3 高斯滤波
高斯滤波是一种常用的图像处理技术,主要用于平滑图像、去除噪声。它通过使用高斯函数(正态分布)作为卷积核来对图像进行模糊处理。
前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。还记得我们在自适应二值化里是怎么生成高斯核的吗?这里跟自适应二值化里生成高斯核的步骤是一样的,都是以核的中心位置为坐标原点,然后计算周围点的坐标,然后带入下面的高斯公式中。
其中,x和 y 是相对于中心点的坐标偏移量,σ 是标准差,控制着高斯函数的宽度和高度。较大的 σ 值会导致更广泛的平滑效果。
卷积核通常是一个方形矩阵,其元素值根据高斯函数计算得出,并且这些值加起来等于1,近似于正态分布,以确保输出图像的亮度保持不变。
其中的值也是与自适应二值化里的一样,当时会取固定的系数,当kernel大于7并且没有设置时,会使用固定的公式进行计算\sigma的值:
我们还是以3*3的卷积核为例,其核值如下所示:
得到了卷积核的核值之后,其滤波过程与上面两种滤波方式的滤波过程一样,都是用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值不同。
在滤波算法组件中,当参数filtering_method选为高斯滤波,参数component_param为ksize,sigmaX。下面讲解这2个参数的含义:
ksize:代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
sigmaX:就是高斯函数里的值,σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。均值滤波与高斯滤波的对比结果如下(均值滤波丢失的细节更多):
4.4 中值滤波
中值又叫中位数,是所有数排序后取中间的值。中值滤波没有核值,而是在原图中从左上角开始,将卷积核区域内的像素值进行排序,并选取中值作为卷积核的中点的像素值,其过程如下所示:
中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。
比如下面这张斑点噪声图,用中值滤波显然更好:
在滤波算法组件中,当参数filtering_method选为中值滤波,参数component_param为ksize,代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
4.5 双边滤波
模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。
可以看到,双边滤波明显保留了更多边缘信息,下面来介绍一下双边滤波。
双边滤波的基本思路是同时考虑将要被滤波的像素点的空域信息(周围像素点的位置的权重)和值域信息(周围像素点的像素值的权重)。为什么要添加值域信息呢?是因为假设图像在空间中是缓慢变化的话,那么临近的像素点会更相近,但是这个假设在图像的边缘处会不成立,因为图像的边缘处的像素点必不会相近。因此在边缘处如果只是使用空域信息来进行滤波的话,得到的结果必然是边缘被模糊了,这样我们就丢掉了边缘信息,因此添加了值域信息。
双边滤波采用了两个高斯滤波的结合,一个负责计算空间邻近度的权值(也就是空域信息),也就是上面的高斯滤波器,另一个负责计算像素值相似度的权值(也就是值域信息),也是一个高斯滤波器。其公式如下所示:
其中,
S(i,j):指以(i,j)为中心的邻域的范围
f(k,l):输入的点的像素值,也就是在原始图像中位置 (k,l)的像素值。
ω(i,j,k,l):这是权重函数,它决定了位置 (k,l)的像素值f(k,l)对位置(i,j)的贡献程度。
g(i,j):表示中心点(i,j)的像素值,也就是经过双边滤波处理后,在位置(i,j)的像素值。
分子:∑(k,l)∈S(i,j)f(k,l)ω(i,j,k,l)
这是对邻域内所有像素值 f(k,l)与其对应的权重 ω(i,j,k,l)的乘积求和。这一步计算了加权后的像素值总和。
分母:∑(k,l)∈S(i,j)ω(i,j,k,l)
这是对邻域内所有像素的权重 ω(i,j,k,l) 求和。这一步计算了权重的总和。
计算过程:
确定邻域:选择一个以 (i,j)为中心的邻域 S(i,j)。
计算权重:对于邻域内的每个像素 (k,l),计算其权重 ω(i,j,k,l)。
加权求和:将邻域内每个像素值 f(k,l) 与其权重 ω(i,j,k,l)相乘,并对所有乘积求和。
归一化:将加权求和的结果除以权重总和,得到最终的像素值 g(i,j)。
在本实验中的滤波算法组件中,当参数filtering_method选为双边滤波,参数component_param为ksize,d,sigmaColor,sigmaSpace,下面讲解这4个参数的含义:
ksize:卷积核的大小
d:过滤时周围每个像素领域的直径,这里已经设置了核大小。d=9===>9x9
sigmaColor:在color space(色彩空间)中过滤sigma。参数越大,那些颜色足够相近的的颜色的影响越大。较大的
sigmaColor
值意味着更大的颜色差异将被允许参与到加权平均中,从而使得颜色相近但不完全相同的像素也能够相互影响。sigmaSpace:在coordinate space(坐标空间)中过滤sigma。这个参数是坐标空间中的标准差,决定了像素位置对滤波结果的影响程度。它定义了在图像的空间域中,一个像素可以影响周围像素的最大距离。换句话说,它控制着滤波器作用的范围大小。
关于2个sigma参数:
简单起见,可以令2个sigma的值相等;
如果他们很小(小于10),那么滤波器几乎没有什么效果;
如果他们很大(大于150),那么滤波器的效果会很强,使图像显得非常卡通化。
关于参数d:
过大的滤波器(d>5)执行效率低。
对于实时应用,建议取d=5;
对于需要过滤严重噪声的离线应用,可取d=9;
4.6 小结
- 在不知道用什么滤波器好的时候,优先高斯滤波,然后均值滤波。
- 斑点和椒盐噪声优先使用中值滤波。
- 要去除噪点的同时尽可能保留更多的边缘信息,使用双边滤波。
- 线性滤波方式:均值滤波、方框滤波、高斯滤波(速度相对快)。
- 非线性滤波方式:中值滤波、双边滤波(速度相对慢)。
代码:
import cv2 as cv
#读图
lvbo2=cv.imread("../images/images/lvbo.png")
cv.imshow("old",lvbo2)
lvbo3=cv.imread("../images/images/lvbo3.png")
# 均值滤波
dst1=cv.blur(lvbo2,(3,3))
cv.imshow("dst1",dst1)
# 方框滤波
dst2=cv.boxFilter(lvbo2,-1,(3,3),normalize=False)
cv.imshow("dst2",dst2)
#高斯滤波
dst3=cv.GaussianBlur(lvbo2,(3,3),1)
cv.imshow("dst3",dst3)
#中值滤波
dst4=cv.medianBlur(lvbo3,5)
cv.imshow("dst4",dst4)
#双边滤波
dst5=cv.bilateralFilter(lvbo2,7,75,75)
cv.imshow("dst5",dst5)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果: