☰
VOC2007数据集与YOLO训练:三种标签格式转换及避坑指南
2026/10/14 1:28:19 网站建设 项目流程

简介:面向目标检测入门与实战的YOLO数据集完整资源包,精选VOC2007真实场景图片共10000张,覆盖丰富目标类别,所有图片均经LabelImg标注,并同时提供voc(xml)、coco(json)、yolo(txt)三种格式标签,分别存放于独立文件夹,下载后可直接接入YOLO系列模型训练。压缩包共收录2000个文件,以1986个xml标注文件为主体,另有6个html格式的环境搭建与训练教程页面、5个txt说明列表及3个Python划分脚本,整体体积846.91MB,目录结构清晰。除数据外,附赠Linux与Windows双平台YOLO环境搭建教程、基于案例修改训练自己数据集的完整操作指南,以及训练集/验证集/测试集划分脚本,方便按需灵活拆分数据。资源已有1267人浏览学习,适合需要快速获得标准VOC格式数据并开展目标检测实战的开发者与课程学习者。

1. 为什么YOLO入门都从VOC2007开始:一万张图和三套标签到底解决什么问题

做目标检测第一次接触YOLO,卡住的往往不是网络原理,而是数据。从网上下到源码,教程让你跑VOC数据集,结果你连标签长什么样都没见过:XML里一堆节点是什么意思,coco.json为什么那么大,txt里那五个数字怎么来的。这个VOC2007完整版数据集就是冲着这个问题去的——接近10000张标注图片,每张配齐VOC、COCO、YOLO三种格式标签,再附上划分脚本和训练教程,你不用自己写格式转换,不用自己搜类别列表,照着教程敲命令就能跑起第一个YOLO训练。这篇笔记面向两类人:第一次跑YOLO训练的新手,以及需要一份标准benchmark验证模型改动的工程师。前者跟着步骤走,后者能直接看到这套数据包的边界和坑。

2. 看懂三种标签格式:VOC的XML、COCO的JSON、YOLO的txt差在哪

拿到数据包第一件事不是急着训练,而是把三种格式的结构搞清楚。转换脚本可以照抄,但不懂结构,训练报错时你连日志都看不懂,更不知道去改哪里。三种格式对应三个生态:VOC是数据集的原始标注形态,COCO是统一成单文件JSON的标注规范,YOLO是训练时真正喂给网络的txt标签。它们描述的是同一批目标框,只是坐标写法、类别表示和存储方式完全不同。

2.1 VOC格式的目录约定:JPEGImages、Annotations与XML解析

原始VOC2007的目录结构是固定的三件套:JPEGImages放全部jpg原图,Annotations放与图片同名的XML标注,ImageSets/Main里放划分用的txt文件,每行一个不带扩展名的图片文件名,训练时按这个清单读取。很多二次打包的数据集都保留了这套目录约定,先认目录再认文件,后面所有转换都从这里出发。

XML标注长这样,每个目标框是一个object节点:

<annotation> <folder>VOC2007</folder> <filename>000001.jpg</filename> <size> <width>500</width> <height>375</height> <depth>3</depth> </size> <object> <name>person</name> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>100</xmin> <ymin>50</ymin> <xmax>200</xmax> <ymax>150</ymax> </bndbox> </object> </annotation>

读取XML用Python自带的ElementTree就够了,不需要额外依赖:

import xml.etree.ElementTree as ET tree = ET.parse("Annotations/000001.xml") root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.iter("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) print(name, (xmin, ymin, xmax, ymax))

这里有几个关键点。第一,bndbox里存的是绝对像素坐标,xmin小于xmax、ymin小于ymax,这是VOC的原始约定,后面转COCO和YOLO都基于这四个值。第二,一张图可以出现多个object节点,同一类别也可能重复出现,转换时一个都不能丢。第三,truncated和difficult标记了目标被截断或识别困难,日常训练一般直接忽略,但如果你要做严格的评测对比,这两个字段值得保留,VOC官方的评测协议会按difficult做特殊处理。

2.2 COCO格式:单文件JSON里的images、annotations、categories三张表

COCO格式最大的特点是整个数据集的标注收进一个JSON文件,顶层是images、annotations、categories三张表。images记录每张图的id、文件名和宽高,annotations记录每个目标框属于哪张图、哪个类别、坐标是多少,categories做类别id和类别名的映射。一个最小的COCO标注长这样:

{ "images": [ {"id": 1, "file_name": "000001.jpg", "width": 500, "height": 375} ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [100, 50, 100, 100], "area": 10000, "iscrowd": 0 } ], "categories": [ {"id": 1, "name": "person", "supercategory": "object"} ] }

