【OpenCV篇】OpenCV——03day.图像预处理(2)
目录
前言
一、 插值方法
1最近邻插值
2 双线性插值
3 像素区域插值
4 双三次插值
5 Lanczos插值
6 小结+代码示例
二、边缘填充
1 边界复制(BORDER REPLICATE)
2 边界反射(BORDER REFLECT)
3 边界反射101(BORDER REFLECT_101)
4 边界常数(BORDER CONSTANT)
5 边界包裹(BORDER WRAP)
6 小结+代码示例
三、 图像矫正(透视变换)
四、图像掩膜
1 制作掩膜
2 与运算
3 颜色替换
3.1 制作掩膜
3.2 颜色替换
4 代码示例
五、ROI切割
六、图像添加水印
1 模板输入
2 与运算
3 图像融合(图像位与操作)
4 代码示例
七、图像噪点消除
1 均值滤波
2 方框滤波
3 高斯滤波
4 中值滤波
5 双边滤波
6 小结+代码示例
总结
前言
这一章讲图像处理的插值、填充和图像掩膜与图像消除噪点等
一、 插值方法
在图像处理和计算机图形学中,插值(Interpolation)是一种通过已知数据点之间的推断或估计来获取新数据点的方法。它在图像处理中常用于处理图像的放大、缩小、旋转、变形等操作,以及处理图像中的像素值。
图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。
1最近邻插值
- 原理:将距离目标位置最近的已知像素值直接赋予新像素。
- 特点:计算量极小,速度快,但会产生锯齿和马赛克效应,适用于实时性要求高、对质量不敏感的场景(如实时预览)。
CV2.INTER_NEAREST
new_img1=cv.warpAffine(img,M,(w,h),flags=cv.INTER_NEAREST)
首先给出目标点与原图像点之间坐标的计算公式:
-
:目标图像中某点的x坐标,
-
:目标图像中某点的y坐标,
-
:原图的宽度,
-
:目标图像的宽度;
-
:原图的高度,
-
:目标图像的高度。
-
而
和
:目标图像中的某点对应的原图中的点的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)坐标处的像素值,其他像素点计算规则与此相同。
2 双线性插值
- 原理:基于目标点周围4个邻域像素,分别在水平和垂直方向进行线性加权计算新像素值。公式为:
f(x,y)=(1−u)(1−v)f(i,j)+(1−u)vf(i,j+1)+u(1−v)f(i+1,j)+uvf(i+1,j+1) 。 - 特点:平滑锯齿效应,输出自然但会轻微模糊边缘(低通滤波特性),广泛用于普通图像缩放(如OpenCV的INTER_LINEAR)
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中,为了解决这两个问题,将公式进行了优化,如下所示:
使用该公式计算出原图中的对应坐标后再进行插值计算,就不会出现上面的情况了。
3 像素区域插值
- 原理:基于像素面积关系重采样,通过局部像素区域的均值或积分计算新像素值。
- 特点:图像缩小时可避免莫尔条纹,但放大时等效于最近邻法。适用于图像降采样(如压缩预处理)
cv2.INTER_AREA
像素区域插值主要分两种情况,缩小图像和放大图像的工作原理并不相同。
-
当使用像素区域插值方法进行缩小图像时,它就会变成一个均值滤波器(滤波器其实就是一个核,这里只做简单了解,后面实验中会介绍),其工作原理可以理解为对一个区域内的像素值取平均值。
-
当使用像素区域插值方法进行放大图像时
-
如果图像放大的比例是整数倍,那么其工作原理与最近邻插值类似;
-
如果放大的比例不是整数倍,那么就会调用双线性插值进行放大。
-
其中目标像素点与原图像的像素点的对应公式如下所示:
4 双三次插值
- 原理:利用周围16个邻域像素,通过三次多项式(如双立方样条)加权计算。
- 特点:保留更多高频细节,锐度高于双线性法,计算量较大(如OpenCV的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
代表的是目标像素点与某个像素点之间的相对距离,、
我们要做的就是将上面的16个点相较于p点的位置距离算出来,获取16像素所对应的权重W(d)。然而BiCubic函数是一维的,所以我们需要将像素点的行与列分开计算,比如a00这个点,我们需要将带入BiCubic函数中,计算a00点对于P点的x方向的权重,然后将
带入BiCubic函数中,计算a00点对于P点的y方向的权重,其他像素点也是这样的计算过程,最终我们就可以得到P所对应的目标图像B在(X,Y)处的像素值为:
依此办法我们就可以得到目标图像中所有的像素点的像素值
5 Lanczos插值
- 原理:基于sinc函数截断(通常取8×8邻域),通过高阶核函数加权插值,抑制振铃效应。
- 特点:在保留边缘和纹理细节上优于双三次法,计算复杂度最高,适用于高质量图像放大(如医学影像
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)来表示,如下图所示。
然后给出权重公式:
其中a通常取2或者3,当a=2时,该算法适用于图像缩小。a=3时,该算法适用于图像放大。
与双三次插值一样,这里也需要将像素点分行和列分别带入计算权重值,其他像素点也是这样的计算过程,最终我们就可以得到P所对应的目标图像B在(X,Y)处的像素值为:
其中[x]、[y]表示对坐标值向下取整,通过该方法就可以计算出新的图像中所有的像素点的像素值。
6 小结+代码示例
最近邻插值
计算速度最快,但可能导致图像出现锯齿状边缘和失真,适用于对计算效率要求极高但质量要求不高的场景。
双线性插值
计算速度适中,图像质量显著优于最近邻插值,在大多数情况下能提供较好的平滑效果,OpenCV 默认采用此方法。
双三次插值与 Lanczos 插值
计算速度较慢,但能提供更高的图像质量,适合对细节保留要求较高的场景,如高精度图像放大或专业图像处理任务。
在 OpenCV 中,默认推荐双线性插值,因其在计算效率和图像质量之间取得了较好的平衡,能满足大部分应用需求。
import cv2 as cv# 读图
cat = cv.imread("../images/cat1.png")
# cv.imshow("cat", cat)# 旋转 先获取旋转矩阵 shape[0]行 shape[1]列 shape[2]通道
w = cat.shape[1]
h = cat.shape[0]
M = cv.getRotationMatrix2D((w//2, h//2), 45, 1)# 仿射变换
dst1 = cv.warpAffine(cat, M, (w, h))
cv.imshow("dst1", dst1)# 最近邻插值
dst2 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST)
cv.imshow("dst2", dst2)# 双线性插值 单线性插值 插两次:一次水平 一次垂直
dst3 = cv.warpAffine(cat, M, (w, h), cv.INTER_LINEAR)
cv.imshow("dst3", dst3)# 像素区域插值 缩小:均值滤波 放大:整数 最近邻 不是整数 双线性插值 4点 2x2
dst4 = cv.warpAffine(cat, M, (w, h), cv.INTER_AREA)
cv.imshow("dst4", dst4)# 双三次插值 16个点 4x4
dst5 = cv.warpAffine(cat, M, (w, h), cv.INTER_CUBIC)
cv.imshow("dst5", dst5)# Lancer插值 64个点 8x8
dst6 = cv.warpAffine(cat, M, (w, h), cv.INTER_LANCZOS4)
cv.imshow("dst6", dst6)cv.waitKey(0)
cv.destroyAllWindows()
二、边缘填充
填充用于处理卷积、滤波等操作时的边界像素缺失问题。
1 边界复制(BORDER REPLICATE)
- 原理:直接复制图像边缘像素值(如
aaaa|abcdefg|gggg
)。 - 场景:简单高效,适用于一般滤波(如均值滤波)
new_img=cv.warpAffine(img,M,(w,h),cv.INTER_LANCZOS4,borderMode=cv.BORDER_REPLICATE)
边界复制会将边界处的像素值进行复制,然后作为边界填充的像素值,如下图所示,可以看到四周的像素值都一样。
2 边界反射(BORDER REFLECT)
- 原理:以边界为轴镜像反射内部像素(如
fedcb|abcdefg|fedcb
)。 - 场景:保持边界连续性,适合边缘检测(如Sobel算子)
new_img=cv.warpAffine(img,M,(w,h),cv.INTER_LANCZOS4,borderMode=cv.BORDER_REFLECT)
如下图所示,会根据原图的边缘进行反射。
3 边界反射101(BORDER REFLECT_101)
- 原理:改进版反射,排除边界像素本身(如
gfedc|abcdefg|fedcb
),OpenCV默认方式。 - 场景:减少边界突变,用于深度学习模型预处理。
new_img=cv.warpAffine(img,M,(w,h),cv.INTER_LANCZOS4,borderMode=cv.BORDER_REFLECT_101)
与边界反射不同的是,不再反射边缘的像素点,如下图所示。
4 边界常数(BORDER CONSTANT)
- 原理:用固定值(如黑色或自定义颜色)填充边界(如
0000|abcdefg|0000
)。 - 场景:需隔离边界的任务(如OCR文字分割)。
new_img=cv.warpAffine(img,M,(w,h),cv.INTER_LANCZOS4,borderMode=cv.BORDER_CONSTANT,borderValue=(0,0,255))
当选择边界常数时,还要指定常数值是多少,默认的填充常数值为0,如下图所示。
5 边界包裹(BORDER WRAP)
- 原理:平铺重复图像(如
cdefg|abcdefg|abcde
)。 - 场景:周期性纹理处理(如天文图像拼接)。
new_img=cv.warpAffine(img,M,(w,h),cv.INTER_LANCZOS4,borderMode=cv.BORDER_WRAP)
6 小结+代码示例
填充类型 | 原理 | 特点 | 适用场景 |
---|---|---|---|
边界常数(BORDER_CONSTANT ) | 用固定颜色(默认黑色)填充边界 | 简单高效,但可能形成明显色块边界 | 需明确区分填充区域的场景(如OCR分割) |
边界复制(BORDER_REPLICATE ) | 复制最边缘像素值向外延伸 | 避免新颜色引入,但可能产生"拉伸"痕迹 | 边缘像素变化平缓的快速处理 |
边界反射(BORDER_REFLECT ) | 以边缘为轴镜像反射内部像素(含边缘像素) | 保持内容连续性,计算量适中 | 通用滤波操作(如高斯模糊) |
反射101(BORDER_REFLECT_101 ) | 镜像反射但排除边缘像素(OpenCV默认方式) | 过渡更平滑,减少边缘突变 | 深度学习预处理、边缘检测 |
边界包裹(BORDER_WRAP ) | 将图像另一侧像素环绕填充(如左侧用右侧像素) | 产生周期性纹理,可能不自然 | 周期性图像(如纹理合成、卫星影像拼接) |
import cv2 as cv# 读图
cat = cv.imread("../images/cat1.png")
# cv.imshow("cat", cat)# 旋转 先获取旋转矩阵 shape[0]行 shape[1]列 shape[2]通道
w = cat.shape[1]
h = cat.shape[0]
M = cv.getRotationMatrix2D((w//2, h//2), 45, 0.5)# 都使用最近邻插值# 边界复制
dst1 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST, borderMode=cv.BORDER_REPLICATE)
cv.imshow("dst1", dst1)# 边界反射
dst2 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST, borderMode=cv.BORDER_REFLECT)
cv.imshow("dst2", dst2)# 边界反射101
dst3 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST, borderMode=cv.BORDER_REFLECT_101)
cv.imshow("dst3", dst3)# 边界常数
dst4 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST, cv.BORDER_CONSTANT, borderValue=(255, 255, 255))
cv.imshow("dst4", dst4)# 边界包裹
dst5 = cv.warpAffine(cat, M, (w, h), cv.INTER_NEAREST, borderMode=cv.BORDER_WRAP )
cv.imshow("dst5", dst5)cv.waitKey(0)
cv.destroyAllWindows()
三、 图像矫正(透视变换)
图像矫正的原理是透视变换,下面来介绍一下透视变换的概念。
我们在图像旋转里接触过仿射变换,知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程,转换过程坐标点的相对位置和属性不发生变换,是一个线性变换,该过程只发生旋转和平移过程。因此,一个平行四边形经过仿射变换后还是一个平行四边形。
而透视变换是把一个图像投影到一个新的视平面的过程,在现实世界中,我们观察到的物体在视觉上会受到透视效果的影响,即远处的物体看起来会比近处的物体小。透视投影是指将三维空间中的物体投影到二维平面上的过程,这个过程会导致物体在图像中出现形变和透视畸变。透视变换可以通过数学模型来校正这种透视畸变,使得图像中的物体看起来更符合我们的直观感受。通俗的讲,透视变换的作用其实就是改变一下图像里的目标物体的被观察的视角。
假设我们有一个点 (x,y,z)在三维空间中,并且我们想要将其投影到二维平面上。我们可以先将其转换为齐次坐标, (x,y,z),然后进行透视投影,得到了经过透视投影后的二维坐标 (x′,y′)。通过将 X和Y 分别除以Z,我们可以模拟出真实的透视效果。
与仿射变换一样,透视变换也有自己的透视变换矩阵:
由此可得新的坐标的表达式为:
其中x、y是原始图像点的坐标,x^{\prime}、y^{\prime}是变换后的坐标,a11,a12,…,a33则是一些旋转量和平移量,由于透视变换矩阵的推导涉及三维的转换,所以这里不具体研究该矩阵,只要会使用就行,而OpenCV里也提供了getPerspectiveTransform()函数用来生成该3*3的透视变换矩阵。
-
M=getPerspectiveTransform(src,dst)
在该函数中,需要提供两个参数:
src:原图像上需要进行透视变化的四个点的坐标,这四个点用于定义一个原图中的四边形区域。
dst:透视变换后,src的四个点在新目标图像的四个新坐标。
该函数会返回一个透视变换矩阵,得到透视变化矩阵之后,使用warpPerspective()函数即可进行透视变化计算,并得到新的图像。该函
数需要提供如下参数:
cv2.warpPerspective(src, M, dsize, flags, borderMode)
src:输入图像。
M:透视变换矩阵。这个矩阵可以通过getPerspectiveTransform函数计算得到。
dsize:输出图像的大小。它可以是一个Size对象,也可以是一个二元组。
flags:插值方法的标记。
borderMode:边界填充的模式。
四、图像掩膜
1 制作掩膜
掩膜(Mask)是一种在图像处理中常见的操作,它用于选择性地遮挡图像的某些部分,以实现特定任务的目标。掩膜通常是一个二值化图像,并且与原图像的大小相同,其中目标区域被设置为1(或白色),而其他区域被设置为0(或黑色),并且目标区域可以根据HSV的颜色范围进行修改,如下图就是制作红色掩膜的过程:
通过这个掩膜,我们就可以对掩膜中的白色区域所对应的原图中的区域进行处理与操作。
mask=cv.inRange(img,color_low,color_high)
cv2.inRange用于进行多通道图像(尤其是彩色图像)的阈值操作。
2 与运算
我们在高中时学过逻辑运算中的“与”运算,其规则是当两个命题都是真时,其结果才为真。而在图像处理中,“与”运算被用来对图像的像素值进行操作。具体来说,就是将两个图像中所有的对应像素值一一进行“与”运算,从而得到新的图像。从上面的图片我们可以看出,掩膜中有很多地方是黑色的,其像素值为0,那么在与原图像进行“与”运算的时候,得到的新图像的对应位置也是黑色的,如下图所示:
通过掩膜与原图的与运算,我们就可以提取出图像中被掩膜覆盖的区域(扣图)。
cv2.bitwise_and(src1,src2[,mask])
src1:第一个输入数组。通常是输入的原始图像。
src2:第二个输入数组。它可以是另一个图像、一个常数值或者与 src1 相同的图像。
mask:掩膜(可选)。输入数组元素只有在该掩膜非零时才被处理。是一个8位单通道的数组,尺寸必须与src1和src2相同。
返回值:输出数组,应用掩膜后的图像,与输入数组大小和类型相同。
当应用掩膜时,这个参数经常就是src1本身;即对同一个图像进行操作。如果对两个不同的图像执行按位与操作(例如,将两张图片的某些部分组合在一起),可以分别将它们作为 src1 和 src2 输入到 cv2.bitwise_and() 函数中,创建复杂的图像效果或进行图像合成。
3 颜色替换
3.1 制作掩膜
掩膜(Mask)是一种在图像处理中常见的操作,它用于选择性地遮挡图像的某些部分,以实现特定任务的目标。掩膜通常是一个二值化图像,并且与原图像的大小相同,其中目标区域被设置为1(或白色),而其他区域被设置为0(或黑色),并且目标区域则可以根据HSV的颜色范围进行修改,如下图所示,可以选择制作不同颜色的掩膜:
通过这个掩膜,我们就可以对掩膜中的白色区域所对应的原图中的区域(也就是原图中的红色区域)进行像素值的修改,从而完成颜色替换的功能。
3.2 颜色替换
由于掩膜与原图的大小相同,并且像素位置一一对应,那么我们就可以得到掩膜中白色(也就是像素值为255)区域的坐标,并将其带入到原图像中,即可得到原图中的红色区域的坐标,然后就可以修改像素值了,这样就完成了颜色的替换,如下图所示:
mask_image_np == 255: 这一部分实际上是在生成一个布尔数组,其形状与mask_image_np相同。
image_np[...] = (0, 255, 0): 这里使用了NumPy的高级索引功能。
4 代码示例
import cv2 as cv
import numpy as npdemo = cv.imread("../images/demo.png")
demo = cv.resize(demo, (640, 640))
cv.imshow("demo", demo)
# 转颜色空间 HSV
demo_hsv = cv.cvtColor(demo, cv.COLOR_BGR2HSV)# 定义颜色范围
lower = np.array([0, 43, 46])
high = np.array([10, 255, 255])# 创建掩膜 cv.inRange(img, low, high) 传HSV颜色空间下的图像
mask = cv.inRange(demo_hsv, lower, high)
cv.imshow("mask", mask)# 颜色提取 cv.bitwise_and(img1, img2, mask) 传的是原图
res = cv.bitwise_and(demo, demo, mask=mask)
cv.imshow("res", res)cv.waitKey(0)
cv.destroyAllWindows()
颜色替换:
import cv2 as cv
import numpy as npdemo = cv.imread("../images/demo.png")
demo = cv.resize(demo, (640, 640))
cv.imshow("demo", demo)
# 转颜色空间 HSV
demo_hsv = cv.cvtColor(demo, cv.COLOR_BGR2HSV)# 定义颜色范围
lower = np.array([0, 43, 46])
high = np.array([10, 255, 255])# 创建掩膜 cv.inRange(img, low, high) 传HSV颜色空间下的图像
mask = cv.inRange(demo_hsv, lower, high)
cv.imshow("mask", mask)# 颜色替换
arr = mask == 255
demo[arr] = [0, 255, 0]# demo[mask == 255] = [0, 255, 0]
cv.imshow("demo1", demo)cv.waitKey(0)
cv.destroyAllWindows()
五、ROI切割
提取图像中特定区域:通过坐标框选:roi = img[y1:y2, x1:x2]。
ROI:Region of Interest,翻译过来就是感兴趣的区域。什么意思呢?比如对于一个人的照片,假如我们要检测眼睛,因为眼睛肯定在脸上,所以我们感兴趣的只有脸这部分,其他都不care,所以可以单独把脸截取出来,这样就可以大大节省计算量,提高运行速度。
还记得Numpy这个库吗?我们在使用OpenCV进行读取图像时,图像数据会被存储为Numpy数组,这也意味着我们可以使用Numpy数组的一些操作来对图像数据进行处理,比如切片。而本实验的原理也是基于Numpy数组的切片操作来完成的,因此在对应的组件中就需要填我们要切割的ROI区域的坐标来完成ROI切割操作。
注意:在OpenCV中,坐标的x轴的正方向是水平向右,y轴的正方向是垂直向下,与数学上的二维坐标并不相同。
在计算机视觉中,当我们使用OpenCV读取RGB三通道图像时,它会被转换成一个三维的Numpy数组。这个数组里的每个元素值都表示图像的一个像素值。这个三维数组的第一个维度(即轴0)通常代表图像的高度,第二个维度(即轴1)代表图像的宽度,而第三个维度(即轴2)代表图像的三个颜色通道(B、G、R,OpenCV读取到的图像以BGR的方式存储)所对应的像素值。
因此,我们可以通过指定切片的范围来选择特定的高度和宽度区域。这样,我们就能够获取这个区域内的所有像素值,即得到了这个区域的图像块,通过Numpy的切片操作,我们就完成了ROI切割的操作。这种提取ROI的方法允许我们仅获取感兴趣区域内的像素,而忽略其他不相关的部分,从而大大减少数据处理和存储的负担。
六、图像添加水印
水印添加的核心是将源图像中的目标物体提取并叠加到目标图像上。这个过程涉及图像处理技术,通过创建掩膜实现物体与背景的分离。
1 模板输入
该组件起到的就是图片输入的功能,只不过使用模板输入所输入的图片其实是作为要添加的水印,有了水印的彩色图之后,我们需要用它来制作一个掩模,这就用到了灰度化和二值化,即先灰度化后二值化,这就得到了带有水印图案的掩模。
2 与运算
有了模板的掩膜之后(也就是二值化图),我们就可以在要添加水印的图像中,根据掩膜的大小切割出一个ROI区域,也就是我们要添加水印的区域,之后让其与模板的掩膜进行与运算,我们知道,与运算的过程中,只要有黑色像素,那么得到的结果图中的对应位置也会是黑色像素。由于模板的掩膜中目标物体的像素值为黑色,所以经过与运算后,就会在ROI区域中得到模板图的形状。
3 图像融合(图像位与操作)
将模板的形状添加到水印区域之后,就可以将该图像与原始的模板图进行图像融合。该组件的目的就是将图像对应的数组中的对应元素进行相加(一定要注意这里的两个数组是规格相同的,也就是说要么都是灰度图,要么都是彩图),其过程如下图所示。
因此就可以让原始的模板图与添加模板图形状的ROI区域进行图像融合,得到添加水印的ROI区域。
4 代码示例
import cv2 as cv
import numpy as npbg1 = cv.imread('../images/bg.png')
bg = cv.resize(bg1, (0, 0), fx=0.5, fy=0.5)
logo = cv.imread('../images/logohq.png')
# h, w, c = logo.shape # logo的宽高
h, w =logo.shape[0:2]
bg_size = bg[:h, :w] # 背景的宽高# 为logo创建二值图 做掩膜 白字logo 为了提取出文字
# 第一步 转为灰度图
gray = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)# 第二步 创建白底黑logo 然后和背景进行与运算
_, mask1 = cv.threshold(gray, 200, 255, cv.THRESH_BINARY)
bg1 = cv.bitwise_and(bg_size, bg_size, 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)# 第四步 融合
res = cv.add(bg1, logo1)
bg_size[:] = res
cv.imshow("bg", bg)cv.waitKey(0)
cv.destroyAllWindows()
七、图像噪点消除
首先介绍一些概念:
噪声:指图像中的一些干扰因素,通常是由图像采集设备、传输信道等因素造成的,表现为图像中随机的亮度,也可以理解为有那么一些点的像素值与周围的像素值格格不入。常见的噪声类型包括高斯噪声和椒盐噪声。高斯噪声是一种分布符合正态分布的噪声,会使图像变得模糊或有噪点。椒盐噪声则是一些黑白色的像素值分布在原图像中。
滤波器:也可以叫做卷积核,与自适应二值化中的核一样,本身是一个小的区域,有着特定的核值,并且工作原理也是在原图上进行滑动并计算中心像素点的像素值。滤波器可分为线性滤波和非线性滤波,线性滤波对邻域中的像素进行线性运算,如在核的范围内进行加权求和,常见的线性滤波器有均值滤波、高斯滤波等。非线性滤波则是利用原始图像与模板之间的一种逻辑关系得到结果,常见的非线性滤波器中有中值滤波器、双边滤波器等。
滤波与模糊联系与区别:
-
它们都属于卷积,不同滤波方法之间只是卷积核不同(对线性滤波而言)
-
低通滤波器是模糊,高通滤波器是锐化
-
低通滤波器就是允许低频信号通过,在图像中边缘和噪点都相当于高频部分,所以低通滤波器用于去除噪点、平滑和模糊图像。高通滤波器则反之,用来增强图像边缘,进行锐化处理。
注意:椒盐噪声可以理解为斑点,随机出现在图像中的黑点或白点;高斯噪声可以理解为拍摄图片时由于光照等原因造成的噪声。
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的图像的像素值。
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,相当于求区域内的像素和。
其滤波的过程与均值滤波一模一样,都采用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值可能会不同。
3 高斯滤波
高斯滤波是一种常用的图像处理技术,主要用于平滑图像、去除噪声。它通过使用高斯函数(正态分布)作为卷积核来对图像进行模糊处理。
前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。还记得我们在自适应二值化里是怎么生成高斯核的吗?这里跟自适应二值化里生成高斯核的步骤是一样的,都是以核的中心位置为坐标原点,然后计算周围点的坐标,然后带入下面的高斯公式中。
其中,x和 y 是相对于中心点的坐标偏移量,σ 是标准差,控制着高斯函数的宽度和高度。较大的 σ 值会导致更广泛的平滑效果。
卷积核通常是一个方形矩阵,其元素值根据高斯函数计算得出,并且这些值加起来等于1,近似于正态分布,以确保输出图像的亮度保持不变。
其中的值也是与自适应二值化里的一样,当时会取固定的系数,当kernel大于7并且没有设置时,会使用固定的公式进行计算的值:
我们还是以3*3的卷积核为例,其核值如下所示:
得到了卷积核的核值之后,其滤波过程与上面两种滤波方式的滤波过程一样,都是用卷积核从图像左上角开始,逐个计算对应位置的像素值,并从左至右、从上至下滑动卷积核,直至到达图像右下角,唯一的区别就是核值不同。
在滤波算法组件中,当参数filtering_method选为高斯滤波,参数component_param为ksize,sigmaX。讲解这2个参数的含义:
ksize:代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
sigmaX:就是高斯函数里的值,σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。
4 中值滤波
中值又叫中位数,是所有数排序后取中间的值。中值滤波没有核值,而是在原图中从左上角开始,将卷积核区域内的像素值进行排序,并选取中值作为卷积核的中点的像素值
中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。
在滤波算法组件中,当参数filtering_method选为中值滤波,参数component_param为ksize,代表卷积核的大小,eg:ksize=3,则代表使用3×3的卷积核。
5 双边滤波
模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。
双边滤波的基本思路是同时考虑将要被滤波的像素点的空域信息(周围像素点的位置的权重)和值域信息(周围像素点的像素值的权重)。为什么要添加值域信息呢?是因为假设图像在空间中是缓慢变化的话,那么临近的像素点会更相近,但是这个假设在图像的边缘处会不成立,因为图像的边缘处的像素点必不会相近。因此在边缘处如果只是使用空域信息来进行滤波的话,得到的结果必然是边缘被模糊了,这样我们就丢掉了边缘信息,因此添加了值域信息。
双边滤波采用了两个高斯滤波的结合,一个负责计算空间邻近度的权值(也就是空域信息),也就是上面的高斯滤波器,另一个负责计算像素值相似度的权值(也就是值域信息),也是一个高斯滤波器。其公式如下所示:
-
:指以
为中心的邻域的范围
-
:输入的点的像素值,也就是在原始图像中位置
的像素值。
-
:这是权重函数,它决定了位置
的像素值
对位置
的贡献程度。
-
:表示中心点
的像素值,也就是经过双边滤波处理后,在位置
的像素值。
-
分子:这是对邻域内所有像素值
与其对应的权重
的乘积求和。这一步计算了加权后的像素值总和。
-
分母:这是对邻域内所有像素的权重
求和。这一步计算了权重的总和。
计算过程:
-
确定邻域:选择一个以
为中心的邻域
。
-
计算权重:对于邻域内的每个像素
,计算其权重
。
-
加权求和:将邻域内每个像素值
与其权重
相乘,并对所有乘积求和。
-
归一化:将加权求和的结果除以权重总和,得到最终的像素值
。
上述公式我们进行转化,假设公式中为m,则有
设:
此时可以看到,这与上面的滤波中计算过程已经一模一样了,就代表了第一个点的权重。接下来我们看看
是怎么来的,令
和
可以看到,对于来说,这就是普通的高斯滤波函数,其带入的坐标是坐标值,
是程序输入值,该函数是在空间临近度上计算的。而
是计算像素值相似度,也是高斯函数带入坐标值,然后得到对应点的像素值,进行两个点像素值插值的绝对值的平方。也就是说,双边滤波的核值不再是一个固定值,而是随着滤波的过程在不断发生变化的。
在本实验中的滤波算法组件中,当参数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;
6 小结+代码示例
在需要使用滤波器的时候,不知道用什么滤波器好的时候,优先高斯滤波,然后均值滤波。斑点和椒盐噪声优先使用中值滤波。要去除噪点的同时尽可能保留更多的边缘信息,使用双边滤波。
线性滤波方式:均值滤波、方框滤波、高斯滤波(速度相对快)。
非线性滤波方式:中值滤波、双边滤波(速度相对慢)。
import cv2 as cv
import numpy as nplvbo2 = cv.imread("../images/lvbo2.png")
lvbo3 = cv.imread("../images/lvbo3.png")
cv.imshow("lvbo2", lvbo2)# 均值滤波 cv.blur(src 图片, ksize 核值大小)
dst1 = cv.blur(lvbo2, (5, 5))
cv.imshow("junzhi", dst1)# 方框滤波 cv.boxFilter(src 图片, ddepth 输出图片的深度, ksize 核值大小, normalize )
dst2 = cv.boxFilter(lvbo2, -1, (5, 5), normalize=False)
# cv.imshow("fangkuang", dst2)# 高斯滤波 cv.GaussianBlur(src 图片, ksize 核值大小, sigmaX 高斯核的sigma)
dst3 = cv.GaussianBlur(lvbo2, (5, 5), 0)
cv.imshow("gaussian", dst3)# 中值滤波 cv.medianBlur(src 图片, ksize 核值大小) 适合用来去除椒盐噪声和斑点噪声
dst4 = cv.medianBlur(lvbo3, 5)
cv.imshow("zhongzhi", dst4)# 双边滤波 cv.bilateralFilter(src 图片, dsize 滤波后的图片大小, sigmaColor 颜色空间标准差, sigmaSpace 空间标准差)
dst5 = cv.bilateralFilter(lvbo2, 5, 75, 75)
cv.imshow("shuangbian", dst5)cv.waitKey(0)
cv.destroyAllWindows()
总结
图像处理技术链的核心在于平衡精度、效率与场景需求:
插值方法:质量敏感选双三次/Lanczos,速度敏感选最近邻/双线性。
边缘填充:反射类(BORDER_REFLECT_101)通用性强,常数填充(BORDER_CONSTANT)适用于隔离边界。
几何矫正:透视变换解决视角畸变,需精准匹配特征点。
局部操作:掩膜与ROI切割实现区域定制化处理,水印添加依赖融合技术。
噪声消除:中值滤波应对脉冲噪声,双边滤波保边需求首选,高斯滤波通用性高
这一章的知识较难理解,多看多理解,不懂的地方多重复看