简介:1400张车辆图片及配套XML标注文件组成的数据包,覆盖公交车、家用车、消防车、工程车等多类别车辆目标场景,面向熟悉YOLOv3/v4/v5框架的深度学习开发者,可直接用于训练车辆检测模型。整套资源共2800个文件,包含1400个jpg原图与1400个xml标注文件,标注由人工专业完成,记录目标框坐标与类别信息,压缩包大小约709.83MB,解压后即可按VOC格式组织训练数据。目前已有2475人学习下载,适合智慧交通、辅助驾驶、安防监控等项目的模型微调与效果验证。图片与XML文件名一一对应,省去重复整理标注的环节,配合YOLO系列框架可快速生成训练集和验证集,实测识别度超过98%,能有效缩短数据准备周期并提升车辆检测迭代效率。
1. 1400张手工标注车辆数据集:第一眼怎么判断它值不值得用
拿到一个号称“手工专业标注完成”的1400张车辆数据集时,我的第一反应是怀疑:这个量级够训练一个能上线的检测模型吗?实际上如果只看数量,1400张确实不算大,但看过具体类别和标注质量后会发现,车辆检测和通用检测不太一样,公交、家用车、消防车、工程车这些类别外观结构高度规整,类别数量有限,精标1400张配合合理的数据增强和训练策略,是能跑出可用模型的。这篇笔记就从怎么拆这个数据集讲起,一路落到接入YOLOv8训练链路的完整操作,再把我在标注数据上踩过的几个坑翻出来,给正在评估类似车辆数据集的同行一个可参考的路径。
2. 车辆数据集内容拆解:类别分布、标注格式与质量验证
拿到数据集的第一步不是急着训练,而是先把数据的家底盘清楚。这一步做得越细,后面训练和部署阶段的返工越少。我这里说的家底包括三样:图像和场景构成、标注文件格式、以及标注质量本身的可靠性。很多人把这三个检查压缩成“看一眼目录结构就开跑”,结果训练到一半才发现类别id对不上或者标签缺失,再回头排错的时间远超当初省下的那点功夫。
2.1 1400张数据里到底有什么:车辆类别与场景构成
打开数据集先看目录结构。常见的数据集组织方式是images下按场景分子文件夹,annotations下按图片名一一对应标注文件。这个1400张规模的车辆数据集,图片尺寸一般在1920×1080左右,拍摄场景覆盖城市道路、高速路口、施工区域、小区停车场,光线条件从白天到黄昏都有,这对车辆检测来说算是比较友好的分布。碰到同类车辆数据集,我也会先按这个标准过一遍:有白天有黄昏、有近景有远景、场景不要集中在同一条路上。
车辆类别的覆盖,从标题能看到的已经有公交车、家用车、消防车、工程车。实际往下挖,常见的数据集还会把货车、SUV、皮卡单独拆出来,具体拆多细取决于标注规范怎么约定。类别多不等于好,对目标检测而言,类别之间的可区分度决定了标注难度和质量。公交车和客车外观高度相似,消防车和工程车同样偏红色涂装容易混淆,标注规范是否对这类边缘情况做了约定,是衡量“专业标注”的试金石。
还有一点容易被忽略:1400张是图片数,目标数通常是图片数的2到3倍。如果每张图平均停着2.5辆车,实际标注框可能超过3500个,这个量级对四到六类车辆检测来说已经不算寒碜。我拿到数据集后会顺手统计一下总目标数,因为它比图片数更能反映数据规模。我之前遇到过一份标得很干净的车辆数据集,细查发现消防车和工程车在某些角度下被标反了接近8%。这类错误直接喂给模型,最后模型学到的判定边界就会变成玄学,时灵时不灵。所以拿到数据集后,第一件事是抽样目检,把标注框可视化叠到原图上,从每个类别抽几十张快速扫一遍,重点看有没有大范围的框偏移和类别错标。这个动作不要偷懒,花四五十分钟做完,比训练完再返工省太多时间。
2.2 标注格式:三种常见存储方式怎么选
车辆数据集最常见的三种标注存储格式是VOC XML、COCO JSON和YOLO txt。三者各有适用场景,选择依据是后续训练工具链的输入要求。下面这张表列出它们的主要差异,方便对照。
| 格式 | 文件组织 | 坐标表示 | 适用场景 |
|---|---|---|---|
| VOC XML | 每图一个XML文件 | xmin/ymin/xmax/ymax绝对像素值 | labelImg等经典工具导出、人工可读 |
| COCO JSON | 所有标注汇总到一个JSON | xmin/ymin/width/height绝对像素值 | 需要分割掩码或大数据量统一管理 |
| YOLO txt | 每图一个txt文件,每行一个目标 | 归一化中心x/y和宽高 | YOLO系列训练直接读取,磁盘占用小 |
VOC XML是PASCAL VOC遗留的标注格式,每个目标在XML文件里用一个object节点描述,字段包括类别名、bndbox的xmin、ymin、xmax、ymax。它的优点是结构清晰、人眼可读,用labelImg打开后可以直接修改,缺点是一个文件只对应一张图,批量处理时需要先解析XML。COCO JSON则把所有图片的标注汇总到一个大JSON文件里,包含images、annotations、categories三张表,格式紧凑但人工难以直接编辑,程序处理时需要注意annotation的id和image_id关联关系,这两个id一旦错位,目标框就会张冠李戴。YOLO txt是darknet流传下来的格式,每行代表一个目标,五个数字分别代表类别id、归一化中心x、归一化中心y、归一化宽度w、归一化高度h,优点是读取快、和YOLO系列框架天然兼容,缺点是只有框没有分割轮廓,无法支持分割任务,一旦有标注框越界也不容易直观发现。
我在实际项目里更倾向先用VOC XML作为中间格式。原因是labelImg、CVAT这些标注工具都能原生导出VOC XML,同时XML的标签信息比txt丰富,出错后也容易定位。等数据清洗完毕、确认无误后,再转成YOLO txt喂给训练脚本,这样能把格式转换后的排错成本压到最低。注意一个边界情况:如果数据集里已经包含了多边形分割标注,VOC XML表达不了,这时候用COCO JSON作为中间格式更合适。
2.3 验证标注质量的三个硬指标
判断一份车辆数据集值不值得用,我通常会盯三个指标。第一个是类别间的样本均衡度,具体看每个类别的目标数量直方图,发现差距超过一个量级就需要考虑是否做类别过采样或loss权重调整。第二个是边界框质量,用脚本检查有没有框超出图像边界、宽度或高度为负、框与框大范围重叠但类别完全不同的情况。第三个是标注一致性,也就是同一个物体在相邻几帧或相似场景里,类别标注和框尺寸是否稳定,消防车在这一帧叫fire_truck、下一帧叫truck,这种前后不一致对模型是致命的。
这三个指标都不用依赖训练就能先验证。拿一个Python脚本扫一遍标注文件,统计类别分布、非法框数量,再随机抽图做可视化,整个过程不到半小时就能完成。脚本逻辑大致是这样的:
import os import xml.etree.ElementTree as ET from collections import Counter annotation_dir = "data/annotations" class_counter = Counter() invalid_boxes = [] for xml_file in os.listdir(annotation_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(annotation_dir, xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.findall("object"): class_counter[obj.find("name").text] += 1 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) if xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: invalid_boxes.append((xml_file, obj.find("name").text)) print("类别分布:", dict(class_counter)) print("非法框数量:", len(invalid_boxes)) for item in invalid_boxes[:10]: print("非法框示例:", item)这段脚本干了两件事:统计类别分布,挑出越界框。越界框不等于一定不能用,但比例超过3%就要警惕标注质量。做完这一步,数据集能不能进入训练阶段,心里就有底了。很多做车辆检测的同行会在这一步先跑一个极短的训练验证流程,比如5个epoch,只为了确认数据和代码链路是通的,这个习惯我认为非常值,比盲目把300个epoch跑完再排查问题要高效得多。
3. 接入YOLOv8训练链路:XML转txt、目录划分与增强配置
数据验证完毕,接下来就是把这个数据集接进YOLOv8的训练链路。这个环节的坑集中在格式转换和目录组织上,看清楚了再动手可以少走弯路。我的完整流程是:写转换脚本把VOC XML转成YOLO txt,然后按8:1:1划分train/val/test并写data.yaml,最后配置数据增强。每一步之间都留一个快速检查的节点,不把问题带到下一步。
3.1 用脚本把VOC XML转成YOLO txt
首先需要一个把VOC XML转成YOLO txt的脚本。这个脚本要做的事情很直接:读取XML文件,解析每个object的类别名和边界框坐标,按类别映射表把类别名换成数字id,最后把坐标归一化后写入txt文件。车辆类别总数一般在4到8个之间,映射表在脚本里写死成dict即可,换数据集时只改这个dict。
import os import xml.etree.ElementTree as ET # 类别映射表,顺序即训练时的类别id顺序 CLASS_MAPPING = { "bus": 0, "car": 1, "fire_truck": 2, "construction_vehicle": 3, "truck": 4, "suv": 5, } def convert_voc_to_yolo(xml_path, output_dir): tree = ET.parse(xml_path) root = tree.getroot() img_width = int(root.find("size/width").text) img_height = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAPPING: # 不在映射表里的类别直接跳过,避免生成错误id print(f"warning: unknown class {name} in {xml_path}") continue 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) / img_width y_center = ((ymin + ymax) / 2) / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height # 防止边缘异常值超出0-1范围,越界数据直接截断 x_center = min(max(x_center, 0), 1) y_center = min(max(y_center, 0), 1) w = min(max(w, 0), 1) h = min(max(h, 0), 1) lines.append(f"{CLASS_MAPPING[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(output_dir, txt_name), "w") as f: f.write("\n".join(lines)) # 批量转换:遍历annotations目录下所有XML xml_dir = "data/annotations" txt_dir = "data/labels" os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith(".xml"): convert_voc_to_yolo(os.path.join(xml_dir, xml_file), txt_dir)这段脚本有两个地方值得注意。类别映射表的顺序就是YOLO训练时的类别id顺序,一旦训练开始就不要随意更换,否则已经生成的txt全部作废。边界框的归一化做了min/max截断,防止部分XML里出现坐标越界的脏数据直接污染后面的训练。转换后最好对比几个文件的输出,确认类别id和框坐标与原始XML一致,我习惯随机挑三个XML文件人工核对一遍,再写一个批量检查脚本统计输出txt的行数,行数和标注文件的目标数量对不上就说明有解析遗漏。
提示:xml.etree.ElementTree对XML格式要求较严格,如果标注平台导出时字段缺失,这里会直接抛异常。遇到解析报错时先看具体行号,多数情况是缺了size/width这类基础字段。
3.2 按8:1:1划分train/val/test:随机划分不固定会翻车
很多新手踩过这样的坑:直接把全部数据一次性丢给训练脚本,用ultralytics自带的参数划分数据集。虽然能用,但如果你后续需要公平对比不同模型的性能,这样生成的划分是黑匣子,不可复现。更稳妥的做法是在转换完成后,就固定好train/val/test三个目录,以后的所有训练都用同一份数据划分。
import os import random import shutil random.seed(42) # 固定随机种子,保证划分可复现 image_dir = "data/images" label_dir = "data/labels" # 三个目标目录,图片和标签保持同名同路径结构 train_img_dir = "data/train/images" val_img_dir = "data/val/images" test_img_dir = "data/test/images" train_lbl_dir = "data/train/labels" val_lbl_dir = "data/val/labels" test_lbl_dir = "data/test/labels" for d in [train_img_dir, val_img_dir, test_img_dir, train_lbl_dir, val_lbl_dir, test_lbl_dir]: os.makedirs(d, exist_ok=True) img_files = [f for f in os.listdir(image_dir) if f.endswith(".jpg")] random.shuffle(img_files) train_split = int(len(img_files) * 0.8) # 前80%进train val_split = int(len(img_files) * 0.9) # 接着10%进val,剩余10%进test for i, img in enumerate(img_files): label = img.replace(".jpg", ".txt") if not os.path.exists(os.path.join(label_dir, label)): print(f"skip {img}, 无对应标签") continue if i < train_split: shutil.copy(os.path.join(image_dir, img), train_img_dir) shutil.copy(os.path.join(label_dir, label), train_lbl_dir) elif i < val_split: shutil.copy(os.path.join(image_dir, img), val_img_dir) shutil.copy(os.path.join(label_dir, label), val_lbl_dir) else: shutil.copy(os.path.join(image_dir, img), test_img_dir) shutil.copy(os.path.join(label_dir, label), test_lbl_dir)随机种子要固定下来,这句话值得重复一遍。不同的种子会生成不同的划分,直接影响最终mAP的数值比较。很多人复现不出别人的结果,问题往往不是模型代码,而是数据集划分不一致。这里有个值得留意的细节:如果原始数据里同一场景有多张连续帧,随机划分很容易把同一辆车同时分进train和val,导致验证指标虚高。更严谨的方式是按场景目录划分,而不是按单张图划分,我后面在避坑章节会专门展开这个问题。
划分完成后,写一个data.yaml指向这些目录。YOLOv8的data.yaml格式很简单,核心是三行路径加一行类别名列表。下面是一个实际的示例:
train: data/train/images val: data/val/images test: data/test/images nc: 6 names: ["bus", "car", "fire_truck", "construction_vehicle", "truck", "suv"]这里names的顺序必须和转换脚本里的CLASS_MAPPING的值一致。类别顺序错位是训练中最隐蔽的问题之一,训练日志不会报错,但验证时预测结果会张冠李戴。
3.3 数据增强配置:1400张怎么撑起一个能用的训练集
1400张原始图对检测模型来说偏少,但配合YOLOv8内置的数据增强,可以在不影响验证集真实性的前提下把有效训练量放大好几倍。常见做法是把mosaic、hsv、flip这几项开起来。在ultralytics的配置里,这些参数可以在训练命令的超参数里覆盖,也可以在data.yaml的augmentation字段里控制。
以ultralytics YOLOv8为例,Python训练接口的增强参数是这样传的:
from ultralytics import YOLO model = YOLO("yolov8n.pt") model.train( data="data.yaml", epochs=300, batch=16, imgsz=640, mosaic=1.0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, fliplr=0.5, cache=True, )mosaic=1.0表示训练过程中每批数据都执行mosaic拼接,把四张图拼成一张,这对小物体检测尤其有效,车辆的远处小目标在mosaic下能获得更多训练样本。但注意ultralytics在训练最后10个epoch会自动关闭mosaic,避免模型依赖拼接图的分布。hsv_h、hsv_s、hsv_v控制颜色扰动幅度。车辆数据集颜色分布受涂装影响很大,消防车红、工程车黄,颜色扰动系数不宜过大,否则模型会丢掉颜色这个重要特征。fliplr=0.5的水平翻转对车辆检测几乎是无损增强,车辆左右对称,可以用满0.5甚至0.8。
cache=True把图像缓存到内存里,1400张图不会占太多内存,但能明显加快每个epoch的迭代速度,尤其当数据在机械硬盘上时提升明显。如果显存有富余,还可以配合更大的worker数,训练总时长能压缩近一半。
提示:mosaic开满后,前几个epoch的loss曲线会显得偏高且抖动,这是正常的。不要因为前20个epoch没看到loss下降就急着改学习率,mosaic引入的分布差异需要一点时间消化。
4. 训练参数这样设:车辆检测的batch、lr与epoch不靠猜
数据集和增强准备好了,进入训练环节。这一章说清楚车辆检测任务里batch size、学习率、epoch这些参数的取值范围,以及训练过程中哪些指标值得盯。这里不列花哨配置,给的是我在类似规模的车辆数据集上反复试出来的边界值。
4.1 显存与耗时估算:先在脑子里过一遍账
很多人在训练前不算显存,直接按默认batch size跑,结果跑两步就OOM。实际上YOLOv8n的显存占用很小,8G显存就能跑batch=16、imgsz=640,但换成YOLOv8x想跑同样的batch,16G显存也未必够。对1400张这样的数据量,模型容量不是首要瓶颈,显存才是。下面这张表是我在单卡环境下实测过的参考范围。
| 模型 | batch size | imgsz | 单卡显存参考 | 300 epoch参考耗时 |
|---|---|---|---|---|
| YOLOv8n | 16 | 640 | 约4G | 2小时级别 |
| YOLOv8s | 16 | 640 | 约6G | 3小时级别 |
| YOLOv8m | 8 | 640 | 约6G | 4小时级别 |
| YOLOv8l | 4 | 640 | 约7G | 5小时级别 |
这个表不是精确基准测试,只是一个量级参考。训练成本不高时,可以多跑几组对比实验,看不同模型容量在这个数据量级上的收益。一般来说,1400张车辆数据用YOLOv8s到头了,再往上加模型容量收益很小且过拟合风险明显上升。训练前还可以用显存监控命令先跑一个batch试水,观察占用是否符合预期再决定要不要调batch。
4.2 这六个超参数先定下来,后面不动
车辆检测任务里,我最先固定的是六个超参数。batch size取16,能平衡显存与梯度稳定性;epoch先跑300,结合早停观察是否过拟合;学习率用0.01配合余弦退火,这是YOLO系训练的标准起点;weight decay取0.0005,对车辆这类中等类别数的检测任务够用;warmup epochs设为3,让学习率平稳爬升;最后是imgsz,输入分辨率取640,和预训练权重匹配。
这些参数不是拍脑袋定的。车辆检测和通用目标检测在超参数上没有本质区别,直接把ultralytics官方COCO训练配置里的值搬过来,再按显存调小batch size即可。真正需要手工调整的是类别数量带来的输出层变化,data.yaml里nc改成实际类别数即可,模型会自动适配最后的检测头。
关于lr的边界:车辆数据集小而类别清晰,学习率太高会过早收敛到次优解,太低又会让训练进度拖沓。我用0.01起手,如果前50个epoch的loss曲线纹丝不动,就降到0.005重来。类似地,weight decay如果开太大,模型的框回归头会被压得太狠,表现为mAP50震荡但上不去。调参的顺序也有讲究:先定batch和epoch,再调lr,最后才碰增强参数,一次只改一个变量,这样才能定位到影响精度的真正因素。
4.3 训练时盯住mAP50和mAP50-95这组数
训练过程里控制台会持续输出box_loss、cls_loss、mAP50、mAP50-95这些指标。mAP50表示IoU阈值0.5下的平均精度,对车辆检测的工程落地来说mAP50更重要,因为它允许框位置有一定偏移,更贴近实际应用中对“框住车”的直观要求;mAP50-95是更严苛的跨阈值平均,适合论文和模型横向对比。
我一般会在训练到一半时暂停,看mAP50的上升曲线是否已经放缓。如果到了200个epoch还在明显上升,说明模型没学完,可以延长epoch;如果100个epoch后就已经平顶,说明数据量或模型容量到极限了,继续跑只是浪费电。yolo输出的results.png是很好的可视化依据,把mAP曲线和loss曲线一起看比只看数值有用得多,因为loss下降但mAP不动,通常意味着模型在过拟合背景。
训练结束后还有一个值得做的事:把val集上每张图的预测结果可视化保存下来,重点看类别错标和漏检。mAP数值只能告诉你整体水平,可视化能告诉你错在哪些车辆上、哪些角度是弱项。这个动作对后续是否需要补充数据提供了直接依据,比单纯盯着数字调参有效得多。
5. 车辆数据集避坑指南:5条踩坑记录与排查路径
这部分把我在1400张车辆数据集上实际遇到过的坑整理出来,每条按“现象→原因→解决”的路径写清楚,遇到同类问题时可以直接对照排查。这些坑不是从教科书上抄来的,是标注数据接入训练链路时真实走过的弯路。
5.1 类别标签错位:标注文件里的名字和训练yaml对不上
现象:训练正常走完,但验证时发现预测框的类别名张冠李戴,消防车被识别成工程车,货车被识别成家用车。
原因:VOC XML里的类别名和转换脚本里的CLASS_MAPPING不一致,或者txt生成顺序和最终data.yaml里的names顺序不一致。YOLO训练时用数字id解析类别,如果txt里的id=2在yaml里对应的是construction_vehicle而原本意图是fire_truck,模型学到的东西自然全错。
解决:训练前先写一个打印脚本,从train_labels里取几个文件,打印txt每个框的id和data.yaml的names对照表,逐行核对。我在项目里把这个环节做成了强制检查项,每次换数据集或换配置都要跑一遍,这个习惯救过我好几次。脚本逻辑不复杂:读一个txt,打印每行第一个数字,再对照names数组看名字。
5.2 样本不均衡:家用车500辆,消防车只有20辆
现象:模型对家用车的mAP50到了90%,消防车却只有40%左右,漏检严重。在消防车上投入再多的训练轮次也改善不了。
原因:数据集的类别分布天然不均衡,标注者会不自觉地多采常见车辆、少采特种车辆。YOLO的loss计算时主导类别权重过大,小类别被大类别淹没。
解决:有两种常用策略。第一种是做类别重采样,对小类别进行过采样,复制样本但配合不同的随机增强,等效增加消防车在训练中的占比。第二种是调整loss权重,在ultralytics源码里可以给每个类别设置不同的cls_loss权重来实现,但配置要深入源码,不如过采样简单直接。我一般两种都试,看mAP50的变化决定留哪一种。需要注意的是,过采样不能简单地把同一张图重复放进数据集,那样模型会把重复样本背下来,配合增强才有效。
5.3 边界框过紧导致mAP偏低
现象:肉眼看着框和物体贴合很好,但模型输出后NMS一过滤,同一辆车被输出多个候选框,mAP始终上不去。
原因:标注的边界框贴得太紧,包含的上下文信息太少。目标检测模型在训练时会学习目标周围的一小圈背景区域来辅助判断,如果框边界正好压在车辆轮廓上,模型学到的特征就缺少稳定上下文。
解决:训练前对边界框做轻微扩充,把每个框向外扩展2%到3%的像素,或者用labelImg人工将部分框调整到贴合中略带余量的状态。注意不要扩得太多,扩太多会把旁边的物体框进来,引入错误的负样本信号。我自己的经验是,3%的扩充在车辆这类矩形物体上最稳,mAP50能涨1到2个点。
5.4 数据泄漏:同一辆车出现在train和val
现象:训练得很好,val上的mAP也高,但一到完全没有见过的现场图片就掉点严重。
原因:数据划分前没有按“场景”去重。车辆数据集里同一个拍摄点经常连拍很多帧,模型已经记住这辆车的车牌和涂装,验证集等于开卷考试,换场景自然露馅。
解决:按场景而不是按单张图划分数据集。如果数据组织方式是每个场景一个文件夹,直接以文件夹为单位划分train/val/test。如果标注文件里没有场景信息,可以用图像hash做相似图去重,把相似度超过阈值的图片放到同一组。这一步在车辆检测里尤其重要,因为车辆特征高度结构化,模型记车比记场景容易得多。
5.5 标注软件导出格式和水印问题
现象:从某些标注平台导出的VOC XML打开直接报错,或者图片上被盖了水印,训练时mAP上不去。
原因:部分协作标注平台导出时会缺字段、压缩图片或者加水印,导致后续处理流程被破坏。此外CVAT和labelImg导出的XML虽然都是VOC格式,但字段顺序和命名可能存在细微差异。
解决:统一用固定版本的标注工具或平台,在导出后用一个解析脚本检查每个XML的字段完整性,项目里我习惯写一个十行级别的检查脚本,遇到缺字段直接报警输出文件名,再决定是补标注还是丢弃。水印问题更简单,训练前用程序批量裁掉图片边缘区域,或者重新导出无水印版本,不要让水印成为模型学到的特征。
6. 进阶玩法:1400张精标车辆数据的提点与验证技巧
数据量只有1400张,单靠调参的天花板很快就能摸到。如果想把这套数据用得更狠,常见做法集中在两条线:让数据变多,或者让推理变稳。
先让数据变多。训练一个收敛的初版模型,拿它对几万张无标签车辆图片做批量预测,置信度高于0.9的框直接写成伪标签,人工抽检一批确认没有系统性错误后,混入原数据再训练一版。这套半监督扩样流程在车辆场景里非常有效,因为车辆的外观结构高度规整,高置信度伪标签的噪声比想象中低。我一个项目里用这个办法把有效训练量从1400张扩到3500张左右,mAP50提升了3个多点,扩出来的数据比换模型收益更明显。
推理变稳方面,YOLOv8的predict接口自带测试时增强,翻转加缩放后合并预测结果。车辆检测的对称性很强,水平翻转TTA能稳定带来mAP50提升1个点左右,代价是推理耗时约翻倍。如果只是验证集或者离线分析,直接开着用;线上实时服务里我会把它关掉,不会为了这一个点冒险。使用方式很简单,在predict时传一个augment=True参数。
除了数据增广和TTA,还有一条容易被忽略的路:换预训练权重。版权干净的前提下,找在车辆相关数据集上预训练过的权重做微调起点,比从COCO起步收敛更快,小样本下的最终精度也更高。
最后说一个我养成的习惯:每次训练跑完,不看最高点,看最后20个epoch的平均mAP。最高点是过拟合的峰值,没参考价值;最后20轮的均值稳定,才说明模型没在训练后期跑偏。这个小改动让我在选最终模型时少踩了好几次坑,希望帮到你。
本文还有配套的精品资源,点击获取