简介:面向自动驾驶与智能交通场景的多类车辆目标检测数据集,适合目标检测算法开发者、ADAS/自动驾驶感知工程师及计算机视觉研究者使用。数据集中包含自行车、公交车、轿车、摩托车、卡车及通用车辆6个类别,已按827/233/114划分训练/验证/测试集,采用YOLO格式的类别索引与归一化边界框标注,可直接用于YOLOv5/v7/v8/v12等主流框架训练。资源为zip压缩包,共2000个文件,主要为824张jpg道路实拍图像、1174个txt标注文件,另附yaml配置与docx数据说明文档,包体约64.26MB。素材取自真实交通环境,覆盖不同角度、距离、遮挡及光照条件,且同时提供通用Vehicle与细分类别标签,可支撑从粗粒度到细粒度的车辆检测需求。目前已有96人浏览/学习,适合快速开展车辆识别模型训练与算法验证。
1. 为什么一个zip压缩包,常常卡住目标检测项目的起步
车流密度高、目标尺寸从几十像素横跨到几百像素、类别间长得还像——多类车辆目标检测听起来只是目标检测里的一个细分,真正上手才知道,它是项目里最容易在数据集上反复翻车的方向。多类车辆目标检测数据集.zip这类压缩包,解决的是这条线上最贵的一步:采集和标注。解压后你拿到的是一批带标注框的车辆图片集,轿车、卡车、公交车、摩托车等类别齐全,可以直接喂给训练流程做训练、验证和调参,不用从零攒数据。这篇文章按我自己的实操顺序来写:拿到zip先干什么、标注格式怎么认、怎么改造成YOLO能直接吃的结构、训练参数怎么调,以及哪些坑值得你花时间提前绕开。
2. 解压后先别急着训练:先读懂车辆数据集的标注与分布
很多人拿到数据集的第一反应是直接开训,我见过最快的翻车记录是解压完两小时就报错。多类车辆数据集的坑不在模型,而在数据本身的组织方式、标注格式和样本分布。这一章先把这三件事讲清楚。
2.1 zip解压这一步的坑:编码、目录结构与一个检查清单
国内很多数据集的zip是在Windows上用压缩软件打包的,文件注释和目录名默认用GBK编码。你在Linux服务器上直接unzip,轻则文件名乱码,重则解压报错。我一般习惯这样解压:
install -d dataset unzip -O gbk multi_class_vehicle_dataset.zip -d dataset参数说明:-O gbk让解压工具按GBK解码文件名,处理Windows压缩包最常见。如果你的zip是macOS或Linux下打出来的,去掉-O或改成UTF-8即可。macOS自带的ditto在处理中文文件名时也靠谱,但按我的经验,统一用7z x -p配合7-Zip处理最省心。
解压完第一件事绝对不是看图片,而是看目录。先把目录数和文件数摸清楚:
find dataset -maxdepth 2 -type d | sort | head -50 find dataset -type f | wc -l find dataset -type f \( -name '*.jpg' -o -name '*.png' \) | wc -l find dataset -type f \( -name '*.xml' -o -name '*.txt' -o -name '*.json' \) | wc -l这里的三条命令分别回答三个问题:数据怎么分层、总文件数多少、图片和标注数量是否对得上。我踩过一次坑:某个车辆数据集标注文件比图片少了600多张,直接训练后那几百张图等于没参与监督,模型学到的是有偏分布。所以解压后最好对一下图片与标注的数量差,差得多就先补标注或剔除。
接下来看根目录里有没有README或classes.txt。有就打开读,它通常会写清楚类别定义、标注格式、图片分辨率和版权说明。没有说明文档的数据集要格外谨慎,因为后面所有格式转换都要靠你猜。
一个典型的多类车辆数据集目录长这样:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt └── README.md如果看到的是扁平目录,所有jpg和xml混在一起,也正常,那说明需要你自己做划分。看到嵌套层级超过五层的目录结构,先做个扁平化处理,把图片全部find -name '*.jpg' -exec mv到统一目录,减少后续路径问题。
2.2 一眼分辨VOC/COCO/YOLO三种标注格式
多类车辆数据集的标注格式基本都是这三选一:VOC的XML、COCO的JSON、YOLO的TXT。格式认错是纯白费时间,判断方法很简单。
打开一张标注文件看结构。VOC格式是XML,结构里能直接看到object节点,每个object里有name、bndbox、xmin、ymin、xmax、ymax。这是目标检测最经典的格式,labelImg默认输出这种。
COCO格式是单个大JSON,顶层是images、annotations、categories三个数组,annotations里每条的bbox是[x, y, w, h],注意是左上角坐标加宽度高度,不是对角坐标。
YOLO格式是图片同名TXT,每行五个数字:class_id cx cy w h,cx cy是归一化到0到1的中心点坐标,w h是归一化的宽高。这是YOLO系列最直接的输入格式。
分辨方法:一个标注文件里如果看到尖括号就是VOC,看到花括号和大数组就是COCO,看到五行空格分隔数字就是YOLO。三类格式之间可以互相转换,但转换脚本我需要拆到第3章去讲,因为那里牵扯归一化、类别映射和坐标边界判断。
另外说一句标注工具,这类数据集你拿到手之后大概率要补标注或改标注。常用的工具是labelImg(VOC/YOLO)、labelme(更偏分割)、X-AnyLabeling(YOLO系列)、Roboflow(在线处理格式转换),按你的原始格式选就行。
2.3 多类车辆的不平衡分布:统计完才知道先处理谁
多类车辆数据集最典型的问题是类别不平衡。比如轿车可能有几万框,三轮车只有几百框,公交车和卡车还经常互相遮挡。你直接训练,模型大概率学会的是车辆的整体轮廓,而不是类别差异。
所以解压后第二件事是统计所有类别的标注框数量。一个简单的Python脚本:
# 统计YOLO txt格式下每个类别的标注框数量 from collections import defaultdict import os def count_annotations(label_dir): counter = defaultdict(int) total_boxes = 0 for name in os.listdir(label_dir): if not name.endswith('.txt'): continue path = os.path.join(label_dir, name) with open(path, 'r', encoding='utf-8') as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) counter[cls_id] += 1 total_boxes += 1 return counter, total_boxes counter, total = count_annotations('dataset/labels/train') print('total boxes:', total) for cls_id in sorted(counter.keys()): print(f'class {cls_id}: {counter[cls_id]}')这个脚本只读TXT前五列,遇到长度不等于5的行直接跳过,避免损坏标注干扰统计。输出结果配合classes.txt看,就能看到长尾分布:头部类别占比可能超过60%,尾部类别占比不到2%。
这里给一个实际判断标准:如果某个类别的框数小于总量5%,它在训练里的贡献就非常微弱,需要靠类别权重或数据增强去拉。第4章的训练参数我会专门讲这个问题。而统计完发现某类框数为0,则说明标注文件里类别编号写错了或classes.txt顺序对不上,先修复再训练。
3. 把多类车辆数据集改造成YOLO可训练格式:统计、转换与划分
热门的训练框架是YOLO系列,yolov8目标检测数据集处理、yolov8训练自己的数据集这类需求满天飞,说明大家拿到的数据多半不是YOLO格式。这一章给出从VOC/COCO格式变成YOLO可训练数据集的最短路径,并交代每一步该卡在哪里的参数。
3.1 动手前的三项统计:类别数量、框尺寸、小目标占比
前面我们只统计了类别数量。但要真正开始训练,我建议再做两组统计:所有标注框的尺寸分布,以及小目标占比。这对后面的anchor设置和增强策略影响巨大。
import os import numpy as np def box_size_stats(label_dir, img_dir): widths, heights = [], [] for name in os.listdir(label_dir): if not name.endswith('.txt'): continue txt_path = os.path.join(label_dir, name) with open(txt_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) == 5: w = float(parts[3]); h = float(parts[4]) # 归一化尺寸,假设图片宽高为1.0 widths.append(w); heights.append(h) widths, heights = np.array(widths), np.array(heights) print('median shape: %.3f x %.3f' % (np.median(widths), np.median(heights))) print('shape p90: %.3f x %.3f' % (np.percentile(widths, 90), np.percentile(heights, 90))) box_size_stats('dataset/labels/train', 'dataset/images/train')这里没有读图片尺寸,直接用归一化坐标做分布估计。归一化框的像素尺度等价于乘以图片宽高,所以百分比分布本身就能看出目标受力范围。车辆检测的典型情况是:大头大框、小头小框,像远处驶来的车辆,框尺寸可能只有全图的1%不到。
小目标占比的判断标准建议直接套COCO的定义:面积小于32乘32像素算小目标。如果你的数据集里小目标数量超过40%,这就是一个以难例为主的数据集,训练难度直线上升,第三个小节的增强配置就得往小目标倾斜。
3.2 VOC标注转YOLO txt:一个够用的转换脚本
拿到VOC格式XML,转换YOLO txt是最常见的操作。需要把XML里的xmin ymin xmax ymax变成YOLO需要的cx cy w h,并且除以图片宽高归一化。
import xml.etree.ElementTree as ET import os CLASS_MAP = {'car': 0, 'truck': 1, 'bus': 2, 'motorcycle': 3, 'bicycle': 4, 'ambulance': 5, 'police_car': 6} def voc_to_yolo(xml_path, out_path): 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.findall('object'): name = obj.find('name').text if name not in CLASS_MAP: continue # 未知类别直接跳过 cls_id = CLASS_MAP[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) w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h # 边界保护:越界坐标裁剪到[0,1],防止训练崩溃 cx = min(max(cx, 0), 1) cy = min(max(cy, 0), 1) w = min(max(w, 0), 1 - cx) h = min(max(h, 0), 1 - cy) lines.append(f'{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}') with open(out_path, 'w') as f: f.write('\n'.join(lines)) # 批量转换 for xml_file in os.listdir('dataset/annotations_xml'): if not xml_file.endswith('.xml'): continue voc_to_yolo( os.path.join('dataset/annotations_xml', xml_file), os.path.join('dataset/labels_yolo', xml_file.replace('.xml', '.txt')) )这段代码几个地方值得注意。第一,CLASS_MAP必须和后面YOLO训练时的data.yaml里names顺序一致,错一位全盘皆错。第二,坐标边界保护很关键,很多数据集里的xmax偶尔比图片宽还大,不裁剪,训练时YOLO会对越界边框报警。第三,空标注文件也要生成,否则YOLO训练时找不到对应的txt会直接把该图跳过。
COCO转YOLO同理,只是从JSON里解出bbox数组,注意bbox是[x, y, w, h],中心点坐标要手动cx = x + w / 2,cy = y + h / 2。别直接套VOC脚本,很多坑都出在这。
3.3 划分train/val/test:按场景分而不是随机分
数据划分看起来最简单,实际最阴险。单纯随机把图片分成训练验证测试,车辆数据集的常见后果是:同一段视频里的连续帧同时出现在训练集和验证集,验证指标虚高,测试时换个场景就崩。
我的习惯是先用文件名或目录名识别“场景ID”。多类车辆数据集往往是连续拍摄的视频抽帧,文件名通常带视频片段前缀,比如clip_001_frame_0032.jpg。划分时以clip为单位,同一个片段的所有帧必须全落在一个集合里:
import os import random from collections import defaultdict random.seed(42) images = os.listdir('dataset/images') group = defaultdict(list) for img in images: clip_id = img.split('_')[0] # 假设命名规则:clip_xxx_frame_xxx.jpg group[clip_id].append(img) clip_ids = list(group.keys()) random.shuffle(clip_ids) n = len(clip_ids) train_clips = clip_ids[:int(n * 0.7)] val_clips = clip_ids[int(n * 0.7):int(n * 0.85)] test_clips = clip_ids[int(n * 0.85):] def write_split(ids, split_name): with open(f'{split_name}.txt', 'w') as f: for cid in ids: for img in group[cid]: f.write(img + '\n') write_split(train_clips, 'train') write_split(val_clips, 'val') write_split(test_clips, 'test')如果文件名不是clip开头,也可以按拍摄目录分。核心原则是:同一个场景的相似画面不能同时出现在训练和验证。车辆检测对场景拍摄角度、光照、天气极其敏感,出现帧级泄露,验证集的mAP会虚高两到三个点,部署时原形毕露。
4. 训练配置与参数在多类车辆上的调法:不要白跑一轮
数据集处理好之后,进入训练配置。多类车辆和通用目标对象(比如COCO的80类)的调法有区别:车辆类别强、长宽比变化大、小目标数量多、类别间外观相似度高。这一章讲三个关键参数:anchor、类别权重、数据增强。
4.1 生成与数据集匹配的anchor尺寸
YOLOv8和YOLOv5在训练时会自动计算anchor,但自动计算基于的是当前数据集的整体统计。如果你的数据集里大卡车和远处小轿车同时存在,默认anchor要么偏向大目标,要么偏向小目标。我建议训练前拿3.1节统计出的框宽高分布,用kmeans聚类生成9组anchor。
import numpy as np from sklearn.cluster import KMeans # boxes: 数组,形状为 [N, 2],每行是 (w, h),已经是像素/图片宽高比的相对值 def kmeans_anchors(boxes, num_anchors=9): kmeans = KMeans(n_clusters=num_anchors, random_state=0, n_init=10) kmeans.fit(boxes) anchors = sorted(kmeans.cluster_centers_.tolist(), key=lambda x: x[0] * x[1]) return anchors anchors = kmeans_anchors(boxes, 9) for i, a in enumerate(anchors): print(f'anchor {i}: {a[0]:.4f} x {a[1]:.4f}')聚类出来的9组anchor按面积从小到大排序,小面积anchor分配到检测小目标的特征层,大面积anchor分配到深层特征层。如果聚类结果里最大anchor甚至超过中等车辆的面积,说明大目标数量极多,训练时你要么缩小输入分辨率(牺牲小目标),要么保持分辨率但把大anchor对应的网格数量调少。车辆检测我一般用640或1280的输入,目标太小就上1280,推理性能吃紧再用tiling方案,后文第6章说。
4.2 用类别权重与损失函数纠正类别失衡
多类车辆数据集最容易出现的失衡案例是卡车和公交车数量远多于三轮车、摩托车。这种失衡靠改训练步数没用,得在损失函数层面拉一把。YOLO系列支持per-class损失权重,我的做法是直接给稀有类别加大权重,让漏检稀有类的惩罚更大。
# 假设统计结果是 counter: {0: 8000, 1: 2000, 2: 500, 3: 120} total = sum(counter.values()) weights = {cls_id: total / (len(counter) * count) for cls_id, count in counter.items()} max_w = max(weights.values()) weights = {cls_id: w / max_w for cls_id, w in weights.items()} print(weights)这个公式的核心是归一化逆频率,计算出的权重让最稀有的类别权重为1.0,最频繁的类别权重接近0.2左右。通常在YOLOv5的hyp.yaml里设置cls_pw,在YOLOv8的model.yml里按类别设置cls_weights。不存在一个万能的固定值,先按这个公式跑一轮,再根据验证集每类AP的差距微调。如果某个类别提升不明显,把权重再乘1.5,同时观察是否影响其他类别掉点。
另外,如果数据集是天然的长尾分布,光改损失还不够,还得配合copy-paste增强,把稀有类别目标从一张图抠出来贴到其他图上。这样模型见到的稀有类别样本更多,且不增加标注负担。
4.3 数据增强取舍:遮挡、夜晚与小目标的专项增强
车辆检测的难点往往不在车的正脸,而在遮挡、夜间、雨天、小目标。我在多类车辆数据集上的增强配置和通用配置不一样,有几个地方是故意强化的。
首先是马赛克增强。YOLOv5、YOLOv8里mosaic默认开启,它对小目标有帮助,因为马赛克把四张图拼在一起,目标相对尺度变小。但车辆数据集有个副作用:大卡车在马赛克图里会被裁掉一半,模型学到的卡车特征不完整。我的经验是白天正常开mosaic,但开启mosaic_prob=0.8,不要拉满。其次是复制粘贴增强,把稀有类目标的小尺寸框贴到无目标区域,这个方法对车辆特别管用,因为车辆在真实场景里的背景大多是路面,把轿车贴到路面上很自然。我用YOLOv8的copy_paste=0.3,类别不均衡时提到0.5。
夜晚和低照度场景要有针对性的颜色增强。HSV增强里把饱和度抖动范围调大一点,让模型适应不同光照。更好的做法是直接做图像亮度使图像整体亮度随机暗化,模拟夜间,权重放到Loss上的效果不如数据里直接加场景增强。如果数据集本身包含夜间样本,不用特别调。
小目标专项增强是四个字:切片采样。用SAHI、slice或者自己写tiling:把大图切成几个重叠的patch,放大后参与训练。切块大小按车辆尺寸来,比如一个1080p的视频帧切成640x640,重叠50%。这样小目标在patch里的相对尺寸变大,检测能力明显提升,代价是训练时间翻倍。
# yolov8 data.yaml 示例 path: /home/user/dataset train: images/train val: images/val names: 0: car 1: truck 2: bus 3: motorcycle 4: bicycle # 训练命令示例 # yolo detect train data=vehicle.yaml model=yolov8s.pt epochs=100 imgsz=640这个yaml重点是names顺序,必须和转换脚本里的CLASS_MAP完全一致。训练命令里imgsz我建议先640跑通全流程,再尝试1280看小目标收益。
5. 避坑指南:从zip到车辆检测模型的5个高频翻车点
我真见过有人倒在这里:数据准备了两天,训练一轮报错,回去查才发现是解压环节的问题。这一章写五个最高频的坑,每条都是现象、原因、解决的结构。
5.1 解压提示zip损坏,但再用别的工具又能解开
现象:Linux下unzip报End-of-central-directory signature错误,但文件换到Windows的WinRAR能正常打开。
原因:这个zip是在Windows上用旧版压缩软件生成的,zip的中央目录可能有轻微偏移,或使用了跨卷分段压缩后合并不当。更常见的是浏览器下载中断导致文件尾部缺失,unzip要求中央目录在文件尾部,找不到就报错。
解决:先看文件大小是否和页面标注一致,不一致就重新下载。大小一致还有问题,用7-Zip的7z t做测试,很多unzip解不了的文件7z能解开。顺便说,解压工具别只用一种,我有一次就是unzip解不开、bsdtar也解不开,最后用7z成功解开,里面的数据本身没坏。
5.2 zip伪加密与密码问题:解压开启的另一种报错
现象:解压时提示输入密码。数据集作者很可能只是加密了文件名或整个压缩包。这类包有两个走向:真加密和伪加密。伪加密是指zip头部设置了加密标志位,但实际数据流并未加密,设计初衷是防止无脑读取文件列表。在Linux上unzip遇到伪加密也会要求密码。
原因:看zip头里general purpose bit flag的第0位是否置1,如果置1但数据流其实可读,就是伪加密。WinRAR常把这类包显示为带锁状态。
解决:先判断是真加密还是伪加密。用zipinfo或Python的zipfile读取文件名列表,能读到文件名但解压时报错、报密码错误,多数是伪加密。我用Python的zipfile做一次全量读取测试,如果文件名列表可见且压缩数据流能正常流式读取,就直接重写整个zip,去掉伪加密标志:
import zipfile with zipfile.ZipFile('input.zip') as zin: with zipfile.ZipFile('output.zip', 'w') as zout: for item in zin.infolist(): data = zin.read(item.filename) zout.writestr(item.filename, data)这个脚本把整个包重新打包,去掉伪加密的flag位。如果zin.read直接抛Bad password或者RuntimeError,那就说明是真加密,此时唯一的合法办法是联系数据集的作者或获取途径,千万不要去尝试任何暴力猜解。对公开下载的数据集来说,作者为防爬设置一个公开口令是常见做法,在数据集的说明页或下载描述里通常能直接找到。
5.3 标注框越界、负坐标与类别编号错位
现象:训练时YOLO报错box size is negative或者训练正常但某个类别的AP始终为0。
原因:标注XML里存在xmin == xmax或ymin == ymax的空框,以及负坐标。另一个更隐蔽的原因是转换时CLASS_MAP和data.yaml顺序不一致,把卡车类别标到了轿车类别上。
解决:转换脚本里做边界保护只是兜底。训练前跑一次全量清洗,统计每个txt里有没有非正数宽高:
import os, sys label_dir = 'dataset/labels/train' bad_count = 0 for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname)) as f: lines = f.readlines() for li, line in enumerate(lines): parts = line.strip().split() if len(parts) != 5: print(f'{fname}:{li + 1} malformed {line.strip()}') bad_count += 1 continue w, h = float(parts[3]), float(parts[4]) if w <= 0 or h <= 0: print(f'{fname}:{li + 1} zero/nagative box') bad_count += 1 print('bad boxes:', bad_count)发现异常框的图片直接丢到清理列表,因为一张图里只要有一个坏框,整张图训练时就会报警。类别编号错位的问题,最有效的排查方法是取每个类别的图各出几张可视化,直接看框和类名是否对得上。这个可视化脚本我放在第6章。
5.4 小目标评估被大目标“平均”掉:mAP的虚高与漏检并存
现象:验证集mAP很高,但实际跑一段视频,远处的小车几乎漏检,大车框得很准。看mAP看不出问题。
原因:mAP对所有目标一视同仁。当大卡车占多数时,大目标的召回会撑起整体mAP,小目标贡献被稀释。有些车辆数据集的AP_L能到0.95,AP_S才0.3,均值仍然接近0.8。
解决:训练后不仅看mAP50和mAP50-95,还要单独看metrics/small、metrics/medium、metrics/large这三项。YOLOv8验证输出里自带ap_s、ap_m、ap_l参数。如果AP_S明显低,就回到数据增强,用4.3节的切片采样方案,把小目标放大;或者把输入分辨率从640提到1280。还有一个判断标准:ap_s低于ap_l一半时,模型不适合直接部署,必须处理小目标。
5.5 混用多套数据集导致风格漂移,模型在测试集上集体失灵
现象:为了补充类别,把两个来源的车辆数据集合并训练,训练集mAP不错,但到了你的真实场景测试集上精度骤降。
原因:不同数据集的图片分辨率、摄像头高度、镜头焦距、光照色温差异太大。多类车辆数据集来源很杂,有的是行车记录仪,有的是监控摄像头,有的是自动驾驶采集车。模型学到了A数据集的纹理和B数据集的色偏,到了C场景泛化不出来。
解决:混用前先检查每套数据的平均分辨率、平均框尺寸、每类数量。如果两套数据的框尺寸分布差一个数量级,训练时就要统一resize到同一目标分辨率,并保持训练和推理的分辨率一致。更稳妥的做法是先用小模型在目标场景采集一批未标注数据,用训练好的模型做伪标签,混合后再微调一遍,把真实场景的风格拉进来。这一步其实就是第6章要讲的难例挖掘,把模型往你的部署场景上拉。
6. 进阶技巧:用难例挖掘与可视化验证,把数据集的价值榨干
6.1 用第一次训练的坏例做难例挖掘
第一次训练完成不代表数据集用完。我常用的方法是让模型在验证集和额外一批无标注数据上跑一遍推理,把所有置信度超过0.3但属于误检的检测框截取下来,作为难负样本加入训练集。对车辆检测来说,最容易产生的误检是交通标志、树干、道路护栏、广告牌上的卡车图案。把这些难负例以背景类别或无目标图片的形式喂回训练,就能大幅压低误报。
脚本思路很简单:跑一次预测,输出置信度大于阈值但真实IoU小于0.3的框,把对应的原图裁剪保存到hard_negatives/,再把它们的空标签文件放进images目录,训练时单独给这批样本提高权重。这个流程跑两轮,模型的误检率下降通常非常明显。
6.2 可视化bad case的三个要素
我调试车辆检测模型的速度一直靠可视化。一个实用的bad case图必须包含三样:真实框、预测框、两框的类别和置信度。下面是个快速绘制的脚本:
from PIL import Image, ImageDraw def draw_result(img_path, gt_boxes, pred_boxes, class_names): img = Image.open(img_path) draw = ImageDraw.Draw(img) for box, cls_id in gt_boxes: draw.rectangle(box, outline='green', width=2) draw.text((box[0], box[1] - 12), class_names[cls_id], fill='green') for box, cls_id, score in pred_boxes: draw.rectangle(box, outline='red', width=2) draw.text((box[0], box[1] - 12), f'{class_names[cls_id]} {score:.2f}', fill='red') img.save('bad_case.png')我习惯把漏检(没有绿框但有红框)画在一块,把误检(只有红框没有绿框)另画一块,这样能快速定位是类别混淆问题还是小目标问题。多类车辆里最常见的bad case就是卡车和公交车互相混、轿车和SUV混。看到这种混淆,优先去查这两个类别的标注是否存在错标,而不是急着调模型参数。
最后说个我的教训:拿到任何车辆目标检测数据集,第一周我从来不碰模型,只做数据清洗和统计。有一次跳过这个阶段直接训练,前三天全在给模型擦屁股,后来老老实实把数据集过一遍,训出来的模型反而省了三周调参时间。数据集的干净程度决定了你训练一天还是训练一个月。希望帮到你。
本文还有配套的精品资源,点击获取