feat(detect): 把 Label Studio 的 YOLO 导出装配成带 train/val 划分的数据集

scripts/build_yolo_dataset.py:导出(zip 或目录)+ 复核材料里的 images/ -> data/ 下
可训练的数据集。Label Studio 的 YOLO 导出只有标签,本地存储的图片不会打包进去
(get_local_path 对 local-files 直接返回磁盘路径,YOLO_WITH_IMAGES 也一样),
所以图片从准备复核材料的目录按文件名取,硬链接过去。

- 类别顺序照抄导出的 classes.txt:转换器把标签收进 set 再排序编号,与 labeling
  config 的顺序无关,标签里的 class_id 是按它写的。
- 按类别分层切分,分层用"框最多的那个类别"——人工复核后一张图可能有多个类别的框。
- 标签逐行校验:字段数、类别下标、坐标范围。坏标签训练时不会崩,只会悄悄拉低指标。
- 空标签默认保留为负样本;标签找不到图直接报错,图多于标签则告警。

顺带把 link_or_copy 提到 utils/fs.py 共用(此前 generate_boxes 与
prepare_label_studio 各有一份)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-17 16:42:53 +08:00
co-authored by Claude Opus 5
parent 65bc6b2045
commit 998e6601d4
5 changed files with 556 additions and 18 deletions
+3 -1
View File
@@ -1,4 +1,4 @@
"""通用工具:配置、注册表、日志、随机种子、设备检查。"""
"""通用工具:配置、注册表、日志、随机种子、设备检查、文件落盘。"""
from utils.config import Config, load_config
from utils.device import (
@@ -10,6 +10,7 @@ from utils.device import (
resolve_amp,
resolve_device,
)
from utils.fs import link_or_copy
from utils.logging import get_logger, setup_logging
from utils.registry import Registry
from utils.seed import seed_everything
@@ -28,4 +29,5 @@ __all__ = [
"setup_logging",
"Registry",
"seed_everything",
"link_or_copy",
]
+28
View File
@@ -0,0 +1,28 @@
"""文件落盘的小工具。"""
from __future__ import annotations
import errno
import os
import shutil
from pathlib import Path
def link_or_copy(source: str | Path, destination: str | Path) -> str:
"""给文件建硬链接;跨文件系统时退回复制。返回 "hard" 或 "copy"。
硬链接不占额外磁盘,却表现得和真实文件一样——产出目录可以整个拷走。
数据集动辄上万张图,链接与复制的差别是几百 MB 起步。
"""
destination = Path(destination)
if destination.exists() or destination.is_symlink():
destination.unlink()
destination.parent.mkdir(parents=True, exist_ok=True)
try:
os.link(source, destination)
return "hard"
except OSError as exc:
if exc.errno != errno.EXDEV:
raise
shutil.copy2(source, destination)
return "copy"