☰
YOLO民族服饰识别数据集:三格式标签与训练全流程指南
2026/10/2 3:15:30 网站建设 项目流程

简介:面向目标检测学习与实战的YOLO民族服饰识别数据集,适合计算机视觉初学者、课程设计及项目开发者使用。内含1000张真实场景下的高质量服饰图片,均使用LabelImg标注,标注框质量可靠;标签按voc(xml)、coco(json)、yolo(txt)三种常用格式分别存放,可直接切换至不同YOLO版本训练。压缩包共2000个文件,以xml、txt标签为主,另含6个html教程页面、3个Python划分脚本和1个yaml配置文件,整体约50.2MB,目录结构清晰便于检索。附带Windows与Linux双环境下的YOLO环境搭建和训练案例教程,并提供训练集、验证集、测试集划分脚本,可自由定制数据比例,减少前期准备成本。目前已有454人学习下载,适合希望快速走通数据标注、格式转换、模型训练全流程的读者。

1. 这个数据集包解决的,是民族服饰识别的三个现实门槛

做民族服饰识别的人,十有八九不是死在算法上,而是死在数据准备上。从博物馆藏品数字化、非遗纹样归档,到电商平台的民族服装类目自动打标,需求一直都有,但想训练一个像样的YOLO模型,第一步就得先解决三件事:有图、有标签、标签格式能喂给训练脚本。这三件事每一件都能耗掉几周时间。这个标题里的"YOLO民族服饰识别数据集(含1000张图片)+对应voc、coco和yolo三种格式标签+划分脚本+训练教程.rar",本质上就是把最耗时间的脏活累活打包好了——图片有了、三种主流标注格式都给了、划分脚本和训练教程也配齐了,你拿到手可以直接把精力放在调模型和踩训练坑上。它适合两类人:一类是刚接触目标检测、想用现成数据跑通YOLO全流程的学生和转行者;另一类是手里有自己的民族服饰图片、但被VOC、COCO、YOLO三种格式转换折腾到崩溃的工程师。这篇文章就围绕这个包,把数据校验、格式转换、划分逻辑、训练参数和最常见的坑一层层拆开讲透。

2. 数据摸底:1000张图片够不够训,标签长什么样

2.1 类别构成与标签格式:先看yaml再谈训练

拿到压缩包第一步不是解压后直接开训,而是先看数据组织方式。常见做法是解压后先打开目录结构,确认图片、标注文件、划分脚本、训练教程各自放在哪里。一个合格的数据集包内部通常长这样:

dataset/ ├── images/ │ ├── train/ # 训练图片 │ ├── val/ # 验证图片 │ └── test/ # 测试图片 ├── labels/ │ ├── voc/ # XML标注,PASCAL VOC格式 │ ├── coco/ # JSON标注,COCO格式 │ └── yolo/ # TXT标注,YOLO格式 ├── data.yaml # YOLO训练需要的类别配置文件 ├── split_data.py # 划分脚本 └── train.ipynb # 或 train.py 训练教程

先打开data.yaml看类别名和类别数量。民族服饰数据集的类别一般是按服饰部位、服饰类型或民族划分的,比如headwear、coat、skirt、embroidery这样,也可能是tibetan_robe、miao_silver_ornaments这种按民族细分的。类别定义直接决定训练任务的粒度——按部位划分适合做属性识别,按民族划分适合做分类检索,两者混在一起会让模型困惑。

这里有一个最容易被忽略的点:打开YOLO格式的标签文件,看类别ID是否从0开始且没有跳号。YOLO训练脚本的类别索引是从0开始的,如果标签文件里出现了类别ID为5的数据,但data.yaml只定义了5个类别(ID 0-4),训练时模型会照常跑,但损失值异常高,推理时这些样本会被强制归到最后一个类别,后续排查极其痛苦。所以我拿到任何数据集的第一件事就是全局扫描标签文件中的最大类别ID。

# 在Linux或macOS终端里一行命令扫出所有yolo标签用到的类别ID cat labels/yolo/*.txt | awk '{print $1}' | sort -n | uniq -c

这条命令会把所有TXT标签的第一列(即类别ID)提取出来,排序后统计每个ID出现的次数。如果输出里出现的最大值大于data.yaml里的类别数减1,这个数据集的标签就有问题,不能直接拿来训练。

2.2 三种格式的差异:voc/xml、coco/json、yolo/txt各自什么时候用

