☰
YOLO车辆检测实战:969张带标签图像训练全攻略
2026/10/11 20:13:37 网站建设 项目流程

简介:面向yolo系列目标检测算法的实际训练需求,这份车辆数据集覆盖汽车、自行车、公共汽车三类常见目标,包含969张带标注图像,可支撑目标检测模型的训练、验证与测试。标注内容同时提供yolo格式(txt)和voc格式(xml)两套方案,分别保存在独立文件夹中,其中yolo格式每行记录类别索引及归一化后的中心点坐标与宽高,方便二次处理;数据已预先划分训练与验证集合,并内置data.yaml配置文件,可无缝对接到yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流算法,减少环境配置和目录调整时间。压缩包内共2000个文件,包括969个xml标注、969个txt标注、61个jpg图像样本和1个yaml配置,整体大小57.08MB,结构清晰,便于按需取用。目前已有164人学习浏览,适合正在准备车辆检测项目、课程设计或需要快速评估yolo模型性能的开发者,能有效省去数据收集与手动标注的繁琐环节,让工作重心放在网络调参和结果分析上。

1. 969张带标签的车辆图能干什么:先讲清楚这个数据集的真实价值

刚拿到"yolo算法-车辆数据集-969张图像带标签"这个压缩包,很多人第一反应是嫌图太少。我的判断是:969张带标签的车辆图像,正好够你把yolo算法从训练、验证到部署的完整链路跑通,上手速度比拿一万张图快得多。

它的定位是冷启动:汽车、自行车、公共汽车三个类别贴近真实道路监控视角,标注框齐全,用来做模型选型、参数调优、迁移学习验证都顺手,解决的是从0到1的问题。

适合刚接触目标检测的开发者,也适合需要一份干净小样本去验证数据增强策略的工程师。别指望它直接上生产,用它练完手,再换到BDD100K这类大车辆检测数据集时,你已经知道坑在哪。

2. 拆包看货:标签格式、类别分布与图片抽查三件事

拿到压缩包先别急着解压训练。我一般会先做三件事:确认标签格式、统计类别分布、抽查标注框和原图是否对齐。这三件事做完,模型选型和参数设置才有依据。很多教程让你直接跑训练命令,结果第一轮loss异常,回头才发现是标签格式不对,白烧几小时显卡。

2.1 标签是txt还是xml:先写个小脚本把家底盘清楚

YOLO生态里标签最常见有两种形态。一种是txt格式,每行由"类别ID 中心点x 中心点y 宽 高"组成,坐标全部归一化到0到1;另一种是Pascal VOC的xml格式,记录xmin、ymin、xmax、ymax绝对像素值。压缩包名字只写了"带标签",没写具体格式,所以第一步是扫目录。

import os from collections import Counter def scan_label_dir(label_dir): ext_counter = Counter() for fname in os.listdir(label_dir): ext = os.path.splitext(fname)[1].lower() ext_counter[ext] += 1 print("标签文件扩展名统计:", dict(ext_counter)) scan_label_dir("labels") # 换成你解压后标签所在目录

这段脚本的逻辑很简单:遍历标签目录,统计扩展名。如果输出里.xml占多数,后面第一件事就是转成YOLO要的txt;如果.txt占多数,就随机打开三五个文件,确认每行是不是正好5列。常见的情况是txt里可能被标注工具多写了一个置信度列变成6列,YOLOv8训练时不会报错,但这种脏数据最好在源头清掉。

如果标签是xml,最省事的转换方式是顺手写一个xml转txt的脚本。别去下载一堆不知名的转换工具,一个脚本能解决的问题不要引入新的黑匣子。

import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, out_path, class_list): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_list: continue cls_id = class_list.index(name) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines))

这个函数的参数有三个:xml_path是输入的VOC标注,out_path是输出的txt标签路径,class_list是类别列表。注意class_list的顺序就是类别ID,比如["car", "bicycle", "bus"]表示car是0、bicycle是1、bus是2。转换时最容易被忽略的是分母:xmin加xmax除以2之后还要除以图片宽度,才是归一化中心点。很多人在这一步直接把像素值写进txt,训练时坐标全部超出0到1范围,轻则框偏移,重则loss直接变为NaN。

