7  阈值分割

到目前为止,我们的处理对象一直是”图像”——一张灰度值的二维阵列。但产线真正关心的是”目标”:这颗芯片的引脚在哪里、这个孔的直径是多少、这块面板上有没有脏污。从像素的世界跨入目标的世界,第一步几乎总是分割(segmentation)——把属于目标的像素和属于背景的像素分开。而工业视觉中最常用、最快、也最容易被低估的分割手段,就是阈值分割(thresholding):选一个灰度值 \(T\),比它暗(或亮)的像素归为前景,其余归为背景,输出一幅黑白二值图像。

图 7.1 展示了一个典型场景:背光照明下的面板,深色胶带网格在明亮的导光板前成为清晰的剪影。一次自动阈值就把胶带网格完整地提取成了白色前景——不需要任何复杂算法。这正是 章节 4 中”恰当的打光能把问题简化为一次阈值分割”的兑现。但同一枚硬币的另一面是:阈值分割对灰度的依赖有多直接,它对照明变化的敏感就有多剧烈。本章用一组可量化的实验把这两面都讲清楚。

(a) 背光面板原图
(b) Otsu 阈值分割结果
图 7.1: 真实图像的阈值分割。(a) 背光照明下的面板,深色胶带网格衬在明亮的导光板前;(b) Otsu 自动阈值(自动选出 \(T=125\))的二值化结果,灰度低于 \(T\) 的胶带与周边暗区被标为前景(白色),网格结构被干净提取。

7.1 全局阈值与直方图

全局阈值(global thresholding)是阈值分割最朴素的形式:整幅图像共用一个阈值 \(T\)。以提取暗目标为例,

\[ g[n,m] = \begin{cases} 255, & f[n,m] \le T,\\[2pt] 0, & f[n,m] > T. \end{cases} \]

更一般的形式是带阈值(band thresholding):给定区间 \([T_{\mathrm{low}}, T_{\mathrm{high}}]\),灰度落在带内的像素为前景。单阈值只是 \(T_{\mathrm{low}}=0\)(或 \(T_{\mathrm{high}}=255\))的特例。

\(T\) 选在哪里?答案藏在灰度直方图(histogram)里。如果目标与背景各自灰度均匀、对比明显,直方图会呈现两座山峰——前景一座、背景一座,中间隔着一道山谷,这就是双峰直方图(bimodal histogram)。把 \(T\) 放进谷底,两个群体就被干净切开。本章的合成实验场景正是这样设计的:\(480\times360\) 的图像上,灰度 160 的亮背景衬着 48 个 \(20\times20\) 像素的暗方块(灰度 60,类似背光下的网点标定板),叠加标准差 \(\sigma=6\) 的高斯噪声(固定种子,可复现),真值前景共 19200 像素。图 7.2 (a) 是均匀照明版本;图 7.2 (b) 是同一场景叠加水平照明渐变的版本——左端增益 \(\times 0.6\)、右端 \(\times 1.2\),模拟单侧打光不均。

(a) 均匀照明场景
(b) 水平渐变照明场景
图 7.2: 合成测试场景。(a) 均匀照明:背景 160、方块 60,叠加 \(\sigma=6\) 高斯噪声;(b) 同一场景叠乘 \(0.6\)(左)至 \(1.2\)(右)的水平照明增益,左暗右亮。

\(T\) 要放在谷底正中,而不是贴着某座峰的山脚。噪声 \(\sigma=6\) 时,两个群体各有约 \(\pm 3\sigma=\pm18\) 级的散布;\(T\) 离哪个峰太近,那个群体的”长尾”就会越界。60 与 160 之间取 \(T=110\),距两峰各 50 级,余量充足。

固定阈值 \(T=110\)(带 \([0,110]\))在均匀场景上的表现堪称完美:172800 个像素中误分类 0 个图 7.3 (a))。但换到渐变场景,同一个 \(T\) 立刻破产(图 7.3 (b)):左端背景灰度被增益压到 \(160\times0.6\approx96\),低于 110,于是图像左侧约 70 列背景整体落入阈值带——误分类 23728 像素(13.73%),且全部是背景被误判为前景的虚警(false positive)。

(a) 固定 \(T=110\),均匀场景
(b) 固定 \(T=110\),渐变场景
图 7.3: 固定阈值的成与败。(a) 均匀场景:48 个方块全部干净提取,误分类 0;(b) 渐变场景:左端背景灰度跌破 110,整片被误判为前景,误分类 23728 像素(13.73%,全为虚警)。

