4. 图像采集与数据管理:自动化采集脚本编写、数据集命名规范、数据存储架构设计、数据版本管理
图像采集和数据管理,听起来像是脏活累活。但说实话,我见过太多项目死在这一步。算法调得再好,数据一塌糊涂,落地就是空谈。这一章,我们就把这些“基建”工作捋清楚。
4.1 自动化采集脚本编写
人工按快门?别闹了。产线节拍是按秒算的,人根本跟不上。自动化采集是第一步,也是决定数据质量的关键。
我个人习惯把采集脚本分成三层:硬件控制层、触发逻辑层、数据流管理层。这样后期维护起来,脑子不疼。
4.1.1 硬件控制层
说白了,就是让相机、光源、传感器听你指挥。不同的相机接口,代码写法差异很大。我拿最常见的 USB 相机和 GigE 相机举个例子。
# USB 相机采集示例 (OpenCV)
import cv2
cap = cv2.VideoCapture(0) # 0 是设备ID
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 2592)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1944)
cap.set(cv2.CAP_PROP_FPS, 30)
ret, frame = cap.read()
if ret:
cv2.imwrite("capture_usb.jpg", frame)
cap.release()
# GigE 相机采集示例 (使用厂商SDK,如Basler pypylon)
from pypylon import pylon
camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())
camera.Open()
camera.StartGrabbingMax(1)
grab_result = camera.RetrieveResult(5000)
if grab_result.GrabSucceeded():
img = grab_result.GetArray()
# 保存或处理图像
camera.Close()
4.1.2 触发逻辑层
产线上不是一直拍,而是有产品来了才拍。触发方式一般有两种:硬触发和软触发。
- 硬触发:通过PLC或光电传感器给相机一个电平信号。延迟低,可靠性高。适合高速产线。
- 软触发:通过软件指令触发采集。灵活性高,但延迟受系统调度影响。适合实验室或低速场景。
我建议,只要产线速度超过每分钟60个,就老老实实用硬触发。软触发在Windows下,偶尔会给你来个“卡顿”,那漏检率就上去了。
4.1.3 数据流管理层
采集到的图像,不能直接扔硬盘。要加个缓冲队列,防止写入速度跟不上采集速度。我常用的方案是生产者-消费者模式。
# 伪代码:生产者-消费者模式
import queue
import threading
image_queue = queue.Queue(maxsize=100)
def producer(): # 采集线程
while True:
frame = camera.capture()
image_queue.put(frame)
def consumer(): # 写入线程
while True:
frame = image_queue.get()
save_to_disk(frame, generate_filename())
4.2 数据集命名规范
命名规范这件事,看似简单,但做不好就是灾难。你想想看,几个月后,面对几千张“IMG_001.jpg”、“IMG_002.jpg”,你还能记得哪张是哪个批次、哪种缺陷吗?
我推荐一套命名规则,经过多个项目验证,比较通用:
格式:{项目代号}_{产品型号}_{批次号}_{缺陷类型}_{采集日期}_{序号}.{扩展名}
举个例子:
BJEV_BatteryCell_B20241001_Scratch_20241015_0001.pngBJEV_BatteryCell_B20241001_Dent_20241015_0002.pngBJEV_BatteryCell_B20241001_NG_20241015_0003.png(OK品用OK,NG品用NG)
| 字段 | 说明 | 示例 |
|---|---|---|
| 项目代号 | 项目唯一标识 | BJEV |
| 产品型号 | 被检产品型号 | BatteryCell |
| 批次号 | 生产批次,方便追溯 | B20241001 |
| 缺陷类型 | OK/NG/具体缺陷名 | Scratch |
| 采集日期 | YYYYMMDD格式 | 20241015 |
| 序号 | 4位流水号,从0001开始 | 0001 |
4.3 数据存储架构设计
数据存哪里?怎么存?这直接影响到后续的训练效率和推理速度。我见过有人把所有图片塞一个文件夹里,结果训练时加载数据成了瓶颈。
我推荐的分层存储架构如下:
data/
├── raw/ # 原始数据,只读不写
│ ├── project_A/
│ │ ├── batch_001/
│ │ └── batch_002/
│ └── project_B/
├── processed/ # 预处理后的数据
│ ├── train/
│ ├── val/
│ └── test/
├── annotations/ # 标注文件
│ ├── coco_format/
│ └── yolo_format/
└── models/ # 训练好的模型权重
├── v1.0/
└── v2.0/
这个结构的好处是:原始数据和加工数据分离。你可以在raw里永远保留一份最原始的数据,万一预处理脚本写错了,还能重来。
对于存储介质的选择,我一般这样分配:
- 热数据(当前项目正在用的):NVMe SSD,读写快,容量不用太大,1-2TB足够。
- 温数据(近期项目,偶尔回顾):SATA SSD 或 企业级 HDD,容量大,成本适中。
- 冷数据(归档项目,几乎不访问):磁带库或云存储归档,成本最低。
4.4 数据版本管理
代码有版本管理,数据也得有。你想想看,模型训到一半,发现数据被人改过,那结果还能信吗?
数据版本管理,我主要用两种方式:
4.4.1 基于文件系统的版本管理
简单粗暴,但有效。每次数据变更,都复制一份完整的数据集,并打上版本标签。
dataset/
├── v1.0/
│ ├── train/
│ └── val/
├── v1.1/
│ ├── train/
│ └── val/
└── v2.0/
├── train/
└── val/
这种方式适合小团队,数据量不大。缺点就是占空间,每次都是全量复制。
4.4.2 基于 DVC (Data Version Control) 的版本管理
DVC 是专门为数据版本管理设计的工具。它不存数据本身,而是存数据的元数据和指针。数据可以存在本地、NAS或云存储上。
# DVC 基本操作
dvc init
dvc add data/raw/
git add data/raw.dvc .gitignore
git commit -m "add raw data v1.0"
dvc push # 将数据推送到远程存储
DVC 的好处是:
- 数据版本和代码版本可以一一对应。
- 只存差异,不存全量,节省空间。
- 支持多种远程存储(S3、GCS、NAS等)。
4.5 本章知识体系总览
下面这张图,把图像采集与数据管理的核心流程串起来了。你可以把它当作一个检查清单,做项目时对着看,不容易漏项。
嗯,这一章的内容就到这里。从采集脚本到版本管理,每一步都是为后面的模型训练和部署打基础。别嫌麻烦,这些基础打好了,后面会顺很多。