
简介面向行人与车辆自动检测任务的数据集资源包含4500张训练图像与500张验证图像合计5000张已标注图片专门适配YOLOv5等主流目标检测框架可直接用于模型训练、验证与效果对比。所有图像中的行人、车辆实例均以边界框形式标注同步提供xml与txt两种标注格式xml可查看类别和坐标信息txt则便于直接套用到YOLO训练管线jpg原始图像与两种标注文件通过统一编号一一对应完成数据划分和预处理非常顺手。整个压缩包共包含16821个文件三类文件各5607个资源包大小约499.51MB目录结构清晰规整适合批量加载和多次迭代。当前已有2886人学习下载该资源的实用性与认可度已有一定验证。借助此数据集可系统开展检测实验覆盖不同光照、视角与遮挡条件实测验证集上平均精度均值约达98%既能用于不同算法间的横向评测也可支撑自动驾驶、交通监控等真实应用场景是目标检测入门研究与工程落地的优质基准资料。1. 一份行人车辆检测数据集一共4500张验证集500张到底能训出什么手里这套行人车辆检测数据集一共4500张验证集500张很多人第一反应是“才4500张图够训模型吗”。做过几个交通感知项目之后我可以坦白讲5000张图配合一份干净标注在目标检测里属于“小而能打”的规模价值不在数量而在你怎么组织这4500张训练集和那500张验证集。这篇笔记按我实际跑项目的顺序来先做数据核验与划分再做格式转换和训练参数调优最后集中讲最容易翻车的几个坑。适合手里有类似规模数据集、想用YOLO系模型做出可靠行人车辆检测结果的从业者也适合刚入目标检测、还没搞清楚训练集和验证集到底该怎么用的新手。2. 拿到数据集第一件事不是开训先做图片质量与类别分布的核验很多人拿到数据集就急着跑yolo detect train结果训练到一半 loss 突然飘掉或者验证集 mAP 高得吓人、一上视频就漏检。问题往往不在模型在数据本身。4500 张训练图加 500 张验证图如果标注是人工画的漏标、错标、坐标越界几乎是必然如果图片来自视频抽帧还可能混着大量高度相似的连续帧。所以第一步不是写训练脚本是写一个数据体检脚本把质量、类别分布、划分方式这三件事先钉死。2.1 图片质量筛查模糊、过曝和坏图的三个硬指标我一般会用 OpenCV 把全量图片过一遍指标就三个能不能正常读、清不清晰、亮度是否极端。下面这段脚本能在一两分钟内跑完 5000 张图的体检。import cv2 import os img_dir images bad_files [] for name in os.listdir(img_dir): path os.path.join(img_dir, name) img cv2.imread(path, cv2.IMREAD_COLOR) if img is None: bad_files.append((name, unreadable)) continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur_val cv2.Laplacian(gray, cv2.CV_64F).var() mean_bright gray.mean() if blur_val 30: bad_files.append((name, fblur{blur_val:.1f})) elif mean_bright 20 or mean_bright 240: bad_files.append((name, fbrightness{mean_bright:.1f})) print(ftotal{len(os.listdir(img_dir))}, suspicious{len(bad_files)}) for f in bad_files[:50]: print(f)逻辑很简单cv2.imread返回 None 的就是坏图读取后转灰度用 Laplacian 算子的方差衡量清晰度方差越低图越糊。为什么阈值定 30这是我跑白天城市道路场景的常用值如果数据里夜间图多可以把阈值放宽到 15否则夜间正常的暗光图会被误杀。亮度均值的判断同理全黑图、过曝图会直接把模型训练带偏。这一环节有个容易被忽略的细节坏图不要直接删而是记录路径后归入一个exclude目录。因为有些运动模糊帧虽然质量差却代表真实场景中的“目标运动过快”删掉它模型对运动模糊的鲁棒性会更差。我给自己定的规则是可疑图逐个人工过一遍只有完全不可读或标注无法对齐的才剔除如果可疑图超过全量的 3%说明数据源采集本身有问题这时候要停下来重新看采集方案而不是硬着头皮继续。2.2 类别分布统计4500张训练集里行人和车辆的比例是否失衡图片质量只是第一关更关键的是类别分布。假设标注是 VOC 格式的 xml我会用下面这个脚本统计每类目标的框数、有目标图片数和总框数。如果你的标注是 COCO json 或 YOLO txt核心逻辑相同只是解析方式不同。import xml.etree.ElementTree as ET import os from collections import Counter ann_dir Annotations cls_counter Counter() imgs_with_obj 0 total_boxes 0 for xml_file in os.listdir(ann_dir): tree ET.parse(os.path.join(ann_dir, xml_file)) root tree.getroot() cnt 0 for obj in root.iter(object): name obj.find(name).text.strip() cls_counter[name] 1 total_boxes 1 cnt 1 if cnt 0: imgs_with_obj 1 print(cls_counter) print(total_boxes:, total_boxes) print(imgs_with_obj:, imgs_with_obj)跑完看两个数。第一每类框数是否悬殊。如果车辆框是行人框的三倍以上模型会天然偏向车辆行人这一类在 loss 里的贡献被稀释。我一般要求最少类别实例数不低于最多类别的五分之一低于这个比例就得做类别权重或复制粘贴增强这在第 4 章会展开。第二平均每图目标数。如果 4500 张图一共只有 6000 个框平均一张图 1.3 个目标模型很难学到拥挤场景下的遮挡关系推理时遇到两个人并排走就会漏检。这个环节还要顺手做一件事类别名归一。同一个数据集里出现person和people并存、car和Car并存的情况我在实际项目里见过不止一次两个名字在代码里是两个类别直接导致分类数虚高、mAP 被稀释。处理办法是做一次字典映射统一成一套命名再重新统计。2.3 验证集500张不能随便分划分时的三个前提训练集 4500 张、验证集 500 张这个比例本身没问题问题在怎么把这 500 张选出来。很多教程教你random.shuffle后按比例切如果你的图片全是相互独立的网络抓图这样没问题但如果图片来自视频抽帧或同一批摄像头随机划分会把同一段画面的相邻帧同时分进训练集和验证集模型在训练时已经“背过答案”验证 mAP 虚高一上真实场景立刻现原形。我常用的做法是按来源分桶。假设文件名前缀代表视频源或摄像头编号用下面这段脚本把每个桶里的图按 9:1 拆开而不是全量随机打乱。for cam in $(ls images | cut -d_ -f1 | sort -u); do mkdir -p split/train/$cam split/val/$cam ls images/${cam}_*.jpg | sort /tmp/${cam}.txt total$(wc -l /tmp/${cam}.txt) val_n$((total / 10)) head -n $val_n /tmp/${cam}.txt | xargs -I {} mv {} split/val/$cam/ tail -n $((val_n1)) /tmp/${cam}.txt | xargs -I {} mv {} split/train/$cam/ done原理是先把属于同一视频源的图片按文件名聚到一起再按时间顺序取前 10% 作为验证保证同一时刻相邻帧只会出现在一个集合里。val_n取总数的十分之一是为了对齐“训练 4500 验证 500”这个结构。分完之后不要急着开训再跑一遍上一小节的统计确认验证集里行人和车辆两类图片的占比和训练集基本一致如果某类在验证集里一个框都没有后面该类 mAP 直接报 0排查起来很费劲。还有一点关于验证集规模的认识500 张图跑出来的 mAP 本身有约 ±0.03 到 ±0.05 的随机波动两个模型 mAP 差 0.02 并不能说明谁更好。想判断真实差异同一个 checkpoint 换不同验证顺序跑两次取均值或者把验证集切两半分别评估。这个误差意识能帮你少走很多弯路。3. 把数据集组织成YOLO能直接训练的样子目录结构、标注格式与anchors重算数据和划分都确认没问题后下一件事是把标注格式转换成目标检测框架认识的形态。常见工作流里数据集原始标注大概率是 VOC 的 xml 或 COCO 的 json而 YOLO 系训练脚本读的是每张图对应一个 txt 的归一化坐标这一步转换不做对后面所有环节都会跟着出错。3.1 三套主流标注格式的换算VOC、COCO、YOLO txt先理清三种格式的本质差异用一个表记住就够格式组织方式坐标体系典型文件VOC图片目录 每张图一个 xml左上右下像素坐标 xmin,ymin,xmax,ymaxJPEGImages/.jpg、Annotations/.xmlCOCO一个或多个 json 汇总所有标注左上角像素坐标加宽高 x,y,w,hannotations/instances_train.jsonYOLO txt每张图一个 txt一行一个目标中心点加宽高且除以图宽高的归一化值images/.jpg、labels/.txt我遇到最多的情况是 VOC 转 YOLO因为很多标注工具导出的是 VOC。转换脚本网上到处都有但大部分没处理边界情况。下面这段是能直接用的版本import xml.etree.ElementTree as ET import os CLASSES [person, car, truck, bus] def convert(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASSES: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) bw xmax - xmin bh ymax - ymin if bw 0 or bh 0: continue x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw bw / w bh bh / h lines.append(f{CLASSES.index(name)} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) for xml_file in os.listdir(Annotations): convert(os.path.join(Annotations, xml_file), os.path.join(labels, xml_file.replace(.xml, .txt)))逻辑不复杂VOC 的归一化要把像素坐标同时除以图宽高中心点取 xmin 和 xmax 的中点。.6f格式化很关键防止 Python 在某些数值上输出科学计数法导致解析错乱。bw 0 or bh 0这个过滤是我加进去的标注框两个角点重合的退化框在 VOC 里偶尔会出现不过滤的话训练时 loss 计算可能除零表现为训练到一半数值爆炸排查要花很久。还要提醒一句如果类别名不在 CLASSES 里这里选择直接跳过会悄悄丢标注建议改为打印未知名让你人工确认而不是静默丢弃。COCO 转 YOLO 的思路类似用 pycocotools 读 json 后按 annotation 的 bbox 字段换算但要注意 COCO 的 bbox 是x, y, width, height不是 xmax、ymax很多人第一次转就在这里写错公式。另外如果你做的是俯视或遥感视角的行人车辆检测要换成旋转框的 mmrotate/DOTA 那条管线坐标定义和评估方式和水平框完全不同不在这次讨论范围内。3.2 目录结构与data.yaml按YOLOv8/v5的方式组织4500500格式转完后目录结构我建议严格照搬 YOLO 官方约定的样子省得训练脚本报路径错误。典型的组织方式是这样dataset/ ├── images/ │ ├── train/ # 4500张 │ └── val/ # 500张 ├── labels/ │ ├── train/ # 与images同名同前缀的txt │ └── val/ └── data.yaml对应的data.yaml长这样train: dataset/images/train val: dataset/images/val nc: 4 names: [person, car, truck, bus]这里有两个细节值得单独说。第一路径我习惯写相对路径并在训练命令里cd到 dataset 的父目录而不是写死绝对路径绝对路径换台机器就全部作废这种坑我在给同事交接项目时碰上过太多次。第二图片名和标签名必须严格同名大小写也不能差Linux 下a.jpg和A.jpg是两个文件Windows 下却是同一个跨平台训练时最容易在这上面翻车。名字对齐后做一次硬校验确保每一张训练图都有对应标签find images/train -name *.jpg | wc -l # 期望4500 find images/val -name *.jpg | wc -l # 期望500 for f in $(find images/train -name *.jpg); do base$(basename $f .jpg) [ -f labels/train/$base.txt ] || echo missing label: $f done如果图片没有对应标签训练时这张图会被当成纯背景图参与训练。少量背景图对模型有正面意义能压低误检但如果验证集里混入大量无标签图明明是漏检目标评估时却算作正确背景mAP 虚高就是这么来的。所以我建议训练集里可以有少量空标签图验证集里保留一些但不要太多并且心里清楚它会影响分数解读。3.3 要不要重算anchors4500张训练集上的一个关键决策老玩家会纠结 anchors 要不要重算这块要分框架说清楚。YOLOv5 用--autoanchor可以在训练前基于你的标注框做 k-means 聚类自动算出更适合当前数据集目标尺度的先验框YOLOv8 的检测头是 anchor-free 的不依赖预置 anchors所以基本不用管这件事。如果是 YOLOv5我会在第一次训练前开 autoanchor 看它算出的新 anchors 和默认值差多少。差别在 10% 以内不值得折腾差别大说明你的目标尺度和 COCO 分布差异明显比如行人普遍只有 20x40 像素而车辆占半幅画面。这时用 autoanchor 跑出的结果替换默认配置对小目标召回有一定帮助。还有一个和 anchors 相关但常被忽略的统计把 4500 张训练图里所有标注框的宽高聚类画出来能直观看到数据集的目标尺度分布。这个分布直接决定你后续该用 640 还是 1280 的输入分辨率是第 4 章参数选择的重要依据千万别跳过。4. 4500张训练集怎么用到极限数据增强、训练参数与小样本策略数据量就这么多模型能不能出效果拼的是对 4500 张训练图的利用效率。增强策略选得好一张图能顶三张用选得不好轻则过拟合重则把夜间场景的泛化能力直接毁掉。这一章的参数都来自我实际跑过的人车检测项目可以拿来当起点再根据你的数据调整。4.1 增强参数的取舍mosaic、mixup、hsv在行人和车辆上的表现下面这条命令是我训练 YOLOv8 做行人车辆检测的常用起点参数不是默认值都有针对性yolo detect train \ modelyolov8n.pt \ datadata.yaml \ imgsz640 \ batch16 \ epochs100 \ hsv_h0.015 hsv_s0.7 hsv_v0.4 \ translate0.1 scale0.5 fliplr0.5 \ mosaic0.8 mixup0.2 \ patience20 \ projectruns/traffic \ nameperson_vehicle逐个说参数为什么这么设。mosaic0.8而不是默认的 1.0是因为 mosaic 把四张图拼成一张目标落在拼接缝处会被直接切断行人检测里被切断的半个人像会让模型学到错误语义密集人群场景尤其明显。保留 0.8 能给模型一定的多尺度上下文又不会让截断样本太多。mixup0.2对拥挤场景有帮助但比例太高会把行人和车辆的边缘特征糊在一起0.2 是我试下来比较稳的值。hsv_v0.4是我踩过坑的地方。如果你的数据里有相当比例的夜间图亮度增强太强会把夜间图在训练时拉成白天亮度模型学到的夜间特征被污染验证 mAP 不低、夜间泛化却很差。夜间样本占比较多时我会把hsv_v降到 0.1 甚至关掉。这个规律反过来也成立全是白天图时适当提一点 hsv_v 能提升对逆光的鲁棒性但要亲眼看过增强后的效果再定别只看曲线。还有一个参数容易忽略scale0.5控制随机缩放范围。行人车辆共存的场景目标尺度差异大scale 太大会让本来就小的行人缩成几个像素失去学习意义。0.5 是比较平衡的值如果你验证集里大量行人小于 30 像素可以把 scale 降到 0.3减少训练时目标被缩没的概率。4.2 训练参数的硬指标imgsz、batch、epochs在小数据集上怎么设小数据集上的参数选择最核心的是输入分辨率 imgsz。640 是性价比最高的默认值但行人这种小目标640 输入下原图 30 像素的一个人会被缩到不到 20 像素特征基本丢了。如果你的目标尺度统计显示行人普遍偏小直接上 1280 是提升召回最立竿见影的手段代价是显存占用约为 640 的四倍batch 往往要减半。batch 和显存的关系有个粗略估算YOLOv8n 在 640 分辨率下16 张图的显存占用大约 4 到 6 GB1280 分辨率同样 batch 会逼近 16 GB 以上。显存不够时优先降 imgsz 而不是降 batch因为 imgsz 对 mAP 的影响通常比 batch 大用 8 的 batch 配合梯度累积一样能训练只是收敛节奏慢一点。epochs 我的建议是 100 起步配合patience20早停。4500 张图不算多通常到第 50 到 80 轮就收敛了继续硬顶只会过拟合验证集。早停监控的是验证集 loss别把训练 loss 当成早停依据训练 loss 一定会降不代表模型在 500 张验证图上表现好。也可以考虑用batch-1让 YOLO 自动探测显存并选一个合适的 batch省去手动试错的功夫。4.3 数据不够时先别急着上生成式增强用复制粘贴和分层采样很多人数据不够第一反应是上生成模型扩数据我持保留态度。生成式增强的标注和真实场景之间的分布偏移很难控制4500 张图这个规模更稳的是传统复制粘贴增强把行人实例从原图抠出来粘贴到车辆较多的背景图上同时写入新标注。这在 CrowdHuman 一类密集行人数据集上被反复验证过是提升小样本类别召回的有效手段。import cv2 import numpy as np def paste_instance(bg_img, obj_img, mask, label_lines, class_id, max_try10): h, w bg_img.shape[:2] oh, ow obj_img.shape[:2] if ow w or oh h: return False for _ in range(max_try): x np.random.randint(0, w - ow) y np.random.randint(0, h - oh) roi bg_img[y:yoh, x:xow] roi[mask 0] obj_img[mask 0] cx (x ow / 2) / w cy (y oh / 2) / h bw ow / w bh oh / h label_lines.append(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return True return False这段代码的核心是“贴完后同步更新标签”很容易漏。mask 是目标前景掩码简单实现可以用标注框中心区域近似不需要精细到像素级分割。三个使用原则粘贴时避开已有目标不然两个框重叠会让模型学到错误遮挡关系粘贴位置要随机但不能离谱比如行人出现在车顶就违背常识扩充比例控制在单类实例数的 20% 到 50%太多会让模型学到“行人总出现在奇怪位置”的假分布。分层采样是对划分的补充构造一个包含has_person和has_vehicle两列的元数据表按这两个字段分组后分别抽样保证划分出的验证集中两类出现的图片比例与训练集一致。固定random_state让划分可复现是值得养成的习惯。5. 行人车辆检测训练中的5个常见坑从loss不降到mAP虚高这一章写训练和评估阶段最常见的五个问题每条都按“现象、原因、解决”三段来都是我在类似规模数据集上实际遇到过的排序按发生的频率。5.1 训练loss正常但验证mAP极低先查划分泄漏现象训练曲线下降得很漂亮训练集 loss 和验证集 loss 都正常但验证集 mAP 只有 0.1 左右跟同规模公开数据集的水平差了一个数量级。原因多半是数据划分出了问题图片来自视频抽帧随机划分把同一段画面的相邻帧同时分进训练集和验证集模型在训练时已经记住这些画面而验证时看到的“下一帧”由于光照、遮挡变化模型根本没泛化过。解决回到第 2 章按摄像头或视频源分桶后再划分或者先把视频按时间间隔 5 帧抽 1 帧去掉高度相似的冗余帧再做划分。判断是不是这个原因可以把验证集里每张图在训练集里按文件名前缀找同源图片若命中率极高就是泄漏。5.2 验证集500张mAP虚高实拍视频却漏检成片评估时开着增强现象验证集 500 张跑出 0.7 的 mAP满心欢喜去测实拍视频行人漏检严重像是换了个模型。原因之一是评估流程不干净YOLO 在验证阶段默认也会做一些数据增强如果开了 mosaic 或 mixup相当于在“增强后的图片”上评估分数虚高另一个常见原因是训练用了 1280 分辨率验证时却忘了指定imgsz1280默认 640 直接把小目标缩没了。解决验证命令显式关掉增强yolo detect val modelbest.pt datadata.yaml imgsz1280 augmentFalse。关掉增强后分数会掉一截那个掉下来的数字才是真实水平。5.3 车检得好、行人检得差类别不平衡的三个应对现象car 的 AP 有 0.85person 只有 0.4差了一倍还多。原因通常是训练集里车辆框数量远多于行人框且行人平均尺度更小模型优化时把容量倾斜给了车辆类。解决按三步走先用第 2 章的统计脚本确认两类实例数的真实比例再在训练配置里给行人这一类更高的 loss 权重YOLOv8 支持按类别设置cls权重比例我一般按“车辆框数除以行人框数”的倒数来设最后用复制粘贴把行人实例数量补到车辆的一半以上补完重新统计分布。做完这三步person 的 AP 通常会有 5 到 10 个点的提升。5.4 训练到一半loss突然NaN坏图和越界框的连锁反应现象epoch 跑到 40 多轮loss 突然变成 NaN之后无法恢复。原因有三类按概率排序标注框坐标越界txt 里出现了 x_center 大于 1 或宽高为负的值某张训练图损坏加载时读到坏像素mosaic 增强时拼接图里出现了异常目标。解决训练前跑脚本过滤越界框凡是中心点不在 0 到 1 范围或宽高小于等于 0 的行直接剔除训练命令加cacheTrue让数据在启动阶段就全量加载并校验坏图会在开训时报错而不是训练中突然爆炸排查时先把mosaic0.0 mixup0.0跑 20 轮如果不再出现 NaN就锁定是增强触发的再往回加参数。5.5 验证集500张全是白天场景分布不均导致上线翻车现象验证 mAP 稳定在 0.75模型一部署到夜间或雨天场景马上被打回原形。原因很直白500 张验证集是随机抽出来的白天图占原始数据大头验证集自然以白天为主模型夜间表现差但分数完全看不出来。解决划分时按场景分层强制夜间、逆光、雨天各保留不少于 50 张进验证集如果原始数据里根本没有这些场景任何数据增强和调参都救不了先回去补数据。这个坑我在项目里踩过之后养成了习惯拿到数据集先按时间段和天气打标签再决定怎么分验证集而不是让随机性替我做决定。6. 用验证集的500张做一次“集采式”质检混淆矩阵、错误类型和阈值调整训练完成后我一般不会直接看一个 mAP 就收工而是把 500 张验证集当成一次“集采质检”逐个类别看它们的失败模式再决定模型能不能上线。6.1 不只盯mAP在500张验证集上计算每类召回和置信度曲线mAP 是综合指标对上线来说每类的召回曲线更直接。用yolo detect val生成 COCO 格式的预测结果再用 pycocotools 对 person 类单独画 Precision-Recall 曲线找到召回率掉到 0.8 时对应的置信度阈值。这个阈值可以作为后处理时的参考如果业务能容忍误检阈值就取下限多召回如果误检代价高阈值往上提 0.05 再看漏检增量。我见过太多人直接信默认 0.25 的置信度其实那个数不一定适合你自己的类别分布。6.2 把错误分成三类遮挡、小目标、误检背景我会从验证集里挑出 50 张预测最差的图按错误类型归一下因然后对应到三个动作错误类型典型表现优先动作遮挡漏检两个行人并排只检出一个NMS 阈值降到 0.3 到 0.4或多尺度测试小目标漏检远处行人全 missedimgsz 提到 1280或对验证图做切片推理背景误检树干、灯柱被框成 person审查标注负样本置信度阈值上调 0.05这个流程走下来你能说清楚模型当前的主要短板是数据、标注还是后处理而不是笼统地“再训几轮试试”。我自己带项目时也养成了同样的习惯训练前先花半小时把验证集的场景分布、类别分布和目标尺度打印出来确认它不是“好看的 500 张”再决定要不要开训。这个小习惯帮我避开过好几次上线翻车的尴尬希望帮到你。本文还有配套的精品资源点击获取