☰
图像分割与测量全解析:从阈值到分水岭的完整技术指南
2026/10/16 22:19:10 网站建设 项目流程

数字图像处理进行到第七周,终于轮到整个课程里最“出活”的一章:图像分割及测量。前面几章讲增强、滤波、变换,本质上都是在为这一章做铺垫——把图像处理成适合分析的状态,然后从中把目标区域抠出来,再用量化的手段描述它。简单说,分割就是回答“目标在哪里”,测量就是回答“目标有多大、什么形状、什么位置”。这两个环节串起来,才真正从“处理图像”走向“分析图像”。

这一章内容非常密集,邻域与连通性、区域生长、阈值分割、迭代阈值、Otsu、分水岭、边缘检测、区域标记、特征测量……每一项都能单独写一篇长文。这篇文章把整章内容重新梳理一遍,重点讲清楚每个方法的原理、适用场景、参数怎么调、有哪些坑,最后给一个人人可复现的测量实验设计。

1. 分割的数学基础:邻域、连通性与像素距离

分割的本质是把像素归类。归类要有个依据,而这个依据不可避免地涉及“某个像素和它周围的像素是什么关系”。所以区域分割的理论起点,不是某个高级算法,而是三个最基础的概念:邻域、连通性和像素间距离。

1.1 四邻域与八邻域:为什么“邻居”的定义这么重要

数字图像里,一个像素的“邻居”通常有两种定义方式。四邻域(4-neighborhood)指上下左右四个直接相邻的像素,坐标差满足 ( |dx| + |dy| = 1 );八邻域(8-neighborhood)则把对角线方向也包含进来,坐标差满足 ( \max(|dx|, |dy|) = 1 )。

很多人一开始觉得这是废话,像素挨着就是挨着,分那么细干什么。但实际上,选择不同的邻域定义会直接改变连通区域的判定结果。想象一条单像素宽的对角线,如果采用四连通判断,这条线会被拆成一段段互不相连的孤立点;如果采用八连通判断,则是一条完整通路。反过来,一个由四连通组成的小方块,在八连通视角下可能和其他斜向接触的区域粘在一起,导致误判。

这就是为什么所有高级分割算法都必须先声明自己用的是哪种邻域。对初学者来说,最稳妥的原则是:分析细长、倾斜结构时用八邻域;分析方块状、规则排列结构时用四邻域。实际项目中我很少同时混用两种定义做同一套流程,因为后续的区域标记、特征提取全都是以连通性为基础,一旦基础定义变了,所有统计结果都会跟着变。

1.2 连通域与像素距离度量:D4与D8的实际意义

连通性定义了“哪些像素属于同一个物体”,而距离度量则定义了“两个像素之间隔了多远”。图像处理中常用的有三类距离:欧氏距离、D4距离(曼哈顿距离)和D8距离(棋盘距离)。

欧氏距离就是常规的直线距离 ( \sqrt{(x_1-x_2)^2 + (y_1-y_2)^2} ),最直观,但涉及开方运算,在实时系统里计算量偏大。D4距离是 ( |x_1-x_2| + |y_1-y_2| ),只允许横向和纵向移动。D8距离是 ( \max(|x_1-x_2|, |y_1-y_2|) ),允许斜向移动。

D4和D8在形态学处理、距离变换、骨架提取里特别有用。举个例子,用形态学腐蚀做目标分离时,腐蚀的“圆盘”结构元素在离散网格上实际就是按D4或D8距离定义的。你觉得自己在用一个圆形结构元素,其实计算形态是菱形或正方形,如果没搞清楚这点,参数调半天都达不到预期效果。

距离定义还会影响测量结果。同一副图像里,用D4距离计算出的物体周长,和用D8距离算出来的会有偏差。做精确测量时,我习惯在实验报告里明确标注“距离度量方式”,否则数据复现时对不上。

2. 区域生长法:从种子点开始,让区域自己长大

理解了邻域和连通性,就可以讲第一种经典分割方法:区域生长。这个方法的思路非常朴素——先确定一个或多个种子点,然后从种子点出发,不断把邻近的、满足相似性准则的像素吸收进区域,直到没有新像素可加入为止。

2.1 种子点怎么选,生长准则怎么定

