第四章:图像分割算法——阈值分割、边缘检测与区域生长

图像分割,说白了就是把一张图里我们感兴趣的东西“抠”出来。在AOI检测里,这一步做不好,后面所有分析都是白搭。我做了这么多年机器视觉,见过太多项目因为分割参数没调好,导致误判率居高不下。今天咱们就把这块硬骨头啃下来。

核心观点:没有万能的图像分割算法,只有最适合当前场景的算法。理解每种算法的脾气秉性,比背一百个公式都管用。

4.1 阈值分割:最朴素但最实用的方法

阈值分割的原理简单到令人发指——设定一个灰度值T,大于T的算一类,小于T的算另一类。但你别小看它,我经手的AOI项目里,大概有40%的场景用阈值分割就能搞定。

4.1.1 全局阈值分割

全局阈值就是整张图用一个固定的T值。适合什么场景?背景和目标的灰度差异非常明显的时候。比如检测白色陶瓷基板上的黑色焊点,这种对比度高的场景,全局阈值一把梭就行。

// 伪代码示例:全局阈值分割
for each pixel (x, y) in image:
    if image[x][y] > T:
        output[x][y] = 255  // 目标区域
    else:
        output[x][y] = 0    // 背景区域

但这里有个坑——光照不均匀怎么办?我遇到过一条产线,灯管老化导致左边亮右边暗,全局阈值死活调不好。这时候就得请出自适应阈值了。

避坑指南:我曾经在一个PCB焊锡检测项目里,直接用全局阈值分割,结果良品率报告显示30%的焊点虚焊。后来发现是照明系统老化导致图像亮度漂移。从那以后,我养成了一个习惯:每次换线或换班后,先拍一张标准样品的图像,确认灰度分布是否在正常范围内。

4.1.2 自适应阈值分割

自适应阈值,说白了就是把图像切成很多个小块,每个小块单独算阈值。这样即使光照不均匀,每个局部也能找到合适的分割点。

常用的方法有:

  • 均值自适应:计算局部窗口内的平均灰度,减去一个偏移量C作为阈值
  • 高斯自适应:用高斯加权平均代替简单平均,边缘处的过渡更平滑

我个人习惯用高斯自适应,因为它在处理噪声和细节之间平衡得更好。但要注意窗口大小的选择——窗口太小,会把纹理细节误判为前景;窗口太大,又退化成全局阈值了。

实战技巧:窗口大小一般取目标物体直径的1.5到2倍。比如检测直径50像素的焊点,窗口设75到100像素比较合适。这个经验值是我在十几个项目里试出来的,你可以直接拿去用。

4.2 边缘检测:找到物体的“骨架”

阈值分割适合处理区域均匀的目标,但有些场景下,我们更关心物体的轮廓。比如检测芯片引脚是否对齐,或者划痕的长度测量。这时候边缘检测就派上用场了。

4.2.1 Sobel算子:简单粗暴的梯度计算

Sobel算子就是计算图像在水平和垂直方向上的梯度。说白了,就是看像素值变化得有多剧烈。变化大的地方,就是边缘。

// Sobel水平方向核
Gx = [[-1, 0, 1],
      [-2, 0, 2],
      [-1, 0, 1]]

// Sobel垂直方向核
Gy = [[-1, -2, -1],
      [ 0,  0,  0],
      [ 1,  2,  1]]

Sobel的优点是速度快,计算量小。但缺点也很明显——对噪声敏感,而且检测出的边缘比较粗。我一般用它来做粗定位,或者对实时性要求高的场景。

4.2.2 Canny边缘检测:工业界的黄金标准

Canny算法是我在AOI项目里用得最多的边缘检测方法。它不像Sobel那样直接算梯度,而是经过了一系列精心设计的步骤:

  1. 高斯滤波:先降噪,避免把噪声当成边缘
  2. 计算梯度幅值和方向:这一步和Sobel类似
  3. 非极大值抑制:只保留梯度方向上的局部最大值,让边缘变细
  4. 双阈值检测:高阈值确定强边缘,低阈值连接弱边缘