2.2 汽车、自行车、公共汽车的类别分布直接决定训练策略

标签格式确认后,下一步是统计每个类别有多少个标注框。这一步的意义在于:汽车在道路场景里出现频率最高,框又大又多;自行车框小,常和行人混在一起,容易互相遮挡;公共汽车框大但数量少。三类样本极端不均衡时,训练出来的模型会偏向样本多的类别。

import os from collections import Counter labels_dir = "labels" class_names = ["car", "bicycle", "bus"] counter = Counter() empty_files = 0 total_boxes = 0 for fname in os.listdir(labels_dir): if not fname.endswith(".txt"): continue with open(os.path.join(labels_dir, fname), "r") as f: lines = [line.strip() for line in f if line.strip()] if not lines: empty_files += 1 continue for line in lines: parts = line.split() if len(parts) != 5: continue cls_id = int(parts[0]) counter[class_names[cls_id]] += 1 total_boxes += 1 print("各类别框数:", dict(counter)) print("空标签文件数:", empty_files) print("总框数:", total_boxes)

这段脚本做了两件事:统计每个类别的框数,同时记录空标签文件数。空标签文件数是一个重要信号,如果它占比很大,说明一部分图片可能确实没有目标,也可能是标注工具保存失败,这个数字直接影响后面数据划分策略。比如空标签文件太多,训练集里大量负样本会把模型往漏检方向带。另外类别框数比例如果超过5比1,就要考虑做类别均衡,具体做法在第4章讲。

2.3 图片尺寸与标注框的对应关系:归一化坐标的底层逻辑

归一化坐标听起来玄学,其实就是一个比例。txt里的cx、cy、bw、bh全部是相对图片宽高的比例,取值在0到1之间。训练时YOLO会把图片缩放到640×640,但标签不需要重新计算,因为缩放前后归一化坐标不变,这就是YOLO选归一化而不是像素坐标的原因。真正容易出问题的场景是:标注工具生成标签时图片是1920×1080,后来有人把图片压缩成1280×720,但标签没重新生成。这时比例没变,标注框对应的像素位置却变了,模型学到的框中心点会系统性偏离目标。

所以训练前的抽查不能省。我一般会写一个可视化脚本,随机抽20张图,把标注框画上去,人眼过一遍。

import cv2 def draw_label(image_path, label_path, class_names, out_path): img = cv2.imread(image_path) h, w = img.shape[:2] with open(label_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls, cx, cy, bw, bh = parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(cls)], (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img)

这个脚本的关键参数就两个:图片路径和标签路径。画框时把归一化坐标乘以实际图片宽高,还原成像素坐标。抽查时重点看三样东西:框是否贴合目标、类别名字是否对得上、有没有框跨出图片边界。如果发现一批标签的框整体偏移,多半是标注时和图片尺寸不匹配,趁早重新生成,别等到训练完再回来找原因,那时候排查成本翻倍。图像算法这条路上,数据质量永远比模型结构更值得花时间。

3. 用YOLOv8跑通最小训练闭环:从目录编排到第一次出图

数据检查完,下一步就是把数据集编排成YOLO能直接吃的结构。我不用zip里的原始摆放方式,哪怕它看似已经分好了目录,也要统一成images和labels平级的两棵树。这样做的好处是:后面换模型、加数据、跑交叉验证时,脚本逻辑不用改。

3.1 按YOLO惯例整理目录与data.yaml

常见做法是构建下面这个结构:

vehicle_dataset/ images/ train/ val/ labels/ train/ val/ data.yaml

images和labels下的train与val一一对应,图片文件名和标签文件名的主干部分必须一致。如果压缩包里只有images和labels两个平级目录,没有分train/val,就要自己划分。常见做法是随机抽15%到20%做验证集,剩下的做训练集。注意划分时图片和标签必须一起搬,很多新手只搬了图片,训练时一堆label文件找不到的报错。

