OpenCV 形态学变换进阶教程:Opening、Closing、Morphological Gradient、Top Hat 与 Black Hat 的 morphologyEx 实战解析
2026/9/7 2:11:24 网站建设 项目流程

OpenCV 形态学变换进阶教程:Opening、Closing、Morphological Gradient、Top Hat 与 Black Hat 的 morphologyEx 实战解析

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

本教程面向已掌握腐蚀(erosion)与膨胀(dilation)基础的开发者,系统讲解 OpenCV 中基于cv::morphologyEx实现的五种高级形态学变换——开运算(Opening)、闭运算(Closing)、形态学梯度(Morphological Gradient)、顶帽(Top Hat)与黑帽(Black Hat)。结合当前仓库中 Morphology_2.cpp 交互式演示程序的完整源码与modules/imgproc/src/morph.dispatch.cpp底层实现,你将掌握五种算子的数学定义、适用场景、核(结构化元素)选取技巧,并能写出属于自己的可交互调试工具。

本教程依据 OpenCV 官方文档 opening_closing_hats.markdown(原作者 Ana Huamán,适用于 OpenCV ≥ 3.0,当前仓库以 5.x 代码为准)整理并扩展。

学习目标

学完本教程你将能够:

  • 使用 OpenCV 函数cv::morphologyEx完成以下五种形态学变换:
    • 开运算 Opening
    • 闭运算 Closing
    • 形态学梯度 Morphological Gradient
    • 顶帽 Top Hat
    • 黑帽 Black Hat
  • 理解这五种变换与基础腐蚀/膨胀操作之间的推导关系,明白各自的数学本质;
  • 使用cv::getStructuringElement构造矩形、十字、椭圆等不同形状的结构化元素,理解morph_operatormorph_elemmorph_size三类核心参数对结果的影响;
  • 参考仓库中的 C++ / Python 交互式示例,搭建带轨迹条(Trackbar)的实时预览程序。

理论:从腐蚀与膨胀出发的五种高级形态学算子

上一教程(Erosion 与 Dilation)中我们已掌握两种最基本的形态学操作。在此基础上,通过把腐蚀与膨胀进行组合求差,就能派生出功能更丰富的五种变换。下面逐一介绍(理论表述源自 Bradski 与 Kaehler 所著《Learning OpenCV》)。

1. 开运算 Opening

开运算由先腐蚀后膨胀得到:

dst = open(src, element) = dilate(erode(src, element))

适用场景:移除图像中的细小物体(假设目标是亮背景上的亮色小目标)。上图中左侧原图经开运算后,右侧结果里的小点已经消失,而主体较大目标的形状基本保持。

2. 闭运算 Closing

闭运算由先膨胀后腐蚀得到:

dst = close(src, element) = erode(dilate(src, element))

适用场景:移除目标内部细小的孔洞(暗色区域)。例如对二值分割结果中目标内部的空洞,或字符笔画中的断点,闭运算都有很好的连接与填补效果。

3. 形态学梯度 Morphological Gradient

形态学梯度定义为图像膨胀与腐蚀之差

dst = morph_grad(src, element) = dilate(src, element) - erode(src, element)

适用场景:提取目标的轮廓(outline)。膨胀会把边界向外扩张,腐蚀会把边界向内收缩,两者之差恰好"勾勒"出目标边界带的宽度,等价于一种与核形状相关的边缘检测手段。

4. 顶帽 Top Hat

顶帽变换定义为输入图像与其开运算之差

dst = tophat(src, element) = src - open(src, element)

适用场景:由于开运算会移除比核小的亮目标,原图减去开运算结果后,剩下的恰好是这些被"过滤掉"的小而亮的结构。顶帽常用于不均匀光照背景下的亮细节提取。

5. 黑帽 Black Hat

黑帽变换定义为闭运算与输入图像之差

dst = blackhat(src, element) = close(src, element) - src

适用场景:闭运算会填补暗色孔洞,用闭运算结果减去原图,即得到被填补的那些暗色细节。黑帽与顶帽互为"补"关系——前者突出暗特征,后者突出亮特征。

深入底层:morphologyEx 如何组合这些算子

cv::morphologyEx是执行上述全部五种变换的统一入口,在头文件 imgproc.hpp 中定义了枚举类型MorphTypes

枚举值数值含义
MORPH_OPEN2开运算
MORPH_CLOSE3闭运算
MORPH_GRADIENT4形态学梯度
MORPH_TOPHAT5顶帽
MORPH_BLACKHAT6黑帽

注意数值范围是 2–6,这正是示例代码中要把轨迹条取值+2的原因(详见下文)。而ElementShape枚举则定义了结构化元素的形状:MORPH_RECT = 0MORPH_CROSS = 1MORPH_ELLIPSE = 2MORPH_DIAMOND = 3(钻石形按曼哈顿距离定义)。