从VOC转COCO时有三个坑是固定的。第一,COCO的bbox是[x, y, width, height],即左上角坐标加宽高,而不是VOC的xmin、ymin、xmax、ymax,转换时width要算成xmax减去xmin。第二,COCO原生约定category_id从1开始,不是从0开始,很多转换脚本在这里写错,训练时类别全体错位。第三,area字段在计算mAP时会用到,COCO的评测协议要求它真实反映框的面积,不要随便填0。

2.3 YOLO格式:归一化坐标与一行一个目标的txt标签

YOLO训练时实际读取的是txt标签,每张图对应一个同名txt文件,放在labels目录下。txt里每一行描述一个目标,一共五个数:类别id、归一化后的中心点x、中心点y、宽、高。前一个已经是数字id而不是类别名字符串了,id的取值对应你配置文件里类别列表的下标,0就是第一个类,19就是第二十个类。

归一化公式是转换脚本的核心逻辑,把VOC的绝对坐标除以图片宽高:

def voc_bbox_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): # 中心点坐标 = (左上角 + 右下角) / 2,再除以图片宽高 x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h # 宽高 = 右下角 - 左上角,再除以图片宽高 w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 保留6位小数足够,YOLO训练时读float32不会丢精度 return round(x_center, 6), round(y_center, 6), round(w, 6), round(h, 6)

转换完的txt内容长这样,类别7在我的类别列表里对应cat:

7 0.512000 0.413333 0.412000 0.522667 7 0.715000 0.384000 0.152000 0.184000

第一列是类别id,后四列全部落在0到1之间。如果你看到某个值明显大于1,说明转换脚本里的图片宽高取错了,或者原标注本身就越界了,这种脏数据后面会单独讲怎么排查。三种格式的核心差异用一张表就能看明白:

格式存储方式坐标形式类别表示典型用途
VOC每图一个XMLxmin,ymin,xmax,ymax绝对像素类别名字符串原始数据集、跨框架交换
COCO整个数据集一个JSONx,y,w,h绝对像素数字id从1开始大规模检测、实例分割评测
YOLO每图一个txtx_center,y_center,w,h归一化数字id从0开始YOLO系列训练

3. 格式转换与划分脚本:从XML到coco.json和yolo标签,再到train/val/test

这一章给的是可以直接抄的脚本。常见做法是先用Python脚本把Annotations目录下的XML批量转成YOLO的txt和COCO的JSON,再用另一个脚本把图片按比例分成训练集、验证集、测试集。两步分开做,出问题时好定位:格式转换出了问题,看labels目录里的txt就够;划分出了问题,单独查ImageSets/Main下的清单文件。

3.1 VOC转YOLO脚本:解析XML输出归一化txt

转换脚本最关键的是维护一份写死的类别列表,顺序一旦确定,后面所有的脚本和配置文件都引用它:

import os import xml.etree.ElementTree as ET # 类别列表顺序就是YOLO的class id,写死一份,全项目统一引用 VOC_CLASSES = [ "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor" ] def convert_voc_to_yolo(xml_dir, labels_dir, jpeg_dir): os.makedirs(labels_dir, exist_ok=True) converted = 0 skipped = 0 for xml_name in os.listdir(xml_dir): if not xml_name.endswith(".xml"): continue stem = xml_name[:-4] # 去掉.xml后缀,得到与图片同名的文件主干 jpg_path = os.path.join(jpeg_dir, stem + ".jpg") if not os.path.exists(jpg_path): print("缺少图片,跳过:", stem) skipped += 1 continue tree = ET.parse(os.path.join(xml_dir, xml_name)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in VOC_CLASSES: # 不在20类里的标注直接忽略,VOC2007不应出现 continue cls_id = VOC_CLASSES.index(name) bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) x_center = round(((xmin + xmax) / 2) / img_w, 6) y_center = round(((ymin + ymax) / 2) / img_h, 6) w = round((xmax - xmin) / img_w, 6) h = round((ymax - ymin) / img_h, 6) lines.append(f"{cls_id} {x_center} {y_center} {w} {h}") with open(os.path.join(labels_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines)) converted += 1 print("转换完成:", converted, "张,跳过:", skipped) if __name__ == "__main__": convert_voc_to_yolo("Annotations", "labels", "JPEGImages")

脚本执行完,labels目录下的txt数量应该和Annotations下的xml数量一致,除非有xml对应的jpg缺失。建议转完随手做一次数量核对:统计labels目录文件数,再统计XML目录文件数,两者相等才算干净。小数位保留6位是常见工程习惯,YOLO训练时读的是float32,六位小数带来的误差远小于一个像素,够用。

3.2 VOC转COCO脚本:拼装JSON并处理类别id

转COCO的脚本结构稍有不同,因为COCO是单文件JSON,需要把所有图的信息汇总起来再一次性写出:

import os import json import xml.etree.ElementTree as ET VOC_CLASSES = [ "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor" ] def convert_voc_to_coco(xml_dir, json_path): coco_images = [] coco_annotations = [] ann_id = 1 # COCO要求annotation的id全局唯一,从1递增 for img_id, xml_name in enumerate(os.listdir(xml_dir), start=1): if not xml_name.endswith(".xml"): continue stem = xml_name[:-4] tree = ET.parse(os.path.join(xml_dir, xml_name)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) coco_images.append({ "id": img_id, "file_name": stem + ".jpg", "width": img_w, "height": img_h }) for obj in root.iter("object"): name = obj.find("name").text if name not in VOC_CLASSES: continue # COCO类别id从1开始,与YOLO的0开始正好差1 category_id = VOC_CLASSES.index(name) + 1 bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) width = xmax - xmin height = ymax - ymin coco_annotations.append({ "id": ann_id, "image_id": img_id, "category_id": category_id, "bbox": [xmin, ymin, width, height], "area": width * height, "iscrowd": 0 }) ann_id += 1 coco_data = { "images": coco_images, "annotations": coco_annotations, "categories": [ {"id": i + 1, "name": name, "supercategory": "object"} for i, name in enumerate(VOC_CLASSES) ] } with open(json_path, "w", encoding="utf-8") as f: json.dump(coco_data, f) print("images:", len(coco_images), "annotations:", len(coco_annotations))

COCO脚本里最容易出错的两处代码我已经用注释标出来了:category_id加1,以及bbox由两角点转成x、y、width、height。有不少YOLO工程的内部逻辑直接把COCO的category_id当作模型类别下标用,如果你用的框架是这种约定,转出来的JSON里category_id就不要加1,以框架源码为准。

3.3 划分脚本:固定随机种子,按比例切分并校验集合不相交

划分这一步决定训练集、验证集、测试集各有多少图。常见比例是7:1.5:1.5,数据量大可以放宽到8:1:1。最朴素的划分方法是全体随机洗牌后按比例切,对10000张图这个量级完全够用。如果你担心某个类别恰好只进了训练集导致验证集没样本,可以做按类别分层,但多标签图会同时落在多个类别桶里,处理不好反而制造出train和val重叠的脏数据。

我一般先用简单可靠的全体随机划分:

