Skip to content

腐蚀与膨胀

本教程中你将学到如何:

  • 应用两种非常常见的形态学算子:腐蚀(Erosion)与膨胀(Dilation)。为此,你将使用如下 OpenCV 函数:
    • cv::erode
    • cv::dilate

注意: 下面的说明出自 Bradski 与 Kaehler 所著的 Learning OpenCV 一书。

  • 简而言之:这是一组基于形状处理图像的运算。形态学运算对一个输入图像应用一个结构元素(structuring element),并生成一幅输出图像。
  • 最基本的形态学运算是:腐蚀与膨胀。它们有广泛的用途,例如:
    • 去除噪声
    • 分离单个元素,以及合并图像中彼此分离的元素。
    • 寻找图像中的亮度凸起或孔洞
  • 我们将以下图为例,简要说明膨胀与腐蚀:

Morphology_1_Tutorial_Theory_Dilation.png

  • 该运算将图像 AA 与某个核(BB)做卷积,核可以是任意形状或大小,通常为方形或圆形。

  • 核 BB 有一个定义好的锚点(anchor point),通常位于核的中心。

  • 当核 BB 在图像上滑动时,我们计算被 BB 覆盖区域内的最大像素值,并用该最大值替换锚点位置处的图像像素。可以推断,这种最大化操作会使图像中的亮区域“生长”(因此得名 dilation,膨胀)。

  • 膨胀运算为:dst(x,y)=max⁡(x′,y′): element(x′,y′)≠0src(x+x′,y+y′)\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')

  • 以上图为例,应用膨胀可以得到:

  • 字母的亮区域向背景的黑色区域膨胀。

Morphology_1_Tutorial_Theory_Erosion.png

  • 该运算是膨胀的姊妹运算。它在给定核的区域内计算局部最小值。
  • 当核 BB 在图像上滑动时,我们计算被 BB 覆盖区域内的最小像素值,并用该最小值替换锚点位置处的图像像素。
  • 腐蚀运算为:dst(x,y)=min⁡(x′,y′): element(x′,y′)≠0src(x+x′,y+y′)\texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')
  • 与膨胀的例子类似,我们可以对原始图像(如上所示)应用腐蚀算子。从下面的结果可以看到,图像中的亮区域变细,而暗区域变大。

本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例

这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。让我们先看一下这个 C++ 程序的总体结构:

  1. 载入一幅图像(可以是 BGR 或灰度图)
  2. 创建两个窗口(一个用于膨胀输出,另一个用于腐蚀)
  3. 为每种运算创建一组包含两个 Trackbar 的滑块:
    • 第一个 trackbar “Element” 返回 erosion_elem 或 dilation_elem
    • 第二个 trackbar “Kernel size” 返回对应运算的 erosion_size 或 dilation_size。
  4. 调用一次腐蚀与膨胀,以显示初始图像。

每次我们移动任意滑块时,用户的 Erosion 或 Dilation 函数都会被调用,并根据当前的 trackbar 值更新输出图像。

我们来分析这两个函数:

执行腐蚀运算的函数是 cv::erode。可以看到,它接收三个参数:

  • src:源图像

  • erosion_dst:输出图像

  • element:这是我们执行该运算所用的核。若不指定,默认是一个简单的 3x3 矩阵;否则我们可以指定其形状。为此,我们需要使用函数 cv::getStructuringElement:

    我们可以为核选择以下三种形状中的任意一种:

    • 矩形:MORPH_RECT

    • 十字形:MORPH_CROSS

    • 椭圆形:MORPH_ELLIPSE

    • 菱形:MORPH_DIAMOND

      然后,我们只需指定核的大小以及锚点。若不指定,则默认位于中心。

至此,我们就可以对图像执行腐蚀了。

代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。 这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。不过,我们先看一下该 Java 类的总体结构。这个 Java 类有 4 个主要部分:

  • 类的构造函数,用于设置待填充窗口组件的窗口
  • addComponentsToPane 方法,用于填充窗口
  • update 方法,用于决定用户更改任意值时的行为
  • main 方法,即程序的入口

