ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

YOLO监控场景行人检测实战:数据集训练全流程与避坑指南

2026/10/4 21:53:13 拓冰建站 浏览量
YOLO监控场景行人检测实战:数据集训练全流程与避坑指南 简介面向街道监控场景的行人检测数据集共包含1200张监控视角抓拍图片标注类别为person最大特点是已按YOLO系列要求生成txt标签可直接用于YOLOv3至YOLOv10等全系列算法训练。资源包内总计2000个文件由788张jpg图像、1211个对应的YOLO格式txt标签和1个yaml配置文件组成压缩包约138.6MB目录结构清晰。数据集已预先划分为训练集、验证集与测试集无需任何转换操作即可投入训练标注精准据描述经YOLOv9训练准确率达96.4%可支撑科研实验、课程设计、毕业设计及实际安防项目落地。所有图片均来自真实街道监控视角贴近实际部署环境有助于提升模型在复杂背景下的泛化能力。目前已有723人学习下载适合需要快速获取高质量行人检测数据的算法开发者与学习者。1. 先泼一盆冷水YOLO训练效果不好八成是数据集的坑训练集中一张图、验证集一张图、测试集一张图这种“三张图跑通YOLO”的教程你肯定看吐了。真到了自己的落地场景比如街道监控的行人检测你会发现模型在公开数据集上刷得再高换到实际监控画面里就开始漏检、误检。为什么因为监控视角和公开数据集差异太大了——俯视、仰视、逆光、夜间红外、行人密集遮挡这类场景在COCO里占比极低。这份数据集是1200张街道监控视角的实拍抓拍图已经标注成YOLO格式的txt标签且按训练集、验证集、测试集划分完毕。也就是说你不用做任何格式转换解压之后直接进YOLOv5、YOLOv8、YOLOv9甚至YOLOv10的训练流程。如果你正在做监控场景下的行人检测、课设、毕设或者要在安防项目里快速验证算法效果这套数据能让你省掉最脏最累的“标数据”阶段。下面我按实际使用顺序把这套数据从解压到训练结束的完整流程包括那些不跑一遍根本发现不了的坑全部拆开讲。2. 先认识货目录结构、标注格式和图片采样情况2.1 解压后的目录长什么样拿到这个zip压缩包第一件事不是急着开训练而是先看清目录结构。解压后你会看到典型的YOLO风格划分rootlocal:~/datasets/person_monitor$ tree -L 2 . ├── train │ ├── images │ │ ├── screenshot_43_png.rf.a78e8ddd2b704e751e5de3efd1814223.jpg │ │ ├── screenshot_45_png.rf.8dfa8a5c6009cf8efc9cf4264e0a3684.jpg │ │ └── ... │ └── labels │ ├── screenshot_43_png.rf.a78e8ddd2b704e751e5de3efd1814223.txt │ ├── screenshot_45_png.rf.8dfa8a5c6009cf8efc9cf4264e0a3684.txt │ └── ... ├── val │ ├── images │ └── labels └── test ├── images └── labels这里有个细节值得注意图片和标签的文件名是一一对应的前缀完全相同后缀从.jpg变成.txt。这种命名习惯是Roboflow导出YOLO格式时最常见的风格文件名里的.rf.就是标记来源。train、val、test三个子目录下再分别放images和labels这就是YOLO训练的标准默认结构YOLOv5、YOLOv8、YOLOv9都能直接认。2.2 标注文件内容YOLO的txt到底写了什么打开一个txt标签文件里面每一行代表一个目标框。行人检测只有一类所以每行是五个数字$ cat train/labels/screenshot_46_png.rf.c630c5fa00185e1dd4d1cd893a1ae99a.txt 0 0.482031 0.361111 0.128125 0.255556 0 0.576562 0.255556 0.101562 0.157407 0 0.696875 0.412963 0.179688 0.300000五列含义分别是类别id这里全部为0对应类名列表里的第0个person、归一化后的中心点x、归一化后的中心点y、归一化后的框宽w、归一化后的框高h。注意所有坐标都是相对于图片宽高的比例值域在0到1之间。如果你用cv2读图后要手动计算框的像素位置就是x_pixel x_norm * image_width。我习惯先跑一段脚本统计一下这套数据的标签分布确认每个txt里是否有多行目标、有没有空标注、框尺寸的分布区间。这个动作能提前暴露标注质量问题import os label_dir train/labels total_boxes 0 empty_files 0 max_boxes 0 box_sizes [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue fpath os.path.join(label_dir, fname) with open(fpath, r) as f: lines [line.strip() for line in f.readlines() if line.strip()] if len(lines) 0: empty_files 1 continue total_boxes len(lines) max_boxes max(max_boxes, len(lines)) for line in lines: parts line.split() w float(parts[3]) h float(parts[4]) box_sizes.append(w * h) # 归一化后框面积 print(f标注文件总数: {len(os.listdir(label_dir))}) print(f空标注文件数: {empty_files}) print(f总目标框数: {total_boxes}) print(f单图最多目标数: {max_boxes}) print(f平均每图目标数: {total_boxes / len(os.listdir(label_dir)):.2f})这段脚本的核心价值有两个第一统计空标注文件空标注在训练时会被YOLO默认忽略但如果空标注文件太多说明数据集里大量图片没有行人模型容易被带偏第二统计框尺寸分布如果大量框的面积小于0.01归一化后说明场景里有大量小目标行人这直接影响后续选择多大分辨率的输入和是否加小目标检测头。2.3 图片采样质量监控视角的典型特征从我抽查的这批图片来看图片内容基本符合街道监控的典型特征视角固定在3到5米高度略带俯视画面里行人大小不一近处行人占画面比例大远处行人小到只有几十个像素。这种分布和普通公开数据集里“行人占画面主体”的风格完全不同所以这套数据在监控场景下的代表性很强。hard example的比例不低——部分图片存在行人重叠、骑车人混入、逆光下轮廓不清的情况这是好事盲试跑出来的模型在这些hard example上的表现才能反映真实水平。3. 验证划分合理性不检查划分比例后面全是白干3.1 三种划分比例背后的问题这套数据已经划分好了train、val、test。但“划分好了”不代表“划分合理”解压后第一步我建议先验证一下三个集合的图片数量和标签内容是否匹配。最常出现的翻车情况是val或test里漏了labels子目录或者images里有图片但labels目录下对应txt缺失。这种问题一旦发生训练时报错一堆“image not found”还算好的更隐蔽的是YOLO会默默跳过无标注的图片导致验证集实际参与评测的图片数变少指标跑出来虚高。# 统计三组下images和labels的数量是否一致 for split in train val test; do img_count$(ls $split/images/*.jpg 2/dev/null | wc -l) lbl_count$(ls $split/labels/*.txt 2/dev/null | wc -l) echo $split images: $img_count, labels: $lbl_count done这条命令的关键是wc -l统计的是ls输出的行数如果images和labels数量不一致马上能发现。正常情况下三组中images和labels数量应该完全相等因为每张图片对应一个txt文件。如果val和test的图片数量偏少比如不足50张评测阶段的置信度波动会很大一个模型的好坏很难靠这么小的样本量判断出来。3.2 重划数据集的通用脚本如果你对现有的划分比例不满意想自己按新比例重新划分或者想把原有的划分打散重来建议按下面的脚本走import os import random import shutil random.seed(42) all_images os.listdir(images) all_labels os.listdir(labels) # 排除没有对应标注文件的图片 valid_pairs [] for img in all_images: base os.path.splitext(img)[0] label_file base .txt if label_file in all_labels: valid_pairs.append((img, label_file)) random.shuffle(valid_pairs) train_ratio, val_ratio 0.7, 0.15 train_num int(len(valid_pairs) * train_ratio) val_num int(len(valid_pairs) * val_ratio) test_pairs valid_pairs[train_num val_num:] def copy_pairs(pairs, split): os.makedirs(f{split}/images, exist_okTrue) os.makedirs(f{split}/labels, exist_okTrue) for img, lbl in pairs: shutil.copy(os.path.join(images, img), f{split}/images/) shutil.copy(os.path.join(labels, lbl), f{split}/labels/) copy_pairs(valid_pairs[:train_num], train) copy_pairs(valid_pairs[train_num:train_num val_num], val) copy_pairs(test_pairs, test)random.seed(42)的作用是复现划分结果这样前后几次实验的可比性才有保障。train_ratio设为0.7是常见做法监控类数据集的难点在于场景分布如果val选到的图片和train高度相似val指标会显得特别好看但这只是假象真正现场表现还得靠test集说话。所以我自己划分时一般会把test的比例卡在15%以上。3.3 划分前先检查类别分布还有一个容易被忽略的动作按子目录统计每张图片的标注框数量画一个直方图。如果发现大量图片只有1到2个行人框而少量图片有10个以上框训练时模型会倾向于漏检密集场景。YOLO训练时每张图的loss是平均过的但验证集里密集图片占比如果太小模型在密集行人场景的效果好坏就看不出来。针对这种情况我一般在重新划分时优先保证val和test中存在一定比例的密集图片。4. 直接训练从YOLOv5到YOLOv9的最短路径4.1 哪个版本最省心这套数据直接用即可不需要任何格式转换。你只需要写一个data.yaml指向三个目录然后选定模型版本开始训练。YOLO全系列算法从v3到v10输入端的标注格式没有任何变化这本身就是YOLO格式最大的兼容性优势。我自己的经验是v5适合在CPU或低显存环境快速验证流程v8是默认稳妥选项训练速度合理精度也不错v9如果只是想复现标题里那个96.4%的精度数字值得跑一遍但v9对训练参数更敏感不适合新手硬磕。下面分别说。4.2 YOLOv5训练最稳妥的上手路径先建data.yaml# data.yaml - 注意修改为你自己的绝对路径 path: /root/datasets/person_monitor train: train/images val: val/images test: test/images nc: 1 names: [person]这段配置里path是数据集根目录train、val、test是相对path的子目录。nc是类别数names是类别名列表顺序必须和标注文件里的类别id一致因为标注文件里class id是从0开始计数的。写好后放到任意位置训练命令指定这个yaml的路径即可。python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --project runs/person_monitor \ --name v5s_baselinetrain.py是YOLOv5仓库里的入口脚本。--img 640是输入分辨率这里有一个权衡监控场景中行人属于中小目标输入分辨率从640提升到1280小目标召回率会明显提升但显存占用接近翻倍--batch 16在约16GB显存的情况下比较稳妥如果你的卡只有8GB显存把batch降到8或者img降到512--epochs 100对这个体量的数据集来说已经偏多1200张图的数据集跑100个epoch大概需要1到2小时足够收敛。4.3 YOLOv8训练换了入口但换汤不换药YOLOv8不再用train.py脚本而是统一走命令行yolo detect train \ data/root/datasets/person_monitor/data.yaml \ modelyolov8s.pt \ imgsz640 \ batch16 \ epochs100 \ projectruns/person_monitor \ namev8s_baselineyolo是ultralytics包提供的命令行工具。detect train指定做检测任务的训练model参数可以用yolov8s.pt这种预训练权重也可以直接写yolov8s.yaml从零开始训练不推荐1200张图太小从预训练权重迁移效果远好于从零训练。用与YOLOv5完全相同的超参数对比v8通常会在同样的epoch数下得到略高的mAP。4.4 YOLOv9训练复现高精度时要注意什么YOLOv9这份数据集的说明里提到训练准确率达到96.4%但复现这个数字有几个隐性条件。YOLOv9的训练配置和v5/v8有较大差别尤其是--hyp超参数文件、--save-period断点保存这类细节。我跑YOLOv9时用的是官方仓库原版命令python train_dual.py \ --batch 16 \ --img 640 \ --epochs 150 \ --data data.yaml \ --weights yolov9-c.pt \ --hyp hyp.scratch-high.yaml \ --device 0 \ --project runs/person_monitor \ --name v9c_baselinetrain_dual.py是YOLOv9仓库的主训练脚本。hyp.scratch-high.yaml是官方的高精度实验超参它的学习率、增强参数和默认值差别很大直接套默认hyp跑出来会偏低这不是数据的问题是配置没对齐。我一般会顺手把epochs加到150因为v9的收敛速度比v8慢100个epoch时可能还在波动。如果复现结果比96.4%低了2到3个百分点别急着下结论先检查你用的预训练权重是否是yolov9-c.pt以及输入分辨率是不是640这两个变量对最终精度影响最大。4.5 参数速查与显存预算模型输入尺寸batch单卡显存参考推荐GPU预期精度预期训练时长YOLOv5s640168GBT4/3080中高约2hYOLOv5m6401612GBT4/2080Ti更高约3hYOLOv8s640168GBT4/3060中高约2hYOLOv8m6401612GB3080更高约3hYOLOv9-c6401616GB3090/A10最高约4hYOLOv9-e6401224GB3090/4090极高约5h表格里的显存是训练过程中峰值显存的参考值实测时frames per second这类数据也要关注但训练阶段最先卡住你的总是显存。如果你的显卡只有6GB显存优先选v5s或v8s并把batch降到8这比硬上v9然后OOM要实用得多。5. 避坑手册监控场景行人检测最常见的六个翻车点5.1 小目标漏检val指标好看现场全漏现象在测试集图片上近处大目标全部检出但画面中远处的小行人全部漏掉。模型mAP可能还有0.9可是视频里行人距离稍远就完全不报警。原因监控视角下远处行人的像素高度往往低于32像素这个尺寸在COCO里算是小目标但在测试时如果输入分辨率只有640小目标经过多次下采样后特征图上的信息只剩一个点检测头根本提取不到有效特征。标注文件里那些归一化框宽高在0.05以下的目标基本全属于此类。解决第一个方案是提升输入分辨率img从640提到1280小目标AP的提升立竿见影第二个方案是改模型结构在v8里把检测头从默认的3个P3/P4/P5下采样倍数增加P2层专门负责小目标。但这些调整都会增加训练和部署成本建议先用1280分辨率跑一轮如果还是漏再动检测头结构。5.2 夜间低光照误检在训练集上没见过的场景现象白天图片上的行人检测效果良好夜间或逆光情况下行人和背景的对比度极低甚至行人轮廓融进暗部。模型要么漏检、要么把路灯柱子当成人框出来。原因很多人忽略了一个事实这份数据集虽然全部来自监控视角但监控摄像头有红外夜视模式、有逆光补偿模式夜间的图片亮度直方图和白天完全不同模型在白天训练时看到的亮度特征分布和夜间输入完全不同。说到底还是数据样本分布问题。解决如果你这个项目确实需要夜间工作 нельзя只靠这1200张图就指望搞定。我的做法是把训练集的图片做数据增强模拟夜间效果。YOLOv8的--augment参数里可以开启hsv_h、hsv_s、hsv_v的随机扰动把v通道的扰动范围调大相当于人工把亮度压低强迫模型学会在低亮度特征下识别行人。但增强只能缓解不能根治真正的做法还是补充夜间实拍图。5.3 多类别混淆单类数据集当多类用的教训现象你手里这套数据只有person一类但训练时你把nc写成了2names里加了一个car。原因这是典型的配置错误标注文件里每一行只有一个类别id0如果你在data.yaml里声明nc为2那id0仍然对应person但id1没有任何标注样本等于YOLO会在训练时把背景误当作第二类来预测损失函数计算会出现空梯度模型训练根本不会收敛到可用状态。解决除了检查data.yaml确认nc: 1还可以跑一遍我以前提到的标签统计脚本把所有txt里的第一个数字全部读出来用set去重看一下实际出现的类别id有0到几。确认只有0那nc就写1。凡是自动生成的数据在这类配置问题上都容易踩配置文件的错误往往在训练日志里完全不报错只在精度上悄悄杀人。5.4 过拟合训练集acc高val始终上不去现象train的box_loss一路降到0.01附近但val的box_loss在某个值后不再降甚至反弹。mAP50可能还不错mAP50-95却徘徊不前。原因1200张图片的规模对YOLO来说偏小模型容量大训练阶段把训练集的特征记住但泛化能力没练出来。监控场景中大片连续背景被重复采样加深了过拟合。解决除了调低epochs80到100我习惯再加深度数据增强。YOLOv5里开启--augment时默认有几项是关掉的比如mosaic在最后10个epoch会关闭copy_paste这种增强也不在默认开启范围。说实话1200张图偏少除了训练手段更实用的做法是直接引入公开的行人检测数据集做联合训练比如把CityPersons或者EuroCity Persons里和监控视角类似的图片挑几百张加进来再做一次清洗效果比任何增强技巧都猛。5.5 框的边界溢出归一化坐标大于1或小于0现象训练时日志里不时出现label out of bounds之类的警告v5常见推理时部分检测框明显超出了图片边界。原因标注时如果目标被画面边缘截断标注框可能会算出一个中心点靠近边缘的坐标极端情况下框的xy坐标加宽高会超过1.0。YOLO训练时对这种标签的处理是和边界做clip相当于脏标签被强行裁剪模型学到的框位置信息本身就是偏的。解决用脚本预处理一遍所有标签把坐标裁剪到[0, 1]区间同时剔除宽高小于0.001的异常框。这点工作别偷懒700行以下的文本扫描一两分钟就完成。5.6 训练集足够多但val指标波动大现象每次训练val的AP值浮动超过3个百分点同一份数据同一套超参连续跑两次结果不一样。原因数据集总量小val划分里的图片只有一百多张每一张图的检测情况都会显著影响总体AP。再加上随机初始化、增强随机性波动在所难免。不是bug是统计效应。解决这类数据集我一般用三次训练取平均值的方案或者把val的划分比例从10%提到15%到20%牺牲一点训练集数量换稳定评测。另外最优做法是用test set做最终评测而非valval只用于调参这样可以减少调参过程中的主观偏差。6. 验证模型有效性从损失曲线到真实监控画面的完整闭环建好模型只是第一步关键是一套可信的验证方法。我的做法分四步看损失曲线、跑test集、用自写推理脚本做单图测试、最后接真实镜头验证。损失曲线的核心是验证是否收敛。把训练日志里的box_loss按epoch画出来正常曲线应该前50个epoch快速下降之后进入平台期。如果loss还有明显下降趋势就停了说明欠拟合需要加epoch如果val loss在后期反弹说明过拟合需要提前终止或增强。画曲线代码import matplotlib.pyplot as plt # 从results.csv读YOLOv5/v8的训练日志v5存成csvv8也类似 lines open(runs/person_monitor/v8s_baseline/results.csv).readlines() epochs [] t_box_loss [] v_box_loss [] for line in lines[1:]: parts line.strip().split(,) epochs.append(float(parts[0])) t_box_loss.append(float(parts[2])) # 按results.csv的列顺序取 v_box_loss.append(float(parts[3])) plt.plot(epochs, t_box_loss, labeltrain_box_loss) plt.plot(epochs, v_box_loss, labelval_box_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss_curve.png)这只是一个简单版本实际日志的列顺序在不同版本里不一样v5的results.csv里第2列是train/box_loss第3列是val/box_loss这个不一定准确你打印一下columns自己核对因为各小版本列序确实不同这里我强调的是方法看相对趋势不是看绝对值。然后是单图推理验证。用训练好的模型对test里抽样图片做推理核心是用自有脚本判断conf threshold设在哪里比较合理。YOLO默认conf是0.25监控场景我一般会调高到0.35到0.4因为误检的代价比漏检高。方法是用模型对50张test图片跑一遍记录所有检测框的置信度画出置信度的直方图。如果大量框的置信度集中在0.2到0.35之间那这些图在默认阈值下会被滤掉说明模型对行人这个类别的置信度偏低需要检查是不是图片质量本身太差反之如果置信度普遍在0.6以上就算把阈值提到0.5检测框数量也不会下降太多。最后一环是摄像头实测。取样一段真实监控视频用训练好的模型跑一次推理。我建议关注两个指标一种是每帧推理耗时一帧推理耗时取决于部署设备性能在Jetson Nano上v8s大约能到15到20帧每秒老式的Hikvision摄像头用RTSP拉流也一般ultralytics自带的source参数只支持到单路视频多路并发需要自己写多线程布丁另一种是指定检测帧间隔比如每隔3帧做一次检测中间帧直接复用上一帧的检测结果这样能达到约等于3倍推理速度的效果但代价是快速移动的行人会有明显框滞后。街道监控场景通常会接受这个代价。从那以后我每拿到一份数据集都强制走一遍这套流程标签统计、划分比例校验、空标注排查、训练日志可视化、真实场景验证。这套流程多次帮我避免过拟合和数据泄漏希望帮到你。本文还有配套的精品资源点击获取