种子点选择有三种常见方式。第一种是人工交互选取,适合单目标分割,例如医学影像中医生手动点一个肿瘤中心。第二种是通过先验知识自动选取,例如已知目标灰度范围,直接在直方图里找第一个满足条件的像素簇。第三种是用前置算法(如局部极大值检测)自动找种子,这通常用于分水岭等后续算法。

生长准则最常用的是灰度差准则:比较待判断像素与当前区域平均灰度的差值,小于阈值则并入。注意,这里比较的对象是“区域平均灰度”而不是“种子点灰度”,这样能适应区域内部缓慢的灰度变化,避免生长过早停滞。

我实际测试过一个案例:一张光照不均匀的细胞图像,直接用固定灰度阈值分割,结果亮区背景被误认为细胞,暗区细胞被漏掉;改用区域生长后,以每个细胞中心为种子,灰度差阈值设为15,生长结果明显干净得多。

2.2 区域生长常见的过生长与欠生长问题

过生长是指区域突破真实边界,一路长到背景里。最常见的原因是种子点落在边界附近,生长时越过了灰度突变带。解决办法很简单:在做区域生长前,先做一次高斯平滑,把细小噪声滤掉,再用Canny边缘检测生成一个“禁止穿越”的掩膜,生长时遇到边缘像素就停。

欠生长则是区域长不到真实边界就停了,常见于目标内部灰度变化较大,均值和边缘差距明显的情况。这时可以改用“像素与种子点灰度差”而不是“与区域均值的差”作为准则,同时适当放宽阈值。

我调区域生长算法时,习惯先把灰度直方图画出来,大致确定目标和背景的灰度分布区间,再反推合适的生长阈值。如果目标和背景灰度分布重叠严重,说明区域生长本身就不是最优方案,应该考虑转用纹理特征或边缘约束,而不是硬调阈值。

3. 灰度阈值分割:最经典也最容易被低估的方法

阈值分割大概是图像分割里最古老的方法了,原理一句话就能说清:设一个阈值T,灰度大于等于T的像素属于目标,小于T的属于背景,或者反过来。但真正做好阈值分割,涉及的问题远比“选个数”复杂:阈值怎么定?全局阈值和局部阈值怎么选?光照不均匀怎么办?

3.1 直方图双峰法与固定阈值:什么情况下管用

当图像的灰度直方图呈现明显的双峰分布时,两个峰分别对应背景和目标,谷底对应的灰度值就是最佳阈值。这是直方图双峰法的基本思想,也是最直观的阈值确定方式。

这种方法的适用前提是:目标和背景的灰度分布相对集中、重叠较少,且光照均匀。满足这些条件时,固定阈值简单高效,计算量几乎可以忽略。例如白纸上的黑色打印字符、浅色传送带上的深色缺陷,这类场景用固定阈值就能得到很好的分割效果。

但真实场景没这么友好。光照不均匀时,图像同一目标在左边和右边的灰度可能差出几十个灰度级,全局固定阈值必然顾此失彼。这时候要么用自适应阈值,要么先做背景校正,把光照成分减掉。

我在某次处理电路板检测图像时就踩过这个坑。板子中央被机台光源照得发白,两边偏暗,固定阈值不是把中央的导线和背景粘在一起,就是把两边的导线全部吞掉。后来先做了形态学开运算估计背景,用原图减背景后再阈值化,效果才稳定下来。

3.2 迭代阈值法:原理与实现

迭代阈值法是固定阈值的一种自动改进版。它不再依赖人工观察直方图,而是通过迭代计算逼近最佳阈值。算法流程很固定,几乎是教科书级的:

  1. 设定初始阈值T0,通常取图像最大灰度和最小灰度的平均值;
  2. 用T0将图像分成目标和背景两类;
  3. 分别计算目标像素平均灰度 ( \mu_1 ) 和背景像素平均灰度 ( \mu_2 );
  4. 新阈值 ( T_{new} = (\mu_1 + \mu_2) / 2 );
  5. 比较 ( T_{new} ) 与 ( T_{old} ),若差值的绝对值小于预设精度(例如0.5),则停止;否则回到第2步继续迭代。

