形态学与连通域分析:先定义物理尺度,再决定哪些像素属于同一缺陷


开闭运算可以让缺陷图更干净,也可能把细裂纹删除或把相邻目标合并。本文从像素尺度、邻域连通性和图像边界出发,推导核尺寸与面积阈值,给出可复现的小矩阵实验和适合工程验收的参数检查流程。

一个“去噪”参数如何变成漏检原因

二值缺陷图有许多零散亮点,开发时加入 5×5 开运算,图像立即干净。上线之后,宽度约 3 像素的细划痕也消失了。算法按设定删除了无法容纳结构元素的区域,而任务把这些区域定义成必须检出的缺陷。

形态学处理不是无损美化。必须先说明前景含义、最小目标、可接受间隙和像素对应的物理尺度,再讨论核大小。本文将缺陷定义为白色前景,背景为零,主要讨论固定倍率下的二维检测;透视变化明显时,应先建立空间变化的尺度模型。

核尺寸必须与物理尺度一起保存

假设物方采样为 0.02 mm/像素,最小必须保留的裂纹宽度为 0.06 mm,则只有约 3 像素。一个 5×5 方核先腐蚀时,需要局部至少有足够大的完整白色邻域,这类细线很容易被删除。

若相机分辨率翻倍且视场相同,原来 3 像素的目标变成 6 像素。继续使用相同的 5×5 核,物理含义已经变了。反过来,ROI 裁剪不会自动改变像素尺度;真正的缩放、像素合并和镜头倍率变化才会改变它。参数文件里应把采样尺度与处理分辨率一起保存。

用二维坐标表达结构元素时,矩形核对方向敏感,圆盘或离散椭圆核更接近各向同性,但离散化之后仍不是连续圆。细长核可以针对某个方向连接断裂,却会对旋转目标产生不同结果。因此“用椭圆核就没有方向偏差”也需要通过旋转样例验证。

从集合操作理解开闭运算

对二值前景集合 A 和结构元素 B,腐蚀保留那些 B 放进去后仍完全位于 A 内的位置;膨胀则扩张前景支持范围。开运算是先腐蚀再膨胀,闭运算是先膨胀再腐蚀。算子和边界参数可参照 OpenCV 图像滤波及形态学接口。

一个半径 r 像素的膨胀,会让两侧前景向间隙扩张。对简单平行边界,间隔约不大于 2r 的区域可能被连接;闭运算能保留连接后的桥。因此先闭运算再统计连通域,会改变目标数量。连接是否合理,需要由“这是同一个破损目标还是两个独立缺陷”的任务定义决定。

下面是一段不依赖第三方库的 Python 3 实验。使用 3×3 方核,图外固定为背景;它仅用于解释操作语义,处理大图时应使用经过优化的图像库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def morph(a, mode):
h, w = len(a), len(a[0])
out = [[0] * w for _ in range(h)]
for y in range(h):
for x in range(w):
values = []
for dy in (-1, 0, 1):
for dx in (-1, 0, 1):
yy, xx = y + dy, x + dx
values.append(a[yy][xx] if 0 <= yy < h and 0 <= xx < w else 0)
out[y][x] = int(all(values) if mode == "erode" else any(values))
return out

image = [[0] * 11 for _ in range(9)]
for y in range(1, 8):
image[y][3] = 1 # 单像素细线
for y in range(3, 6):
for x in range(6, 9):
image[y][x] = 1 # 3×3 实心块

opened = morph(morph(image, "erode"), "dilate")
assert sum(map(sum, image)) == 16
assert sum(map(sum, opened)) == 9
print("细线消失,实心块保留")

这个例子也说明面积相同不代表形态学结果相同。九个像素组成的 3×3 方块可以保留,九个像素组成的细线可能完全消失。不能根据“噪点面积小”就推导“开运算只删除小面积目标”。

连通性不是实现细节

考虑下面两个对角接触的像素:

1
2
1 0
0 1

4 邻域只承认上下左右连接,结果是两个目标;8 邻域还承认对角连接,结果是一个目标。对细裂纹,8 邻域通常更容易保持斜向链条;对紧邻颗粒,它也更容易把仅在角点接触的对象合并。没有一个选择适合所有任务。

OpenCV 的 connectedComponentsWithStats 可返回每个标签的外接框、像素面积和质心,标签 0 表示背景;输入是 8 位单通道图像。接口含义见 OpenCV 连通域与形状分析文档。不要把背景面积作为最大缺陷,也不要依赖标签编号长期对应同一目标。

