4. 数据集准备与标注:工业场景下的“食材”与“厨艺”
做目标检测,尤其是工业场景,我常说一句话:“模型是锅,数据是米”。锅好不好固然重要,但米要是夹生、有沙,那这锅饭基本就废了。这一章,咱们就聊聊怎么搞到好米,怎么淘米,怎么下锅。
核心观点:工业场景的数据集,90% 的功夫在“清洗”和“标注规范”上,而不是模型结构。你想想看,一个标注错位的框,能让你的 mAP 掉好几个点。
4.1 工业数据集从哪来?别只盯着公开数据集
很多新手喜欢直接下载 COCO、VOC 来练手。但工业场景,说白了,公开数据集基本没用。你见过哪个公开数据集里有“PCB 板上的微小划痕”或者“汽车焊点飞溅”的图片?
我个人习惯把工业数据来源分成三类:
| 来源类型 | 典型场景 | 获取难度 | 我的建议 |
|---|---|---|---|
| 现场采集 | 产线摄像头实时抓拍、巡检机器人拍照 | 高(需协调产线) | 这是最靠谱的,但注意光照、角度多样性 |
| 历史归档 | 质检部门保存的缺陷图片、维修记录照片 | 中(需IT部门配合) | 数据量大,但往往只有“坏样本”,缺少“好样本” |
| 合成/增强 | 3D渲染、GAN生成、数据增强 | 低(自己动手) | 适合补充长尾缺陷,但别完全依赖,容易过拟合 |
我的经验:我曾经帮一个汽车零部件厂做检测,他们只有 200 张缺陷图。我建议他们用“现场采集 + 数据增强”的方式,把产线摄像头架了三天,拍了 5000 张正常件,然后对那 200 张缺陷图做旋转、亮度变化、加噪,最终凑了 2000 张缺陷样本。效果比单纯用那 200 张图好了不止一倍。
4.2 标注工具:LabelImg vs LabelStudio
工具这东西,顺手最重要。我主要用两个:LabelImg 和 LabelStudio。它们各有千秋,别纠结,选一个用熟就行。
4.2.1 LabelImg:轻量、快速、离线
LabelImg 是我最早接触的标注工具。它就是个纯 Python 写的 Qt 应用,安装简单,打开就能用。
- 优点:启动快,不依赖网络,支持 PascalVOC 和 YOLO 格式直接导出。
- 缺点:功能单一,不支持多人协作,标注大图时有点卡。
- 适用场景:个人项目、小团队、快速验证。
安装方式很简单:
# 推荐用 pip 安装
pip install labelImg
# 或者直接下载 exe(Windows 用户)
# 然后命令行运行
labelImg
打开后,界面很直观。左边是工具栏,中间是图片,右边是标签列表。快捷键记几个常用的:W 画框,A 上一张,D 下一张,Ctrl+S 保存。
注意:LabelImg 默认保存为 PascalVOC 格式(XML 文件)。如果你要用 YOLO 格式,记得在菜单栏 View -> Auto Save 旁边,把 PascalVOC 切换成 YOLO。不然你导出的数据格式不对,训练时会报错。
4.2.2 LabelStudio:协作、多模态、功能强大
LabelStudio 是另一个方向。它是个 Web 应用,支持多人同时标注,还能标文本、音频、甚至时间序列。对于工业场景,我主要用它来做团队协作和复杂标注。
- 优点:支持多人协作、项目管理、数据版本控制、API 接口。
- 缺点:需要部署服务端,启动慢,资源占用高。
- 适用场景:团队项目、需要多人审核、数据量大。
部署也很简单:
# 使用 Docker 一键部署
docker run -it -p 8080:8080 -v $(pwd)/my_data:/label-studio/data heartexlabs/label-studio:latest
我个人习惯:小项目用 LabelImg,大项目用 LabelStudio。别在工具上浪费太多时间,核心是把数据标好。
4.3 数据格式转换:XML 与 YOLO 的“翻译官”
标注完数据,你会发现一个问题:LabelImg 默认存的是 XML(PascalVOC 格式),但 YOLO 训练需要的是 TXT(YOLO 格式)。这就好比你有中文的说明书,但机器只认英文。所以,我们需要一个“翻译官”。
两种格式的核心区别在于坐标表示方式:
| 格式 | 坐标表示 | 文件内容示例 |
|---|---|---|
| PascalVOC (XML) | 坐标 (xmin, ymin, xmax, ymax) | <xmin>100</xmin> <ymin>150</ymin> <xmax>200</xmax> <ymax>250</ymax> |
| YOLO (TXT) | 归一化坐标 (class, x_center, y_center, width, height) | 0 0.5 0.5 0.2 0.3 |
转换逻辑其实很简单,就是做个归一化:
# 假设图片宽为 W,高为 H
# XML 中的坐标:xmin, ymin, xmax, ymax
# 计算中心点和宽高
x_center = (xmin + xmax) / 2.0 / W
y_center = (ymin + ymax) / 2.0 / H
width = (xmax - xmin) / W
height = (ymax - ymin) / H
# 写入 TXT 文件
# 格式:class_id x_center y_center width height
# 注意:所有值都是 0~1 之间的浮点数
避坑指南:我曾经在转换时,直接把 xmin 和 ymin 当成了中心点,结果训练出来的模型,检测框全偏了。后来花了整整一天排查,才发现是坐标转换写错了。所以,转换完一定要可视化检查,别偷懒。
为了方便,我写了一个简单的转换脚本,你们可以直接用:
import os
import xml.etree.ElementTree as ET
def convert_voc_to_yolo(xml_file, output_dir, classes):
tree = ET.parse(xml_file)
root = tree.getroot()
# 获取图片尺寸
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
# 获取文件名(不含扩展名)
filename = os.path.splitext(os.path.basename(xml_file))[0]
# 打开 YOLO 格式文件
with open(os.path.join(output_dir, filename + '.txt'), 'w') as f:
for obj in root.iter('object'):
class_name = obj.find('name').text
if class_name not in classes:
continue
class_id = classes.index(class_name)
bbox = obj.find('bndbox')
xmin = int(bbox.find('xmin').text)
ymin = int(bbox.find('ymin').text)
xmax = int(bbox.find('xmax').text)
ymax = int(bbox.find('ymax').text)
# 归一化
x_center = (xmin + xmax) / 2.0 / w
y_center = (ymin + ymax) / 2.0 / h
width = (xmax - xmin) / w
height = (ymax - ymin) / h
f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")
# 使用示例
classes = ['defect', 'scratch', 'dent'] # 你的类别列表
xml_folder = 'path/to/xml'
output_folder = 'path/to/yolo_labels'
os.makedirs(output_folder, exist_ok=True)
for xml_file in os.listdir(xml_folder):
if xml_file.endswith('.xml'):
convert_voc_to_yolo(os.path.join(xml_folder, xml_file), output_folder, classes)
4.4 本章知识体系:一张图看懂
说了这么多,我画了张图,把整个流程串起来。你一看就明白了。
我的习惯:每次转换完,我都会写一个简单的可视化脚本,把 YOLO 的框画回原图上,看一眼。如果框的位置不对,或者类别标错了,马上就能发现。别等到训练完才发现,那代价就大了。