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_operator、morph_elem、morph_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_OPEN | 2 | 开运算 |
MORPH_CLOSE | 3 | 闭运算 |
MORPH_GRADIENT | 4 | 形态学梯度 |
MORPH_TOPHAT | 5 | 顶帽 |
MORPH_BLACKHAT | 6 | 黑帽 |
注意数值范围是 2–6,这正是示例代码中要把轨迹条取值+2的原因(详见下文)。而ElementShape枚举则定义了结构化元素的形状:MORPH_RECT = 0、MORPH_CROSS = 1、MORPH_ELLIPSE = 2、MORPH_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。程序的整体结构是:
- 加载一张输入图像;
- 创建显示窗口;
- 创建三条轨迹条(Trackbar)分别控制:算子类型 Operator、元素/核形状 Element、核尺寸 Kernel Size;
- 拖动任意滑块都会触发回调函数
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; }三个轨迹条的含义与取值范围如下表:
| 轨迹条 | 控制变量 | 取值范围 | 说明 |
|---|---|---|---|
| Operator | morph_operator | 0–4 | 0=Opening,1=Closing,2=Gradient,3=Top Hat,4=Black Hat |
| Element | morph_elem | 0–3 | 0=Rect,1=Cross,2=Ellipse,3=Diamond |
| Kernel Size | morph_size | 0–21 | 滑块值n对应实际边长2n+1 |
其中 C++ 分支把morph_elem直接作为getStructuringElement的shape参数传入(支持到 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 | 提取暗色细节,常配合顶帽做形态学重建的前处理 |
实践中注意三点:
- 先降噪再选算子:形态学对椒盐类噪声敏感,必要时先用高斯模糊或中值滤波预处理;
- 核尺寸需匹配目标尺度:开/闭运算本质上是以核尺寸为"标尺"筛选结构,核应略大于要剔除/填补的特征尺度;
- 顶帽/黑帽对光照补偿友好:它们是"减法型"算子,在分块处理前用于消除缓变背景很有价值,可与阈值化(
cv::threshold)配合完成不均匀光照下的二值化。
小结
本教程沿着"腐蚀/膨胀 → 五种高级形态学算子 →morphologyEx底层组合逻辑 → 可交互示例程序 → 实际调参"的脉络,完整演示了MORPH_OPEN、MORPH_CLOSE、MORPH_GRADIENT、MORPH_TOPHAT、MORPH_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),仅供参考