简介:用于YOLO系列模型训练的三类别车辆检测数据集,收录1793张车辆实拍图像,覆盖car(汽车)、bus(公交车)、truck(卡车)三类目标,适合计算机视觉初学者以及自动驾驶、交通监控等场景的开发者用于模型训练与效果验证。压缩包大小542.36MB,共5380个文件,包含1793张jpg图像、1793个xml标注及1794个txt标签,同时提供VOC与YOLO两种通用标注格式,并附classes.txt类别清单,目录结构清晰,可直接接入主流训练流程。目前已有349人学习下载。图像目标清晰、场景多样,既可作为多类别车辆检测的基准训练集,也便于研究者进行迁移学习、超参数调优与模型精度对比实验,是快速搭建车辆检测项目的实用基础数据。
1. 1793张三类别车辆数据,够不够喂饱一个YOLO检测器?
第一次拿到“YOLO车辆检测三类别数据集 1793张(car-detect-dataset三种类型)”时,我的第一反应是“这么点数据能干嘛”。但实际跑下来,这个量级在车辆检测这种目标外形规整、类间差异明显的任务上,已经足够让 YOLO 新手完整走通从训练到评估的流程,也能在固定场景下得到一个可用的检测模型。这个数据集解决的是地下车库出入口、高速卡口、停车场监控这类相对固定的视野下,识别三类车辆目标的需求。适合刚接触 YOLO、不想从爬图标注开始,又希望尽快看到 mAP 数字的学生、毕业设计作者和做产品原型的工程师。它不适合零基础直接部署到开放道路,但作为跑通流程、做算法验证和基线对比,1793 张的规模并不寒酸。
2. 把数据集拆开看:目录结构、标注格式与三类目标怎么对应
2.1 先查目录布局:images、labels 与 data.yaml 三件套
拿到手第一件事不是直接训练,而是先看目录组织。YOLO 系列数据集的常见结构是 images/ 和 labels/ 平级,下面再按 train/val/test 划分,另外配一个 data.yaml 或 classes.txt 记录类别名。car-detect-dataset 如果按这种结构组织,训练阶段几乎零改动;如果它把所有 jpg 和 txt 混在一个源目录里,就得先自己写划分脚本。
car-detect-dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── img_0001.txt │ │ └── ... │ ├── val/ │ └── test/ └── data.yaml这是最常见的 YOLO 数据组织方式。train 和 val 分开是必须的,因为训练过程中验证集负责判断模型是否过拟合,没有它你只能靠 loss 曲线猜。test 目录在严格实验中只用于最终评估,不要用它反复调参,否则测试集就失去了独立性。很多公开数据集只给 train 和 val,test 可以留空,YOLOv8 也能正常训练。
如果数据集只给了一个全是图片和标签的平铺目录,你需要按 8:1:1 划分。我一般用脚本随机切,但要注意先按图片的“场景”而不是按文件名简单哈希分,否则同一时段连续帧可能同时出现在训练和验证里,会造成 mAP 虚高。场景重复在车辆检测数据里尤其常见,因为监控视频抽帧得到的图片,前后几帧内容几乎一样,应该把同一段连续帧放到同一个子集里。
2.2 YOLO 标注的五个数字:从 bbox 到归一化坐标
每张图片在 labels/train 下都有一个同名 txt,内容形如:
0 0.621094 0.482292 0.298437 0.539583 1 0.813672 0.391667 0.321094 0.466667 2 0.451172 0.593750 0.274219 0.452083第一列是类别 ID,后面四个数是 x_center、y_center、width、height。这四个数全部相对于图片宽高做了归一化,取值范围在 0 到 1 之间。注意这里不是 xmin/ymin/xmax/ymax 的 PASCAL VOC 格式,很多从 VOC 转过来的人会在这里翻车,把左上右下坐标原样塞进去,导致框跑到画面外,训练时 loss 还在降,但推理结果全是乱框。
验证标注是否规范,最直接的方法是写一个统计脚本,检查坐标范围、框面积和类别分布。我拿到的每个数据集都会先跑一遍这样的检查,不花多少时间,但能避免后面训练一小时后才发现标签有毒。
import os from collections import Counter label_dir = "labels/train" cls_counter = Counter() bad_lines = [] for name in os.listdir(label_dir): path = os.path.join(label_dir, name) with open(path, "r") as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad_lines.append((name, line_no, "columns != 5")) continue cls = int(parts[0]) x, y, w, h = map(float, parts[1:]) if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_lines.append((name, line_no, "out of range")) cls_counter[cls] += 1 print("class distribution:", cls_counter) print("bad lines count:", len(bad_lines)) for item in bad_lines[:20]: print(item)这段代码做了三件事:统计每个类别 ID 出现次数,检查每行是否有 5 个字段,检查坐标是否越界。类别分布是后面判断是否均衡、要不要做重采样的依据;越界行可能来自标注工具导出错误,也可能是裁剪图片后没重新归一化。如果 bad_lines 不为空,先修数据再训练,否则训练过程中会出现莫名其妙的 loss 上升甚至 NaN。
2.3 三类目标具体是哪三类:不要猜,先看 classes.txt
标题只写了“三类别”,没有明确说出是哪三类。常见做法是把车辆分成 car、truck、bus,也有数据集用 car、bus、van,甚至把摩托车算作第三类。这个关键信息写在 data.yaml 的 names 字段里,不去确认就直接训练,后面看混淆矩阵时会对不上号。
import yaml with open("data.yaml", "r", encoding="utf-8") as f: cfg = yaml.safe_load(f) print(cfg.get("names"))输出大概率是 ['car', 'truck', 'bus'] 这种按顺序排列的字符串列表。类别 ID 的排列顺序决定了训练时数字 0 对应第一个名字。如果 data.yaml 里 names 是 ['car', 'bus', 'truck'],但标注 txt 里的 0 实际代表 truck,那么模型学到的映射完全错位,mAP 再高也没有意义。这是数据集本身最隐蔽也最容易踩的坑,拿到后先打印前几个标签文件确认。
import os label_dir = "labels/train" for name in sorted(os.listdir(label_dir))[:3]: path = os.path.join(label_dir, name) print(f"--- {name} ---") with open(path) as f: print(f.read().strip())把输出的类别 ID 和 data.yaml 里的 names 一一对照。如果发现 ID 顺序与 names 不一致,要么改 txt 里的 ID,要么改 data.yaml,我一般选择改 data.yaml,因为改标签文件要遍历所有文件,容易改错。这里还有一个细节:YOLOv8 会把 classes 数量作为模型输出层维度,如果 names 里写了 3 类,但标签里出现了类别 ID 3,训练时不会立刻报错,而是在计算损失时越界,导致 loss 直接变成 NaN。所以上面的统计脚本里,最好把类别 ID 和 len(names) 再做一次比较。
3. 用 YOLOv8 在这个数据集上跑通训练:命令、参数与最小配置
3.1 数据集的 YAML 配置怎么写
YOLOv8 训练时通过 data 参数指定数据配置文件,不需要改动模型源码。如果数据集自带 data.yaml,先确认里面 path、train、val 是绝对路径还是相对路径。绝对路径在自己机器上能用,换一台机器就失效;我习惯改成相对路径,方便复现和拷贝。
path: car-detect-dataset train: images/train val: images/val test: images/test names: 0: car 1: truck 2: bustrain 和 val 指向的是图片目录而不是标签目录。Ultralytics 的 DataLoader 会在读取图片路径后,自动把路径里的 images 替换成 labels 去找同名 txt。所以如果你手滑把 train 写成了 labels/train,它反而会尝试读取 images/labels/train 下的图片,直接报找不到文件。这是配置阶段最常见的低级错误,但报错信息不一定直接说是路径问题,有时会提示“Dataset not found”或“No images found in ...”,新手容易在这里卡很久。
验证路径是否正确的快速方法是直接打印目录列表。如果 train 有 1434 张图片、val 有 359 张,比例大概 8:2,就符合预期。如果 val 目录是空的,训练也会启动,但在最后评估时 mAP 全为 0,且 loss 曲线里 val 线是平的。所以配置完后我一般先跑一次yolo detect train ... epochs=1看前置日志,日志会打印加载了多少训练和验证图片,这时确认数量和目录一致再上全量训练。
3.2 训练命令与关键超参数(epochs、batch、imgsz)
在数据集根目录执行下面的命令,是最小的可跑通配置:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ device=0 \ project=runs \ name=car_detect_v8nmodel 用 yolov8n.pt 是官方预训练权重,不是从零开始训练。这对 1793 张的数据量至关重要,迁移学习能让你少跑一半的 epoch,并且最终 mAP 也明显更高。epochs 设 100 对这个小数据集来说偏多,通常 50 轮左右已经收敛,搭配早停更稳。batch 16 在显存 8G 左右的显卡上是安全值,如果显存溢出就降到 8,或者加cache=False减少缓存占用。imgsz 640 是默认值,车辆如果以中大型目标为主,640 够用;如果监控画面里车辆离得远、目标只占几十个像素,建议把 imgsz 提到 960,或者降到 480 配合多尺度训练。
训练过程中日志会输出每轮的 box_loss、cls_loss、dfl_loss,以及验证集的 mAP50、mAP50-95。不要只看 train loss,它下降不代表泛化就好。关注 val 曲线:如果 val mAP 连续 20 轮不涨,就是早停条件。YOLOv8 默认没有开启早停,需要显式加patience=20;我一般加上,避免人不在机器前时白等几个小时。
还有一种常见做法是加plots=True,训练结束自动生成 results.png、confusion_matrix.png、PR_curve.png。这些图都在 project 目录下。建议第一次训练时加上它,后面分析结果基本靠这些图,而不是只看终端数字。如果换了小显存机器,还可以加workers=4控制数据加载线程;在 Windows 上 workers 设 0 或 2 更稳,线程太高容易触发 DataLoader 崩溃。
3.3 预训练权重怎么选:yolov8n 与 yolov8m 的取舍
官方预训练权重按规模分成 n、s、m、l、x 五档,参数量从 3M 到 70M 不等。1793 张、三类目标,直接上 x 是自找麻烦,模型容量大但数据不足,反而容易过拟合,训练时间也成倍增加。我一般先用 n 把整个链路跑通,看 loss 和 mAP 是否正常,再换 m 冲一次精度。
yolo detect predict model=yolov8m.pt source=sample.jpg第一次执行这个命令,会自动把 yolov8m.pt 下载到当前目录的 weights 缓存位置。预训练模型下载是 YOLO 生态里体验最好的环节之一,不需要手动找链接,命令行直接拖下来。n 和 m 在 COCO 上的公开参考指标分别是 mAP50 约 37 和 50 左右,但这只是参考,在你自己的车辆数据集上会不同。m 比 n 精度更高,但推理速度更慢、内存占用更大。如果之后要部署到 RK3588、树莓派这类边缘设备,n 是更现实的选择;如果只是在服务器上做实验、不急着上设备,m 对小车和远车的召回率明显更好。
选择权重的另一个维度是和数据集场景是否接近。COCO 预训练权重里本身包含车辆类目,所以迁移过来是自然顺畅的。如果你拿到的数据集是红外、夜间或无人机视角,与 COCO 的自然光场景差异很大,预训练带来的收益会下降,这时候应该考虑用更大的 m 权重,让模型有更多特征拟合空间。在 V100 或 A100 上,m 和 n 的训练时间差距不大,但在 1060 这类老卡上,n 的训练速度快一倍以上,先用 n 做调试更省时间。
4. 训练后怎么看结果:混淆矩阵、损失曲线与 mAP 的前后故事
4.1 损失曲线三条线:box_loss、cls_loss、dfl_loss 怎么读
训练结束后,runs/car_detect_v8n/ 目录下会有 results.png 和 results.csv。YOLOv8 的损失分成三部分:box_loss 是预测框和真值框的 IoU 差异,cls_loss 是分类误差,dfl_loss 是分布焦点损失,负责框的边界回归。三者量级不同,第一次看的人会误以为没收敛,因为 box_loss 通常在 1.0 左右,cls_loss 在 0.5 左右,dfl_loss 在 0.8 左右。判断收敛不能只看某个 loss 的绝对值,要看它的曲线是否进入平台期。
我一般会写一个小脚本读取 results.csv,把最后一行的验证指标打印出来,省得翻图:
import csv with open("runs/car_detect_v8n/results.csv") as f: rows = list(csv.DictReader(f)) last = rows[-1] print("epoch:", last["epoch"]) print("mAP50:", last["metrics/mAP50(B)"]) print("mAP50-95:", last["metrics/mAP50-95(B)"])csv 列名在不同版本里可能略有差异,如果 KeyError,可以先打印 last.keys() 看实际列名。逻辑上只看三个指标:验证集的 mAP50、mAP50-95 和验证集 loss。如果 train loss 一直降但 val loss 在后半段翻转上升,说明过拟合了。1793 张的数据量下过拟合很常见,表现就是 mAP50 在峰值后开始小幅度下降。这时不要加数据,先降低模型复杂度或增强正则,比如把dropout调高、把mosaic关掉、提前在峰值点取权重。
还有一类特殊情况:cls_loss 从一开始就极低,比如 0.01 以下。这通常意味着类别严重不均衡,模型把所有目标都预测成最多的那一类,因为这样分类损失已经很小。单看 cls_loss 会发现它漂亮收敛,但 mAP 起不来。出现这种情况要回到 2.2 的类别分布统计,给少类目标做重采样,而不是继续调超参。
4.2 混淆矩阵“总和不为 1”的坑与类别偏移问题
YOLOv8 画出的混淆矩阵是归一化的,但它的归一化方向是“按真实类别的样本数归一化”,也就是说每一行的和为 1,而不是整个矩阵的总和为 1。很多人在热力图里看到对角线外的格子也有数字,全表总和不是 1,就以为训练出错了,其实这是 YOLO 系列一直以来的展示方式。
要看类别偏移,正确做法是横着看每一行。比如真实 truck 这一行,模型把 68% 的 truck 判成了 truck,把 30% 判成了 car,说明模型对 truck 和 car 的区分度不够。这时要看是不是 truck 样本太少,或者图像里卡车尾部和轿车尾部视觉上太接近。不要直接加大 conf 阈值,那是把误检压下去了,但漏检也会上升,正确做法是增加 truck 的样本量或做针对性的数据增强。
如果某个类别的训练样本只有十几个,混淆矩阵的百分比会失真,一个误判就能让数字跳 10 个点。所以看混淆矩阵时,还要看 Ultralytics 生成的 confusion_matrix_normalized.png 旁边有没有 counts 图。counts 矩阵显示每个格子的具体数量,能帮你判断 30% 这个比例背后到底是一个误检还是三十个误检。如果总量少,宁可多跑几个 epoch 再用统计指标说话。
4.3 mAP50 与 mAP50-95 的差距说明了什么
验证结果里两个关键指标:mAP50 计算 IoU 阈值取 0.5 时的精度,mAP50-95 把阈值从 0.5 到 0.95 每 0.05 取一档,再对所有阈值求平均。如果 mAP50 高但 mAP50-95 明显低,说明模型能框出车,但框的位置不够紧致,边界偏移大。这在车辆检测里非常常见,因为斜停的车和横穿的卡车形状差异大,固定锚框很难贴合真实轮廓。
遇到这个情况,优先检查数据集中是否存在很多宽高比极端的目标。YOLOv8 的 anchor 是自动学习的,能做的调整有限,真正有效的是提高 imgsz 到 960,让目标在特征图上占据更多像素,边界回归更精细。另一个方法是训练时开多尺度,比如imgsz=640配合scale=0.5,让模型适应不同距离下的目标尺度。部署端也可以降低 NMS 的 IoU 阈值试试,比如从 0.7 降到 0.5,能减少重叠框之间的相互抑制。
mAP50 和 mAP50-95 差距过大的另一个原因,是验证集里标注框本身就画得松。如果标注人员只框了车体而不包括后视镜和保险杠,模型预测的框会更紧,IoU 计算吃亏。我在评估前会随机抽 20 张图,把标签框画在原图上人工看一眼,确认标注质量。这一环省不得,因为数据集的 mAP 高低有一半是由标注一致性和边界定义决定的。
5. 车辆检测数据集踩坑手册:从标注校验到训练翻车的五个常见问题
5.1 现象:训练 loss 正常但 AP 全为 0
训练和验证都跑完了,loss 正常下降,但打印出的 mAP50 和 mAP50-95 全是 0。最常见原因是标签类别 ID 从 1 开始写,而 data.yaml 的 names 下标从 0 开始,所有类别整体前移了一格。比如真实的 car 应该是 0,数据集里写成了 1,模型学习到的类别 1 实际上对应 bus,验证时对不上。
解决方法是写脚本把 txt 第一列全部减 1,并检查最小值是否变成 0。运行前先备份原始标签。
import os for split in ["train", "val"]: folder = f"labels/{split}" for name in os.listdir(folder): path = os.path.join(folder, name) with open(path, "r") as f: lines = f.readlines() new_lines = [] for line in lines: parts = line.strip().split() if not parts: continue cls = int(parts[0]) - 1 if cls < 0: print("negative cls found:", name, parts[0]) continue new_lines.append(f"{cls} " + " ".join(parts[1:]) + "\n") with open(path, "w") as f: f.writelines(new_lines)逻辑就是暴力减 1,如果减出负数就打印出来提醒你检查原始标签。这个操作只解决 ID 偏移,不是万能修复。还有一类 AP 全为 0 的原因是验证图片和训练图片完全重复,模型在训练时看到过验证图,但验证时因为类别映射错乱反而全错,此时优先检查是否存在数据泄露。
5.2 现象:验证集 mAP 很高,实际视频里漏检
在验证集上 mAP50 到了 0.85,但拿到实际监控视频里跑,远处的车经常漏检。原因是验证集里的目标尺寸普遍较大,而视频中远距离车辆的框在 640x640 缩放下只有十几个像素,YOLOv8 的下采样倍数会把小目标特征直接扔掉。
先统计标签里小框占比:
import os THRESH = 0.02 # 对应 640 图上约 13 像素 for name in os.listdir("labels/train"): with open(f"labels/train/{name}") as f: for line in f.read().strip().splitlines(): parts = line.split() w, h = float(parts[3]), float(parts[4]) if w < THRESH or h < THRESH: print(f"{name}: small box {w:.3f} x {h:.3f}")如果大量目标小于 0.02,就要把输入分辨率提到 960 或 1280,或者把原始大图切成四块分别检测再合并。切片检测能保留小目标信息,但推理时间翻倍,不适合实时场景。另一个思路是换用带 P2 输出头的模型结构,它能保留更高分辨率的特征图,对小目标更友好。修改模型结构对新手不推荐,先把 imgsz 提上去是最直接的后悔药。
5.3 现象:truck 被误检成 car
truck 被大量误检成 car,通常在类别不均衡或两类目标外形接近时出现。侧面视角下,卡车和轿车的轮廓确实难以区分,特别是皮卡车型。模型缺少足够多的 truck 样本,自然会偏向把不确定目标判成样本多的 car。
先用命令统计每类框数:
awk -F' ' '{count[$1]++} END {for (k in count) print k, count[k]}' labels/train/*.txt如果 truck 只有 car 的三分之一,先做最简单的复制增强:把含 truck 的图片复制几份,每份做不同的亮度、平移、翻转,再放进训练集。这个方法效果立竿见影,我曾在只有 200 张 truck 样本的数据集上把 truck 的 AP 从 0.61 提到 0.74。如果复制后提升不明显,再考虑在 Mosaic 增强中强制混入多张含 truck 的图片,让模型每次迭代都能看到足够多的 truck 特征。
5.4 现象:训练到一半 loss 突然 NaN
训练到某一步 loss 变成 NaN,常见原因有三个:学习率过大、batch 归一化不稳定、标签里有零面积或非法坐标框。YOLOv8 默认 lr0=0.01,在有些自定义数据上会偏大;AMP 混合精度在特定显卡驱动下也会触发 NaN。但根据我的血泪经验,90% 的 NaN 是标签问题,而不是超参问题。
先运行这把标签扫描器:
import os issues = [] for split in ["train", "val"]: label_dir = f"labels/{split}" for name in os.listdir(label_dir): path = os.path.join(label_dir, name) with open(path) as f: for i, line in enumerate(f, 1): parts = list(map(float, line.split())) if len(parts) != 5: issues.append((name, i, "5 columns expected")) elif parts[2] <= 0 or parts[3] <= 0 or parts[4] <= 0: issues.append((name, i, "zero area")) for issue in issues[:30]: print(issue)如果找到零面积框,删除该行或重新标注。如果没有坏标签,就把lr0改成 0.005,加amp=False关掉混合精度,再试一轮。注意不要把两个改动同时做,否则不知道是哪个因素修好的。我习惯先关 AMP,因为它在 10 系显卡上触发 NaN 的概率很高;如果关掉后正常,再逐步把学习率调回去找边界。
5.5 现象:标签文件里出现小数超过 1
标签里出现 x > 1 或 y > 1 的值,说明标注工具导出的是像素坐标,没有按图片宽高归一化;或者图片被裁剪后,标签没有重新计算比例。YOLO 训练虽然不会立刻报错,但会把框算到画面外,导致该目标永远学不进去。
用脚本做归一化修复:
import os from PIL import Image for split in ["train", "val"]: img_dir = f"images/{split}" label_dir = f"labels/{split}" for name in os.listdir(img_dir): img_path = os.path.join(img_dir, name) label_path = os.path.splitext(img_path)[0].replace("images", "labels") + ".txt" if not os.path.exists(label_path): continue w_img, h_img = Image.open(img_path).size with open(label_path) as f: lines = f.read().strip().splitlines() new_lines = [] for line in lines: cls, x, y, w, h = line.split() x, y, w, h = map(float, (x, y, w, h)) if x > 1 or y > 1 or w > 1 or h > 1: x, y = x / w_img, y / h_img w, h = w / w_img, h / h_img new_lines.append(f"{cls} {x:.6f} {y:.6f} {w:.6f} {h:.6f}") with open(label_path, "w") as f: f.write("\n".join(new_lines))这里最容易漏的是图片扩展名,.jpg、.jpeg、.png混在一起时,用os.path.splitext(img_path)[0]取主文件名最稳妥。脚本假设大于 1 的字段都是像素坐标,如果某些框已经归一化且宽高恰好超过 1,会被误处理。所以运行前先打印原始坐标 max 值确认。修复后重新跑 2.2 的检查脚本,看到所有坐标都在 0 到 1 区间内才算修干净。
6. 让 1793 张数据发挥出两倍价值:增强策略、类别重加权与部署前验证
6.1 用增强参数把有限样本变出更多视角
YTLOv8 的数据增强可以通过训练命令直接覆盖,不需要写额外代码。车辆检测对光照和视角变化敏感,我一般会在默认增强基础上做小幅调整:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=60 \ batch=16 \ imgsz=640 \ hsv_h=0.02 hsv_s=0.7 hsv_v=0.4 \ translate=0.1 scale=0.5 fliplr=0.5 \ mosaic=0.8hsv 扰动模拟早晚阳光和阴影,车辆反光场景很吃这一套。scale 和 translate 控制在 0.5 和 0.1 以内,避免把车切出画面太多。mosaic 不要保持默认的 1.0,尤其是小数据集下马赛克拼接会让模型学到“车都带边框”的假特征,反而降低泛化。0.8 是折中值,既能混入背景,又保留大部分完整车辆。
6.2 类别重加权:用样本复制解决不均衡
YOLOv8 没有直接的类别权重参数,最实用的做法是给少数类做图像级复制,每次复制配不同增强。不要只复制原图,否则模型会把重复样本背下来,验证集里一旦出现接近重复的图,mAP 会虚高到不可信。我一般复制时同时做水平翻转和亮度变化,这样等效于扩充了真实场景。
这里要提到一个容易踩的坑:复制后标签文件也要同步复制,且文件名不能重复。最常见的翻车方式是只复制了图片而忘了复制 txt,训练时找不到标签直接跳过该图。批量操作时用脚本统一命名,别手动拖。
6.3 部署前验证:导出 ONNX 并对照同一张图
训练完最后一步不是看 mAP,而是把模型导出到 ONNX 或 TensorRT,再用同一张测试图对比 PyTorch 输出和部署引擎输出。命令:
yolo export model=runs/car_detect_v8n/weights/best.pt format=onnx imgsz=640导出后要先查输出节点名和形状,很多部署框架要求固定输入尺寸。比对时如果两边框相差超过 2% 的 IoU,先检查预处理是否一致:归一化方式、通道顺序、缩放插值算法。我之前有一次部署翻车,就是因为训练时用的是 BGR,部署端用了 RGB,结果所有框向右偏移了十几个像素,人眼很难看出来,但和真值框一比立刻现形。
我的习惯是每次拿到新数据集,先用 10 张图跑 1 轮验证代码链路,确认数据加载、标签读取、loss 计算都正常,再上全量训练。这个习惯帮我省下了很多无效的等训练时间。数据量小不是失败理由,标注质量、类别平衡和部署一致性才是真正拉开差距的地方。希望这些经验对你有帮助。
本文还有配套的精品资源,点击获取