第四章:图像分割算法——阈值分割、边缘检测与区域生长
图像分割,说白了就是把一张图里我们感兴趣的东西“抠”出来。在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那样直接算梯度,而是经过了一系列精心设计的步骤:
- 高斯滤波:先降噪,避免把噪声当成边缘
- 计算梯度幅值和方向:这一步和Sobel类似
- 非极大值抑制:只保留梯度方向上的局部最大值,让边缘变细
- 双阈值检测:高阈值确定强边缘,低阈值连接弱边缘
为什么Canny效果好?关键在于双阈值机制。高阈值决定了哪些边缘是“铁定算数的”,低阈值则负责把断开的边缘连起来。我调参时有个习惯:先固定高阈值,然后慢慢降低低阈值,直到边缘连续但不出现太多假边缘为止。
经验之谈:高阈值和低阈值的比例,我一般设在2:1到3:1之间。比如高阈值设150,低阈值就设50到75。这个比例在大多数工业场景下都适用。但如果你检测的是低对比度的目标(比如透明玻璃上的划痕),比例可能要降到1.5:1。
4.3 区域生长法:从种子开始的“圈地运动”
阈值分割和边缘检测都是基于像素的局部特征,而区域生长法更像是一种“全局思维”。它的逻辑很简单:先选一个种子点,然后看它周围的像素是否满足某种相似性条件,满足就合并进来,直到没有新像素加入为止。
区域生长法的关键参数有三个:
- 种子点的选择:可以手动选,也可以自动选(比如取图像中最亮的点)
- 生长准则:通常是灰度差小于某个阈值,或者颜色相似度
- 停止条件:没有新像素满足条件,或者区域大小达到上限
我记得有一次做手机屏幕的划痕检测,划痕很浅,阈值分割和边缘检测都搞不定。后来我用区域生长法,把种子点放在划痕的起点,设置一个很小的灰度差阈值(比如5个灰度级),居然把整条划痕完整地提取出来了。那一刻我真心觉得,有时候最“笨”的方法反而是最有效的。
注意:区域生长法有个致命弱点——计算量大。如果图像分辨率是2000x2000,生长区域又很大,跑一次可能要好几秒。所以它更适合离线分析或者小区域检测。在线AOI场景下,我一般只把它作为备选方案。
4.4 三种算法的对比与选型
说了这么多,到底什么时候用哪种?我整理了一个表格,方便你快速决策:
| 算法 | 适用场景 | 优点 | 缺点 | 我常用的场景 |
|---|---|---|---|---|
| 全局阈值 | 高对比度、光照均匀 | 速度快、实现简单 | 对光照变化敏感 | 焊点检测、字符识别 |
| 自适应阈值 | 光照不均匀、背景渐变 | 鲁棒性好 | 参数调优较麻烦 | PCB板检测、薄膜表面 |
| Sobel | 实时性要求高、粗定位 | 计算量小 | 边缘粗、噪声敏感 | 快速定位、尺寸测量 |
| Canny | 精细边缘提取 | 边缘连续、定位准 | 计算量较大 | 划痕检测、引脚对齐 |
| 区域生长 | 弱边缘、不规则区域 | 分割完整 | 速度慢、种子点依赖 | 划痕追踪、缺陷分割 |
4.5 本章知识体系
下面这张图是我自己画的,把本章的核心逻辑串起来了。你看一眼就能明白每种算法在什么位置、怎么选。
嗯,看到这张图你应该能明白,图像分割没有银弹。每种算法都有自己的脾气,关键是你得知道什么时候该用谁。我刚开始做AOI那会儿,也犯过“手里拿着锤子看什么都像钉子”的毛病,后来项目做多了才慢慢悟出来——算法是为场景服务的,不是反过来。
最后分享一个我的调试习惯:拿到一个新项目,我会先用全局阈值试一下,如果效果不好,再换自适应阈值。边缘检测方面,先上Sobel看看梯度分布,如果边缘太乱再上Canny。区域生长法我一般放在最后,因为它慢,但有时候能解决前两种方法搞不定的问题。这个“由简到繁”的思路,能帮你节省大量调试时间。