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()
我的经验:GigE 相机一定要先配好IP和网卡巨帧。我曾经在客户现场,因为网卡没调,丢包丢到怀疑人生。后来发现是巨帧没开,开了之后稳如老狗。

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())
注意:队列大小要设上限。否则内存爆了,整个系统都会挂掉。我曾经在调试时忘了设maxsize,结果采集了10分钟,电脑直接蓝屏。

4.2 数据集命名规范

命名规范这件事,看似简单,但做不好就是灾难。你想想看,几个月后,面对几千张“IMG_001.jpg”、“IMG_002.jpg”,你还能记得哪张是哪个批次、哪种缺陷吗?

我推荐一套命名规则,经过多个项目验证,比较通用:

格式:{项目代号}_{产品型号}_{批次号}_{缺陷类型}_{采集日期}_{序号}.{扩展名}

举个例子:

  • BJEV_BatteryCell_B20241001_Scratch_20241015_0001.png
  • BJEV_BatteryCell_B20241001_Dent_20241015_0002.png
  • BJEV_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等)。
我的建议:团队超过3个人,或者数据量超过100GB,就别用文件系统复制了。上DVC,省心省力。我第一次用DVC时,觉得多此一举。直到有一次误删了标注文件,git checkout一下就恢复了,那一刻我服了。

4.5 本章知识体系总览

下面这张图,把图像采集与数据管理的核心流程串起来了。你可以把它当作一个检查清单,做项目时对着看,不容易漏项。

图像采集与数据管理流程总览 图像采集与数据管理流程总览 自动化采集 数据集命名规范 存储架构设计 数据版本管理 采集子模块 硬件控制层 (USB/GigE/SDK) 触发逻辑层 (硬触发/软触发) 数据流管理层 (生产者-消费者) 命名规范要素 项目代号_产品型号_批次号 缺陷类型_采集日期_序号 自解释、可追溯、唯一性 存储架构分层 raw/ (原始数据,只读) processed/ (预处理后数据) annotations/ + models/ 版本管理方案 文件系统复制 (小团队) DVC (推荐,适合中大型团队)

嗯,这一章的内容就到这里。从采集脚本到版本管理,每一步都是为后面的模型训练和部署打基础。别嫌麻烦,这些基础打好了,后面会顺很多。