从 morph.dispatch.cpp 的源码可以看出,高级算子本质上都是基础腐蚀/膨胀的组合,而非全新的算法内核:

  • MORPH_OPEN:先用当前核完成一次MORPH_ERODE(结果写入临时缓冲_temp),再对_temp执行MORPH_DILATE得到输出;
  • MORPH_CLOSE:顺序与开运算相反,先MORPH_DILATE到临时缓冲,再MORPH_ERODE得最终结果;
  • MORPH_GRADIENT:先腐蚀得到临时结果temp,再膨胀,并在膨胀阶段利用MORPH_GRADIENT模式与临时结果做差;
  • MORPH_TOPHAT:先完成一次开运算(腐蚀 + 膨胀到_temp),随后用MORPH_TOPHAT模式以原图_src减去该开运算结果;
  • MORPH_BLACKHAT:先完成一次闭运算(膨胀 + 腐蚀到_temp),随后用MORPH_BLACKHAT模式让闭运算结果减去原图_src

可以看到,OpenCV 在实现层面把 "先 erode 再 dilate"、"两图求差" 等公共步骤下沉到同一条 OpenCL(ocl_morphOp)与 CPU 通用路径中,不同的MorphTypes只决定了中间缓冲的编排次序和最终的加减方向。因此,腐蚀/膨胀越熟练,对这五种算子的掌控就越直接。

示例程序逐段剖析

官方文档在 C++、Java、Python 三个语言分支下各提供了一份演示程序。本仓库中可确认的有 C++ 版 Morphology_2.cpp 与 Python 版 morphology_2.py。程序的整体结构是:

  1. 加载一张输入图像;
  2. 创建显示窗口;
  3. 创建三条轨迹条(Trackbar)分别控制:算子类型 Operator元素/核形状 Element核尺寸 Kernel Size
  4. 拖动任意滑块都会触发回调函数Morphology_Operations,按当前参数重算形态学结果并刷新显示。

主函数与轨迹条创建(C++)

#include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include <iostream> using namespace cv; Mat src, dst; int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; int const max_elem = 3; int const max_kernel_size = 21; const char* window_name = "Morphology Transformations Demo"; int main( int argc, char** argv ) { CommandLineParser parser( argc, argv, "{@input | baboon.jpg | input image}" ); src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); if (src.empty()) { std::cout << "Could not open or find the image!\n" << std::endl; std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl; return EXIT_FAILURE; } namedWindow( window_name, WINDOW_AUTOSIZE ); // 轨迹条1:选择形态学算子(0-4,映射到 MORPH_OPEN..MORPH_BLACKHAT) createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations ); // 轨迹条2:选择结构化元素形状(0: Rect - 1: Cross - 2: Ellipse - 3: Diamond) createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond", window_name, &morph_elem, max_elem, Morphology_Operations ); // 轨迹条3:选择核尺寸(滑块值 n,实际核尺寸为 2n+1,上限 21) createTrackbar( "Kernel size:\n 2n +1", window_name, &morph_size, max_kernel_size, Morphology_Operations ); Morphology_Operations( 0, 0 ); // 默认参数先执行一次 waitKey(0); return 0; }

三个轨迹条的含义与取值范围如下表:

轨迹条控制变量取值范围说明
Operatormorph_operator0–40=Opening,1=Closing,2=Gradient,3=Top Hat,4=Black Hat
Elementmorph_elem0–30=Rect,1=Cross,2=Ellipse,3=Diamond
Kernel Sizemorph_size0–21滑块值n对应实际边长2n+1

其中 C++ 分支把morph_elem直接作为getStructuringElementshape参数传入(支持到 Diamond);Python 分支则用 if/elif 把轨迹条取值显式映射为cv.MORPH_RECT / MORPH_CROSS / MORPH_ELLIPSE / MORPH_DIAMOND。两条轨迹条结构高度一致,体现同一套 UI 交互逻辑的多语言复刻。

核心回调 Morphology_Operations

void Morphology_Operations( int, void* ) { // 由于 MORPH_X 的枚举值从 2 开始(2,3,4,5,6),滑块值需 +2 int operation = morph_operator + 2; // 依据当前形状与尺寸构造结构化元素,锚点放在核中心 Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size + 1 ), Point( morph_size, morph_size ) ); // 应用指定形态学操作 morphologyEx( src, dst, operation, element ); imshow( window_name, dst ); }

回调里只显式传了四个参数:

  • src:输入图像;
  • dst:输出图像;
  • operation:形态学变换类型。注意由于MorphTypes中开/闭/梯度/顶帽/黑帽的取值是 2–6,因此这里将轨迹条值morph_operator加上偏移 (+2) 再传入,而不是直接用原始滑块值;
  • element:参与运算的结构化元素(核),由getStructuringElement生成,其余参数(迭代次数、边界模式等)均保持默认值。

Python 等价实现

仓库同样提供了 Python 版本 morphology_2.py。它先建立算子字典:

morph_op_dic = {0: cv.MORPH_OPEN, 1: cv.MORPH_CLOSE, 2: cv.MORPH_GRADIENT, 3: cv.MORPH_TOPHAT, 4: cv.MORPH_BLACKHAT}

回调函数读取三条轨迹条并执行一次形态学运算:

def morphology_operations(val): morph_operator = cv.getTrackbarPos(title_trackbar_operator_type, title_window) morph_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_window) morph_elem = 0 val_type = cv.getTrackbarPos(title_trackbar_element_type, title_window) if val_type == 0: morph_elem = cv.MORPH_RECT elif val_type == 1: morph_elem = cv.MORPH_CROSS elif val_type == 2: morph_elem = cv.MORPH_ELLIPSE elif val_type == 3: morph_elem = cv.MORPH_DIAMOND element = cv.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size + 1), (morph_size, morph_size)) operation = morph_op_dic[morph_operator] dst = cv.morphologyEx(src, operation, element) cv.imshow(title_window, dst)