这组对照给出固定阈值的适用前提:照明稳定、对比可控。这两个条件不是算法给的,而是 章节 4 的打光设计给的——背光剪影、窄带滤光抗环境光、平场标定,都是在为”一个 \(T\) 用到底”创造条件。条件满足时,固定阈值是最快、最可预期的方案;条件不满足时,再精心手调的 \(T\) 也只是在为下一次照明波动埋单。

7.2 Otsu 自动阈值

手动选 \(T\) 依赖工程师盯着直方图调参。Otsu 方法(Otsu’s method)把这一步自动化:它遍历所有候选阈值,选出”把两个群体分得最开”的那一个。设灰度 \(i\) 的归一化直方图为 \(p_i\),阈值 \(T\) 把像素分成两类,两类的占比与均值分别为

\[ \omega_0 = \sum_{i=0}^{T} p_i, \quad \omega_1 = \sum_{i=T+1}^{255} p_i, \quad \mu_0 = \frac{1}{\omega_0}\sum_{i=0}^{T} i\, p_i, \quad \mu_1 = \frac{1}{\omega_1}\sum_{i=T+1}^{255} i\, p_i. \]

Otsu 准则是最大化类间方差(between-class variance)

\[ \sigma_B^2(T) = \omega_0\,\omega_1\,(\mu_0-\mu_1)^2, \qquad T^\ast = \arg\max_T \sigma_B^2(T). \]

直觉上,\((\mu_0-\mu_1)^2\) 奖励”两类的中心离得远”,\(\omega_0\omega_1\) 惩罚”一类大一类小”的失衡切分。由 章节 2 的方差分解可知,总方差 \(\sigma^2 = \sigma_W^2 + \sigma_B^2\)(类内方差加类间方差)是与 \(T\) 无关的常数,所以最大化类间方差等价于最小化类内方差——让每一类内部尽可能紧凑。

在均匀场景上,Otsu 自动选出 \(T=86\),误分类同样为 0。图 7.4 显示了它的落点:双峰之间的谷底,与手调结果一样好,却不需要任何人工干预。照明整体变亮变暗时(例如光源老化),两座峰一起平移,Otsu 选出的 \(T\) 会自动跟着走——这是它对固定阈值的真正优势。

图 7.4: 均匀场景的灰度直方图。左侧小峰为前景方块(中心约 60,19200 像素),右侧大峰为背景(中心约 160);竖线为 Otsu 自动选出的阈值 \(T=86\),恰落在双峰之间的谷底。

但请看渐变场景上的结果(图 7.5):Otsu 选出 \(T=129\),误分类 52475 像素(30.37%)——比固定阈值的 13.73% 还要糟一倍以上。原因不难理解:水平渐变把背景灰度从单一的 160 拉花成 \(96\)\(192\) 的宽幅斜坡,直方图右峰被抹成一片连绵的高原,“双峰夹一谷”的结构不复存在。Otsu 仍然忠实地执行了类间方差最大化,但在畸变的直方图上,\(T^\ast=129\) 落进了被拉宽的背景模式内部——左侧约三分之一的背景灰度低于 129,整片沦为虚警。

图 7.5: Otsu 在渐变场景上的失败:自动选出 \(T=129\),图像左侧大片背景被误判为前景,误分类 52475 像素(30.37%),比固定阈值更糟。

“自动”不等于”自适应”。Otsu 自动的是”选 \(T\) 的动作”,不是”对空间变化的适应”——它仍然输出一个全局 \(T\)。直方图是把空间信息完全抹掉的统计量:照明渐变破坏的恰恰是”同一类目标灰度相同”这一前提,再聪明的直方图算法也无从补救。

这是本章最重要的教学点:Otsu 的隐含前提是直方图近似双峰。前提成立时它是免调参的利器;前提坍塌时它不仅不能救场,还会因为”看起来自动”而更具迷惑性——工程师往往要到产线批量误判后才意识到,自动选出的阈值从第一天起就选在了错误的位置。

7.3 自适应阈值

照明渐变的本质是:阈值应该随位置变化。自适应阈值(adaptive thresholding)正面回应这一点——不再用全局统计量,而是在每个像素周围开一个 \(K\times K\) 的局部窗口,用窗口内的统计量现场定阈值。最常用的形式是”局部均值减偏置”:

\[ T[n,m] = \mu_K[n,m] - \beta, \]

其中 \(\mu_K\) 是局部均值,\(\beta\) 是固定偏置。像素比自己邻域的平均水平暗超过 \(\beta\),就判为(暗)前景。照明渐变在窗口尺度内近似恒定,会同时抬高像素值与局部均值,相减后被自动抵消——这正是全局方法做不到的”空间自适应”。偏置 \(\beta\) 则负责压制平坦区域的噪声:没有 \(\beta\) 时,均匀背景上约一半像素天然低于局部均值,会冒出漫天碎点。

