腐蚀与膨胀
本教程中你将学到如何:
- 应用两种非常常见的形态学算子:腐蚀(Erosion)与膨胀(Dilation)。为此,你将使用如下 OpenCV 函数:
- cv::erode
- cv::dilate
注意: 下面的说明出自 Bradski 与 Kaehler 所著的 Learning OpenCV 一书。
- 简而言之:这是一组基于形状处理图像的运算。形态学运算对一个输入图像应用一个结构元素(structuring element),并生成一幅输出图像。
- 最基本的形态学运算是:腐蚀与膨胀。它们有广泛的用途,例如:
- 去除噪声
- 分离单个元素,以及合并图像中彼此分离的元素。
- 寻找图像中的亮度凸起或孔洞
- 我们将以下图为例,简要说明膨胀与腐蚀:
膨胀(Dilation)
Section titled “膨胀(Dilation)”
-
该运算将图像 与某个核()做卷积,核可以是任意形状或大小,通常为方形或圆形。
-
核 有一个定义好的锚点(anchor point),通常位于核的中心。
-
当核 在图像上滑动时,我们计算被 覆盖区域内的最大像素值,并用该最大值替换锚点位置处的图像像素。可以推断,这种最大化操作会使图像中的亮区域“生长”(因此得名 dilation,膨胀)。
-
膨胀运算为:
-
以上图为例,应用膨胀可以得到:
-
字母的亮区域向背景的黑色区域膨胀。
腐蚀(Erosion)
Section titled “腐蚀(Erosion)”
- 该运算是膨胀的姊妹运算。它在给定核的区域内计算局部最小值。
- 当核 在图像上滑动时,我们计算被 覆盖区域内的最小像素值,并用该最小值替换锚点位置处的图像像素。
- 腐蚀运算为:
- 与膨胀的例子类似,我们可以对原始图像(如上所示)应用腐蚀算子。从下面的结果可以看到,图像中的亮区域变细,而暗区域变大。
本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例 本教程的代码如下所示。你也可以从 这里 下载。 代码见下方示例
这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。让我们先看一下这个 C++ 程序的总体结构:
- 载入一幅图像(可以是 BGR 或灰度图)
- 创建两个窗口(一个用于膨胀输出,另一个用于腐蚀)
- 为每种运算创建一组包含两个 Trackbar 的滑块:
- 第一个 trackbar “Element” 返回 erosion_elem 或 dilation_elem
- 第二个 trackbar “Kernel size” 返回对应运算的 erosion_size 或 dilation_size。
- 调用一次腐蚀与膨胀,以显示初始图像。
每次我们移动任意滑块时,用户的 Erosion 或 Dilation 函数都会被调用,并根据当前的 trackbar 值更新输出图像。
我们来分析这两个函数:
腐蚀函数(CPP)
Section titled “腐蚀函数(CPP)”执行腐蚀运算的函数是 cv::erode。可以看到,它接收三个参数:
-
src:源图像
-
erosion_dst:输出图像
-
element:这是我们执行该运算所用的核。若不指定,默认是一个简单的
3x3矩阵;否则我们可以指定其形状。为此,我们需要使用函数 cv::getStructuringElement:我们可以为核选择以下三种形状中的任意一种:
-
矩形:MORPH_RECT
-
十字形:MORPH_CROSS
-
椭圆形:MORPH_ELLIPSE
-
菱形:MORPH_DIAMOND
然后,我们只需指定核的大小以及锚点。若不指定,则默认位于中心。
-
至此,我们就可以对图像执行腐蚀了。
膨胀函数(CPP)
Section titled “膨胀函数(CPP)”代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。 这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。不过,我们先看一下该 Java 类的总体结构。这个 Java 类有 4 个主要部分:
- 类的构造函数,用于设置待填充窗口组件的窗口
addComponentsToPane方法,用于填充窗口update方法,用于决定用户更改任意值时的行为main方法,即程序的入口
本教程我们将重点介绍 addComponentsToPane 与 update 方法。不过为完整起见,构造函数中执行的步骤如下:
- 载入一幅图像(可以是 BGR 或灰度图)
- 创建一个窗口
- 用
addComponentsToPane添加各类控件组件 - 显示窗口
各组件由以下方法添加:
简而言之,我们
- 为滑块创建一个面板
- 为元素类型创建一个组合框
- 为核大小创建一个滑块
- 为要使用的形态学函数(腐蚀或膨胀)创建一个组合框
添加的 action 和 state changed 监听器最终都会调用 update 方法,该方法会根据当前滑块值更新图像。因此每次我们移动任意滑块时,都会触发 update 方法。
更新图像(Java)
Section titled “更新图像(Java)”我们使用如下实现来更新图像:
换言之,我们
- 获取用户选择的结构元素
- 根据
doErosion执行 erosion 或 dilation 函数 - 重新载入应用了形态学运算后的图像
- 重绘 frame
我们来分析 erode 与 dilate 方法:
腐蚀方法(Java)
Section titled “腐蚀方法(Java)”执行腐蚀运算的函数是 cv::erode。可以看到,它接收三个参数:
-
src:源图像
-
erosion_dst:输出图像
-
element:这是我们执行该运算所用的核。要指定其形状,我们需要使用函数 cv::getStructuringElement:
我们可以为核选择以下三种形状中的任意一种:
-
矩形:Imgproc.SHAPE_RECT
-
十字形:Imgproc.SHAPE_CROSS
-
椭圆形:Imgproc.SHAPE_ELLIPSE
除了形状之外,我们还要指定核的大小以及锚点。若未指定锚点,则默认位于中心。
-
至此,我们就可以对图像执行腐蚀了。
膨胀函数(Java)
Section titled “膨胀函数(Java)”代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。 这里展示的大部分内容都很简单(如有疑问,请参阅前面章节的教程)。让我们先看一下这个 Python 脚本的总体结构:
- 载入一幅图像(可以是 BGR 或灰度图)
- 创建两个窗口(一个用于腐蚀输出,另一个用于膨胀),每个窗口都带有一组 trackbar
- 第一个 trackbar “Element” 返回要映射的形态学类型值(1 = 矩形,2 = 十字形,3 = 椭圆形)
- 第二个 trackbar “Kernel size” 返回对应运算的元素大小
- 调用一次腐蚀与膨胀,以显示初始图像
每次我们移动任意滑块时,用户的 erosion 或 dilation 函数都会被调用,并根据当前的 trackbar 值更新输出图像。
我们来分析这两个函数:
腐蚀函数(Python)
Section titled “腐蚀函数(Python)”执行腐蚀运算的函数是 cv::erode。可以看到,它接收两个参数并返回处理后的图像:
-
src:源图像
-
element:我们执行该运算所用的核。我们可以通过函数 cv::getStructuringElement 来指定其形状:
我们可以为核选择以下三种形状中的任意一种:
- 矩形:MORPH_RECT
- 十字形:MORPH_CROSS
- 椭圆形:MORPH_ELLIPSE
- 菱形:MORPH_DIAMOND
然后,我们只需指定核的大小以及锚点。若未指定锚点,则默认位于中心。
至此,我们就可以对图像执行腐蚀了。
膨胀函数(Python)
Section titled “膨胀函数(Python)”代码如下。可以看到,它与 erosion 的代码片段完全类似。这里我们同样可以定义自己的核、锚点以及所用算子的大小。
注意: 此外,还有更多参数允许你一次执行多次腐蚀/膨胀(iterations),并能设置边界类型和边界值。不过在这个简单教程中我们并未用到它们。详情请参阅参考文档。

编译上面的代码并执行它(若使用 Python,则运行该脚本),以一幅图像作为参数。若不提供图像作为参数,则使用默认的示例图像(LinuxLogo.jpg)。
例如,使用这幅图像:
我们得到如下结果。改变 Trackbar 中的取值自然会产生不同的输出图像。动手试试看!你甚至可以尝试添加第三个 Trackbar 来控制迭代次数。
(根据所用编程语言的不同,输出可能会有细微差别,或者只有一个窗口)