这个方法比固定阈值稳健,因为它迭代过程中自动考虑了当前分割结果,相当于是“反馈式”的。实测通常3到10次迭代就能收敛,速度很快。但它的缺点是只考虑灰度均值,假设目标和背景灰度分布近似对称。如果两类像素数量差异巨大,例如目标只占全图1%的面积,迭代阈值就容易偏向背景侧。

3.3 Otsu方法:自动确定最优阈值的经典算法

Otsu法(最大类间方差法)是阈值分割里绕不开的名字。它的核心思想是:把图像分成两类,使得两类之间的方差最大,两类内部的方差最小。从直觉上说,目标和背景差异越大,越容易分割;所以最佳阈值应该让两类的区分度最大化。

具体推导过程可以这样理解:设阈值为T,目标像素占比为 ( w_1 ),灰度均值为 ( \mu_1 );背景像素占比为 ( w_2 ),灰度均值为 ( \mu_2 );全图灰度均值为 ( u_T )。类间方差定义为:

[ \sigma_B^2(T) = w_1 ( \mu_1 - u_T )^2 + w_2 ( \mu_2 - u_T )^2 ]

对每个可能的灰度级计算 ( \sigma_B^2 ),取最大值对应的灰度级就是Otsu阈值。由于灰度级最多256个,用直方图统计后计算量很小,完全可以实时运行。

我在实验里对比过:同一副细胞图像,迭代阈值法得到的分割结果总是把细胞核和细胞质过度合并,Otsu法的结果则明显更贴合人工标注的边界。尤其是当目标和背景面积比接近1比1时,Otsu几乎总能给出令人满意的结果。

Otsu也有失效场景。最典型的就是目标和背景面积比极端悬殊,例如在一张99%都是黑色背景的图像里找几个白色亮点,Otsu会倾向于把大部分背景也划为“目标”,导致分割失效。这类情况可以考虑局部Otsu,把图像分块,每块独立求阈值,效果往往更好。

4. 分水岭算法:用地理学思维解决粘连区域分割

在目标相互粘连的场景里,Otsu和区域生长的表现都不够好。比如一堆相互接触的细胞、挤在一起的颗粒、距离很近的字符,常规阈值分割会把它们连成一片,数目标个数时全部失败。这时候就要用到分水岭算法。

4.1 从地形淹没到图像分割:分水岭的原理

分水岭算法的灵感来自地理学。把图像灰度看作海拔高度,灰度值高的地方是山脊,灰度值低的地方是山谷。往地形里注水,水会从局部最低点(局部极小值)开始上涨,不同汇水盆地的水在边界处相遇后,就在那里筑起“分水岭”。这条分水岭就是分割边界。

对应到图像处理中,每个局部极小值点都是一个种子,被水淹没的区域不断扩张,当两个区域即将汇合时,算法会在交汇处生成一道分割线。这样就把一个相连的大区域拆成了多个独立的子区域,恰好解决了粘连目标的分割难题。

但直接使用分水岭有个著名的问题:过分割。图像里的噪声和轻微灰度波动会产生大量伪极小值点,每个点都会引发一次独立的“涨水”,最终把完整目标切成无数碎块。

4.2 标记控制的分水岭:抑制过分割的标准解法

解决过分割的思路是:不让算法自动从所有极小值点开始淹没,而是只在标记(marker)指定的位置开始。这就是标记控制的分水岭。

完整流程我实测过多次,大致这样:

  1. 对原图做形态学腐蚀或距离变换,找到目标内部区域作为前景标记;
  2. 对原图做形态学膨胀或直接取背景区域作为背景标记;
  3. 把前景标记和背景标记叠加到梯度图像上;
  4. 对梯度图做分水岭分割,同时保证标记位置必须作为种子点。

这里用梯度图而不用原图,是为了让分水岭边界落在灰度变化剧烈的位置,也就是真实目标边缘。梯度图里,物体内部相对平坦,边缘处梯度很高,注水时水流会沿着低梯度区域蔓延,在高梯度处停下,天然贴合真实边界。

我做颗粒分析时常用的标记获取方式是:先对二值图做距离变换,像素值代表它到最近背景点的距离,距离图的局部极大值点就是每个颗粒的中心。这一招在分割重叠圆球类目标时特别好用,准确率比直接腐蚀高不少。