import os import random random.seed(42) # 固定随机种子,换机器重跑也能得到完全相同的划分 JPEG_DIR = "JPEGImages" ANNO_DIR = "Annotations" RATIOS = {"train": 0.7, "val": 0.15, "test": 0.15} stems = [] for xml_name in os.listdir(ANNO_DIR): if not xml_name.endswith(".xml"): continue stem = xml_name[:-4] if not os.path.exists(os.path.join(JPEG_DIR, stem + ".jpg")): print("警告:有标注无图片,跳过", stem) continue stems.append(stem) random.shuffle(stems) n_train = int(len(stems) * RATIOS["train"]) n_val = int(len(stems) * RATIOS["val"]) split_sets = { "train": stems[:n_train], "val": stems[n_train:n_train + n_val], "test": stems[n_train + n_val:] } os.makedirs("ImageSets/Main", exist_ok=True) for split_name, split_stems in split_sets.items(): with open(f"ImageSets/Main/{split_name}.txt", "w") as f: for stem in sorted(split_stems): # sorted保证每次写入顺序一致 f.write(stem + "\n") print(split_name, len(split_stems))

划分完必须做一次集合不相交校验,这一步不能省。train和val一旦重叠,验证时的mAP会虚高,看起来模型很强,换到没见过的图上立刻露馅:

train_set = set(open("ImageSets/Main/train.txt").read().split()) val_set = set(open("ImageSets/Main/val.txt").read().split()) test_set = set(open("ImageSets/Main/test.txt").read().split()) print("train/val重叠:", len(train_set & val_set)) print("train/test重叠:", len(train_set & test_set)) print("val/test重叠:", len(val_set & test_set)) print("三集合去重后总数:", len(train_set | val_set | test_set))

三个重叠数都应该是0,去重后的总数应该等于图片总数。划分结果要当作资产保存,同一个项目后续所有实验都用这一份train.txt、val.txt、test.txt,不要在每次跑实验前重新划分,否则模型之间没法对比。

划分完之后,如果打算按YOLO工程的惯例按目录读取数据,再把图片和标签移动成images/train这样的结构:

mkdir -p images/train images/val images/test mkdir -p labels/train labels/val labels/test for split in train val test; do while read stem; do mv "JPEGImages/${stem}.jpg" "images/${split}/" mv "labels/${stem}.txt" "labels/${split}/" done < "ImageSets/Main/${split}.txt" done

移动完成后最终目录结构是这样:

目录内容
images/train、images/val、images/test按划分归类的jpg原图
labels/train、labels/val、labels/test与images同名的YOLO格式txt
Annotations/原始XML,保留做备份和溯源
coco.jsonCOCO格式标注,给支持JSON的训练框架用
ImageSets/Main/train.txt、val.txt、test.txt三份清单

注意labels目录下txt的行数应该等于对应XML里object节点的个数,可以随机抽三个文件人工比对,这个抽查习惯能拦住绝大多数转换错误。

4. 用划分后的VOC2007跑通YOLO训练:yaml配置、启动命令与日志解读

数据准备好之后,训练本身其实是被YOLO工程高度封装过的,你真正要关心的只有三件事:数据集配置文件写对、启动命令参数合理、训练日志看得懂。这一章以最常见的YOLOv5工程命令行写法为例,新版YOLO工程改成yolo detect train子命令风格,参数名大同小异,照着对应关系迁移即可。

4.1 数据集yaml配置:目录布局与20个类别的顺序

YOLO工程通过一个yaml文件描述数据集位置和类别信息。这个文件写错了,训练能启动但结果全是乱的,最典型的就是names顺序和转换脚本里的类别列表不一致:

path: /data/datasets/VOC2007 # 数据集根目录,建议写绝对路径 train: images/train # 训练图片目录,相对path val: images/val # 验证图片目录,相对path test: images/test # 测试图片目录,可不填 nc: 20 # 类别数量,必须和names长度一致 names: ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

names列表的顺序就是模型输出的类别下标顺序,必须和第3章转换脚本里的VOC_CLASSES一字不差。我在这上面翻过车:转换脚本和yaml各自维护了一份类别列表,后来在yaml里调整了顺序,训练全程不报错,但验证时person被识别成car,mAP还是个不低的值,非常迷惑。解决方法是全项目只维护一份类别清单,转换脚本从它生成,yaml直接复制,两边不要各写各的。

4.2 训练启动命令:img、batch、epochs、workers的合理起点

划分好的目录配上yaml,训练命令就很简单了。第一次跑不要追求精度,目标是把流程走通:

