简介:面向道路交通监控与车辆识别检测任务,提供覆盖自行车、电动自行车、摩托车、三轮车、面包车等9类常见道路车辆的YOLO格式标注数据。完整数据集共2534张图片,按两部分拆分,当前为第1部分,适合YOLO全系列算法直接训练与试验,并已划分训练/验证/测试集。压缩包内共2000个文件,其中1999个txt标签文件对应各图像的检测框标注,另有1个yaml文件定义类别与数据路径,整体约634.3MB,结构清晰,无需额外转换即可加载使用。目前已有574人浏览学习,可见其适配科研、课设与落地项目的实用价值。标注精准,类别覆盖城市交通常见车型,可直接用于交通违规检测、车流量统计等场景;按YOLOv9训练可达到92.3%的准确率,帮助用户快速获得有效的道路车辆识别基线结果,也可作为算法改进与模型评估的基准数据。
1. 9类道路车辆识别数据集:2534张带yolo txt标签的图够不够用
做道路车辆检测的人,第一次卡住的通常不是模型结构,而是没有带标注的数据。9类道路车辆识别检测数据集这个资源,提供了2534张已经标注好的真实场景图像,覆盖自行车、电动自行车、摩托车、三轮车、面包车等常见路面车辆类型,并且每张图都配套yolo格式的txt标签,解压之后可以直接进入YOLO训练流程,不用自己去画框。适合正在做交通流量统计、路口监控分析或智慧城市试点,手头缺标注样本的开发者。需要注意的是,标题里写着“第1部分”,说明这个数据集可能是分批发出的,先拿到第一部分的人,可以先把目录结构、训练流程和评价指标跑通盘顺,等后续部分到位,再合并训练,降低试错成本。
2. 拿到压缩包先别急着训练:目录结构、标签格式与类别映射
做目标检测最忌讳的是拿到数据包后直接双击一张图看一眼,然后立刻去敲训练命令。我踩过这个大坑:当年拿到一个行车记录仪数据集,没检查标签就开训,结果损失函数第一个epoch直接NaN,整整排查了一下午,最后发现是一个标签文件里某一行坐标写成负数。所以解压之后的前十分钟,一定用来摸清楚数据的底细。
2.1 解压后的目录结构怎么看
先把压缩包解开,再把目录结构打印出来:
unzip 9类道路车辆识别检测数据集2534张含yolo格式txt标签*第1部分.zip -d vehicle_dataset cd vehicle_dataset find . -maxdepth 2 -type d | sort第一行命令把zip包解压到 vehicle_dataset 目录。注意这里的-d参数,它指定了解压目标目录,避免把一堆文件直接散落在当前文件夹里。第二行的find -maxdepth 2 -type d只显示两层目录,因为大多数数据集的嵌套层级不会超过这个深度。如果输出结果很乱,就重点确认三件事:images 和 labels 是不是平级、有没有自带 train/val 子目录、README 或类别说明文件是否存在。
常见的布局有三种:images/和labels/平级、图像和标签混在同一目录、已经按train/val划分好。第一种最常见,后两种都需要在第3章做目录重排处理。这里多花五分钟,第4章的训练配置就不用反复改路径。
如果发现解压后文件特别多,可以用ls -l | wc -l快速统计文件数量,跟标题声称的2534张对一下。我见过有人拿到的所谓数据集,实际只有2000张图,其余都是重复文件,这一步能帮你提前发现货不对板。
2.2 YOLO txt标签的真实格式长什么样
YOLO标签不是XML也不是JSON,就是纯文本,每一行代表图片里的一个目标框,共5个字段:
类别id 归一化中心x 归一化中心y 归一化宽度 归一化高度读一个标签文件看看实际内容:
from glob import glob import os label_dir = "vehicle_dataset/labels" label = glob(os.path.join(label_dir, "*.txt"))[0] with open(label, "r") as f: lines = f.readlines() print("标签文件:", label) print("目标数量:", len(lines)) for line in lines[:5]: parts = line.strip().split() if len(parts) == 5: cls, x, y, w, h = parts print(f"类别:{cls} 中心x:{x} 中心y:{y} 宽:{w} 高:{h}") else: print("异常行:", line.strip())这段代码顺手把列数不是5的行也标出来了,因为这是YOLO标签最容易被动手脚的位置。坐标全部是归一化后的浮点数,范围应该在0到1之间。如果看到1.0532这种值,说明目标框已经超出图像边界,得标记出来准备清洗。类别id从0开始计数,0就是类别说明文件里定义的第1个类别。
2.3 用一条命令统计每个类别的样本分布
了解类别分布对后续训练策略极其重要,直接统计所有标签文件的第一列:
for f in vehicle_dataset/labels/*.txt; do awk '{print $1}' "$f"; done | sort | uniq -c | sort -rn解释一下:外层遍历每个标签文件,awk '{print $1}'取每行第一个字段也就是类别id,合并排序后用uniq -c统计次数,最后的sort -rn按次数从大到小排。输出像这样:
520 1 413 0 270 4第二列是类别id,第一列是出现次数。如果看到某个类只有几十个样本、另一个类有上千个,那就是典型的类别不平衡数据集,后面第3章会专门处理。
这里有个细节容易被忽略:如果类别id不连续,比如跳过2直接出现3,要么重新映射为连续的0到N-1,要么保证训练yaml里的names跟原始id对齐。实际项目中不连续id最容易让预测阶段类别编号对不上号。
2.4 数据质量检查:图像和标签能否一一对上
我每次拿到新数据集都会全量检查一次,2534张图规模下这种检查成本很低,但能省掉你后面几天的排查时间:
import os from glob import glob from PIL import Image image_dir = "vehicle_dataset/images" label_dir = "vehicle_dataset/labels" images = glob(os.path.join(image_dir, "*")) label_stems = {os.path.splitext(os.path.basename(p))[0] for p in glob(os.path.join(label_dir, "*.txt"))} image_stems = {os.path.splitext(os.path.basename(p))[0] for p in images} # 检查一:配对情况 only_img = image_stems - label_stems only_lbl = label_stems - image_stems print(f"有图无标签: {len(only_img)}, 有标签无图: {len(only_lbl)}") # 检查二:每张图能否正常打开 bad_imgs = [] for img_path in images: try: with Image.open(img_path) as im: im.load() except Exception as e: bad_imgs.append((img_path, str(e))) if bad_imgs: print("损坏图像:", bad_imgs[:5]) else: print("所有图像可以正常打开") # 检查三:标签坐标合法性 bad_labels = [] for lbl_path in glob(os.path.join(label_dir, "*.txt")): for line_num, line in enumerate(open(lbl_path), 1): parts = line.strip().split() if len(parts) != 5: bad_labels.append((lbl_path, line_num, "字段数不为5")) continue try: x, y, w, h = map(float, parts[1:5]) except ValueError: bad_labels.append((lbl_path, line_num, "坐标包含非数字")) continue if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_labels.append((lbl_path, line_num, f"坐标越界: {line.strip()}")) if bad_labels: print(f"发现问题标签 {len(bad_labels)} 处,前5条:") for item in bad_labels[:5]: print(item) else: print("标签坐标全部合法")这个脚本做了三件事:配对检查、图像完整性检查、标签坐标合法检查。配对检查解决的是训练过程中那句Found no labels报错;图像完整性检查过滤掉无法解码的文件;坐标合法性检查直接避免损失函数NaN。2534张图全量跑一遍不会超过十几秒,不要抽样。
如果你在标签里发现少量越界框,不要整张图删掉,做一个边界裁剪把它修正过来,好的目标框不影响使用。做完这一步,数据集的底就算彻底摸清了。
3. 把2534张图改成YOLO训练标准结构:目录重排、随机划分与data.yaml生成
YOLOv5和YOLOv8的训练器默认从 data.yaml 里获取数据集路径和类别信息。第2章解压出来的原始目录,大概率不能直接拿来训练,你得先把数据结构标准化。YOLO标准的数据集结构大概是:
yolo_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml3.1 为什么不直接改yaml路径而要重排目录
很多新手会想:既然yaml里能指定路径,直接让 train 指到vehicle_dataset/images不就行了?问题在于YOLO会默认在图像同级目录找标签,或者根据给的两个路径去推算。当 images/labels 分开放、文件名还带不同扩展名时,只要这样写了,训练器就会报 label not found,最后还是得回去改目录。与其在yaml上做各种绕弯,不如一次性重排成标准结构,后面所有脚本都能按同一个约定来写。
3.2 目录重排与随机划分脚本
写一个Python脚本完成三件事:创建目标目录、随机打乱、按比例拷贝到train和val:
import os import shutil import random from glob import glob random.seed(42) src_img = "vehicle_dataset/images" src_lbl = "vehicle_dataset/labels" dst = "yolo_dataset" # 创建标准目录结构 for split in ["train", "val"]: os.makedirs(os.path.join(dst, f"images/{split}"), exist_ok=True) os.makedirs(os.path.join(dst, f"labels/{split}"), exist_ok=True) # 找出所有图像文件 images = [p for p in glob(os.path.join(src_img, "*")) if p.lower().endswith((".jpg", ".jpeg", ".png", ".bmp"))] random.shuffle(images) # 20%作为验证集,其余训练 val_count = int(len(images) * 0.2) val_images, train_images = images[:val_count], images[val_count:] val_count = max(val_count, 1) # 防止数据集过小时验证集为0 print(f"训练集 {len(train_images)} 张,验证集 {len(val_images)} 张") for name, img_list in [("train", train_images), ("val", val_images)]: for img_path in img_list: stem = os.path.splitext(os.path.basename(img_path))[0] ext = os.path.splitext(img_path)[1].lower() # 图像和标签保持相同stem,这是YOLO系列的文件匹配约定 shutil.copy(img_path, os.path.join(dst, f"images/{name}/{stem}{ext}")) lbl_path = os.path.join(src_lbl, f"{stem}.txt") if os.path.exists(lbl_path): shutil.copy(lbl_path, os.path.join(dst, f"labels/{name}/{stem}.txt")) else: print(f"警告: {stem} 缺少标签")random.seed(42)固定随机种子,保证每次划分结果一致,这对后续复现实验非常重要。val_count取图像总数的20%,2534张图大概会分到500张验证集。用shutil.copy而不是os.rename,保留原始数据不动,万一划分错了还能重新来。图像文件名保留了原始扩展名,但建议你后续统一改成.jpg,因为opencv对部分.bmp的兼容性有些诡异的问题。
划分比例上,20%是经验值。如果验证集太小,置信区间就太宽,评估结果不稳定,可以考虑降到10%或直接做交叉验证。不过2534张图用20%问题不大。
3.3 根据标签内容确认类别数量
类别名单必须和标签里的id严格对应。如果数据集提供了 classes.txt 就直接读:
cat vehicle_dataset/classes.txt如果没提供,就扫描所有标签文件里的最大类别id:
import glob max_cls = -1 for lbl in glob.glob("vehicle_dataset/labels/*.txt"): with open(lbl) as f: for line in f: cid = int(line.strip().split()[0]) max_cls = max(cid, max_cls) print("类别总数:", max_cls + 1) print("最大类别id:", max_cls)扫描结果直接决定你在yaml里要写多少类。有时候标签文件里某个类别一条都没有,但标题说了有9类,那你要看是不是这个类样本量刚好为0,还是原始数据压根缺了这个类。如果是后者,可以先按实际存在的类别训练,等后续分卷补齐。
3.4 写入data.yaml并验证
用yaml库生成配置文件:
import os import yaml # 注意:这里只是示例,实际类别名以你解压出来的classes.txt为准 class_names = ["bicycle", "electric_bicycle", "motorcycle", "tricycle", "van", "car", "bus", "truck", "other_vehicle"] data_cfg = { "path": os.path.abspath("yolo_dataset"), "train": "images/train", "val": "images/val", "names": {i: name for i, name in enumerate(class_names)}, } yaml_path = os.path.join("yolo_dataset", "data.yaml") with open(yaml_path, "w", encoding="utf-8") as f: yaml.dump(data_cfg, f, allow_unicode=True, sort_keys=False) print("已写入:", yaml_path)三个参数需要较真:path必须是绝对路径,否则YOLO会基于当前工作目录拼接,换个目录运行就找不到数据;names必须是{0: xxx, 1: xxx}这种字典格式,键从0开始连续;allow_unicode=True是为了类别名如果带中文不报编码问题,但类别名强烈建议用英文,避免不同环境下字符集不一致。
3.5 类别不平衡:先跑baseline再决定处理方案
用第2章的统计命令看分布,如果自行车类有800多个框、三轮车只有60多个,差距超过10倍,就得认真考虑不平衡问题。我的处理思路分三步:
第一步,不采样,直接训练一版baseline。用验证脚本输出每个类别的AP,看清楚哪个类最差。第二步,如果小类确实差,优先检查是不是标签错标,比如电动自行车被标成摩托车,这种系统性错误比采样更致命。第三步,确认标签无误后,再对少数类做复制增强,或者调整mosaic增强的参与率,但要注意过采样会导致过拟合。
还要强调一点:验证集划分在类别不平衡时,也要保证各类别都有代表。2534张图的规模下,数据多的大类随机划分基本能均匀分布,但样本量只有50个的小类,有可能全落到训练集或验证集。划分完用第2章的统计命令再跑一次,如果某个类在验证集里一条都没有,就换个随机种子重新分,或者手动分几张进去。
4. 用YOLOv8在本地跑通车辆检测训练:最小命令、关键参数与训练监控
第3章的目录结构和data.yaml都准备好了,下面直接进入训练。现在YOLO系列做目标检测的主流选择是YOLOv8,命令行工具很简洁,适合快速验证数据集质量。
4.1 环境准备:ultralytics安装与CUDA确认
YOLOv8的官方实现封装在 ultralytics 这个Python包里:
pip install ultralytics这个命令会自动带上pytorch、torchvision、opencv-python等依赖。但如果你的机器是老显卡,pip默认装的pytorch可能是CPU版,需要先确认:
import torch print(torch.cuda.is_available()) print(torch.__version__)输出第一行是 True 说明能用GPU,False 就只能在CPU上练。2534张图的规模,CPU训练也不是跑不动,一个epoch可能5到10分钟,100轮就是十几个小时,能接受也行。但我的建议是能搞到GPU就用GPU,别在CPU上耗时间。
4.2 训练命令:先小模型拿baseline
激活Python环境后直接执行:
yolo train model=yolov8n.pt data=yolo_dataset/data.yaml epochs=100 imgsz=640 batch=16 device=0 name=vehicle_v1逐个拆解:model=yolov8n.pt是YOLOv8的nano版本,文件最小、速度最快,适合首轮跑通流程。第一次运行会自动下载 yolo预训练模型到缓存目录,如果网络不好下不动,就手动把 yolov8n.pt 放到项目目录再运行一次,命令行会优先使用本地权重。data=指向第3章生成的yaml;epochs=100是这个量级数据集的合理上限,实际收敛会更早;imgsz=640是速度和精度的平衡点,后面会讲怎么用960提小目标召回;batch=16对应单卡16张图,显存不够就改成8;device=0指定用第0块GPU;name=vehicle_v1是实验任务名,跑完结果全在runs/detect/vehicle_v1下面。
4.3 训练过程看什么:损失曲线和异常告警
训练启动后,终端会实时打印每轮的box_loss、cls_loss、dfl_loss和学习率。新手最容易犯的错是不看这些数值直接挂机睡觉。我建议至少盯住前10个epoch:正常情况三个loss都应该是稳步下降的,如果第一个epoch就出现nan,停下来,回到第2章把标签坐标再检查一遍,不要浪费一整个晚上跑一个注定失败的实验。
训练结束后,runs/detect/vehicle_v1目录下会生成results.png,里面画了损失曲线和mAP曲线。把这个图保存好,后面每次调参都要跟它对比。
4.4 两个最容易遇到的训练异常
第一个是 yolo 训练中 bn 崩溃,现象是loss突然从0.5跳到几万甚至更大。这本质上是批归一化层的统计量被极端输入破坏,通常是大学习率和小batch配合不当造成的。解决方法是把初始学习率调小,比如在命令行加上lr0=0.005,或者把batch调大再试。2534张图这个量级、batch=16的情况下bn崩溃不常见,但一旦遇到,不要怀疑模型,先降学习率。
第二个是训练结束后验证集mAP高、实际视频上效果差。这类问题多半不在训练环节,而在类别不平衡和验证集划分上,前面章节已经讲了,这里不再重复。
4.5 训练多久算够:早停与技术指标判断
YOLOv8命令行支持patience参数,意思是验证集mAP连续多少个epoch没有提升就提前终止:
yolo train model=yolov8n.pt data=yolo_dataset/data.yaml epochs=100 imgsz=640 batch=16 patience=20 device=0 name=vehicle_v1patience=20表示连续20轮mAP没提升就自动停,会把验证集指标最优的那轮权重保存为best.pt。2534张图的小数据集,通常40到60轮已经收敛,设patience能省掉后面四十轮的机器时间。
训练结束后runs/detect/vehicle_v1/weights/下有两个文件:last.pt是最后一轮权重,best.pt是验证集表现最好的权重。做推理和部署时只用best.pt,这是最容易踩错的地方——有人为了省事选了last,结果精度比best低好几个点才发现。
5. 避坑手册:从标签越界到类别不平衡的5条踩坑记录
第2章和第4章该讲的流程走完了,下面集中把我在道路车辆数据集上亲身踩过的坑列出来,方便你对照排查。每个坑都按「现象 → 原因 → 解决」三段式记录。
5.1 标签越界导致box_loss出现NaN
现象:训练第一个epoch就报了NaN,或者loss在前几个epoch正常、到第20个epoch突然变成NaN,整个实验只能重来。
原因:标签txt里某个目标的x或w超过归一化范围,比如1.2。YOLO做正负样本匹配时,这种框会算出异常的正样本数,数学上直接让损失变成无穷或NaN。
解决:用第2.4节的检查脚本扫一遍,把所有越界行打出来。但注意,不要直接把越界目标删掉,因为那个目标可能是一辆真实存在的车。正确做法是反算回像素坐标,裁剪到图像边界再重新归一化写回txt:
from PIL import Image import os def clip_label(img_path, lbl_path): with Image.open(img_path) as im: w_img, h_img = im.size new_lines = [] with open(lbl_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue # 跳过格式错误的行 cid, x, y, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 归一化坐标转像素坐标 px = (x - w / 2) * w_img py = (y - h / 2) * h_img pw = w * w_img ph = h * h_img # 裁剪到图像边界 left = max(0, px) top = max(0, py) right = min(w_img, px + pw) bottom = min(h_img, py + ph) if right - left < 2 or bottom - top < 2: continue # 裁剪后太小,直接忽略 # 重新归一化 new_x = (left + right) / 2 / w_img new_y = (top + bottom) / 2 / h_img new_w = (right - left) / w_img new_h = (bottom - top) / h_img new_lines.append(f"{cid} {new_x:.6f} {new_y:.6f} {new_w:.6f} {new_h:.6f}\n") with open(lbl_path, "w") as f: f.writelines(new_lines)逻辑说明:把归一化坐标换算成像素框,用max/min把越界部分裁掉,最后重新归一化写回。宽或高裁剪后小于2像素的目标说明它本身已经严重脱离视野,留着反而干扰训练,直接删除这一行即可。
5.2 小目标车辆漏检:三轮车在远处基本检不出来
现象:验证集整体跑分看着还行,把模型放到路口视频上,远处一个只有20像素高的三轮车完全没有框。
原因:2534张图像里的远距离小目标样本占比太低,加上YOLOv8在stride=32的大特征图上对小目标本身就弱。它是多尺度输出,但小目标主要靠高分辨率特征层,如果训练样本里的目标普遍偏大,小目标这一支就训练不充分。
解决:尝试更大的推理尺寸imgsz=960或1280,目标在输入图像里的相对尺寸变大,特征更有效。显存不够时把batch降到8。实测中,小目标车辆的召回率往往能提升几个点。代价是训练时间变长,显存占用变高。
5.3 类别不平衡造成的mAP虚高
现象:训练结束打印mAP有0.78,看起来不错,细看per-class AP发现面包车只有0.31,自行车的AP高达0.93。整体数字是被大类抬上去的。
原因:9类车辆样本量相差悬殊,少数类只贡献了极少的正样本,模型学到的特征不完整,AP自然上不去。
解决:不要只看总mAP。用yolo val model=best.pt data=data.yaml跑验证,看它输出的per-class AP和混淆矩阵。某个类太差时,先确认是不是标签系统性误标,再考虑对该类复制样本或把该类单独提出来做增强。注意增强后重新训练,要保持验证集和训练集的数据不重叠,不要让同一个目标既进训练集又出现在验证集。
5.4 图像与标签文件名不一致,训练报找不到标签
现象:训练启动后日志里频繁出现no labels found,或者验证时检测结果为0。
原因:拷贝图像时有人把image_001.JPG改成了image_001.jpg,但标签文件名仍然是image_001.JPG.txt,或大小写不一致。YOLO在Linux下严格区分大小写,一个字符对不上就匹配失败。
解决:全量统一命名。推荐做法是在第3章重排阶段就把所有文件统一成小写扩展名、同名stem。命名规则只有一条:图像的stem和标签的stem必须严格一致,包括大小写和中间的下划线。脚本里加一个断言收尾:assert label_stems == image_stems,不通过就停下来检查,别带着问题往下走。
5.5 训练正常但验证集mAP不稳定
现象:每次重新划分数据重新训练,mAP波动超过2个点,验证时还会看到混淆矩阵各类别行总和每次都不一样。
原因:数据量小的时候,随机划分带来的验证集差异会被放大。如果某个类只有60个目标,验证集分到20个还是40个,对该类AP的影响极大。混淆矩阵行总和不固定,正是因为验证集每次随机采样量不同,不是bug,是计数波动。
解决:固定随机种子,并且每次实验用同一个划分文件。如果要对外报数字,对同一个数据划分跑3次训练取平均,更可靠。另一个办法是交叉验证:把训练集切5份轮流做验证,报告平均mAP和方差。这个做法对2534张图的小数据集特别合适,代价是训练5次,时间成本要提前评估。
6. 模型落地:用best.pt跑真实视频,验证车辆计数与类别分布
训练完拿到 best.pt,下一步不是盯着mAP数字自我感动,而是把模型接到实际视频上,看看它在真实场景里的行为。我一般先用一段30秒的路口视频做冒烟测试,统计9类车辆的出现次数,再和人工数数对比,偏差超过15%就回去调数据。
6.1 用官方接口做视频车辆计数
from ultralytics import YOLO import cv2 model = YOLO("runs/detect/vehicle_v1/weights/best.pt") cap = cv2.VideoCapture("street.mp4") class_totals = {} # 类别名 -> 计数 seen_ids = set() # 已统计过的track id total = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break # track模式:跨帧关联同一个目标,稳定性优于反复predict results = model.track(frame, persist=True, imgsz=640, conf=0.35, iou=0.5) if results[0].boxes.id is not None: boxes = results[0].boxes ids = boxes.id.cpu().numpy() cls_ids = boxes.cls.cpu().numpy() names = model.names for obj_id, c in zip(ids, cls_ids): if obj_id not in seen_ids: seen_ids.add(obj_id) class_name = names[int(c)] class_totals[class_name] = class_totals.get(class_name, 0) + 1 total += 1 annotated = results[0].plot() cv2.imshow("vehicle_count", annotated) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows() print("检测到的车辆总数:", total) for name, count in sorted(class_totals.items()): print(f"{name}: {count}")这段脚本的关键在model.track,它内部会调用ByteTrack做跨帧关联,返回的目标id在连续帧之间保持一致。用track id去重,同一辆车只在第一次出现时计数,避免每帧重复累加。conf=0.35是经验阈值,漏检多就调到0.25,误检多就调到0.5。iou=0.5是NMS参数,目标密集时可以升到0.7,减少重叠框被误删的情况。
6.2 重点验证电动自行车和摩托车这两个易混淆类
9类里外观差异明显的自行车、三轮车、面包车通常不会出大问题,最容易翻车的是电动自行车和摩托车,二者轮廓高度相似。跑完视频后,我会单独把这两个类的检测框截图抽出来看,如果混淆矩阵里这两个类互串,大概率是标签本身有系统性误标。这时最有效的操作不是调模型,而是抽20到30张出错样本回看原图,确认是不是标注阶段把背着小货箱的电瓶车标成了摩托车。
6.3 模型上线前:把推理尺寸和阈值钉死进配置
最后提醒一个生产环境习惯:部署时的imgsz必须和验证时一致,否则同一模型在不同输入尺寸下的mAP会漂移。我见过不少人训练用960、验证用640,然后在线上骂模型不稳定,这种问题全是自己造成的。写配置时把imgsz、conf、iou三个参数钉死,放进项目的config文件里,部署代码只读配置不写死数字,后续换模型就不用改代码。
2534张图的第1部分,入门阶段完全够用。先把这套流程跑熟,等后续分卷的数据到位,合并训练时只需要把第3章的源路径更新一下,第4章的data.yaml如果类别没变可以直接复用。到那时候你已经有一套固定的数据处理脚本和训练策略,扩数据、扩类别的成本会低很多。希望今天的分享对你有帮助,卡数据格式的坑早点跳过去,把精力放在模型调优和真实场景落地这些更重要的事情上。
本文还有配套的精品资源,点击获取