如果希望“斜向细线算一条、对角颗粒又算两个”,仅切换 4/8 邻域无法同时表达这个语义,可能需要距离、方向、细化或基于形状的二次判定。应先将冲突样例加入验收集,再引入更复杂算法。

面积阈值怎样换算成平方毫米

当两个方向物方采样为 sx、sy mm/像素,像素计数 N 的面积为 A=N·sx·sy。假设两方向均为 0.02 mm/像素,要求目标面积至少 0.03 mm²,最低像素面积为 ceil(0.03/0.0004)=75。

面积阈值能抑制小颗粒,但对长而细的裂纹未必合适。长度 2 mm、宽 0.02 mm 的缺陷面积只有 0.04 mm²,却可能比一个更大的圆点严重。目标筛选应同时考虑长度、宽度、方向或任务相关形状,不宜只按面积一刀切。

连通域像素面积也不同于轮廓多边形面积。前者统计被分类为前景的像素格数量,后者对边界坐标积分;小目标、细线和含孔目标可能存在明显差异。验收规范必须明确采用哪种定义,不能把两个函数输出混用。

图像边界代表未知区域,还是已知背景

上述示例显式把图外设为背景,这意味着目标贴着边界时可能被腐蚀。图像库的默认形态学边界可能使用特殊值,并不等同于显式补零;工程代码应写清 borderType、borderValue 或先填充边界,避免依赖隐含行为。

更关键的问题是裁剪目标的测量含义。一个缺陷与 ROI 边界相交,其真实面积可能还在视场之外。把它当普通完整目标,会系统性低估面积;直接删除它,又可能漏掉严重缺陷。可以增加 touches_border 字段,将其判为“检测到但尺寸不完整”,或扩大 ROI 后重新测量。

若采用填充,最后要将坐标转换回原图,并在去除填充后判断是否碰原始边界。对整个场景边缘和内部 ROI 边缘还应区分:内部 ROI 外侧仍有原始图信息时,应优先利用真实邻域,而不是制造一圈黑边。

不改变边界的筛选与改变形状的处理

若需求只是去掉面积很小的独立颗粒,可以先做连通域再按面积筛选,这会保留其他目标原有的边界;开运算则会修改保留目标的局部轮廓。两者都能减少小区域,但对细长结构、尖角和尺寸测量的影响不同,应按任务选择。

如果必须用闭运算把一个目标内部断裂连接起来,可以用处理后的图确定候选区域,再回到原始二值图统计像素面积。这样可以避免把人为填补的桥直接当作真实缺陷面积,不过仍需处理两个真实目标被合并的情况。候选生成与尺寸计量使用不同掩膜时,应明确标注,便于追踪每个统计量的来源。

用参数扰动检查算法是否靠偶然工作

建议准备四类人工可控样例:最小宽度细线、间隙接近阈值的相邻目标、边界截断目标、不同旋转角度的相同形状。再加入真实图像中的低对比度和不均匀照明情况,分别记录检测数量、面积、合并与拆分错误。

把核尺寸从 3 调到 5、阈值上下扰动少量灰度、面积界限上下变化 10%,观察输出是否剧烈跳变。若业务结论只在一个非常窄的参数点成立,说明系统缺少鲁棒余量。应回查采样分辨率、照明和目标定义,而不只是继续寻找一个更精巧的常数。

最终输出最好包含原始掩膜、处理后掩膜、目标统计和被剔除原因。发生漏检时,才能判断目标消失于阈值化、形态学还是筛选阶段。可追踪的处理链,比一张干净的二值图更有工程价值。


 上一篇
C# 有界 Channel:把背压、故障传播与优雅停止设计成一个协议 C# 有界 Channel:把背压、故障传播与优雅停止设计成一个协议
采集速度超过处理速度时,增加队列长度只能推迟故障。本文以测量数据流水线为例,推导容量与延迟的关系,区分停止接收、排空和强制中止,给出有界 Channel 的故障传播方案与验证方法。
2026-10-18
下一篇 
16 位图像为什么发黑:把采集动态范围、数值编码与显示映射分开 16 位图像为什么发黑:把采集动态范围、数值编码与显示映射分开
同一张十六位图在不同软件中亮度不同,通常不是文件损坏。本文区分传感器动态范围、有效位深和显示窗口,用可复算代码讨论固定窗口、百分位拉伸与伽马的作用,并说明跨图比较时应保留哪些参数。
2026-10-18
  目录