一个数据集同时给VOC、COCO、YOLO三种标签,不是闲着没事干,而是因为三条技术路线各是各的生态。理解这三者的差异,后面做转换时才知道自己在干什么。

VOC格式是最老牌的目标检测标注格式,本质是图片同名的一个XML文件。XML里记录着<object>标签的<name>和<bndbox>,<bndbox>给的是绝对像素坐标的xmin、ymin、xmax、ymax。这种格式可读性最好,但凡是深度学习的训练脚本,几乎都要先把XML解析成数组才能用,所以它适合做数据交换和可视化验证。

COCO格式则是一个大的JSON文件或每张图一个JSON,标注内部按images、annotations、categories三个字段组织。annotations里的bbox是[x, y, width, height],注意是左上角坐标加宽高,不是两个对角点。这种格式被Detectron2、MMDetection这类框架原生支持,做迁移学习或对比实验时最方便。

YOLO格式是Ultralytics系训练脚本直接吃的格式,每个TXT文件和图片同名,每行记录class_id x_center y_center width height,坐标全部归一化到0到1之间,是相对图片宽高的比例。这种格式简洁、文件小、训练时解析快,缺点是人类不可读,打开一个TXT看到的是3 0.512 0.288 0.234 0.456这种数字,根本看不出框在哪。

2.3 用脚本校验图片与标签是否对得上

解压后的数据集不管是官方给好的还是从其他地方收集的,都要先过一遍完整性校验。最常见的坑是图片和标签文件数量对不上,或者有标签没有图片、有图片没有标签。YOLO训练过程中如果检测到某个图片找不到对应标签,会直接跳过这张图,但如果你不知道哪些图被跳过了,就会误以为训练数据比实际多。

下面是一个我自己常用来做数据校验的脚本,改一下路径就能用:

import os from pathlib import Path image_dir = Path("dataset/images/train") label_dir = Path("dataset/labels/yolo/train") images = {p.stem for p in image_dir.glob("*.jpg")} labels = {p.stem for p in label_dir.glob("*.txt")} only_images = images - labels only_labels = labels - images print(f"图片总数: {len(images)}") print(f"标签总数: {len(labels)}") print(f"有图无标签: {len(only_images)}") # 只打印前面5个示例,避免刷屏 for name in sorted(only_images)[:5]: print(f" [缺失标签] {name}") print(f"有标签无图片: {len(only_labels)}") for name in sorted(only_labels)[:5]: print(f" [缺失图片] {name}")

如果有图无标签或有标签无图片的结果不为空,需要对这些样本单独处理。倾向于直接丢弃这些残缺样本,而不是手动补标签,因为手工补容易引入坐标错误。另外还需要检查图片本身是否损坏,用Python的PIL库遍历一遍所有图片,能打开就过,打不开就删掉对应图片和标签。

3. 三种标签格式之间的转换:坐标系换算是核心

3.1 VOC转YOLO:四角坐标换中心点加宽高

VOC格式的XML给的是整数像素坐标的左上角和右下角,YOLO格式要的是归一化后的中心点坐标和宽高。转换公式不复杂,但容易搞反。核心逻辑是:

x_center = (xmin + xmax) / 2 / image_width y_center = (ymin + ymax) / 2 / image_height width = (xmax - xmin) / image_width height = (ymax - ymin) / image_height

这里必须强调一点:box坐标是相对于原图片的尺寸归一化。如果你训练前做过任何预处理,比如resize到640x640,归一化基准也要跟着变。常见做法是直接读取原始图片的宽高做归一化,这样训练脚本会自动按模型输入尺寸缩放,无需额外处理。

下面是一个完整的VOC转YOLO脚本,依赖xml.etree.ElementTree解析XML:

import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, class_names, output_dir): tree = ET.parse(xml_file) root = tree.getroot() # 从XML里读取图片尺寸 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) # 从文件名推导输出txt路径 txt_path = output_dir / (Path(xml_file).stem + ".txt") lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text # 类别不在预定义列表里就跳过,避免后续训练报错 if cls_name not in class_names: print(f"跳过未知类别: {cls_name} @ {Path(xml_file).name}") continue cls_id = class_names.index(cls_name) bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 核心换算:绝对坐标 -> 归一化中心点 + 宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 防止坐标越界:归一化后值应该严格在0~1之间 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": # 修改成你自己的类别列表,顺序决定ID class_names = ["hat", "coat", "trousers", "skirt", "embroidery"] xml_dir = Path("labels/voc") yolo_dir = Path("labels/yolo") yolo_dir.mkdir(parents=True, exist_ok=True) for xml_file in xml_dir.glob("*.xml"): voc_to_yolo(xml_file, class_names, yolo_dir)