import os import random import shutil all_images = [f for f in os.listdir("images") if f.endswith((".jpg", ".jpeg", ".png"))] random.seed(42) random.shuffle(all_images) val_count = int(len(all_images) * 0.15) val_set = set(all_images[:val_count]) for f in all_images: stem = os.path.splitext(f)[0] src_img = os.path.join("images", f) src_lbl = os.path.join("labels", stem + ".txt") if f in val_set: shutil.move(src_img, "vehicle_dataset/images/val") shutil.move(src_lbl, "vehicle_dataset/labels/val") else: shutil.move(src_img, "vehicle_dataset/images/train") shutil.move(src_lbl, "vehicle_dataset/labels/train")

这段脚本用随机种子固定划分结果,保证每次复现都一样。val_count按15%计算,比如969张图分出145张做验证。如果标签是xml,把脚本里的".txt"换成".xml",或者先用第2章的转换函数转完再做划分,两条路都行。

目录就绪后,写data.yaml:

path: vehicle_dataset train: images/train val: images/val names: 0: car 1: bicycle 2: bus

data.yaml有四个关键字段:path是数据集根目录,train和val是相对path的训练和验证图片目录,names是类别名到ID的映射。yaml文件里不要写中文注释,YOLO解析时对注释里的特殊字符不友好,报错时很难看出来。

注意:data.yaml的names顺序必须和txt标签里的类别ID一致,这是小数据集训练里最常见也最隐蔽的错位来源。

3.2 训练命令与五个必调参数

目录和yaml就绪后,最小训练命令长这样:

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 patience=20

这五个参数我逐个说。model=yolov8n.pt是预训练权重,n是nano,参数最少、显存占用最低,小数据集上完全够用;如果显卡显存大于12G,可以换yolov8s.pt,精度会好一点。imgsz=640是训练分辨率,车辆属于中大型目标,640是性价比最高的起点;imgsz越大越吃显存,但小目标召回会更好。batch=16是批量大小,8G显存建议8到16,批量太小BN层统计不稳定。epochs=100在小数据集上偏多,配合patience=20早停,实际跑到五六十轮就会停。patience=20的意思是连续20轮验证集指标不提升就停止训练,既省时间也防过拟合。

如果想在训练命令里直接加增强参数,长这样:

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 \ batch=16 patience=20 mosaic=0.5 close_mosaic=10 flipud=0.0

这里mosaic和close_mosaic是第4章要展开讲的增强参数,训练前先按这个配置跑一轮,能得到一条稳定的baseline。第一次跑的时候,与其满世界找yolo算法讲解ppt,不如把训练日志里每一列的曲线变化读明白。日志里train/box_loss稳步下降、val/box_loss先降后平,这是健康信号;val loss从第一轮就开始涨,那是过拟合或者标注有问题,要回到第2章重新查数据。

3.3 第一次验证:从results.csv读训练状态

训练结束后,runs/detect/train/weights/下会生成best.pt和last.pt。best.pt是验证集指标最好的权重,last.pt是最后一轮的权重,常规用best.pt。跑验证:

yolo detect val model=runs/detect/train/weights/best.pt data=data.yaml imgsz=640

验证命令会输出precision、recall、mAP50、mAP50-95四个指标。对车辆检测来说,mAP50是主要参考,mAP50-95更严苛,小数据集上mAP50-95低是正常现象,不用焦虑。

训练过程中YOLO会把每轮的指标写进runs/detect/train/results.csv,用pandas可以直接读:

import pandas as pd df = pd.read_csv("runs/detect/train/results.csv") print(df.columns.tolist()) print(df.tail(3)[["epoch", "train/box_loss", "val/box_loss", "metrics/mAP50(B)"]])

这段代码的作用是跳过训练日志的滚动刷屏,直接看最后几轮的数值。重点看val/box_loss有没有持续下降,metrics/mAP50(B)在哪个区间稳定。如果val/box_loss在第30轮之后开始抬头,说明模型过拟合了,下一步要么加大增强,要么减小模型容量。results.csv是训练过程的黑匣子出口,养成每轮训练后都看一眼的习惯,比盯着终端滚动日志有用得多。

4. 小样本训练策略:969张图如何喂出能落地的检测模型

969张图是典型的小样本规模。直接硬训,几十轮后指标就原地踏步,再多轮就是纯过拟合。常见做法是三条腿走路:增强、迁移、类别均衡。

4.1 内置数据增强参数:mosaic、flip、hsv该开多大