python train.py \ --data voc2007.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --workers 4

四个参数的含义和调整逻辑如下。img是输入分辨率,640是默认值,显存紧张就降到416或320,分辨率降低会损失小目标检测精度,但流程验证阶段无所谓。batch受显存约束,16跑不动就8,8还不行就4,先让训练跑起来再说。epochs给100起步,训练集接近7000张图的情况下100轮足够看收敛趋势,后面再根据曲线决定加不加。workers是数据加载进程数,Linux下4到8都正常,Windows下经常因为多进程启动方式不同卡死,遇到进度条不动先把它改成0再试。

训练过程中用nvidia-smi盯一眼显存占用:

watch -n 1 nvidia-smi

显存占用接近上限时优先减batch,其次是减img,不要一上来就动网络结构。

4.3 训练日志指标解读:loss下降、mAP曲线与早停时机

训练启动后控制台每个epoch打印一行指标,常见的有box_loss、cls_loss、dfl_loss、precision、recall、mAP@0.5和mAP@0.5:0.95。对这些指标要有预期,才不会被数字吓到或骗到:

指标含义正常收敛参考
box_loss边界框回归损失,越小框越准从0.1以上逐步降到0.03左右
cls_loss分类损失,越小类别越准从0.1以上逐步降到0.02左右
mAP@0.5IoU阈值0.5时的平均精度VOC2007用s模型跑到0.7以上正常
mAP@0.5:0.95更严格的综合指标比mAP@0.5低不少,不用慌

看日志最重要的习惯是盯val指标的收敛趋势而不是某个绝对值。前20个epoch里loss快速下降、mAP快速上升,说明数据配置没问题。如果val的mAP连续20个epoch不涨反跌,通常是过拟合或学习率没调对。YOLO工程默认会在每个epoch结束保存last.pt和best.pt,best.pt是按val mAP选出来的,训练完直接用best.pt做推理即可。训练中途想停就停,best.pt已经存下来了,不需要等全部epoch跑完。

5. VOC2007训练避坑指南:从图片损坏到标签错位,五个翻车现场逐个排查

这章写的是我自己和身边人在这套数据上真实遇到过的坑,按现象到原因到解决的顺序展开。每一类问题都有一个共性:训练报错还算好的,最怕的是不报错但结果悄悄变差。

5.1 OSError: image file is truncated,图片损坏导致训练中断

现象:训练到第20多个epoch,控制台突然抛OSError: image file is truncated,进程直接中断,重跑还是同样位置崩溃。原因:数据集里混入了个别下载不完整或拷贝损坏的jpg,平时读图片没事,数据加载器一旦碰上就崩。解决:训练前先全量扫描一遍图片,坏的直接删掉或重下:

from PIL import Image import os bad_files = [] for f in os.listdir("JPEGImages"): path = os.path.join("JPEGImages", f) try: with Image.open(path) as img: img.verify() # verify只检查文件完整性,不解码全部像素 except Exception as e: bad_files.append((f, str(e))) print("损坏图片数量:", len(bad_files)) for name, err in bad_files[:10]: print(name, err)

注意校验输出过一遍,确认损坏图片确实没出现在任何划分清单里,否则还要同步从train.txt里剔除。

5.2 坐标越界与宽高为0:归一化标签里的脏数据

现象:训练时框架报坐标相关的assert错误,比如"all bounding boxes should have positive height and width",或者训练不报错但mAP异常低。原因:部分XML里的xmax比图片宽度还大,或者目标太小取整后框宽为0,归一化后出现大于1或等于0的值。解决:转换脚本里加约束,超出边界的坐标裁剪到图片范围内,宽高为0的框直接丢弃:

xmin = max(int(bbox.find("xmin").text), 0) ymin = max(int(bbox.find("ymin").text), 0) xmax = min(int(bbox.find("xmax").text), img_w) ymax = min(int(bbox.find("ymax").text), img_h) if xmax <= xmin or ymax <= ymin: continue # 无效框直接跳过

改完重新生成labels,再用脚本扫一遍所有txt,过滤出任何值小于0或大于1的行,确认没有越界数据再训练。