这段脚本的逻辑是:遍历VOC目录下所有XML文件,解析出每个目标的类别名和边界框坐标,再通过中心点加宽高的公式换算成归一化值,最后写入同名TXT。有两处细节容易被忽略:第一,class_names列表的顺序就是类别ID的映射,一旦训练开始后就不要改动它,否则标签全乱;第二,公式里除以的是原始图片宽高,不是模型输入尺寸,训练脚本内部会再缩放。

3.2 COCO转YOLO:处理JSON嵌套结构

COCO转YOLO比VOC转YOLO稍麻烦一点,因为COCO的类别ID和你的类别列表索引不一定对应。COCO的JSON结构里,categories是一个列表,每个类目有自己的id字段,这个ID可能是任意正整数,而不是从0开始的自增。转换时必须建立COCO类别ID到YOLO类别索引的映射表。

import json from pathlib import Path def coco_to_yolo(json_path, image_dir, output_dir, class_names): with open(json_path, "r", encoding="utf-8") as f: coco_data = json.load(f) # 建立COCO原始类别id -> 你要的类别索引 的映射 # 注意:只用你在class_names里定义的类别,多余类别忽略 cat_id_map = {} for cat in coco_data["categories"]: if cat["name"] in class_names: cat_id_map[cat["id"]] = class_names.index(cat["name"]) # 建立图片id -> 文件名 的映射 img_id_to_name = {} img_id_to_size = {} for img in coco_data["images"]: img_id_to_name[img["id"]] = img["file_name"] img_id_to_size[img["id"]] = (img["width"], img["height"]) # 按图片id聚合所有标注 img_annotations = {} for ann in coco_data["annotations"]: img_id = ann["image_id"] if img_id not in img_annotations: img_annotations[img_id] = [] img_annotations[img_id].append(ann) output_dir.mkdir(parents=True, exist_ok=True) for img_id, anns in img_annotations.items(): file_name = img_id_to_name[img_id] width, height = img_id_to_size[img_id] txt_path = output_dir / (Path(file_name).stem + ".txt") lines = [] for ann in anns: coco_cat_id = ann["category_id"] if coco_cat_id not in cat_id_map: continue # 跳过不在目标类别列表里的对象 cls_id = cat_id_map[coco_cat_id] # COCO的bbox是[x, y, width, height],且是整数类型 # 注意是左上角坐标加宽高,不是中心点 x, y, w, h = ann["bbox"] x_center = (x + w / 2.0) / width y_center = (y + h / 2.0) / height w_norm = w / width h_norm = h / height x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w_norm = min(max(w_norm, 0.0), 1.0) h_norm = min(max(h_norm, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": class_names = ["hat", "coat", "trousers", "skirt", "embroidery"] coco_to_yolo( json_path="annotations/instances_train.json", image_dir=Path("images/train"), output_dir=Path("labels/yolo/train"), class_names=class_names, )

这段脚本的关键点是cat_id_map的构建逻辑——你不能直接在循环里用enumerate(class_names)来分配ID,因为COCO JSON里的category_id是它自己的编号体系。训练脚本只认class_names列表的下标,所以必须把COCO的类别ID映射到你自己的列表索引上。一个经常踩的坑是忘记了COCO的bbox格式是[x, y, width, height],如果误当成VOC的[xmin, ymin, xmax, ymax]去换算,得到的框中心点和宽高全会偏移。

3.3 划分脚本:train/val/test怎么分才不翻车

数据划分是整个流程里最容易被轻视、但影响最大的环节。很多人的习惯是把所有数据塞进train里,留一部分给val验证。这种做法的问题是:如果图片来自不同的拍摄场景或设备,随机划分可能让同一个场景的图片同时出现在train和val里,验证指标虚高,部署上线后才发现模型对没见过的场景毫无泛化能力。

划分脚本至少要做两件事。第一,按比例切分。常见比例是8:1:1或7:2:1,1000张图片的数据集,建议至少留100张做验证、100张做测试。第二,如果数据集里有重复或近似图片,要先做去重再做划分,否则泄漏问题会让验证结果彻底失真。