YOLOv8内置了一套增强参数,默认值是按大数据集调的,小数据集要手动收着点用。原因很简单:mosaic把四张图拼成一张,数据量大的时候是免费的样本扩充,数据量小的时候模型容易被拼接缝带偏,学到的特征不够真实。

参数建议值理由
mosaic0.5969张图禁不起全程mosaic,开一半即可
close_mosaic10最后10轮关掉mosaic,让模型回归真实分布
fliplr0.5水平翻转符合道路场景,左右来车都常见
flipud0.0车辆不会倒着开,垂直翻转只会制造幻觉样本
hsv_h0.015色相偏移要小,偏移大了红车变绿车
hsv_s / hsv_v0.5 / 0.4饱和度和亮度可以放宽,光照变化本来就有

这组参数直接写进训练命令:

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 \ batch=16 mosaic=0.5 close_mosaic=10 fliplr=0.5 flipud=0.0 \ hsv_h=0.015 hsv_s=0.5 hsv_v=0.4

参数从命令行传进去会覆盖YOLO的默认值。mosaic=0.5的意思是有一半概率对这批图做mosaic增强,如果你发现训练前期box_loss下降很慢,可以把mosaic再往下调到0.3。close_mosaic=10是最值得记住的参数,它让最后10个epoch不使用mosaic增强,相当于考前改用真题而不是模拟题,通常能让mAP50涨一到两个点。

4.2 迁移学习:预训练权重怎么选、冻结层要不要开

yolov8n.pt是在COCO数据集上预训练过的,COCO的80类里正好包含car、bicycle、bus,这意味着预训练模型已经见过和你任务几乎一样的目标。迁移学习的价值就在这里:底层卷积已经学会纹理、边缘、形状,你只需要微调最后的检测头。小数据下最怕的是全量微调导致灾难性遗忘,模型把预训练知识全忘光,重新在噪声里学。

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 freeze=10

freeze=10的意思是冻结前10层,只更新后面的网络。冻结的好处是训练更快、更稳,坏处是可学习的参数变少,表达能力受限。小数据集上从freeze=10起步是对的,如果你发现loss下降得太平、指标上不去,再逐步把freeze减到0。判断依据很简单:跑完看val/box_loss是否还在下降,如果在降就说明欠拟合,可以解冻更多层。

4.3 类别不均衡:自行车样本少时的两种常用做法

如果第2章的统计结果显示自行车框数远少于汽车,直接训出来的模型一定偏科。两个常用做法:一是过采样,把含自行车的图片在训练集里重复放几份,代价是训练时间变长;二是复制粘贴增强,把自行车小目标从一张图里抠出来,随机贴到另一张图上,同时修正标签。

import cv2 import numpy as np def paste_positive(src_img, src_label, dst_img, dst_label, class_id): h, w = dst_img.shape[:2] with open(src_label) as f: boxes = [list(map(float, line.split())) for line in f if line.strip()] targets = [b for b in boxes if int(b[0]) == class_id] if not targets: return dst_img, dst_label cls, cx, cy, bw, bh = targets[0] x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) crop = src_img[y1:y2, x1:x2] nx = np.random.randint(0, max(1, w - (x2 - x1))) ny = np.random.randint(0, max(1, h - (y2 - y1))) dst_img[ny:ny + (y2 - y1), nx:nx + (x2 - x1)] = crop new_cx = (nx + (x2 - x1) / 2) / w new_cy = (ny + (y2 - y1) / 2) / h new_bw = (x2 - x1) / w new_bh = (y2 - y1) / h dst_label.append(f"{cls} {new_cx:.6f} {new_cy:.6f} {new_bw:.6f} {new_bh:.6f}") return dst_img, dst_label

这段代码的参数不复杂:src_img和src_label是抠图的来源,dst_img和dst_label是粘贴的目标,class_id指定要复制哪个类别。逻辑是先找到第一个自行车框,把对应像素区域裁出来,随机放到目标图的空白位置,再把新坐标以归一化格式加进目标标签。注意两个坑:粘贴的位置不能和原图已有目标重叠,否则训练时两个框互相干扰;目标本身小于30像素时,复制粘贴的收益基本为零,只有原图里目标清晰才有正收益。