4.3 分水岭在颗粒计数和细胞分割中的实际效果

实际项目中,分水岭最常出场的场景是颗粒计数和细胞分割。举个例子,一副显微图像里几十个细胞粘连成团,直接数连通域只能得到几个大块,完全没法数出细胞数量。用标记控制的分水岭处理之后,每个细胞被单独分割出来,再做连通域计数,数量才和人工数的一致。

我做过一次细胞图像实验:原图经高斯滤波、Otsu阈值化后得到二值图,连通域数量只有9个,而人工参考是46个;使用距离变换+分水岭之后,数量修正为44个,漏检的2个是因为细胞重叠过于紧密,距离图上没有形成独立峰值。这类误差属于原理性上限,被遮挡的部分本身缺乏分界信息,任何算法都难彻底解决。

分水岭还有一个好处:它输出的分割结果是封闭的轮廓线,直接可以作为测量阶段的边界输入。相比纯阈值分割得到的非规则边缘,分水岭轮廓的连续性明显更好。

5. 边缘检测分割:当“区域”说不清时,“边界”反而更清晰

区域分割方法依赖灰度统计特性,但有些场景里区域的灰度特性并不均匀,边界特征却非常突出。这时候可以换一个思路:先把边界检测出来,再通过边界形成闭合区域,完成分割。这就是边缘检测分割路线。

5.1 从梯度到边缘:Sobel与Laplacian的适用边界

边缘检测的基础是灰度突变,数学上对应像素灰度的高梯度。Sobel算子是最常用的梯度算子之一,它用两个3x3卷积核分别计算水平方向 ( G_x ) 和垂直方向 ( G_y ) 的梯度近似值,然后合成梯度幅值 ( G = \sqrt{G_x^2 + G_y^2} )。

Sobel算子的特点是做了平滑处理,对噪声有一定抵抗能力,同时计算量很小,适合实时应用。但它的缺点是检测到的边缘较粗,定位精度一般。如果后续要做亚像素边缘定位,Sobel的精度就不够了。

Laplacian算子是二阶导数算子,直接计算灰度二阶导数的过零点位置来定位边缘。它对边缘位置的定位比一阶算子精确,但对噪声极其敏感,图像里有一点小噪点就会产生大量假边缘。所以实际使用Laplacian前,必须先做充分的平滑滤波。

我的使用原则是:粗定位用Sobel;精确定位用Canny,因为它内部已经整合了高斯平滑和双阈值,综合效果比单独用Laplacian稳定得多。

5.2 Canny边缘检测的完整流程与双阈值原理

Canny在边缘检测中的地位几乎等同于Otsu在阈值分割中的地位。它被认为是最优的边缘检测算子,原因在于它显式地优化了三个目标:低错误率、良好的定位精度、单一响应。

Canny的流程分为五步:

  1. 用高斯滤波器平滑图像,抑制噪声;
  2. 用Sobel算子计算梯度幅值和方向;
  3. 对梯度幅值做非极大值抑制,只保留梯度方向上的局部最大值点,把粗边缘细化为单像素宽;
  4. 使用双阈值处理:大阈值 ( T_{high} ) 确定强边缘像素,小阈值 ( T_{low} ) 确定弱边缘像素;
  5. 滞后边界跟踪:弱边缘像素如果与强边缘像素连通,则保留为边缘,否则丢弃。

双阈值的妙处在于:只用一个高阈值会得到断续的边缘(因为梯度值偶尔波动会低于阈值);只用一个低阈值则会把噪声全部带进来。双阈值加滞后跟踪,既能保持边缘连续,又能滤掉孤立的弱响应。

调Canny参数时,我一般先观察梯度幅值的直方图:高阈值取直方图中靠后的峰值位置,低阈值取高阈值的二分之一到三分之一。初始值可以设成高阈值50、低阈值20,然后根据边缘连续性微调。边缘断裂太多就降阈值,假边缘太多就升阈值。

5.3 边缘闭合与区域填充:从“线段”到“区域”的关键跳跃

边缘检测得到的是一个个像素点构成的“边缘线”,但分割最终需要的是“封闭区域”。从边缘线到封闭区域的转换,是初学者最容易卡住的地方。