为什么Canny效果好?关键在于双阈值机制。高阈值决定了哪些边缘是“铁定算数的”,低阈值则负责把断开的边缘连起来。我调参时有个习惯:先固定高阈值,然后慢慢降低低阈值,直到边缘连续但不出现太多假边缘为止。

经验之谈:高阈值和低阈值的比例,我一般设在2:1到3:1之间。比如高阈值设150,低阈值就设50到75。这个比例在大多数工业场景下都适用。但如果你检测的是低对比度的目标(比如透明玻璃上的划痕),比例可能要降到1.5:1。

4.3 区域生长法:从种子开始的“圈地运动”

阈值分割和边缘检测都是基于像素的局部特征,而区域生长法更像是一种“全局思维”。它的逻辑很简单:先选一个种子点,然后看它周围的像素是否满足某种相似性条件,满足就合并进来,直到没有新像素加入为止。

区域生长法的关键参数有三个:

  • 种子点的选择:可以手动选,也可以自动选(比如取图像中最亮的点)
  • 生长准则:通常是灰度差小于某个阈值,或者颜色相似度
  • 停止条件:没有新像素满足条件,或者区域大小达到上限

我记得有一次做手机屏幕的划痕检测,划痕很浅,阈值分割和边缘检测都搞不定。后来我用区域生长法,把种子点放在划痕的起点,设置一个很小的灰度差阈值(比如5个灰度级),居然把整条划痕完整地提取出来了。那一刻我真心觉得,有时候最“笨”的方法反而是最有效的。

注意:区域生长法有个致命弱点——计算量大。如果图像分辨率是2000x2000,生长区域又很大,跑一次可能要好几秒。所以它更适合离线分析或者小区域检测。在线AOI场景下,我一般只把它作为备选方案。

4.4 三种算法的对比与选型

说了这么多,到底什么时候用哪种?我整理了一个表格,方便你快速决策:

算法 适用场景 优点 缺点 我常用的场景
全局阈值 高对比度、光照均匀 速度快、实现简单 对光照变化敏感 焊点检测、字符识别
自适应阈值 光照不均匀、背景渐变 鲁棒性好 参数调优较麻烦 PCB板检测、薄膜表面
Sobel 实时性要求高、粗定位 计算量小 边缘粗、噪声敏感 快速定位、尺寸测量
Canny 精细边缘提取 边缘连续、定位准 计算量较大 划痕检测、引脚对齐
区域生长 弱边缘、不规则区域 分割完整 速度慢、种子点依赖 划痕追踪、缺陷分割

4.5 本章知识体系

下面这张图是我自己画的,把本章的核心逻辑串起来了。你看一眼就能明白每种算法在什么位置、怎么选。

图像分割算法知识体系 图像分割 阈值分割 全局阈值(固定T值) 自适应阈值(局部窗口) 边缘检测 Sobel算子(梯度计算) Canny算法(双阈值) 区域生长法 种子点 + 生长准则 选型建议 • 光照均匀、对比度高 → 全局阈值(最快) • 光照不均匀 → 自适应阈值 • 需要精细轮廓 → Canny边缘检测 • 弱边缘、不规则区域 → 区域生长法

嗯,看到这张图你应该能明白,图像分割没有银弹。每种算法都有自己的脾气,关键是你得知道什么时候该用谁。我刚开始做AOI那会儿,也犯过“手里拿着锤子看什么都像钉子”的毛病,后来项目做多了才慢慢悟出来——算法是为场景服务的,不是反过来。

最后分享一个我的调试习惯:拿到一个新项目,我会先用全局阈值试一下,如果效果不好,再换自适应阈值。边缘检测方面,先上Sobel看看梯度分布,如果边缘太乱再上Canny。区域生长法我一般放在最后,因为它慢,但有时候能解决前两种方法搞不定的问题。这个“由简到繁”的思路,能帮你节省大量调试时间。

专注资料整理