提示:复制粘贴增强只适合目标本身清晰且尺寸大于30像素的场景,粘贴位置要避开已有目标框。

如果不想写代码,还有一个更省事的路子:用现成的复制粘贴增强库,但别在没理解坐标变换原理之前用,出了问题你根本不知道是粘贴位置错了还是标签错了。这个数据集的价值就在这:样本少,逼你把每种补救手段的原理搞清楚。

5. 避坑指南:标注格式、标签错位与训练翻车的四类现场

下面四条是我拿小样本车辆数据集训练时踩过的真实坑,每一条都写成现象、原因、解决三段,方便你对照排查。

5.1 xml格式文件没有标签怎么办:先分清空标注和坏标注

现象:解压后发现一批xml文件里根本没有object节点,或者object节点是空的。很多人搜"xml格式文件没有标签怎么办",搜到的教程只会教你解析xml,不会教你判断这张图到底该不该有标签。

原因:通常有两种。一种是标注工具保存失败或误操作,图片里明明有车,但xml没写入任何目标;另一种是图片里确实没有目标,标注员按规则留空。

解决:先别急着删或补。如果图片里确实有车但没标签,这张图会被训练当成负样本,把模型往漏检方向带,轻则拉低recall,重则把整个场景的汽车全压没。我的做法是写脚本把空xml对应的图片全部列出来,人眼过一遍:真的没车就保留,有车就重新标注。这一步花不了十分钟,但能避免训练完才发现recall异常低的返工。

还有一个细节容易被忽略:xml里object存在但name不在class_list里时,转换脚本会直接跳过,结果这张图变成半标注状态。如果一张图有三个目标,其中一个类别不在列表里,转换后txt只剩两个框,那个被忽略的目标在训练时就成了漏检样本。解决方式是把所有xml里出现过的name统计一遍,确认没有第四类目标漏掉。

5.2 类别ID和类别名错位:mAP异常低的常见元凶

现象:训练完mAP一直趴在地上,或者预测时把公交车标成汽车、把自行车标成行人。

原因:labels里0是car,data.yaml里0却写成了bicycle,两者对不上。出现这种错位往往是因为数据集来自多个渠道,每个渠道的类别定义不一样,合并时没人统一ID。

解决:训练前做一次可视化抽查,用第2章的draw_label画20张图,人眼核对框和类别。再配合一个快速命令,看标签文件的前几行:

head -5 labels/train/00001.txt

如果输出第一列全是0、1、2,而data.yaml的names顺序是car、bicycle、bus,那基本能对住。如果发现第一列出现3甚至更大的数字,说明标签里有超出names范围的类别ID,YOLO不会报错,但训练时这部分目标会被当成背景,检测结果就会缺类别。遇到多来源合并的数据集,我习惯先用脚本把所有标签里的类别ID全局统计一遍,再去和data.yaml的names逐行比对,不要相信任何人对"类别顺序没动过"的口头保证。

5.3 训练loss不降或震荡:先查标签再调学习率

现象:train/box_loss前十几轮不降,或者val loss呈现锯齿状,一会在0.08一会在0.14。

原因:三个因素按概率排序。第一是标签坐标越界,比如cx写成1.5,训练时损失函数算出的误差巨大;第二是学习率太大,YOLOv8默认学习率对小数据集偏激进;第三是batch太小,BN统计不稳定。

解决:先跑一遍标签越界检查,把超出0到1范围的坐标揪出来:

import numpy as np from pathlib import Path bad_files = [] for txt in Path("labels/train").glob("*.txt"): arr = np.loadtxt(txt) if arr.size == 0: continue if arr.ndim == 1: arr = arr.reshape(1, -1) if (arr[:, 1:] < 0).any() or (arr[:, 1:] > 1).any(): bad_files.append(txt.name) print("坐标越界的标签文件:", bad_files)

这段代码用numpy批量读取标签,检查第2到第5列是否都在0到1之间。如果发现有越界文件,把坐标clip到合法区间,或者直接删掉这个文件重新标注。标签干净之后,再把学习率从默认值降到0.001,batch尽量保证在8以上。很多所谓的训练玄学,排查下来都是数据问题,不是模型问题。