import os import random import shutil from pathlib import Path def split_dataset(image_root, label_root, train_ratio=0.8, val_ratio=0.1, seed=42): random.seed(seed) images = list(image_root.glob("*.jpg")) random.shuffle(images) n = len(images) n_train = int(n * train_ratio) n_val = int(n * val_ratio) train_images = images[:n_train] val_images = images[n_train:n_train + n_val] test_images = images[n_train + n_val:] splits = { "train": train_images, "val": val_images, "test": test_images, } # 对每一份数据,复制图片和对应的同名txt标签 for split_name, split_images in splits.items(): out_img_dir = image_root.parent / split_name / "images" out_lbl_dir = label_root.parent / split_name / "labels" out_img_dir.mkdir(parents=True, exist_ok=True) out_lbl_dir.mkdir(parents=True, exist_ok=True) for img_path in split_images: label_path = label_root / (img_path.stem + ".txt") if not label_path.exists(): print(f"警告: 图片 {img_path.name} 没有对应标签,已跳过") continue shutil.copy2(img_path, out_img_dir / img_path.name) shutil.copy2(label_path, out_lbl_dir / label_path.name) print(f"训练集: {len(train_images)}") print(f"验证集: {len(val_images)}") print(f"测试集: {len(test_images)}") if __name__ == "__main__": split_dataset( image_root=Path("dataset/images/all"), label_root=Path("dataset/labels/yolo/all"), )

这个脚本的逻辑是:把所有图片打乱后按比例切成三份,再把每张图片和它对应的TXT标签一起复制到目标目录。注意代码里用了shutil.copy2而不是os.rename,这是刻意保留原始文件,万一划分结果不理想可以直接重新跑。随机种子seed固定为42,保证每次跑出来的划分完全一致,后面调参时比较训练结果才有意义。

4. 训练前的检查清单:本地跑通最小训练命令再上全量

4.1 最小训练命令:先让YOLO跑起来

拿到数据后不要急着把全部1000张图丢进训练脚本。第一件事是先跑通一次最小训练——用少量图片、几轮迭代,确认数据流、标签读取、训练循环都没有问题,再放大到全量。这个过程能过滤掉大多数低级错误。

# 用YOLOv8做最小训练:20张图片,跑5轮,只训练一个epoch yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=5 \ imgsz=640 \ batch=4 \ device=0

model=yolov8n.pt会自动下载预训练模型,所以要确保网络能够访问Ultralytics的模型仓库。如果下载慢或失败,可以用model=yolov8n.yaml代替,从零开始训练,但收敛会慢很多。imgsz=640是模型输入尺寸,batch=4是单次迭代的图片数量,优先根据显存调整batch而不是imgsz,batch设多大、显存不够时优先调小哪个参数,属于老生常谈,但新手经常在这里翻车。如果显存只有6G,用batch=2或imgsz=416更稳妥。

跑完以后看两样东西:第一,训练日志里有没有WARNING提示标注文件缺失或类别不匹配;第二,results.png里的损失曲线是否在下降。如果前几轮损失值在0附近跳动,大概率是标签坐标全部为0或归一化出了问题。

4.2 超参数怎么设:epochs、batch、imgsz的联动关系

YOLO训练的几个关键超参数不是独立变量的关系,它们之间有联动。epochs决定遍历全量数据的次数。1000张图片的数据集,训练100个epoch意味着模型看到每张图100次,对小数据集来说足够。batch的大小影响梯度估计的稳定性,batch越大梯度越稳,但显存占用也越大。imgsz影响检测精度和速度的平衡,输入尺寸640是小目标检测的常见选择,如果民族服饰上的精细纹样是重点,可以试imgsz=960,精度会有提升但训练时间可能翻倍。

一个重要但容易被忽略的点:batch和learn_rate是配对的。Ultralytics的默认学习率是针对batch=16调过的,如果用batch=4训练,理论上应该把学习率调低一些,否则容易出现损失值震荡。常见做法是把lr0从默认的0.01改成0.005。

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=8 \ lr0=0.005 \ patience=20

patience=20的意思是连续20轮验证集指标不提升就提前停止训练。1000张图片的数据集,50轮左右基本就能看出模型是否有效,如果到80轮还在明显下降,继续跑;如果50轮后指标停滞,提前停也不亏。

4.3 类别不平衡:民族服饰数据集的隐藏问题