5.3 类别编号错位:names顺序不一致造成的张冠李戴

现象:训练全程不报错,loss正常下降,但验证时发现person被识别成car,dog被识别成cat,而且mAP还不低。原因:转换脚本里的VOC_CLASSES顺序和yaml里的names顺序不一致,两边各自维护了一份列表,后来只改了一边。解决:统一清单后,随机抽几个标签人工比对。抽查方法很简单——先输出一个txt的原始内容,再打开对应图片和XML看第一眼就知道对不对:

import random stems = open("ImageSets/Main/train.txt").read().split() sample = random.choice(stems) print("抽查文件:", sample) print(open(f"labels/{sample}.txt").read())

这个抽查我每次转换完必做,时间成本不到一分钟,能省掉后面几个小时的排查。

5.4 train和val重叠:mAP虚高的假象

现象:训练loss正常,val mAP高到0.9以上,明显超出正常范围,换到没见过的图上推理效果却很差。原因:划分脚本没固定随机种子,或分层划分逻辑有重复分配,导致同一张图同时进了train和val。解决:划分配置固定后,用第3章给的集合交集校验跑一遍,三个重叠数必须全是0。如果发现重叠,不要手动挪图,直接修复脚本重新划分,所有集合清空重来。划分结果确定后把train.txt、val.txt、test.txt存到一个固定目录,后续实验永远引用这一份。

5.5 显存不足与dataloader卡死:batch、workers和img的妥协

现象:启动训练后立刻报CUDA out of memory,或者进度条一直停在第一个batch不动。原因:前者是batch或img设得超出显存,后者在Windows下基本是workers多进程的兼容问题。解决:先降batch到8或4,再降img到416,两步还不行就检查后台是不是有别的进程占着显存。workers在Windows下直接设0,在Linux下也不要超过CPU核心数的一半:

python train.py --data voc2007.yaml --img 416 --batch 8 --workers 0

这个组合是低配环境最稳的启动配置,等流程跑通再逐步加回去。显存问题的本质是batch乘img乘模型尺寸的乘积超过了显存,三者取两个降一个即可。给新手一个建议:同样的报错在同一个工程里反复出现时,优先怀疑环境而不是数据,把workers归零、batch减半,先排除变量再谈优化。

6. 从跑通到涨点:测试集验证、数据增强与难例分析

训练跑通只是起点,后续的价值都在验证和迭代上。训练结束后先用测试集做一次独立验证,这一步很多新手会跳过——训练日志里的val指标来自验证集,而验证集参与过模型选择,严格说已经泄漏了调参信息。测试集是划分时留出来的从没参与训练的1000多张图,用它验证才能反映真实水平:

python val.py \ --data voc2007.yaml \ --weights runs/train/exp/weights/best.pt \ --task test

VOC2007测试集跑出来的mAP才值得写进你的实验记录里。涨点的第一优先级是数据增强参数。YOLO工程默认开了mosaic、hsv扰动、随机翻转,对于VOC这类户外场景图,mosaic对小目标尤其有效,因为拼接后的图里每个目标相对变小,模型被迫去学更丰富的上下文。如果发现小目标类别的召回率低,可以适当把mosaic的启用概率调高。hsv增强的默认强度对VOC够用,不要为了涨点大幅调高,颜色扰动过大反而让真实场景泛化变差。

第二个有效动作是看混淆矩阵。训练输出目录下的confusion_matrix.png直观展示了哪两个类别互相混淆——VOC里最常见的混淆是bottle和person,因为瓶子和行人经常同框,模型容易把瓶子框外的行人区域也归进去。看到混淆集中的类别对,优先补数据或调anchor,而不是无脑加训练轮数。

关于模型选择,s权重跑通后直接换m权重重训,通常mAP能涨3到5个点,代价是显存和训练时间约翻倍。对我个人来说,现在拿到任何一份标注数据,第一件事不是训练,而是随机抽三张图对着标签看一分钟——标签是对的,训练才有意义。这套动作成了习惯之后,我在这上面踩过的坑基本没再犯过。希望帮到你。

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

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

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

立即咨询