23 Blob 分析
阈值分割(章节 7)把图像变成了黑白两色,但产线提出的问题从来不是”哪些像素是白的”,而是:这盘贴片元件上有多少颗?哪几颗彼此搭接、需要返工再上料?最大的那个 blob 有多大、在哪里?要回答这些问题,必须先把”一堆白色像素”组织成”一个个对象”——这正是分割之后的第一站。Blob 分析(blob analysis)= 连通域提取 + 特征计算 + 筛选分类,三步合起来构成了计数、分选、异物检测等大半个检测世界的骨架。
本章用一帧真实工业样图贯穿全部实验(图 23.1):来自 Smart3「blob 分析示例方案」的一幅采集图——深色绒布托盘上散落着一批贴片元件(chip 件,金属端面在同轴光下高亮)。原图为 3840×2748 单通道灰度,盒式降采样 ×4 到 960×687 以适配书内插图。画面里约 24 颗元件以各种转角散布,其中一对端对端搭接、还有一颗被视场下缘截断;托盘绒面在高亮阈值下又会泛起成片的细小散斑。我们的任务,是让算法把元件数清、把搭接对挑出来——途中会遇到的每一个坑,都是产线上的常客。
23.1 连通域与标记
二值图像里的”对象”在数学上叫连通域(connected component):一组前景像素,其中任意两个都能通过一串相邻的前景像素互相到达。关键在”相邻”二字的定义——4 连通(4-connectivity)只认上下左右四个邻居,8 连通(8-connectivity)把四个对角邻居也算进来。
这个看似细枝末节的选择有实打实的后果。两个只在对角线上碰了一个角的斑块,在 8 连通下是一个对象,在 4 连通下是两个;一条一像素宽的斜线,在 8 连通下是一根完整的线,在 4 连通下会碎成一串孤立点。做计数时,连通性选错,计数就系统性地偏多或偏少。工业库的默认值几乎都是 8 连通,因为真实目标经采样后的斜向边界天然以对角方式衔接。
对角线接触在 8 连通下算一个 blob。本章后面那对搭接元件却是另一回事——它们物理上首尾搭在一起,像素集真正重叠,无论 4 连通还是 8 连通都会合并。另一个经典约定:前景用 8 连通时背景应配 4 连通(反之亦然),否则一条闭合的 8 连通曲线围出的内外两侧在 8 连通下竟然也是连通的,拓扑上自相矛盾。
把每个连通域找出来并编号的过程称为连通域标记(connected component labeling)。经典做法是两遍扫描:第一遍逐行给前景像素分配临时标号并记录”哪些标号其实相连”的等价关系,第二遍把等价标号合并成最终编号。基于游程(run-length)编码的实现更高效——先把每行的连续前景段压缩成游程,再在相邻行的游程之间建立连通关系,复杂度只与游程数有关而与面积无关。SciVision 的 SciRegion 正是游程编码的区域表示:阈值分割直接输出 SciRegion,Blob 分析把它拆成一个 SciRegionArray——数组里每个元素就是一个 blob。图 23.2 是本章样图以亮阈值 \(T=80\)(保留灰度 \(\ge 80\) 的高亮像素为前景)分割的结果:元件成为白色前景,但深色绒面在这条阈值下泛起成百上千颗 1~几像素的散斑。这张图——连同它的噪声——就是后续一切分析的输入。
23.2 Blob 特征
拿到 blob 数组后,每个 blob 还只是”一团像素”。要分类,得先把它度量成数字——这就是区域特征(region features)。常用特征可以分成三个家族。
大小与位置。面积(area)就是像素个数,是最便宜也最稳健的特征;重心(centroid)是坐标的平均:
\[ \bar x = \frac{1}{A}\sum_{(x,y)\in R} x, \qquad \bar y = \frac{1}{A}\sum_{(x,y)\in R} y; \]
外接框(bounding box)给出最小包围矩形,常用于可视化与粗略的尺寸把关。
等效椭圆(equivalent ellipse)。由区域的二阶中心矩可以算出一个与该区域惯性矩相同的椭圆,其长半轴 \(R_A\)、短半轴 \(R_B\) 与方向角 \(\varphi\) 概括了区域的”伸展姿态”。两个半轴之比定义了伸长度(anisometry):
\[ \text{anisometry} = \frac{R_A}{R_B} \;\ge\; 1, \]
它与目标的位置、转角无关。本章样图的实测正是最好的注脚:21 颗单粒元件无论转到什么角度,伸长度都聚在 2.7~3.4 这条窄带里(元件长宽比约 2.5∶1);而那个端对端搭接对的伸长度窜到了 5.26,几乎是名义值的两倍——这是后面识别它的关键判据之一。
无量纲形状量。圆形度(circularity)衡量区域与圆的接近程度,常用定义是
\[ c = \frac{4\pi A}{P^2} \in (0, 1], \]
其中 \(P\) 为周长;完美的圆取 1,越细长或边界越粗糙越小。矩形度(rectangularity)是区域面积与其外接矩形面积之比,理想矩形接近 1。SciVision 的 SciFeatureType 枚举把这一族收得很全:面积、重心、外接框、等效椭圆 \(R_A/R_B/\varphi\)、anisometry、circularity、roundness、rectangularity,直到 Hu 矩与孔洞数——本章实验取用其中 10 个。
本章一个实测教训:SciVision 的矩形度(SCI_REGION_RECTANGLEDEGREES)按正接矩形(轴对齐)计算,因而与位姿相关。同一批元件,摆得端正的矩形度高达 0.9,斜置 45° 的却跌到 0.38(其轴对齐外接框近乎正方形)。所以矩形度在这里不能当作位姿无关的判据——能担此任的是面积与伸长度。凡分母里带周长的特征(圆形度)也要当心:数字图像的周长沿像素边界数出,对小目标系统性偏大。
特征那么多,选哪个?三条原则:可解释——出了误判要能向工艺工程师说清”为什么”;对目标差异敏感——搭接对与单粒在伸长度上差近一倍,才值得一用;对噪声与位姿不敏感——面积、伸长度都平移旋转不变,而外接框宽高、轴对齐矩形度都随目标转动而变,不宜直接当分类判据。
23.3 筛选与分类实验
现在把流水线跑通。对 图 23.2 不加任何限制地提取连通域,得到 479 个 blob——绝大多数是托盘绒面的散斑。第一道闸门是面积筛选:保留面积 \(\ge 150\) 的 blob,成片散斑应声出局,只剩 23 个元件尺寸的连通域。第二道闸门是 ignoreROIBoundary——剔除与 ROI 边界接触的 blob,那颗被视场下缘截断的元件被移除,剩 22 个进入分类。
为什么贴边对象必须剔除?因为它们是被视场截断的:只拍到半截,面积、伸长度全部失真——拿不可信的特征去分类,结果必然不可信。正确的做法不是”努力分类”,而是承认这一帧看不全它,把它留给下一帧或邻接相机。这正是 ignoreROIBoundary 参数的工程价值:SDK 原生支持,一个布尔值就把这类系统性错误关在门外。
分类规则只用两个位姿无关的特征:
- 面积 \(\in [300, 1000]\) 且 伸长度 \(\le 4.0\) → 合格单粒(good);
- 否则 → 异常(reject)——搭接对、异物或被截断的残块。
之所以同时设面积上限与伸长度上限,是因为搭接会同时拉高这两个量:端对端搭接的面积约为单粒的两倍、伸长度也约为两倍,任一判据都能擒住它(互为冗余,更鲁棒)。结果如下:
| 类别 | 判定规则 | 检出 | 说明 |
|---|---|---|---|
| 合格单粒 good | 面积 ∈[300,1000] 且 aniso ≤ 4 | 21 | 伸长度实测 2.72~3.41 |
| 异常 reject | 否则 | 1 | 端对端搭接对:面积/伸长度均约 2× 名义 |
图 23.3 把结果画回原图:合格单粒套细白框,异常的搭接对套粗白框加对角叉。仔细读这张图能看到三件事:散落的绒面散斑没有任何框——面积筛选的功劳;下缘那颗被截断的元件也无框——ignoreROIBoundary 在分类前就把它剔除了;而左中部那个被打了叉的粗框,正是 21 颗合格件之外唯一的异常——一对端对端搭接的元件,也是下一节的主角。
23.4 搭接与合并
那对搭接元件是真实样图里现成的钉子:两颗 chip 件首尾搭在一起,金属端面在中缝处连成一片,二值化之后必然粘成一个 blob。图 23.4 (a) 是该区域二值图的 8 倍放大:一根中段带细颈的”弯折长条”,连通域标记对此无能为力,它看到的就是一个对象。
合并 blob 的特征把误判机制暴露得很彻底,也把识别它的钥匙递到手上:面积 1342 px(合格单粒面积中位数为 637 px,比值 2.1)、伸长度 5.26(单粒约 2.8,比值近 2)。两个位姿无关的判据同时报警——这正是它在上一节被判为 reject 的原因。计数的恢复则有一条比形态学更轻的路:面积商。把合并 blob 的面积除以名义单粒面积,\(1342 / 637 = 2.11\),四舍五入得 2——这对元件如数补回。于是全帧的元件计数为 \(21\ (\text{合格}) + 2\ (\text{面积商还原}) = \mathbf{23}\) 颗(外加那颗被下缘截断、留给下一帧的元件)。
搭接不是连通性选择的错,是上料的必然:散料抛撒下来,总有概率首尾相接。值得强调的是这里与第一节对角接触的区别——那对元件像素集真正重叠,4 连通、8 连通都会把它们合并;换连通性救不了,只能从特征层面识别、或从机械层面重新抛料。
经典的另一条对策是腐蚀分离(腐蚀的原理见 章节 8):搭接处往往只有窄窄的”颈”,腐蚀几次本应掐断。实验照做,结果却给真实结构件上了一课:这些 chip 件并非实心亮块,而是”两端高亮端面 + 较暗本体”的结构件——一次 3×3 腐蚀在掐断搭接颈的同时,也把每一颗单粒的端面与本体之间腐断。图 23.4 (b) 是同一区域腐蚀一次后的特写:弯折长条没有干净地一分为二,反而碎成了好几块。代价立刻摆上桌面:全图元件尺寸的 blob 数从 22 暴涨到 32——计数被腐蚀破坏,而非修复。
工程结论必须说得直白:在真实结构件上,腐蚀分离不可盲用。合成场景里相切圆是实心圆盘,腐蚀只削边界、能干净分家;可一旦目标内部有明暗结构(金属端面 vs 本体、镀层 vs 基体),腐蚀就会沿着内部的”暗缝”把好零件也撕开,计数与面积一起失真。真实产线的稳健路线因此是两条腿走路:用面积、伸长度等位姿无关特征识别搭接(把它判成 reject 并报警),用面积商而非连通域个数恢复计数。若确需在像素层面切开粘连,基于距离变换的分水岭分割(watershed)比盲目腐蚀稳妥得多——它在距离图的”山脊”处切开而不削减面积,代价是实现与调参都重得多。
23.5 SciVision 实现
本章流水线由三个类协作完成:SciSvThreshold 产出区域,SciSvBlobAnalysis 拆分与筛选,SciSvRegionFeature 计算特征表。
// 1) 亮阈值分割:保留灰度 [80,255] 的高亮元件,dstRegion 与二值图同时输出
SCIMV::SciSvThreshold th;
SciImage binImg; SciRegion fgRegion;
th.ManualThreshold(img, roi, /*lower*/80, /*upper*/255,
SCI_THRESHOLD_TYPE_WHITE, 0, &binImg, &fgRegion);
// 2) Blob 提取:提取时按面积下限过滤散斑 + 剔除贴边对象
SCIMV::SciSvBlobAnalysis ba;
SciRegion mask; // 默认构造 = 不屏蔽任何区域
SciVarArray minV, maxV, ft; // 特征区间过滤的三元组
ft.Append(SciVar((int)SCI_REGION_AREA));
minV.Append(SciVar(150.0)); maxV.Append(SciVar(5000.0));
SciVarArray fhF, fhMin, fhMax; // 留空 = 不做孔洞填充
SciRegionArray blobs; SciMatrix featM;
ba.BlobAnalysis(fgRegion, img, roi, mask, minV, maxV, ft,
/*fillHole=*/false, fhF, fhMin, fhMax,
/*ignoreROIBoundary=*/true, false, &blobs, &featM);
// 2') 也可先敞开提取,再用 BlobFilter 做面积后滤(便于观察各闸门筛掉了谁)
ba.BlobFilter(blobsAll, featAll, SCI_REGION_AREA, 150.0, 5000.0,
&featArea, &blobsArea);
// 3) 对 blob 数组计算特征(面积、重心、外接框、伸长度、矩形度……)
SCIMV::SciSvRegionFeature rf;
SciVarArray feats; // 依次 Append 若干 SciFeatureType 编号
SciMatrix m;
rf.CalRegionFeature(blobs, feats, &m);几个参数值得逐一说明。ManualThreshold 取 [80,255] 的灰度带、SCI_THRESHOLD_TYPE_WHITE 表示”带内为前景”,对暗背景上的亮目标正合适;最后一个参数 dstRegion 是工程上的甜点:分割结果以 SciRegion 形式直通 BlobAnalysis 的第一个参数,中间不必经过二值图再重新提取。BlobAnalysis 的 minValue/maxValue/featureType 三个数组构成提取时的特征区间过滤(本例只设面积下限去散斑);ignoreROIBoundary = true 即 小节 23.3 所述的贴边剔除。BlobFilter 则提供事后筛选——先敞开拿全集、再按需收紧,便于调参时观察每道闸门各筛掉了谁(479 → 23 → 22 的数字正是这样打印出来的)。注意全图 ROI 用 GenRect1((0,0),(W,H)):右下角是排他端点,传 \((W,H)\) 才能覆盖整幅,误传 \((W{-}1,H{-}1)\) 会漏掉最外行列。
三个坑需要记录。其一,CalRegionFeature 输出的 SciMatrix 行列方向在文档中未注明——本章配套代码按”某一维长度等于特征数”自动判向后再取值,移植时务必保留这层防御。其二,矩形度 SCI_REGION_RECTANGLEDEGREES 按轴对齐外接矩形计算,随目标转角变化(实测同型元件 0.38~0.93),不可当位姿无关判据——位姿不变的形状判据请用伸长度。其三,SciSvBlobAnalysis 等几个头文件无条件定义了 DLL_EXPORTS(即以 dllexport 方式声明),把它们的 #include 放在 dllimport 风格的头之后即可正常链接,实测无碍。
工业案例:散料供料的搭接返工
某 SMD 来料检线用振动盘把贴片元件抛撒到检测托盘上,再用 blob 分析清点每盘数量并核对规格。元件密度一高,偶有两颗首尾搭接,二值化后粘成一个 blob,计数随之少 1,系统误报”缺料”停机——而盘里一颗不少。复查发现误报帧里总有一个面积约两倍于正常元件、伸长度也近两倍的”大 blob”,正是搭接对。一线最初想用腐蚀切开,却发现这些”端面 + 本体”结构件一腐就碎、计数更乱。最终修复没有动用形态学,而是双管齐下:用面积与伸长度双阈值把搭接对识别为异常并触发振动盘二次抛料(机械层面解决粘连),同时把”缺料”判据从连通域个数改为总前景面积除以名义单粒面积取整——搭接对的面积商 \(1342/637\approx2\),恰好补回那一颗。上线后误报清零。教训:计数任务里”面积商”比”连通域数”更鲁棒,搭接改变拓扑却几乎不改变面积;而真实结构件上腐蚀分离要慎之又慎。
23.6 小结
- Blob 分析 = 连通域 + 特征 + 筛选,是把”白色像素堆”变成”可计数对象”的标准三段式;连通性(4/8)的选择决定对角接触算一个还是两个,但物理搭接像素集重叠,换连通性救不了。
- 特征选择三原则:可解释、对目标差异敏感、对噪声与位姿不敏感。面积与伸长度(anisometry)是最稳健的一线特征(本章单粒伸长度稳定在 2.7~3.4,搭接对窜到 5.26);轴对齐矩形度、外接框宽高随转角漂移,含周长的圆形度对小目标不可靠。
- 筛选是分类的前提:面积下限滤掉绒面散斑,
ignoreROIBoundary剔除被视场截断、特征不可信的贴边对象——本章 479 → 23 → 22 的每一步都各司其职。 - 搭接是上料的必然,腐蚀分离要看对象:合成实心目标腐蚀能干净分家,但真实”端面 + 本体”结构件一腐就碎(全图 blob 22→32),计数被破坏而非修复。稳健做法是用面积/伸长度识别搭接、用面积商恢复计数,或改用分水岭;机械层面则可二次抛料。
- Blob 特征只看像素的”占有”,不看边界的”形状走向”——当分类需要更精细的轮廓信息时,请前往轮廓分析(章节 24)。
连通域标记的算法源头可追溯到 Rosenfeld 与 Pfaltz 关于数字图像顺序操作的奠基性论文 (Rosenfeld 和 Pfaltz 1966)——连通分量标号、距离变换乃至骨架的雏形都在其中;区域边界的拓扑分析与游程式提取则可参考 Suzuki 与 Abe 的边界跟踪算法 (Suzuki 和 Abe 1985)。连通域标记与区域特征的教科书式综述见 Gonzalez 与 Woods (Gonzalez 和 Woods 2018);关于区域特征与形态学分割(含分水岭)更系统的工程论述,可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。