在渐变场景上,局部自适应阈值(\(K=81\)\(\beta=20\))的结果见 图 7.6:误分类仅 41 像素(0.02%)——比固定阈值的 23728 和 Otsu 的 52475 低了三个数量级,左端最暗处与右端最亮处的方块被一视同仁地提取出来。

图 7.6: 局部自适应阈值(\(K=81\)\(\beta=20\))在渐变场景上的结果:误分类仅 41 像素(0.02%),照明渐变被局部统计自动抵消。

窗口必须明显大于目标,这是我们实测得到的硬法则:本实验中把核从 81 缩到 41,方块内部就开始被”掏空”——窗口大部分落在 \(20\) px 方块内部时,局部均值本身趋近方块灰度,中心像素不再比 \(\mu_K-\beta\) 更暗,于是方块腹地被判成背景,只剩一圈边框。目标 20 px、核 81 才稳定(SDK 的局部均值带中心加权,有效窗口比名义尺寸小)。

代价是引入了两个新参数。\(\beta\) 的下界由噪声定(应大于约 \(3\sigma\),本例 \(\sigma=6\)\(\beta=20\)),上界由目标对比度定。\(K\) 的法则见边注:窗口必须大于目标尺寸,否则目标内部会被掏空;同时窗口又要小于照明变化的尺度,否则退化回全局阈值。两头都要留余量。

最后把本章与 章节 4 的路线对照一下。面对照明不均,我们现在有两条等价的治理路线:路线一,先做阴影补正把背景拉平,再用全局阈值(章节 4 的方案);路线二,跳过补正,直接上局部自适应阈值(本章方案)。两者的数学内核其实相同——都是”减去局部背景估计”,区别只在这一步发生在灰度域还是判决域。工程取舍是:若校正后的灰度图还要服务于测量、OCR 等其他算法,选路线一,一次补正全线受益;若只需要这一幅二值图,路线二少一道工序、少一次中间图像的内存往返。

四组实验汇总如下(总像素 172800,真值前景 19200):

方法 场景 阈值 误分类(像素) 误分类率
固定 \(T=110\) 均匀 110(手动) 0 0%
固定 \(T=110\) 渐变 110(手动) 23728 13.73%
Otsu 均匀 86(自动) 0 0%
Otsu 渐变 129(自动) 52475 30.37%
自适应 \(K=81\) 渐变 逐像素 41 0.02%

7.4 彩色图像二值化

阈值的思想可以直接推广到彩色图像:把单通道的”带 \([T_{\mathrm{low}}, T_{\mathrm{high}}]\)“扩展为每个通道一个带,三个带的交集就是前景——在 RGB 空间里这相当于用一个轴对齐的长方体框住目标颜色。RGB 的局限在于三个通道都混杂着亮度信息,照明一变,长方体就框不住了;实践中更稳的做法是先转到 HSV 空间,用色调(hue)通道选颜色、用饱和度(saturation)排除灰白区域,对亮度通道则放宽或不设限——颜色身份与照明强度因此解耦,对光强波动远比 RGB 框稳健。SciVision 的彩色二值化接口(SDK 手册 5.3 节)即按通道带阈值的方式工作。本章配套工程聚焦灰度实验,未包含彩色二值化的量化对比,此处仅交代原理。

7.5 SciVision 实现

本章的阈值算法由 SCIMV::SciSvThreshold 类提供。固定(手动)阈值的调用与配套工程一致:

SCIMV::SciSvThreshold th;
SciImage dst;
SciROI roi;
SciPoint tl(0, 0), br(W, H);   // GenRect1 右下角为排他端点:全图须传 (W,H)
roi.GenRect1(tl, br);

// 带阈值 [0,110]:灰度落在带内的像素在输出中为白(255)
long rc = th.ManualThreshold(img, roi, 0, 110, SCI_THRESHOLD_TYPE_WHITE, 0, &dst);

ManualThreshold(src, roi, lower, upper, lightOrDark, checked, dst) 中,lower/upper 是阈值带的上下界。lightOrDark 这个参数名很容易让人以为它选择”提取亮目标还是暗目标”,实测并非如此:它控制的是输出着色,而非目标选择——传 0(SCI_THRESHOLD_TYPE_WHITE)时带内像素输出为白(255)。用带 \([0,110]\) 提取暗目标时必须传 0,才能让目标在输出中为 255,与 AutoThreshold 的输出约定(前景为白)保持一致;传错的话二值图黑白颠倒,后续 章节 23 的连通域统计会把背景当成目标。