程序通过argparse接收--input参数(默认LinuxLogo.jpg),随后依次创建三个轨迹条并立即触发一次默认运算:

cv.namedWindow(title_window) cv.createTrackbar(title_trackbar_operator_type, title_window, 0, max_operator, morphology_operations) cv.createTrackbar(title_trackbar_element_type, title_window, 0, max_elem, morphology_operations) cv.createTrackbar(title_trackbar_kernel_size, title_window, 0, max_kernel_size, morphology_operations) morphology_operations(0) cv.waitKey()

对比可见:C++ 用"整型全局变量 + 取地址"被轨迹条直接绑定,Python 则通过cv.getTrackbarPos在回调内读取滑块位置;核尺寸换算2n+1与锚点(n, n)两边完全一致。

结构化元素详解:getStructuringElement

五种算子都依赖"核"来定义邻域作用范围,核由getStructuringElement(shape, ksize, anchor)生成:

  • shape:元素形状。示例中可取MORPH_RECT(矩形,全部邻域参与)、MORPH_CROSS(十字形,仅轴向邻域参与)、MORPH_ELLIPSE(椭圆/近似圆形)、MORPH_DIAMOND(按曼哈顿距离定义的菱形);
  • ksize:核尺寸。示例中为(2*morph_size+1, 2*morph_size+1),保证核边长为奇数,锚点能落在正中心;
  • anchor:锚点位置。示例传入Point(morph_size, morph_size),即核中心。

实际调参时可以记住:核越大,"过滤"或"变形"的尺度越大。矩形核作用最"钝"(完全邻域取 min/max),十字核只在水平/垂直方向作用,椭圆核更接近各向同性的圆,适合处理圆形目标;不同形状会影响边界平滑程度与方向敏感性。

运行与效果解读

编译 C++ 示例后,传入一张图像路径即可启动:

./Morphology_2 <input image>

Python 示例的启动方式为:

python morphology_2.py --input <input image>

在出现的结果窗口中拖动三条轨迹条,即可实时观察五种算子、四种核形状与 1×1 到 21×21 核尺寸的叠加效果。官方文档结果图即来自该交互式窗口的抓屏:

结合原图(Morphology_2_Tutorial_Original_Image.jpg)可以直观看到:十字形核进行开运算时按轴向结构筛选目标;椭圆核进行黑帽变换时,提取的是闭运算补上的暗色小结构。建议在调试时固定一张图,只改变一个滑块,体会单一参数变化带来的差异。

应用场景与调参小结

把五种算子放入实际图像处理流程中,它们的分工大致是:

算子典型用途
Opening 开运算去噪点、分离细小粘连、去掉比核小的亮目标
Closing 闭运算填补目标内部小洞、连接断裂笔画、平滑暗色凹陷
Morphological Gradient提取目标轮廓/边界,比单纯差分对噪声更稳健
Top Hat不均匀光照下提取亮色细节(如文字、细小亮斑)
Black Hat提取暗色细节,常配合顶帽做形态学重建的前处理

实践中注意三点:

  1. 先降噪再选算子:形态学对椒盐类噪声敏感,必要时先用高斯模糊或中值滤波预处理;
  2. 核尺寸需匹配目标尺度:开/闭运算本质上是以核尺寸为"标尺"筛选结构,核应略大于要剔除/填补的特征尺度;
  3. 顶帽/黑帽对光照补偿友好:它们是"减法型"算子,在分块处理前用于消除缓变背景很有价值,可与阈值化(cv::threshold)配合完成不均匀光照下的二值化。

小结

本教程沿着"腐蚀/膨胀 → 五种高级形态学算子 →morphologyEx底层组合逻辑 → 可交互示例程序 → 实际调参"的脉络,完整演示了MORPH_OPENMORPH_CLOSEMORPH_GRADIENTMORPH_TOPHATMORPH_BLACKHAT五种变换。核心要点是:它们都是腐蚀与膨胀在时序与减法上的组合,算子枚举值从 2 起跳,核形状与核尺寸决定作用的尺度与方向。读者可直接运行 Morphology_2.cpp 或 morphology_2.py 亲手验证,并结合 morph.dispatch.cpp 的源码理解其内部编排,为后续学习击中/击不中变换(Hit-or-Miss)等进阶形态学内容打好基础。

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询