8 形态学
阈值分割(章节 7)的输出从来不是一幅干净的二值图。哪怕阈值选得再好,前景里总会留着几个椒粒般的小孔,边缘上挂着毛刺,两个本该分开的目标被一条细桥粘在一起,一个本该完整的目标又被一条细缝割成几段,背景上还散落着孤立的噪声点。这些瑕疵对人眼无关紧要,对程序却是灾难:后续的 Blob 分析(章节 23)会把每个噪声点都数成一个目标,把粘连的两个目标数成一个,把断裂的一个目标数成三个。数学形态学(mathematical morphology)就是修理二值图的那把扳手——它用一个小小的模板在图像上滑动,按集合运算的规则删除或补全像素,把分割结果修整成后续算法能直接消化的形状。
本章用一幅 480×360 的合成二值场景贯穿所有实验(图 8.1)。场景中的每一处瑕疵都是刻意安排的:一个内部带 1~2 px 椒孔、边缘带 1~2 px 毛刺的实心矩形;两个被一条 2 px 细桥粘连的团块;一个被 1 px 与 2 px 细缝割裂成三段的矩形;一条 3 px 厚的细长条;外加 10 个孤立噪声点(6 个 1×1、4 个 2×2)。全部坐标硬编码、不含随机数,因此每次运行的统计结果完全一致:前景共 47426 个像素,按 8 连通计 16 个 blob。
8.1 结构元与腐蚀膨胀
形态学的全部运算都围绕一个主角展开:结构元(structuring element)。它是一个带锚点(anchor)的小型形状模板——最常用的是 3×3、5×5 的矩形,锚点居中——运算时锚点逐像素扫过图像,结构元覆盖的邻域决定该像素的命运。结构元之于形态学,正如卷积核之于线性滤波(章节 6):模板的形状与尺寸决定了运算的全部性格。
最基本的两种运算是一对”反义词”。腐蚀(erosion)的集合定义为
\[ A \ominus B = \{\, z \mid B_z \subseteq A \,\}, \]
其中 \(A\) 是前景像素集合,\(B_z\) 表示把结构元 \(B\) 的锚点平移到位置 \(z\)。一句话翻译:只有当结构元能完整塞进前景时,锚点位置才保留为前景。任何容不下结构元的地方——细桥、毛刺、孤立小点、目标的最外一圈——统统被剥掉。膨胀(dilation)则相反:
\[ A \oplus B = \{\, z \mid B_z \cap A \neq \emptyset \,\}, \]
只要结构元与前景有一个像素相碰,锚点位置就被置为前景——前景向外长出一圈,细缝与小孔被对面长过来的前景填合。
腐蚀与膨胀互为对偶(duality):\((A \ominus B)^{c} = A^{c} \oplus \check{B}\),即腐蚀前景等价于膨胀背景。这不是数学装饰——它意味着”删除前景小结构”与”填补背景小缝隙”本质上是同一个运算从两侧看的结果,开、闭运算的对偶性(下节)正源于此。
对二值图而言还有一个更直观的等价视角:腐蚀就是邻域最小值滤波(min filter)——窗口里只要有一个背景(0),输出就是 0;膨胀就是邻域最大值滤波(max filter)——窗口里只要有一个前景(255),输出就是 255。这个视角到 小节 8.4 推广灰度形态学时会直接派上用场。
用 3×3 矩形结构元对测试场景各做一次,结果见 图 8.2。腐蚀后前景从 47426 px 降到 44233 px,blob 数从 16 个骤减到 7 个:10 个噪声点全部消失,毛刺被剥光,2 px 的细桥被切断——粘连的双团块分成了两个。代价同样肉眼可见:所有目标都瘦了一圈,椒孔被撑大,细缝变得更宽。膨胀则相反:前景增到 50603 px,椒孔被填平,1 px 与 2 px 的细缝都被合拢,但每个目标都胖了一圈,噪声点不仅没消失反而长大了。
两种运算各自只解决一半问题,又各自引入一个新问题——尺寸变了。在测量场合这是不可接受的:腐蚀一次,目标的面积与轮廓位置就系统性地偏小;膨胀一次则偏大。怎样既享受”删噪/填缝”的好处,又把尺寸还回来?答案是把两者串起来用。
8.2 开闭运算
开运算(opening)定义为先腐蚀、后膨胀:
\[ A \circ B = (A \ominus B) \oplus B, \]
闭运算(closing)则是先膨胀、后腐蚀:
\[ A \bullet B = (A \oplus B) \ominus B. \]
它们的设计思想是同一个:第一步把目标结构”杀掉或救活”,第二步把幸存者的尺寸近似还原。开运算中,腐蚀先删掉所有容不下结构元的小结构——它们一旦消失,后续膨胀无从恢复;而大块目标只是瘦了一圈,膨胀又把这一圈补回来。闭运算对偶地作用于背景:膨胀先填掉所有容不下结构元的小缝小孔,腐蚀再把胀大的目标收回原形。
开与闭都是幂等(idempotent)的:\((A \circ B) \circ B = A \circ B\),闭运算同理。做一次和做一百次结果完全相同——该删的第一次就删干净了,幸存的结构已经”容得下结构元”,再做只是恒等变换。这与平滑滤波”越滤越糊”的累积行为截然不同。
实验结果见 图 8.3,数字把”尺寸近似保持”展示得非常干脆。3×3 开运算后前景为 47225 px,相对原图 47426 px 仅减少 0.4%;3×3 闭运算后为 47801 px,仅增加 0.8%——对比单独腐蚀/膨胀约 ±6.7% 的变化,开闭运算把尺寸偏差压低了一个数量级。而且这一减一增分毫不差:开运算删掉的 201 px 恰好等于 10 个噪声点(22 px)、三根毛刺(59 px)与细桥(120 px)的面积之和——矩形主体一个像素都没少;闭运算补上的 375 px 恰好等于两条细缝(360 px)与全部椒孔(15 px)的面积之和。
blob 计数进一步揭示了两者的分工。开运算后剩 7 个 blob:10 个噪声点没了,细桥断了(粘连一分为二),毛刺也除净了——但矩形内部的椒孔还在。开运算不填孔:它只删除前景里的小凸出结构,对背景里的小孔无能为力。闭运算后剩 14 个 blob:1 px 和 2 px 的缝都合上了,被割裂的三段重新连成一段,椒孔也填平了——但 10 个噪声点一个没少。闭运算不去毛刺、不除噪点。这正是对偶性的体现:开运算修理前景的”多余”,闭运算修理背景的”多余”,谁也替代不了谁。实际工程里两者经常串联使用——先开后闭,先扫净噪声再缝合断缝。本章全部运算的计数结果汇总于 表 8.1。
| 运算 | 前景像素 | 相对原图 | blob 数(8 连通) |
|---|---|---|---|
| 原始场景 | 47426 | — | 16 |
| 腐蚀 3×3 | 44233 | −6.7% | 7 |
| 膨胀 3×3 | 50603 | +6.7% | — |
| 开运算 3×3 | 47225 | −0.4% | 7 |
| 闭运算 3×3 | 47801 | +0.8% | 14 |
| 开运算 5×5 | 46025 | −3.0% | 6 |
8.3 结构元尺寸的选择
开闭运算把”删什么、留什么”的决定权完全交给了结构元的尺寸——容不下结构元的死,容得下的活。那么结构元是不是越大越保险?把开运算的结构元从 3×3 换成 5×5 再跑一次(图 8.4):前景降到 46025 px,blob 只剩 6 个。少掉的那一个不是噪声——是那条 3 px 厚的细长条。它容得下 3×3 的结构元,却容不下 5×5,于是被连根抹掉了。如果这条”细长条”是产品上的一条引线或一道真实划痕,检测系统就在预处理阶段亲手销毁了证据。
由此得到本章最重要的工程法则:结构元必须大于要删除的噪声、小于要保留的最小结构。本例中噪声最大 2×2、最小真实结构 3 px 厚,3×3 恰好落在夹缝里,5×5 则越界了。如果两个边界冲突——噪声颗粒比最小目标还大——那么尺寸这一个维度已经分不开它们,不要硬调结构元,应当换用形状各异的结构元(细长目标配细长结构元)或把残余噪声留给后续的面积筛选。
这条法则与 章节 6 中值滤波的核尺寸法则同构:中值核必须小于要保留的最小结构宽度的两倍,否则细线连同噪声一起被抹掉。这不是巧合——开运算与中值滤波同属”按尺寸排序取舍”的非线性滤波,它们都不认识”缺陷”与”噪声”,只认识尺寸。
8.4 灰度形态学与顶帽
小节 8.1 提到的 min/max 视角让形态学自然地走出二值世界:对灰度图像,灰度腐蚀(grayscale erosion)就是取结构元邻域内的灰度最小值,灰度膨胀(grayscale dilation)取最大值。灰度开运算(先 min 后 max)会削平所有”宽度容不下结构元”的亮峰,而对大面积的背景起伏几乎不动——换句话说,灰度开运算是一个只保留背景的运算。
这立刻派生出一件工业利器。白顶帽变换(white top-hat)定义为原图减去其灰度开运算:
\[ T_b(f) = f - (f \circ b). \]
\(f \circ b\) 是”被抹掉小亮结构的背景估计”,原图减去它,剩下的恰好是那些小亮结构本身——背景无论是平的、斜的还是弯的,都被减干净了。顶帽对背景亮度水平天然免疫,这正是 章节 4 里阴影补正想达到的目标:可以把顶帽理解为”自带背景估计的小目标提取”,背景模型不用另外拍摄、不用拟合,就藏在开运算里。
实验场景(图 8.5)刻意设计成让 章节 7 的全局阈值必败:背景是从 30 到 160 的水平渐变,其上叠加 16 个比局部背景亮 70 个灰度级的小亮斑(2×2 到 5×5 不等)。位于图像左侧的最暗亮斑峰值只有约 116,而右侧最亮处的背景已经是 160——亮斑比背景还暗,不存在能分开两者的全局阈值。用 11×11 的结构元(大于最大的 5×5 亮斑)做白顶帽后,渐变背景被整体减除,再用阈值 35 二值化,结果恰好提取出 16 个 blob、共 216 px——与 16 个亮斑的面积真值(\(4\times(4+9+16+25)=216\))完全一致,一个不多、一个不少。顶帽的结构元尺寸法则与上节相同:必须大于要提取的目标,目标才会被开运算从背景估计中抹掉、从而出现在差值里。
8.5 SciVision 实现
SciVision SDK 的形态学功能由 SciMorphStructure(结构元)与 SCIMV::SciSvMorphology(运算)两个类提供,与本章实验一致的调用方式如下:
// 结构元:CreateElement(rows, cols, anchorX, anchorY, shape, data)
SciMorphStructure se3, se11;
long rc = se3.CreateElement(3, 3, 1, 1, SCI_MS_RECT); // 3×3 矩形,锚点(1,1)居中
if (rc) { /* 返回码必须检查 */ }
rc = se11.CreateElement(11, 11, 5, 5, SCI_MS_RECT); // 顶帽用 11×11
SciROI roi;
SciPoint tl(0, 0), br(W, H); // GenRect1 右下角为排他端点:全图须传 (W,H)
roi.GenRect1(tl, br); // 接收非 const 引用,须用具名变量
SCIMV::SciSvMorphology m;
SciImage dErode, dOpen, dTop;
rc = m.Erode(src, roi, se3, 1, &dErode); // 腐蚀,iteration = 1
rc = m.Open (src, roi, se3, 1, &dOpen); // 开运算
rc = m.TopHat(gsrc, roi, se11, 1, &dTop); // 白顶帽CreateElement 的前两个参数是结构元的行数与列数,anchorX/anchorY 指定锚点位置(常规用法取中心,如 3×3 取 (1,1)、11×11 取 (5,5));shape 取 SCI_MS_RECT 表示实心矩形,最后的 int* data 参数仅在自定义形状时使用,矩形核可缺省。Erode/Dilate/Open/Close/TopHat 五个接口签名一致:输入图像、ROI、结构元、迭代次数与输出指针。iteration 参数表示运算重复执行的次数——对矩形结构元,\(n\) 次 3×3 腐蚀等价于一次 \((2n+1)\times(2n+1)\) 腐蚀,可以用小核迭代代替大核;但对开闭运算,由于幂等性,iteration 大于 1 与等于 1 的结果可能并无差别,通常固定传 1。
一个必须如实交代的陷阱:SciROI::GenRect1 的右下角是排他端点(章节 7 的 SciVision 一节已记录过同一语义)。若按”最后一个像素坐标”的直觉传 \((W-1,H-1)\),最后一行与最后一列就被排除在 ROI 之外、不被处理——形态学输出在该边带上恒为 0,顶帽差值 \(f - 0\) 退化为原图灰度,渐变背景较亮的一侧会留下一条足以击穿阈值的高响应伪影。这条伪影很容易被误读成”SDK 不处理最外一行/列”的缺陷;其实传 \((W,H)\)(或干脆使用未定义 ROI)后 SDK 处理的就是完整图像,伪影随之消失。本章实验全部以 \((W,H)\) 全图 ROI 运行。
工业案例:焊点检测的开运算救场
某 PCB 焊点检测工位上,锡膏飞溅在板面留下大量 1~3 px 的小亮颗粒,阈值分割后全部成为前景,blob 计数比真实焊点数虚高数倍。在分割与计数之间插入一步 3×3 开运算后,飞溅颗粒被除净,计数恢复正常——教科书式的开运算应用。三个月后有工程师把结构元改成 7×7,理由是”更保险”:飞溅确实除得更干净了,但产线随即漏检——该产品最小规格的焊点直径仅 6 px,容不下 7×7 的结构元,被连同飞溅一起抹掉了。复盘结论写进了部门规范:结构元的尺寸上限必须从工艺文件的最小焊点尺寸反推,而不是从”噪声除得干不干净”正推;残余飞溅交给后续面积筛选,比放大结构元安全得多。
8.6 小结
- 形态学是阈值分割与 Blob 分析之间的修理工序:腐蚀按”结构元须完整放入前景”删像素,膨胀按”结构元与前景相碰即置位”补像素;二者互为对偶,等价于邻域 min/max 滤波,但单独使用都会系统性地改变目标尺寸(本章实验约 ±6.7%)。
- 开=先腐后膨,删噪点、毛刺、细桥而不填孔;闭=先膨后腐,缝细缝、填小孔而不除噪点。两者都把尺寸偏差压到 ±1% 以内(47426 → 47225 / 47801),且均幂等——做一次就到位。
- 结构元尺寸法则:大于要删的噪声、小于要保的结构(3×3 保住 3 px 细条,5×5 抹掉它)。这与中值滤波的核尺寸法则同构——非线性滤波只认尺寸,不认语义。
- 白顶帽 \(f-(f\circ b)\) 是自带背景估计的小亮目标提取,对照明梯度免疫:全局阈值必败的渐变场景,顶帽后用一个固定阈值恰好提出全部 16 个亮斑、216 px 与真值吻合。结构元须大于待提取目标。
- 工程提醒:
GenRect1的右下角是排他端点——传 \((W-1,H-1)\) 会把最后一行/列排除在处理之外,顶帽在该边带退化为原图灰度;全图 ROI 必须传 \((W,H)\)。
数学形态学的理论源头是 Serra 的奠基性专著 (Serra 1982),Soille 的著作则系统覆盖了灰度形态学及其应用 (Soille 2004);二值与灰度形态学算子的统一综述可参阅 Haralick、Sternberg 与 Zhuang 的经典论文 (Haralick, Sternberg, 和 Zhuang 1987)。形态学的完整理论体系(击中击不中变换、测地重建、分水岭等进阶专题)可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。








