refactor(detect): 数据集改为扁平结构、不划分 train/val,Label Studio 文件放到根目录
prepare_yolo_dataset.py 的产出先导入 Label Studio 复核,划分留到复核之后,于是:
- 去掉按类别分层的 train/val 切分以及 --val-ratio / --seed,images/ 与 labels/
下直接是文件;name_map.csv 去掉 split 列,summary.json 的逐类数量改为单列。
- label_studio/ 子目录的内容上移一层:扁平的数据集本身就是 label-studio-converter
要求的 <input>/{images,labels,classes.txt},tasks.json 与 labeling config 直接
生成在根目录,不再需要额外的硬链接视图,DOCUMENT_ROOT 即数据集根目录。
- data.yaml 的 train 与 val 只能指向同一个 images/。ultralytics 能读,但验证指标
算的是训练集本身,mAP 虚高、早停与 best.pt 的选择失真。脚本结束时告警,
detect.py 检测到 train == val 时也告警。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a43267b55
commit
af0897f8c7
@@ -589,15 +589,15 @@ runs/<name>/
|
||||
|
||||
## 检测模型训练(YOLO)
|
||||
|
||||
上一步的产出按类别分目录,方便人工核对;检测训练要的是 train/val 划分——类别写在标签
|
||||
第一列,不靠目录名。所以先整理再训练:
|
||||
上一步的产出按类别分目录,方便人工核对;YOLO 的类别写在标签第一列,不靠目录名。
|
||||
所以先拍平整理,导入 Label Studio 复核,再训练:
|
||||
|
||||
```bash
|
||||
# 1) 拍平类别目录、分层切分、硬链接到 data/(不占额外磁盘)
|
||||
uv run python scripts/prepare_yolo_dataset.py --source runs/boxes_gradcam
|
||||
# 1) 拍平类别目录、统一 PNG、按类别重命名,硬链接到 data/;同时生成 Label Studio 任务
|
||||
uv run python scripts/prepare_yolo_dataset.py --source runs/boxes_shap --label-studio
|
||||
# 先看计划加 --dry-run
|
||||
|
||||
# 2) 训练
|
||||
# 2) 训练(训练前需要先划分,见下文"不划分 train/val")
|
||||
uv run python detect.py --config configs/detect/film.yaml
|
||||
|
||||
# 只评估 / 续训
|
||||
@@ -609,15 +609,22 @@ uv run python detect.py --config configs/detect/film.yaml --resume
|
||||
产出:
|
||||
|
||||
```
|
||||
data/boxes_gradcam/
|
||||
├── images/{train,val}/<类别>_<序号>.png # 一律 PNG:PNG 原图硬链接,BMP 等无损转换
|
||||
├── labels/{train,val}/<类别>_<序号>.txt
|
||||
├── classes.txt # 每行一个类别名,行号即 class_id
|
||||
├── name_map.csv # 新文件名 ↔ 源文件路径
|
||||
├── data.yaml # path 写绝对路径,ultralytics 从哪跑都能读到
|
||||
└── summary.json # 划分明细、逐类数量、落盘方式
|
||||
data/boxes_shap/
|
||||
├── images/<类别>_<序号>.png # 一律 PNG:PNG 原图硬链接,BMP 等无损转换
|
||||
├── labels/<类别>_<序号>.txt
|
||||
├── classes.txt # 每行一个类别名,行号即 class_id
|
||||
├── name_map.csv # 新文件名 ↔ 源文件路径
|
||||
├── data.yaml # train 与 val 都指向 images/
|
||||
├── summary.json # 逐类数量、空标签处置、落盘方式
|
||||
├── tasks.json # 仅 --label-studio:每张图一个任务,框作为 predictions
|
||||
└── tasks.label_config.xml # 仅 --label-studio:各类别的 RectangleLabels
|
||||
```
|
||||
|
||||
**不划分 train/val。** 这份数据集先导入 Label Studio 复核,划分留到复核之后。`data.yaml`
|
||||
的 `train` 与 `val` 只能指向同一个 `images/`,ultralytics 能读,但验证指标算的是训练集本身,
|
||||
mAP 会虚高,早停与 `best.pt` 的选择也随之失真。脚本结束时会告警,`detect.py` 检测到
|
||||
train 与 val 相同时也会告警。正式训练前要先划分。
|
||||
|
||||
**图片统一成 PNG。** pearlescent / blue / uv 的原始数据是 BMP(共 10,814 张),
|
||||
`generate_boxes.py` 按原后缀链接,于是 YOLO 数据集里会混着两种格式。这一步按**文件头**
|
||||
(不是后缀)判断:真 PNG 照旧硬链接不占磁盘,其余无损转 PNG——不缩放像素,归一化的框坐标
|
||||
@@ -626,8 +633,8 @@ data/boxes_gradcam/
|
||||
|
||||
**按类别重新编号。** 源文件名五花八门(`反面纵黑点_1_550mmX1561.71m_20260416160441_0.png`、
|
||||
`pearlescent_404.bmp`……),输出统一改成 `<类别>_<序号>.png`,如 `blackdot_0042.png`。
|
||||
类别以标签第一列为准,文件名永远不会与标签矛盾;序号在类内按源路径排序、与 train/val 划分
|
||||
无关,换 `--seed` 或 `--val-ratio` 重跑,同一张图拿到的名字不变。原文件名里的正反面、位置、
|
||||
类别以标签第一列为准,文件名永远不会与标签矛盾;序号在类内按源路径排序,同一份来源重跑,
|
||||
同一张图拿到的名字不变。原文件名里的正反面、位置、
|
||||
时间戳等信息都在 `name_map.csv` 里(带 BOM,Excel 直接打开中文不乱码)。想保留原名加 `--keep-names`。
|
||||
|
||||
**classes.txt** 是给 LabelImg / X-AnyLabeling 这类标注工具用的,人工复核、修改框时
|
||||
@@ -635,18 +642,8 @@ data/boxes_gradcam/
|
||||
|
||||
### 导入 Label Studio 人工复核
|
||||
|
||||
弱监督框需要人工把关。加 `--label-studio` 会额外生成一份可以直接导入的视图:
|
||||
|
||||
```bash
|
||||
uv run python scripts/prepare_yolo_dataset.py --source runs/boxes_shap --label-studio
|
||||
```
|
||||
|
||||
```
|
||||
data/boxes_shap/label_studio/
|
||||
├── images/*.png labels/*.txt classes.txt # 两个 split 拍平,硬链接
|
||||
├── tasks.json # 每张图一个任务,框作为 predictions
|
||||
└── tasks.label_config.xml # 9 个类别的 RectangleLabels
|
||||
```
|
||||
弱监督框需要人工把关。加 `--label-studio` 会在数据集根目录额外生成 `tasks.json` 与
|
||||
`tasks.label_config.xml`,数据集根目录本身就是 Label Studio 的 `DOCUMENT_ROOT`。
|
||||
|
||||
脚本结束时会打印带实际路径的导入步骤:设置 `LABEL_STUDIO_LOCAL_FILES_*` 环境变量启动、
|
||||
粘贴 labeling config、添加 Local files 存储(**只保存不要 Sync**,否则每张图多出一个空任务)、
|
||||
@@ -654,8 +651,8 @@ data/boxes_shap/label_studio/
|
||||
|
||||
几个取舍:
|
||||
|
||||
- **为什么要拍平**:`label-studio-converter import yolo` 只认 `<input>/{images,labels,classes.txt}`,
|
||||
且 `os.listdir` 不递归,看不见 `train/`、`val/`。按类别重命名后文件名全局唯一,可以直接合并。
|
||||
- **为什么是扁平结构**:`label-studio-converter import yolo` 只认 `<input>/{images,labels,classes.txt}`,
|
||||
且 `os.listdir` 不递归,看不见子目录。数据集不划分、直接扁平,正好就是它要的结构。
|
||||
- **硬链接而不是软链接**:Label Studio 读本地文件时会检查路径是否在 `DOCUMENT_ROOT` 之下,
|
||||
软链接解析后可能落到外面。
|
||||
- **框作为 predictions 而不是 annotations**:它们是模型生成的。审核员修改提交后才成为 annotation,
|
||||
@@ -667,11 +664,6 @@ data/boxes_shap/label_studio/
|
||||
> **复核重点是 `normal`。** SHAP 在每张图上都框了东西,`normal` 有 4,295 个框、占 38%。
|
||||
> 若 `normal` 表示无缺陷,这些框要删掉,否则检测器会学着"检测正常"。
|
||||
|
||||
### 划分
|
||||
|
||||
按类别分层(`--val-ratio` 默认 0.2,`--seed` 默认 42,两次运行结果一致)。分层是必需的:
|
||||
`black` 只有 53 张、`others` 69 张,随机切很容易让某个小类在验证集里挂零,指标就成了空的。
|
||||
|
||||
### 没有框的图怎么处理
|
||||
|
||||
Grad-CAM 没框出东西的有 339 张(占 3%)。它们**默认保留为负样本**,对压低误检有用。
|
||||
|
||||
Reference in New Issue
Block a user