常用策略有三种。第一种是形态学闭合:对边缘图做闭运算,把细小的断口连接起来,然后寻找闭合轮廓并填充内部。第二种是Hough变换检测直线或圆,适用于边缘呈现规则几何形状的目标。第三种是轮廓查找算法,直接从边缘图中提取最外层轮廓和内部孔洞轮廓。

我实际用得最多的是第一种加第三种配合:先做闭运算填补断口,再用轮廓查找函数提取闭合边界。如果目标边缘复杂、断口太多,闭运算结构元素可以设大一点,但要注意太大的结构元素会把两个相邻目标的边缘粘在一起,反而得不偿失。

边缘闭合后,对闭合区域内部做填充,就能得到二值掩膜。这个掩膜可以送去测量阶段计算面积、周长等特征,也可以直接作为后续识别算法的输入。整个流程下来,边缘分割和区域分割殊途同归,最终都变成了“一个干净的二值图+一套区域描述参数”。

6. 区域标记与特征测量:把“分割结果”变成“数据”

分割完成后,图像变成了一张二值图:白色区域是目标,黑色是背景。但二值图本身只是像素的集合,要回答“目标有几百个”“每个目标面积多大”“形状是否规则”,还需要把连通区域逐个编号,然后提取每个区域的特征。这就是区域标记与特征测量环节。

6.1 连通域标记算法:两遍扫描法的实现

连通域标记的任务是给二值图中每个独立连通区域一个唯一的编号。最经典的算法是两遍扫描法。

第一遍扫描:逐行遍历图像,对每个前景像素,检查它左边和上方的相邻像素。如果两者都无编号,则给它分配一个新编号;如果只有一个有编号,则沿用;如果两者都有编号且不同,则记录这两个编号等价,并沿用较小的那个。第二遍扫描:处理等价关系,将等价编号统一,并修正所有像素的编号。

这个方法实现简单,内存开销小,非常适合嵌入式平台。Python里也可以用更简洁的方式:直接调用库函数标记连通域,返回每个区域的编号和切片范围。OpenCV里有connectedComponentsWithStats,输入二值图,输出标记图、连通域数、包围盒信息、中心坐标等,做测量十分方便。

但库函数毕竟是黑盒,理解两遍扫描的流程仍然很重要,因为它决定了你能否灵活地处理“只保留面积在某个范围内的区域”这类常见的筛选需求。

6.2 区域描述:面积、周长、质心、矩形度与宽高比

标记完成后,每个区域都可以提取一系列几何特征。面积是像素总数;周长是边界像素的个数(或链码长度);质心是区域内所有像素坐标的平均值;包围盒是包含该区域的最小矩形;矩形度是区域面积与包围盒面积之比;宽高比是包围盒宽度与高度之比。

这些特征在目标筛选时会体现得淋漓尽致。我做过一个螺丝尺寸检测的模拟项目:目标是一堆杂乱摆放的螺丝,需要把合格的挑出来。分割二值化后,先计算每个连通域的面积,面积在正常范围内的才保留,过大过小的全部丢弃。再计算宽高比,筛选出符合标准螺丝长宽比例的区域。最后计算质心位置,用于机械臂抓取定位。

忐忑,但效果立竿见影。原来一堆误检,引入矩形度和宽高比过滤后,准确率直接从不到70%提升到95%以上。这就是测量的价值:分割只能告诉你“哪里有东西”,测量告诉你“东西是什么样、是不是目标”。

6.3 不变矩特征:为什么它对平移、旋转、缩放不敏感

测量特征中还有一个概念很重要:不变矩(Hu矩)。普通矩特征会随目标位置、大小、方向变化而变化,不适合直接做目标匹配。Hu等人提出7个由二阶和三阶中心矩构造的矩量,它们对平移、旋转和缩放不敏感,因此被广泛用于形状识别。

Hu矩的原理说到底就是归一化:通过计算相对质心的中心矩,消除平移影响;通过归一化消除尺度影响;通过组合方式消除旋转变化的影响。听起来很玄,实际用起来就是一个7维向量。识别A形状和B形状时,计算两者的Hu矩向量,距离小于阈值就判为同一类。

我用Hu矩做过一个简单的商标比对实验:同一个商标的旋转版本、缩放版本、轻微变形版本,Hu矩向量差距很小;而不同商标之间,距离明显拉大。作为粗分类特征,它非常可靠;作为细分类特征,精度不够,需要配合面积、周长等几何参数一起用。

