4、气泡缺陷标注:气泡特征分析、标注规则与边界框策略
气泡,在光学玻璃缺陷里算是最“老实”的一种。为什么这么说?因为它形状规则、边界清晰,不像划痕那样飘忽不定,也不像麻点那样密密麻麻。但老实归老实,标注起来还是有不少门道的。我刚开始带团队做玻璃检测项目时,就发现新人最容易在气泡标注上翻车——不是漏标了微小气泡,就是把边界框画得歪歪扭扭。
今天咱们就把气泡标注这件事彻底聊透。
4.1 气泡的视觉特征:为什么它这么好认?
气泡说白了就是玻璃内部的一个小空腔。光线穿过时,会在气泡的前后两个界面发生折射和反射,形成非常独特的视觉信号。
| 特征维度 | 典型表现 | 标注影响 |
|---|---|---|
| 形状 | 圆形或椭圆形,边缘光滑 | 边界框可以紧贴边缘,不需要预留太多余量 |
| 边缘 | 高对比度、清晰锐利 | 边缘检测算法容易捕捉,标注时定位准确 |
| 内部 | 透光或暗斑(取决于照明方式) | 内部纹理均匀,不需要精细分割 |
| 尺寸范围 | 从微米级到毫米级不等 | 小气泡容易漏标,需要放大检查 |
我个人习惯在暗场照明下观察气泡。这时候气泡会呈现明亮的圆点,背景是暗的,对比度极高,标注起来特别舒服。明场下气泡反而像个黑洞,边缘虽然清晰,但内部细节全丢,有时候会跟脏点混淆。
核心判断标准:气泡的边界一定是闭合的、光滑的、连续的。如果看到一个“圆形”缺陷但边缘有毛刺或断裂,那大概率不是气泡,可能是麻点群或者划痕的端部。
4.2 标注规则:边界框到底该怎么画?
这个问题我几乎每次培训都会被问到。说白了,边界框的规则取决于你后续要做什么。如果是做目标检测(比如 YOLO、Faster R-CNN),那规则相对宽松;如果是做分割(比如 Mask R-CNN),那就严格得多。
这里我给出一个经过多个项目验证的通用规则:
- 最小外接矩形原则:边界框必须刚好框住气泡,不切边、不多余。上下左右各留 2-3 个像素的余量就够了。
- 独立气泡独立框:两个气泡即使靠得很近,也要分别画框。除非它们之间的距离小于较小气泡直径的 1/3,这时候可以合并为一个框。
- 重叠气泡的处理:如果气泡有部分重叠,每个气泡仍然独立标注。边界框可以重叠,但标注时要在属性里注明“重叠”。
- 边界气泡:气泡如果被图像边缘切掉一部分,只要剩余部分超过原气泡面积的 50%,就要标注。否则跳过。
我的经验:标注小气泡(直径 < 10 像素)时,别太相信自动标注工具。我遇到过好几次,自动框把两个相邻的小气泡识别成一个椭圆,结果模型训练出来全是误检。这种时候,手动标注反而更靠谱。
4.3 边界框策略:水平框 vs 旋转框
气泡是圆形的,按理说用水平矩形框(HBB)就够了。但实际项目中,我建议你根据气泡的分布密度来选择策略。
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 稀疏分布(每张图 < 10 个气泡) | 水平矩形框(HBB) | 标注快,后处理简单,模型容易收敛 |
| 密集分布(每张图 > 30 个气泡) | 旋转矩形框(RBB) | 减少框之间的重叠,提高 NMS 效果 |
| 气泡呈链状排列 | 旋转矩形框(RBB) | 链状气泡整体呈长条分布,水平框会引入大量背景噪声 |
| 高精度检测需求 | 分割掩码(Segmentation) | 边界框无法精确描述圆形轮廓,分割掩码更准确 |
我记得有一次做手机镜头玻璃的检测项目,客户要求气泡定位精度达到 ±2 像素。用水平框怎么调都差一点,后来换成旋转框,精度直接上去了。你想想看,圆形气泡用水平框框住,四个角全是背景,模型学到的特征里混入了大量无效信息,能不偏差吗?
避坑指南:我曾经在一个项目中,为了省事把所有气泡都用水平框标注。结果训练出来的模型,在气泡密集区域频繁漏检。后来排查发现,是因为水平框之间的 IoU 太高,NMS 把很多真实气泡给抑制掉了。从那以后,我养成了一个习惯:先看数据分布,再定标注策略。
4.4 实战标注案例:从原始图像到标注完成
光说不练假把式。咱们走一遍完整的标注流程,用的是一个实际的玻璃样本图像。
步骤 1:图像预处理
拿到原始图像后,我一般先做两步:
- 调整对比度:用直方图均衡化把气泡和背景的对比度拉开。
- 去噪:用高斯滤波(核大小 3x3)去除传感器噪声,但别用太大核,否则小气泡会被抹掉。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('glass_sample_04.tif', cv2.IMREAD_GRAYSCALE)
# 直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_eq = clahe.apply(img)
# 高斯去噪
img_blur = cv2.GaussianBlur(img_eq, (3, 3), 0)
# 保存预处理结果
cv2.imwrite('glass_sample_04_preprocessed.tif', img_blur)
步骤 2:气泡检测与定位
预处理之后,我会用霍夫圆检测来找候选气泡。这一步不是最终标注,而是帮我快速定位,减少手动查找的工作量。
# 霍夫圆检测
circles = cv2.HoughCircles(
img_blur,
cv2.HOUGH_GRADIENT,
dp=1.2,
minDist=20,
param1=50,
param2=30,
minRadius=3,
maxRadius=100
)
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
for (x, y, r) in circles:
# 在图像上画出候选圆
cv2.circle(img_blur, (x, y), r, (0, 255, 0), 2)
步骤 3:人工复核与标注
自动检测的结果只能作为参考。我会逐个人工复核:
- 确认每个候选圆确实是气泡(不是脏点、不是划痕端部)
- 调整边界框位置和大小(自动检测的圆心有时会偏 1-2 像素)
- 补充自动检测漏掉的小气泡(直径 < 5 像素的经常被漏掉)
步骤 4:导出标注数据
标注完成后,导出为 COCO 格式或 YOLO 格式。我个人偏好 COCO 格式,因为它支持分割掩码,后续做模型迭代时更灵活。
{
"images": [
{
"id": 1,
"file_name": "glass_sample_04.tif",
"width": 2048,
"height": 2048
}
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 4, // 气泡类别 ID
"bbox": [856, 432, 28, 28], // x, y, width, height
"segmentation": [[856, 432, 870, 432, 870, 446, 856, 446]],
"area": 615,
"iscrowd": 0
}
],
"categories": [
{"id": 4, "name": "bubble"}
]
}
关键提醒:标注数据导出后,一定要做一次可视化检查。把标注框画回原图上,肉眼过一遍。我见过太多因为标注工具 bug 导致坐标偏移的案例,这一步能救你命。
4.5 气泡标注知识体系
下面这张图总结了气泡标注的核心流程和关键决策点,你可以把它当作标注时的快速参考。
嗯,气泡标注其实不难,但细节决定成败。你只要把特征认准了、策略选对了、规则执行到位了,这部分基本不会出大问题。我见过太多人栽在“差不多就行”的心态上,结果模型训练出来全是坑。
记住一句话:标注的质量,直接决定了模型的天花板。气泡标注如此,其他缺陷也一样。