图像分割基础:阈值分割、边缘检测与形态学操作
各位同学,欢迎来到第四讲。今天我们要聊的,是叶片表面缺陷自动标注里最核心的几把“手术刀”——图像分割。说白了,就是把缺陷从背景里“切”出来。
我刚开始做视觉项目那会儿,总觉得深度学习万能。后来发现,很多工业场景下,传统图像分割方法反而更稳、更快。尤其是叶片这种高反光、纹理复杂的表面,纯靠深度学习容易过拟合。嗯,今天咱们就把基础打牢。
核心逻辑:图像分割 = 找到目标区域 + 剔除干扰 + 提取特征。阈值分割负责“粗切”,边缘检测负责“精修”,形态学操作负责“善后”。
一、阈值分割:把灰度图变成黑白图
阈值分割,是所有分割方法里最直观的。你设定一个灰度值T,大于T的算一类,小于T的算另一类。就这么简单。
但实际项目中,难点在于“T怎么选”。我记得有一次做叶片裂纹检测,裂纹和背景的灰度差只有10个像素值。全局阈值死活分不开。后来换了自适应阈值,才把裂纹“揪”出来。
1.1 全局阈值
适合光照均匀的场景。比如叶片在标准光源下,背景是纯色。
import cv2
import numpy as np
img = cv2.imread('leaf_defect.jpg', 0)
# 全局阈值,T=127
ret, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('Global Threshold', thresh)
我的习惯:先用直方图看看灰度分布。如果双峰明显,全局阈值就够用。如果单峰或者平缓,赶紧换自适应。
1.2 自适应阈值
说白了,就是让算法根据局部区域自动算T。光照不均的叶片表面,这招特别好使。
# 自适应阈值,blockSize=11,C=2
adaptive_thresh = cv2.adaptiveThreshold(
img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
cv2.imshow('Adaptive Threshold', adaptive_thresh)
避坑指南:blockSize必须是奇数。C值越大,分割越“松”。我曾经设C=10,结果把背景噪声全当成了缺陷。后来调小到2,效果才正常。
二、边缘检测:找到缺陷的“轮廓”
阈值分割只能得到区域,但缺陷的形状、边界信息,还得靠边缘检测。你想想看,叶片上的划痕、凹坑,本质上都是灰度突变的地方。边缘检测就是抓这些突变。
2.1 Sobel算子
Sobel算子是基础的一阶导数算子。它计算x方向和y方向的梯度,然后合成。优点是快,缺点是对噪声敏感。
# Sobel边缘检测
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel_combined = cv2.magnitude(sobel_x, sobel_y)
sobel_combined = np.uint8(np.clip(sobel_combined, 0, 255))
cv2.imshow('Sobel Edge', sobel_combined)
经验之谈:ksize=3最常用。ksize=5会丢失细节,但噪声更少。我一般先试3,如果噪声太多再换5。
2.2 Canny边缘检测
Canny是工业界的老大哥。它做了非极大值抑制和双阈值处理,边缘更干净、更连续。我个人习惯,只要不是实时性要求极高,都用Canny。
# Canny边缘检测,阈值50和150
edges = cv2.Canny(img, 50, 150)
cv2.imshow('Canny Edge', edges)
为什么Canny效果好?因为它先平滑去噪,再找梯度,最后用两个阈值筛选。低阈值以下的不要,高阈值以上的保留,中间的看是否连接。嗯,这个逻辑很聪明。
调参技巧:低阈值:高阈值 ≈ 1:2 或 1:3。比如(30, 90)或(50, 150)。我遇到过叶片表面纹理很密,阈值设低了全是纹理,设高了又漏掉缺陷。后来用(40, 120)才平衡。
三、形态学操作:给分割结果“美容”
分割完的图像,往往有毛刺、空洞、孤立点。形态学操作就是用来处理这些“瑕疵”的。说白了,就是腐蚀、膨胀、开运算、闭运算这四板斧。
| 操作 | 作用 | 典型场景 |
|---|---|---|
| 腐蚀 | 缩小前景区域,去除小噪点 | 去掉叶片上的孤立亮点 |
| 膨胀 | 扩大前景区域,填充小空洞 | 连接断裂的缺陷边缘 |
| 开运算 | 先腐蚀后膨胀,去除毛刺 | 平滑缺陷边界 |
| 闭运算 | 先膨胀后腐蚀,填充空洞 | 填补缺陷内部的孔洞 |
# 形态学操作示例
kernel = np.ones((5,5), np.uint8)
# 腐蚀
erosion = cv2.erode(thresh, kernel, iterations=1)
# 膨胀
dilation = cv2.dilate(thresh, kernel, iterations=1)
# 开运算
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
注意:kernel大小和迭代次数别贪多。我曾经为了去噪,迭代了5次腐蚀,结果缺陷本身也被“吃”掉了。一般1-2次就够了。
四、实战组合拳:叶片缺陷分割流水线
好了,咱们把今天学的串起来。一个典型的叶片缺陷自动标注流程是这样的:
- 灰度化:把彩色图转成单通道,减少计算量。
- 自适应阈值分割:得到缺陷的粗分割区域。
- Canny边缘检测:提取缺陷的精确边界。
- 形态学闭运算:填充边缘内部的空洞,让缺陷区域连续。
- 轮廓提取:用cv2.findContours拿到缺陷的轮廓坐标,用于后续标注。
# 完整流水线示例
def leaf_defect_segmentation(image_path):
img = cv2.imread(image_path, 0)
# 1. 自适应阈值
thresh = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# 2. Canny边缘
edges = cv2.Canny(thresh, 30, 90)
# 3. 闭运算填充
kernel = np.ones((3,3), np.uint8)
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations=2)
# 4. 轮廓提取
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
return contours
核心思路:阈值分割做“粗定位”,边缘检测做“精修”,形态学做“善后”。三步走,缺一不可。
嗯,今天的内容就到这儿。这些方法看着基础,但我在实际项目中,80%的叶片缺陷都能用这套组合拳搞定。剩下的20%,才需要上深度学习。所以,别小看传统方法。
公众号:蓝海资料掘金营,微信deep3321