7. 实验设计与问题记录:从读懂算法到跑通全流程

课程配套实验从第8题到第9题,实际上是一个完整的检测流水线:读图、图像增强、分割、后处理、区域标记、特征测量、结果分析。最后这一部分,我把整个流程串起来做一个可直接参考的实验设计,同时把实操里踩过的坑集中列进速查表。

7.1 一个完整的图像分割与测量实验参考流程

实验目标是:统计图像中细胞个数并输出每个细胞的面积、周长、质心和宽高比。参考流程如下:

  1. 读入灰度图,使用高斯滤波器(核为5x5,sigma为1.2)去除噪声;
  2. 使用Otsu方法生成全局阈值,对图像进行二值化,得到前景目标;
  3. 使用形态学开运算(3x3圆形结构元素)去除细小噪声点;
  4. 使用距离变换,找局部极大值作为标记;
  5. 执行标记控制的分水岭分割,得到独立细胞区域;
  6. 使用连通域标记函数获得每个区域编号;
  7. 遍历区域,计算面积、周长、质心、宽高比,并输出表格。

每个步骤之间用调试代码保存中间结果图。水岭分割前保存距离图,分割后保存标记边界图,这样每一步出问题都能立刻定位到具体环节。

调参经验:高斯核太小去不了噪,太大会让细胞边缘模糊,开运算结构元素太大则容易合并相邻细胞。具体数值应当依据图像分辨率调整,不要照抄任何一个固定值。

7.2 常见问题速查与解决思路

这一章最容易出的问题高度集中,我整理成速查表方便直接对照排查。

问题现象可能原因解决思路
分割结果目标过多、过碎噪声太多或阈值偏低先加强高斯滤波;检查二值图残留噪点;增加形态学开运算
目标粘连无法分开阈值分割只能得到连通团块使用距离变换+分水岭二次分割
边缘断裂、轮廓不闭合梯度幅值阈值设置过严调低Canny双阈值中的低阈值;对边缘图做形态学闭运算
测量面积普遍偏大分割时把背景误并入目标检查阈值;绘制分割边界与原图叠加对比
Otsu分割失效目标比例极端改用分块Otsu或自适应阈值
分水岭过分割严重局部极小值太多使用标记控制分水岭;先做显著平滑
标记区域数量对不上面积过滤条件不合理按像素和物理尺寸换算关系设置面积范围

遇到分割结果不理想时,我建议不要急着改算法参数,先把“中间结果”全部可视化出来。灰度图、滤波后图、二值图、标记图各保存一份,一步步排除问题。百分之九十的“算法效果差”其实是参数问题,而不是算法本身的问题。

7.3 学习建议:这一章怎么才算真正掌握了

很多同学学完阈值分割就以为图像分割学完了,这是比较常见的误区。阈值分割只是最简单的一种情况,真实场景里的目标很少是“灰度均匀+光照均匀”这么理想。

这一章真正要掌握的能力,是面对一幅陌生图像时,能快速回答四个问题:目标和背景在灰度上能不能区分?不能区分时,边界梯度是否明显?连边界也不明显时,要不要引入纹理或颜色特征?最终拿到分割结果后,用什么特征来验证分割好坏?

能把这个问题链跑通,才算真正掌握了图像分割及测量。对着示例代码跑通几次并不难,难的是换一张图后,还能独立地把整个流程搭出来。

我个人在这章上反复踩过坑,印象最深的一次就是分水岭过分割:一开始直接对梯度图用分水岭,结果每个细胞都被切成七八块,一度以为算法不行。后来加上了距离变换和标记控制,效果立刻完全不同。从那以后我就形成了一种习惯:任何分割算法,先想清楚它需要什么样的输入条件,再动手写代码。

这一章的内容,从邻域连通性到区域标记测量,虽然环节多,但逻辑线是清晰的:先定义像素之间的关系,再根据灰度或梯度信息划分归属,然后处理分割中的粘连和噪声问题,最后用量化特征评估分割和描述目标。把这套流程内化之后,后续的物体识别、目标跟踪、图像理解,都会轻松不少。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询