"""YOLO 数据集格式的共用定义。 工作流里只用到"写出"这一侧:scripts/generate_boxes.py 产出的 `labels/<类别>/<图名>.txt` 与 `data.yaml` 是给外部检测训练用的交换格式。 标注文件每行一个目标,坐标相对图像宽高归一化到 [0, 1],中心点格式: ``` ``` """ from __future__ import annotations from collections.abc import Sequence from pathlib import Path IMAGE_SUFFIXES = (".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff", ".webp") def write_classes_txt(path: str | Path, classes: Sequence[str]) -> Path: """写 classes.txt:每行一个类别名,行号(从 0 起)即标签里的 class_id。 ultralytics 训练只读 data.yaml;这份是给 LabelImg / X-AnyLabeling 之类 标注工具用的——人工复核、修改框时它们靠它把 class_id 映射回类别名。 """ bad = [name for name in classes if not name.strip() or "\n" in name] if bad: raise ValueError(f"类别名不能为空或包含换行,否则行号与 class_id 对不上: {bad!r}") path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text("".join(f"{name}\n" for name in classes), encoding="utf-8") return path def write_data_yaml( path: str | Path, classes: Sequence[str], root: str | Path = ".", train: str = "images/train", val: str = "images/val", ) -> Path: """写一个 YOLO 风格的 data.yaml,字段与 ultralytics 通用。""" import yaml path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text( yaml.safe_dump( { "path": str(root), "train": train, "val": val, "nc": len(classes), "names": dict(enumerate(classes)), }, allow_unicode=True, sort_keys=False, ), encoding="utf-8", ) return path