民族服饰数据集的类别通常不是均匀分布的。比如"帽子"出现了800次,而"腰带"只出现了50次,模型会严重偏向高频类别。一个常见的处理是在data.yaml里使用cls参数加大对低频率类别的惩罚权重,或者通过数据增强提高低频率类别的采样率。

# data.yaml 示例 names: 0: hat 1: coat 2: belt 3: skirt 4: embroidery # 类别权重:频率低的类别权重大,频率高的类别权重小 cls: [1.0, 1.0, 3.0, 1.5, 2.0]

cls列表的长度必须和names完全一致。权重的含义是:类别ID为2的"腰带"每贡献一次损失,都会被放大3倍,梯度更新时就更重视这一类。需要强调的是,cls不是越高越好,设过大会让模型过拟合少数类、在多数类上疯狂漏检。一般先从2到3倍开始试,观察验证集上各类的mAP是否趋于均衡。

5. 避坑指南:民族服饰数据集训练中的五个经典问题

5.1 装饰花纹被当成背景

现象:训练后模型对纯色服装检测正常,但对有复杂刺绣、银饰、蜡染纹样的服饰,检测框不是偏了一半就是整个丢失。

原因:标注时把花纹所在的闭合区域也标成了目标,比如把一条裙子上的刺绣区域单独标成了embroidery。模型学到的是"花纹越多越像目标",遇到大面积花纹的背景区域时就被误检。民族服饰的纹样密集且色彩对比度高,很容易让标注者产生"这里也该标一个"的冲动。

解决:检查标签中是否有大量小面积、高纵横比的框。如果要识别的是服饰整体而非局部纹样,应把所有纹样区域从object列表中移除。如果是想识别纹样本身,那就把训练图片裁剪成以纹样为中心的正方形,而不是用原图直接训练。批量检查的方法是把所有标签画回原图上,用cv2.rectangle画框,肉眼扫一遍就能发现问题。

5.2 类别名冲突导致标签错位

现象:训练时提示class name 'skirt' is not in names,或者训练完验证时发现预测框的类别和实际物体不符。

原因:VOC格式的类别名是字符串,YOLO格式的类别是整数ID,转换时的class_names列表顺序一旦变动,ID映射就全错了。最常见的操作是把["hat", "coat", "skirt"]排成["skirt", "hat", "coat"],但TXT里的类别ID还是按旧顺序生成的。

解决:转换脚本和训练配置必须共用同一个class_names列表。常见的做法是把类别列表单独放在一个classes.txt里,转换脚本和data.yaml的编写都从这个文件读取,而不是在两处手写。文件内容每行一个类别名,顺序就是ID。这样做的好处是改类别时只改一个文件。

5.3 图片EXIF旋转信息导致坐标偏移

现象:训练损失很低,但推理时检测框位置整体偏转。

原因:手机或相机拍摄的图片自带EXIF方向信息,某些处理库读图时会自动旋转,某些不会。如果转换脚本读图宽度、高度时拿到的值和训练脚本拿到的值不一致,归一化坐标就会整体错位。YOLO的训练管线会在预处理时自动处理EXIF旋转,但你的转换脚本里用的是原始宽高,两边基准不一致。

解决:在转换脚本里用PIL.Image.open读取图片尺寸前,先调用img.transpose(Image.Transpose.ROTATE_180)等方法处理EXIF,或者干脆先批量把所有图片转成无EXIF的纯RGB文件。最简单的做法是把全部图片统一转成PNG或重新保存为JPG,覆盖EXIF字段。

5.4 数据划分泄漏导致验证集失效

现象:验证集mAP高达0.95,上线实测一塌糊涂。

原因:划分时没有考虑图片间的相关性。比如同一件衣服从不同角度拍的照片被分到train和val两个集合里,模型在训练时已经见过这个衣服,验证时当然是满分。民族服饰数据集里,同一件展品往往有多个角度的照片,这属于典型的泄漏。

解决:划分前先做图片去重,感知哈希或dhash两两比对,相似度高于阈值就归为同组。更简单的方法是按拍摄场景划分——同一场景同一批次拍摄的图片全放train,全放val或全放test,避免同一个场景被切碎到不同集合。

5.5 显存不足,训练中途崩掉

现象:训练跑了十几轮后报CUDA out of memory。

原因:前面能跑是因为batch=4,后面有人为了加速把batch调到16,显存就爆了。YOLO有自动batch调整机制,但如果开了cache=True把所有图片缓存进显存,也会额外占用上百MB。

