到目前为止,我们已经研究了通过直接调整单个像素值(如改变亮度和对比度)或对整个图像结构应用变换(如缩放或旋转)来修改图像的方法。这些都是有效的工具,但许多重要的图像处理任务需要考虑像素的周边信息。它的邻居在做什么?它是一个平滑区域的一部分,还是位于一个尖锐的边缘上?这正是图像滤波(也称为空间滤波)发挥作用的地方。滤波技术不是单独处理像素,而是根据小范围邻近像素的值来修改像素值。可以把它想象成透过一个小窗口观察一个像素,然后根据窗口内看到的一切来决定它的新值。核心理念:邻域操作滤波背后的基本概念是卷积核。卷积核(有时也称为滤波器掩膜或窗口)是一个小型矩阵,通常是正方形的(例如3x3或5x5像素),包含特定数值。这些数值决定了滤波器的特性和效果。以下是一般流程:放置: 卷积核放置在输入图像的一小块区域上,以我们希望计算其输出值的像素为中心。计算: 通过卷积核覆盖的图像区域中的像素值以及卷积核内部的对应值进行计算。最常见的计算是加权和:卷积核下方的每个像素值都乘以对应的卷积核值,然后将所有这些乘积相加。有时,此和会进行归一化处理(例如,除以卷积核值的总和或一个固定数值)。输出: 此计算结果成为输出图像中该像素的值,位置与输入图像中卷积核放置的中心点相同。滑动: 然后卷积核“滑动”一个像素(水平或垂直)到输入图像的下一个位置,此过程重复,直到覆盖输入图像中的每个像素(或尽可能多的像素,考虑到边界问题)。这种滑动卷积核并在每个位置计算加权和的过程是一种卷积形式(或者从技术上讲,是互相关,对于基本滤波中常用的对称核来说,两者非常相似)。目前,可以将其视为在整个图像上应用局部加权平均或差分操作。卷积核操作的可视化想象一个3x3的小型卷积核和它覆盖的图像区域:输入图像块 卷积核 中心输出像素的计算 +---+---+---+ +-----+-----+-----+ | P1| P2| P3| | K1 | K2 | K3 | 输出 = (P1*K1 + P2*K2 + P3*K3 + +---+---+---+ +-----+-----+-----+ P4*K4 + P5*K5 + P6*K6 + | P4| P5| P6| (*) | K4 | K5 | K6 | P7*K7 + P8*K8 + P9*K9) +---+---+---+ +-----+-----+-----+ | P7| P8| P9| | K7 | K8 | K9 | +---+---+---+ +-----+-----+-----+卷积核在图像上滑动。在每一步,计算都使用卷积核下方的图像像素值(P1到P9)和卷积核的权重(K1到K9)来生成与输入中心像素(P5)对应的单个输出像素值。让我们通过一个简单图示来展现这种滑动窗口的理念:digraph G { rankdir=LR; node [shape=plaintext]; subgraph cluster_input { label = "输入图像"; bgcolor="#e9ecef"; I [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P1</TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P2</TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P3</TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P4</TD><TD BGCOLOR="#74c0fc" WIDTH="25" HEIGHT="25"><B>P5</B></TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P6</TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P7</TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P8</TD><TD BGCOLOR="#a5d8ff" WIDTH="25" HEIGHT="25">P9</TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> </TABLE> >]; } subgraph cluster_kernel { label = "卷积核 (例如, 3x3)"; bgcolor="#ffec99"; K [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR><TD WIDTH="25" HEIGHT="25">K1</TD><TD WIDTH="25" HEIGHT="25">K2</TD><TD WIDTH="25" HEIGHT="25">K3</TD></TR> <TR><TD WIDTH="25" HEIGHT="25">K4</TD><TD WIDTH="25" HEIGHT="25"><B>K5</B></TD><TD WIDTH="25" HEIGHT="25">K6</TD></TR> <TR><TD WIDTH="25" HEIGHT="25">K7</TD><TD WIDTH="25" HEIGHT="25">K8</TD><TD WIDTH="25" HEIGHT="25">K9</TD></TR> </TABLE> >]; } subgraph cluster_output { label = "输出图像"; bgcolor="#b2f2bb"; O [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD BGCOLOR="#8ce99a" WIDTH="25" HEIGHT="25"><B>O5</B></TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> <TR><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD><TD WIDTH="25" HEIGHT="25"> </TD></TR> </TABLE> >]; } Calc [label="加权和\nO5 = Σ(Pi * Ki)", shape=box, style=dashed]; I -> Calc [label=" 图像块 (P1-P9)"]; K -> Calc [label=" 卷积核权重 (K1-K9)"]; Calc -> O [label=" 输出像素 (O5)"]; }卷积核(黄色)在输入图像(蓝色)上滑动。在每个位置,它覆盖一个像素块(P1-P9)。使用卷积核的值(K1-K9)进行的加权求和会产生相应的输出像素(O5,绿色)。然后,卷积核移动到下一个位置。处理图像边界一个实际问题出现了:当卷积核到达图像边缘时会发生什么?如果卷积核是3x3,并且它以边界上的像素为中心,那么卷积核的一部分会超出边缘,那里没有图像像素。有几种方法可以处理这种情况,被称为边界外推或填充方法:忽略: 对于卷积核未完全覆盖图像的边界像素,不计算输出值。输出图像将比输入图像略小。常数填充: 对图像边界外的像素假定一个常数值(如0,或黑色)。复制边界: 通过向外重复边界像素值来扩展图像。反射边界: 通过将像素值沿边界反射来扩展图像。填充方法的选择有时会影响结果,尤其是在边缘附近,但对于许多基本应用,库中使用的默认方法(通常是复制或反射)效果良好。我们现在不会深入这些细节,但知道这是需要考虑的一个方面是好的。为何使用滤波?滤波的真正效用在于,通过设计不同的卷积核(为K1到K9等选择不同的值),我们可以实现各种效果:平滑(模糊): 带有正值的卷积核,通常对邻域进行平均,可以平滑噪声和小细节。锐化: 旨在强调相邻像素之间差异的卷积核可以使边缘显得更清晰。边缘检测: 可以设计特定的卷积核来强烈响应强度上的急剧变化,从而突出边缘(我们将在第4章中讨论)。特征增强: 滤波器可用于增强或检测特定模式或纹理。本节介绍了使用卷积核进行图像滤波的理念。在接下来的部分中,我们将查看特定类型的卷积核,从用于基本图像平滑的那些开始。理解滤波操作非常重要,因为它为许多更高级的计算机视觉技术奠定了基础。