本教程我们将重点介绍 addComponentsToPane 与 update 方法。不过为完整起见,构造函数中执行的步骤如下:

  1. 载入一幅图像(可以是 BGR 或灰度图)
  2. 创建一个窗口
  3. 用 addComponentsToPane 添加各类控件组件
  4. 显示窗口

各组件由以下方法添加:

简而言之,我们

  1. 为滑块创建一个面板
  2. 为元素类型创建一个组合框
  3. 为核大小创建一个滑块
  4. 为要使用的形态学函数(腐蚀或膨胀)创建一个组合框

添加的 action 和 state changed 监听器最终都会调用 update 方法,该方法会根据当前滑块值更新图像。因此每次我们移动任意滑块时,都会触发 update 方法。

我们使用如下实现来更新图像:

换言之,我们

  1. 获取用户选择的结构元素
  2. 根据 doErosion 执行 erosion 或 dilation 函数
  3. 重新载入应用了形态学运算后的图像
  4. 重绘 frame

我们来分析 erode 与 dilate 方法:

执行腐蚀运算的函数是 cv::erode。可以看到,它接收三个参数:

  • src:源图像

  • erosion_dst:输出图像

  • element:这是我们执行该运算所用的核。要指定其形状,我们需要使用函数 cv::getStructuringElement:

    我们可以为核选择以下三种形状中的任意一种:

    • 矩形:Imgproc.SHAPE_RECT

    • 十字形:Imgproc.SHAPE_CROSS

    • 椭圆形:Imgproc.SHAPE_ELLIPSE

      除了形状之外,我们还要指定核的大小以及锚点。若未指定锚点,则默认位于中心。

至此,我们就可以对图像执行腐蚀了。

代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。 这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。让我们先看一下这个 Python 脚本的总体结构:

  1. 载入一幅图像(可以是 BGR 或灰度图)
  2. 创建两个窗口(一个用于腐蚀输出,另一个用于膨胀),每个窗口都带有一组 trackbar
    • 第一个 trackbar “Element” 返回要映射的形态学类型值(1 = 矩形,2 = 十字形,3 = 椭圆形)
    • 第二个 trackbar “Kernel size” 返回对应运算的元素大小
  3. 调用一次腐蚀与膨胀,以显示初始图像

每次我们移动任意滑块时,用户的 erosion 或 dilation 函数都会被调用,并根据当前的 trackbar 值更新输出图像。

我们来分析这两个函数:

执行腐蚀运算的函数是 cv::erode。可以看到,它接收两个参数并返回处理后的图像:

  • src:源图像

  • element:我们执行该运算所用的核。我们可以通过函数 cv::getStructuringElement 来指定其形状:

    我们可以为核选择以下三种形状中的任意一种:

    • 矩形:MORPH_RECT
    • 十字形:MORPH_CROSS
    • 椭圆形:MORPH_ELLIPSE
    • 菱形:MORPH_DIAMOND

然后,我们只需指定核的大小以及锚点。若未指定锚点,则默认位于中心。

至此,我们就可以对图像执行腐蚀了。

代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。

注意: 此外,还有更多参数允许你一次执行多次腐蚀/膨胀(iterations),并能设置边界类型和边界值。不过在这个简单教程中我们并未用到它们。详情请参阅参考文档。

Morphology_1_Tutorial_Original_Image.jpg Morphology_1_Result.jpg

编译上面的代码并执行它(若使用 Python,则运行该脚本),以一幅图像作为参数。若不提供图像作为参数,则使用默认的示例图像(LinuxLogo.jpg)。

例如,使用这幅图像:

我们得到如下结果。改变 Trackbar 中的取值自然会产生不同的输出图像。动手试试看!你甚至可以尝试添加第三个 Trackbar 来控制迭代次数。

(根据所用编程语言的不同,输出可能会有细微差别,或者只有一个窗口)

Morphology_1_Tutorial_Theory_Original_Image.png