解决:不用cache=True跑小数据集,1000张图片重读磁盘开销不大,但可以省下显存。如果只想调大batch,配合imgsz从640降到416,显存占用会显著下降。显存不够时优先降imgsz、再降batch,这是比较有效的两个手段,其他参数例如workers对显存影响不大。

6. 训练完怎么验收:mAP之外还要看混淆矩阵

6.1 混淆矩阵是民族服饰模型的照妖镜

训练结束后,大多数人只看mAP50和mAP50-95这个两个数值。mAP50衡量的是IOU阈值0.5下的平均精度,够用但看不出来具体类别谁好谁坏。这时一定要打开训练输出目录里的confusion_matrix.png,看每一类之间互相误检的情况。

对角线上的数字越大越好。如果在"帽子"所在的行里有一列误检到"头巾"的数字也很大,说明这两个类别外观过于接近,模型区分不了。民族服饰有一个让模型头疼的特点:不同民族的头饰结构差异大,但颜色、图案有时高度相似。对角线不高的类别,单独挑出它的所有验证图片,统计预测框与真实框的IOU分布——IOU普遍在0.3以下,说明定位有问题,IOU在0.5左右但类别错,说明分类特征混淆。

from pathlib import Path # 统计验证集中每个类别的框数量,找出低频类别 # 低频类别往往在混淆矩阵里表现最差 val_label_dir = Path("labels/yolo/val") class_counts = {} for txt_path in val_label_dir.glob("*.txt"): with open(txt_path) as f: for line in f: cls_id = int(line.split()[0]) class_counts[cls_id] = class_counts.get(cls_id, 0) + 1 for cls_id, count in sorted(class_counts.items()): print(f"类别 {cls_id}: {count} 个框")

如果某个类别只有几十个框,它的mAP指标天然就不稳定,单看数字下结论没有意义。这种情况下宁可删除该类别,或者去收集更多该类的图片来补数据,也不要靠调参与它较劲。

6.2 验证集的预测结果可视化:直接看框和置信度

跑完训练后,用验证集做一次预测并输出标注图,是最直观、也是最能发现问题的方式:

yolo predict model=best.pt source=dataset/images/test save_txt=True save_conf=True

save_txt=True会把每个预测框的类别ID、置信度和坐标写到一个TXT文件里,再对照原图看。重点检查三类现象:一是同一样式但不同底色的服饰是否都被检测出来;二是意外出现的高置信度误检,比如把背景里的纹样装饰布认成目标;三是一个目标被重复框选。

这三类问题分别对应三种解决方案:第一类是训练数据中颜色多样性不够,需要增加不同底色的样本或做色彩增强;第二类是特征区分不足,考虑增加hsv_h、hsv_s、hsv_v这些色彩增强参数;第三类是非极大值抑制的阈值设太低,把conf参数从默认的0.25调高到0.4左右。

6.3 部署前的最后一道工序:类别ID对齐与输入尺寸确认

训练用的best.pt模型里记录了类别名列表,就是训练时的data.yaml里的names。但实际部署时,你的API服务返回的是类别ID,需要一份部署侧的映射表来转成可读的类别名。映射表的顺序必须和数据转换脚本、训练脚本完全一致。

# 部署侧类别映射表(必须与训练data.yaml的names一致) CLASS_MAP = { 0: "hat", 1: "coat", 2: "belt", 3: "skirt", 4: "embroidery", } # 返回到前端时要转成字符串,不能直接暴露数字ID def format_predictions(preds): results = [] for box in preds.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = [round(x, 2) for x in box.xyxy[0].tolist()] results.append({ "category": CLASS_MAP[cls_id], "confidence": round(conf, 4), "bbox": xyxy, }) return results

我的习惯是每次训练完都把data.yaml复制一份存进实验记录目录,名字加上训练日期。原因很简单:一周以后你可能完全忘了当时用的类别顺序和权重参数,有这份文件在,复现实验或者继续调参都能随时查。另外,best.pt和last.pt不要只留一个,best适合直接部署,last在断点续训时有用,两个文件都留到确认不再继续训练后再清理。

整个流程走下来,这个数据集包最值钱的地方确实不在那1000张图片本身,而是把VOC、COCO、YOLO三套格式和划分脚本、训练教程串成了一条可以直接跑通的流水线。你换了自己的民族服饰图片后,只需要按文中的转换和划分脚本跑一遍,就能得到完全一致的数据组织方式。希望这篇笔记能帮你少走几趟弯路,祝训练顺利。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询