自动阈值(Otsu 与局部自适应)共用一个接口:

int t = -1;
// autoThresholdType=1:Otsu,自动选出的阈值经 t 返回(kernelSize/belta 不起作用)
rc = th.AutoThreshold(img, roi, SCI_THRESHOLD_TYPE_BLACK, 1, 3, 5, 0, &t, &dst);

// autoThresholdType=6:局部自适应,kernelSize=81 为窗口边长,belta=20 为偏置
rc = th.AutoThreshold(img, roi, SCI_THRESHOLD_TYPE_BLACK, 6, 81, 20, 0, &t, &dst);
参数 含义 本章取值与说明
lightOrDark 目标极性 SCI_THRESHOLD_TYPE_BLACK(提取暗目标)
autoThresholdType 算法选择 1 = Otsu;6 = 局部自适应
kernelSize 局部窗口边长 \(K\) 81(须明显大于 20 px 的目标,41 会掏空方块)
belta 局部偏置 \(\beta\) 20(应大于约 \(3\sigma\) 的噪声散布)
value 输出:自动选出的阈值 Otsu 下返回所选 \(T\)(均匀场景 86、渐变场景 129)

两个工程细节值得专门记录。其一是上文的 lightOrDark 语义。其二是 SciROI::GenRect1右下角为排他端点:全图 ROI 必须传 \((W,H)\);若按”最后一个像素坐标”的直觉传 \((W-1,H-1)\),最后一行与最后一列共 \(W+H-1=839\) 个像素会被排除在处理之外、保留原灰度——在二值图上是一圈不易察觉、却足以让逐像素验证翻车的”漏网边框”。生成本章全部实验图像的完整工程位于 code/thresholding/

工业案例:背光测量的阈值漂移

某背光尺寸测量工位最初使用固定阈值提取工件剪影。运行数月后,测得尺寸出现系统性缓慢漂移——排查发现是背光源 LED 老化,亮度逐月下降,固定阈值切出的轮廓随之外扩,尺寸被一点点”量大”。改用 Otsu 后问题消失:光强整体衰减时直方图双峰一起平移,自动阈值跟着走,测量恢复稳定。然而某日产线突发批量误判:导光板局部沾染脏污,背景峰被拖出一条长尾,直方图双峰结构畸变,Otsu 选出的阈值一头扎进背景模式,整批工件轮廓全错——正是本章渐变实验的现场翻版。最终方案是 Otsu 加一道双峰性校验:每次分割前检查直方图两峰间距与谷深,指标越限即告警停判。“自动”必须配上”自检”,才敢在产线上无人值守。

7.6 小结

  • 阈值分割是从像素到目标的第一步:一个(或一带)灰度阈值把图像切成前景与背景的二值图。它的上限由打光决定——照明稳定、对比可控时,固定阈值最快、最可预期(均匀场景误分类 0);照明一旦渐变,同一阈值立刻失效(13.73%)。
  • Otsu 用类间方差最大化 \(\sigma_B^2(T)=\omega_0\omega_1(\mu_0-\mu_1)^2\) 自动选阈值,能跟随照明的整体明暗变化;但它的隐含前提是直方图近似双峰。前提坍塌时它可能比手调更糟(渐变场景 30.37% 对 13.73%)——“自动”不等于”自适应”。
  • 自适应阈值 \(T[n,m]=\mu_K[n,m]-\beta\) 用局部统计抵消照明渐变(误分类降至 0.02%)。窗口 \(K\) 必须明显大于目标尺寸,否则目标内部被掏空(核 41 失败、81 才稳);\(\beta\) 须大于噪声散布。
  • 治理照明不均有两条等价路线:先阴影补正再全局阈值(章节 4),或直接局部自适应阈值(本章)。中间灰度图还有他用时选前者,只要二值图时选后者。
  • 二值图只是中点,不是终点:残留的孤立噪点与毛糙边缘交给 章节 8 的形态学清理,前景像素到目标的聚合与度量是 章节 23 的主题。

本章的 Otsu 方法出自其 1979 年的原始论文 (Otsu 1979);各类阈值化方法的系统比较与定量评测,可参阅 Sezgin 与 Sankur 的综述 (Sezgin 和 Sankur 2004);阈值分割在数字图像处理整体框架中的位置则见 Gonzalez 与 Woods 的教材 (Gonzalez 和 Woods 2018)。关于阈值分割及其与区域提取算法的系统衔接,可进一步阅读 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)