5.4 小目标漏检:自行车为什么总是被漏掉

现象:汽车检测效果很好,自行车频繁漏检,在imgsz=640下小自行车直接消失。

原因:自行车占的像素太少,640分辨率下可能只有十几乘三十像素;再加上自行车常和行人混在一起,身体遮挡让特征残缺;小数据集里自行车样本又少,模型干脆学成"忽略小目标"。

解决:优先把imgsz从640提到1280,显存不够就切图训练,把原图按四块切分,每块单独训练,推理时再把检测框映射回原图坐标。遥感图像标注里处理小目标也是同一套思路:把目标所在区域裁出来重采样,做成额外的高分辨率训练图。如果切图太麻烦,至少在推理时把conf阈值从0.5往下调到0.3试试,低置信度框确实会带来一些假阳性,但小自行车的召回通常先回升。另外,如果你在yaml里显式设置了anchors参数,记得检查anchor尺度是否适配自行车这种小目标,YOLOv8虽然默认自适应anchor,但手动配置一旦沿用旧模型的参数,小目标漏检会雪上加霜。

6. 把969张图用出9690张的效果:半自动标注与五折验证两个技巧

6.1 半自动标注:用训练好的模型回标未标注数据

训练完best.pt之后,最常见的浪费是把模型晾在一边。我一般会用它去回标同场景里还没标注的图片,把小数据集滚大。YOLO的predict命令直接支持输出txt格式的标签:

yolo predict model=runs/detect/train/weights/best.pt \ source=unlabeled_images/ save_txt=True conf=0.5

save_txt=True会把每个预测结果写成同名txt,放到runs/detect/predict/labels/下。conf=0.5是置信度阈值,低于0.5的框不输出,避免一堆假阳性回灌进训练集。YOLO训练读取标签时只取前五列,预测txt里多出来的置信度列会被忽略,所以直接把预测结果拷回labels目录也能训练。但我强烈建议中间加一道人工复核:用labelImg或X-AnyLabeling打开预测图,把低置信度框删掉、漏检的框补上。半自动标注的核心是"人审",不是"机标",省的是画框的时间,不是审核的时间。

6.2 五折交叉验证:小数据集的稳定性评估方法

969张图只训一次,结果方差很大,换一次随机种子指标可能波动好几个点。要想知道这个数据集和这套参数到底稳不稳,常见做法是五折交叉验证:把969张图分成5份,每次4份训练、1份验证,跑5轮,最后看平均mAP和标准差。

先用sklearn生成折数下标:

from sklearn.model_selection import KFold import numpy as np all_images = np.array(range(969)) # 用图片ID代替真实文件名 kf = KFold(n_splits=5, shuffle=True, random_state=42) for fold, (train_idx, val_idx) in enumerate(kf.split(all_images)): print(f"fold {fold}: train {len(train_idx)} val {len(val_idx)}")

这段代码的作用是生成五折的样本下标,让你先确认每折的训练和验证数量是均匀的。sklearn的KFold只做划分,真正的训练要用shell循环来跑:

for fold in 0 1 2 3 4; do python prepare_fold.py --fold $fold yolo detect train data=data_fold$fold.yaml model=yolov8n.pt epochs=100 batch=16 > train_fold$fold.log yolo detect val model=runs/detect/train/weights/best.pt data=data_fold$fold.yaml >> val_fold$fold.log done

prepare_fold.py负责按第fold折的下标生成对应的train和val目录以及data_fold$fold.yaml,每折的names顺序必须保持一致,不能这一折car是0、下一折car变成2。五轮跑完,把每轮val日志里的mAP50收集起来算平均和标准差。如果标准差小于两个百分点,说明模型对这个数据集是稳的,可以放心加大投入;如果标准差很大,说明某些折里类别分布不均,回去做类别均衡比调参更重要。

我自己做小样本检测时最常犯的错是:看到loss降了就以为成了,结果五折一跑才发现换一批数据就翻车。现在的习惯是两条:任何新数据集,先五折验证再谈调参;任何新标签,先画图再谈训练。这969张图的车辆数据集,本质上是个耐用的演练场,把它跑透,比盲目去找更大的数据集更划算。希望帮到你。

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

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

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

立即咨询