ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

夜间行人检测数据集:VOC/COCO/YOLO标签解析与YOLO11训练实战

夜间行人检测数据集:VOC/COCO/YOLO标签解析与YOLO11训练实战 简介面向夜间行人检测实战场景这份资源提供5000张夜id低光街景与道路行人图像涵盖常规、遮挡及严重遮挡样本并配套VOC/COCO/YOLO三种常见标签格式可直接用于监控场景行人检测项目亦可作为通用行人数据集夜间场景的补充。资源包整体为单份PDF文档大小6.07MB文档内主要介绍数据集构成、labelimg标注规范、三种格式说明及获取方式实际图片与标签数据需按文档指引下载。已有611人浏览学习。文档还给出YOLO11一键训练脚本的多平台运行方案覆盖GPU(GPUs)、CPU、Mac(M芯片)环境并附博主训练结果日志便于评估模型效果、快速搭建夜间行人检测训练流程。1. 夜间行人检测为什么白天好用的模型天一黑就翻车做监控场景目标检测的同行应该都有过这种体验白天跑得飞快的行人检测模型一接到夜间视频就原形毕露——漏检、误检、框跳来跳去。原因不复杂夜间图像整体亮度低、对比度差行人边缘和背景融为一体加上路灯反光、车辆大灯过曝、严重遮挡这些干扰模型在白天数据上学到的特征根本不够用。想自己攒一套夜间行人数据得扛着相机去拍、再拿labelimg一张张框周期长到没法接受。这套夜间行人目标检测数据集就是冲着这个痛点来的5000张真实夜间场景图带VOC(xml)、COCO(json)、YOLO(txt)三种格式标签外加一份支持GPU(GPUs)/CPU/Mac(M芯片)三平台直接跑的YOLO11一键训练脚本。对正在做公共场所监控夜间行人检测项目的人来说等于把数据采集、标注、格式转换、训练环境配置这几步全帮你踩完了。下面我按自己的使用习惯把这套资源从数据到训练完整拆一遍。2. 数据集构成5000张夜间图的场景密度与标注规范2.1 夜间场景怎么分层街景、道路与遮挡梯度拿到数据集先别急着训练先把图片翻一遍摸清场景构成比直接调参重要得多。这套数据的核心价值在于它按检测难度做了场景分层夜间街景行人、夜间道路行人、夜间遮挡行人、夜间严重遮挡行人四类都有覆盖。我习惯把遮挡程度当成数据集的难度阶梯——普通街景行人用来撑起模型的基础召回率严重遮挡样本则负责压住误检率。实际项目里监控摄像头装在高处俯拍行人互相遮挡、行人被栏杆或车辆遮挡是常态。这套数据里标注框对遮挡目标的处理方式从训练角度说是合理的只要行人身体可见部分超过一定比例就给框而不是因为遮挡就跳过不标。这样做的好处是模型能学到遮挡目标的局部特征坏处是训练初期loss会偏高但这属于正常现象后面避坑章节会细说。对于正在做监控场景通用行人检测、想补充夜间数据的人来说这套数据可以直接作为现有训练集的夜间分支和白天数据混合训练比单独训一个夜间模型更稳。2.2 三种格式标签的来源与质量判断数据采用labelimg标注这是目前用得最多的开源标注工具之一。labelimg默认输出VOC格式的xml文件COCO的json和YOLO的txt需要转换脚本生成这套数据一次性给齐了三种省去自己写转换的功夫。判断标注质量我一般看两点一是框是否贴边二是类别名是否统一。从数据集截图能看出标注框贴合度比较高没有明显的大幅扩边或漏框。这里要提醒一点拿到任何数据集都建议抽样检查标注别盲信描述。我一般会随机抽50张图用脚本把YOLO的txt框画回原图肉眼看一遍框的位置和大小是否合理。这个检查动作虽然土但能拦下80%的训练翻车问题。2.3 这份数据适合什么、不适合什么适用场景非常明确公共场所监控视角下的夜间行人检测包括小区周界、园区安防、交通路口这类固定机位场景。作为监控通用行人检测的夜间补充数据来用属于教科书级的正确定位。但不适合的场景也要说清楚如果是车载视角的行人检测或者无人机俯拍视角这套数据的视角分布和你实际场景差异会比较大需要额外采集补充。另外数据集主打的是单类别行人检测如果你的业务需要同时检测骑车人、动物或者车辆需要另外找带对应类别的数据集来融合。提示拿到数据后先做一次类别分布统计确认txt标签里类别id只有0这一种。如果混入了其他类别id训练前必须统一处理否则模型会按多类别去学影响检测精度。3. VOC/COCO/YOLO三种标签格式从结构差异到读取实战3.1 三种格式的结构差异与选型逻辑VOC格式每个图片对应一个xml文件标注信息以树状结构存储可读性最好打开就能看到object节点下的name、bndbox坐标COCO格式把所有标注集中在一个json文件里通过images、annotations、categories三个数组关联适合大规模数据管理YOLO格式最简单每张图对应一个txt每行是类别id 中心点x 中心点y 宽 高坐标全部归一化到0~1之间训练时加载最快但人眼不可读。从训练框架兼容性来说YOLO系列原生吃txt格式MMDetection吃COCO json老一代的SSD或Faster R-CNN训练流程吃VOC xml。这套数据一次给齐三种意味着你不管用哪套主流检测框架都不用自己动手做格式转换。我一般会直接跑YOLO11脚本用txt格式但如果要对比其他模型的基线COCO json格式直接就能喂给MMDetection省事。3.2 读VOC格式解析xml标注的参考实现import xml.etree.ElementTree as ET import os def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) width int(size.find(width).text) height int(size.find(height).text) objects [] for obj in root.iter(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(float(bndbox.find(xmin).text)) ymin int(float(bndbox.find(ymin).text)) xmax int(float(bndbox.find(xmax).text)) ymax int(float(bndbox.find(ymax).text)) objects.append({name: name, bbox: [xmin, ymin, xmax, ymax]}) return {filename: filename, width: width, height: height, objects: objects} # 用法示例解析单张标注文件 voc_data parse_voc_xml(JPEGImages/street_night_0001.xml) print(voc_data[filename], voc_data[objects])这段写法是常规解析VOC标注的方式。ET.parse加载整个xml树root.iter(object)遍历所有目标节点兼容单目标和多目标的情况。注意xmin/ymin/xmax/ymax读出来是像素坐标后续想转成YOLO格式需要除以图片宽高做归一化。参数上唯一要留意的是坐标值可能带小数点所以加了float再转int老版本labelimg出来的坐标是整数但某些增强工具改过的xml会有浮点值直接强转会丢精度。3.3 读COCO格式从json里提取类别与框信息import json def parse_coco_json(json_path): with open(json_path, r, encodingutf-8) as f: coco_data json.load(f) id2name {cat[id]: cat[name] for cat in coco_data[categories]} img_id2info {img[id]: img for img in coco_data[images]} result [] for ann in coco_data[annotations]: img_info img_id2info[ann[image_id]] x, y, w, h ann[bbox] result.append({ image: img_info[file_name], category: id2name[ann[category_id]], bbox: [x, y, x w, y h] }) return result instances parse_coco_json(annotations/instances_train2017.json)COCO格式里最容易踩坑的点是bbox存储方式COCO的bbox是[x, y, width, height]左上角坐标加宽高不是VOC那种[xmin, ymin, xmax, ymax]的右下角坐标。转换的时候要自己加回xw和yh。上面的代码里我特意在返回结果里把两种表示都处理了输出的是VOC风格坐标方便后续统一处理。3.4 读YOLO格式最常用也最容易读错def parse_yolo_txt(txt_path, img_width, img_height): boxes [] with open(txt_path, r, encodingutf-8) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) cx float(parts[1]) * img_width cy float(parts[2]) * img_height w float(parts[3]) * img_width h float(parts[4]) * img_height xmin int(cx - w / 2) ymin int(cy - h / 2) xmax int(cx w / 2) ymax int(cy h / 2) boxes.append({cls_id: cls_id, bbox: [xmin, ymin, xmax, ymax]}) return boxesYOLO格式的核心逻辑是归一化坐标txt里存的cx、cy、w、h都是0~1之间的小数乘以图片实际宽高后才得到像素坐标。读的时候要注意两点一是类别id从0开始计数不是从1二是归一化除法用的是图片宽高不是宽高取整后的缩放值。这段代码把归一化坐标还原成像素坐标方便画框检查标签质量。4. YOLO11三平台一键训练GPU/CPU/Mac的配置与实战4.1 环境准备与数据目录组织这套资源附带YOLO11训练脚本支持GPU(GPUs)、CPU、Mac(M芯片)三种平台。拿到手第一步不是跑脚本而是把数据目录调整成YOLO11能直接识别的结构。YOLO系列训练前需要把图片和标签按固定目录组织好并且贴好训练集和验证集的划分。datasets/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 训练集txt标签 │ └── val/ # 验证集txt标签 └── data.yaml # 数据集配置文件脚本里一般会自带数据划分逻辑但如果脚本没有做这个动作我习惯自己用sklearn的train_test_split或者简单的random.shuffle来分。划分比例按8:2走训练集4000张、验证集1000张就够了夜间场景类别单一不需要太多验证样本。划分的时候记得固定随机种子保证每次划分结果可复现不然调参时对比基线会很痛苦。4.2 data.yaml配置与关键参数解读path: /absolute/path/to/datasets # 数据集根目录绝对路径 train: images/train val: images/val nc: 1 names: [person]这个配置文件的坑集中在两点path必须写绝对路径写相对路径时YOLO11会根据当前工作目录解析换目录跑必报错nc是类别数这里只有person一个类写1就行但如果你后续加入白天数据合并训练并新增了类别必须同步改。names列表顺序要和txt标签里的类别id一一对应id0对应names[0]这个顺序错了模型不会报警但预测出来的名字会全错。4.3 三平台训练方式与参数差异GPU(GPUs)平台训练是主力场景默认脚本会自动检测CUDA可用性并切到GPU执行。关键参数我一般这样设epochs100起步夜间数据集5000张图量不算大100轮足够收敛batch-size16如果显存是8G16比较稳妥显存12G以上可以尝试32imgsz640是YOLO11的默认输入尺寸监控画面通常是1920x1080缩到640会丢一部分小目标细节但考虑到夜间行人目标本身不算小640和训练速度之间的平衡是划算的。CPU平台训练没有太多玄学纯靠算力硬吃。batch-size要降下来我一般建议8以下workers0也建议设置一下避免数据加载子进程在CPU训练时抢占计算资源。有一个实用的替代方案CPU机器上可以先用--amp开启混合精度训练YOLO11的amp模式在CPU上能省不少时间代价是loss曲线波动会稍微大一点但最终收敛效果差别不大。Mac(M芯片)平台走的是mps后端Apple Silicon的GPU加速能力近年已经能支撑中等规模的检测训练。M1/M2芯片跑YOLO11batch-size保守取8imgsz降到480也可以毕竟Mac训练通常是验证用最终训练还是会回到GPU上执行。脚本里对这三类平台的识别逻辑一般是先探测torch.cuda.is_available()再探测torch.backends.mps.is_available()都不可用才落到CPU这套检测顺序是合理的。# GPU平台训练最常见 python train.py --data data.yaml --weights yolov11n.pt --epochs 100 --batch-size 16 --imgsz 640 # CPU平台训练无CUDA环境 python train.py --data data.yaml --weights yolov11n.pt --epochs 50 --batch-size 8 --imgsz 640 --device cpu --workers 0 # Mac M芯片训练mps后端 python train.py --data data.yaml --weights yolov11n.pt --epochs 50 --batch-size 8 --imgsz 480 --device mps命令里--weights参数可以填YOLO11官方预训练权重比如yolov11n.pt或yolov11s.pt预训练权重提供了COCO数据集的通用特征抽象基础从预训练权重开始微调比从零训练收敛快得多这也是YOLO系列训练的标准做法。如果拉取权重时网络受限也可以直接不填--weights跑从零训练但效果会差一截夜间小目标检测更明显。5. 夜间行人模型训练避坑现象、原因与解决办法5.1 坑一loss曲线前期剧烈震荡甚至训练中断现象训练前10轮loss上下跳动幅度很大偶尔出现loss变为nan的情况日志里还能看到某些batch的检测框数量异常多。原因夜间图像对比度低部分图片存在过曝区域和纯黑区域这些区域的梯度信号极不稳定。加上严重遮挡样本的标注框在图像上分布不均个别batch里全是困难样本导致loss异常。还有一个常见原因是标签坐标出现了越界值——归一化坐标不在0~1范围内网络输出层计算损失时直接爆炸。解决先用数据检查脚本把所有txt标签的内容扫描一遍过滤坐标不在[0,1]区间内的样本。对夜间图像做直方图均衡化或CLAHE增强提升对比度后再进训练。训练参数上把--warmup-epochs从默认值稍微调大比如设成5轮让学习率平滑爬升。如果单张图里遮挡目标特别多可以考虑在训练时启用mosaic增强但注意mosaic在严重遮挡场景下会生成更多难例建议配合--mosaic 0.5控制增强概率。5.2 坑二白天验证集上指标正常夜间测试集上漏检严重现象训练结束后模型在验证集夜间数据上mAP达到85%以上但拿到真实夜间监控视频上跑漏检目标明显增多尤其是远离摄像头的小尺寸行人。原因数据集划分不够仔细。如果验证集和训练集来自同一批场景的随机划分那么验证集里的夜间街景和训练集高度相似模型相当于背题了。真实场景的摄像头安装角度、光照条件、分辨率与训练数据存在分布偏移。解决按场景而不是按图片划分数据集。把夜间街景、夜间道路、夜间遮挡这几批数据分别打散训练集和验证集各保留不同场景的组合。另一个实用做法是训练完成后做一次夜间视频实拍测试录10秒真实监控画面用训练好的模型逐帧跑一遍统计漏检和误检数量。这一步没有捷径只能说提前预留验证视频比事后返工省时间。5.3 坑三batch-size设太高导致显存溢出现象训练启动时报CUDA out of memory或者跑了几轮之后显存被占满直接进程被杀。原因YOLO11的显存占用峰值通常在forward和backward两个阶段batch-size设16在8G显存上可能刚好卡在边界但夜间图像经过mosaic增强后输入尺寸会临时变大加上同一批里有多张含遮挡目标的图计算图更复杂显存就爆了。解决一个稳妥的做法是先跑一个batch看显存占用再决定最终batch-size。具体操作是把batch-size设成4预跑10步观察日志里的显存占用数字按比例估算batch-size 16的占用是否安全。另一种省显存的方式是开启梯度累积--batch-size 8 --accumulate 2等效于显存只装8张图但梯度按16张图累积更新效果接近batch-size 16显存压力小一半。我日常训练8G显存卡都用这个组合。5.4 坑四Mac平台训练时mps后端报错或速度极慢现象在Mac M芯片上跑脚本提示mps不可用或者训练速度比CPU还慢。原因部分PyTorch版本的mps后端对特定算子如某些注意力机制实现支持不完整遇到不支持的算子会回退到CPU执行速度反而更慢。另外Mac的内存是统一架构GPU和CPU共享内存batch-size设太大时内存换页开销会拖慢整体速度。解决训练前先跑验证命令python -c import torch; print(torch.backends.mps.is_available())返回False就升级PyTorch版本到2.2以上。如果mps可用但速度异常把batch-size降到4--workers 0并且关闭mosaic增强减少复杂算子的调用频率。我在Mac上跑过这个脚本batch-size 4、imgsz 480的情况下3000张训练集100轮大约7小时属于可接受范围但更推荐Mac只做代码验证和调试正式训练还是交给GPU服务器。6. 训练日志怎么读从loss到mAP的验证习惯6.1 训练日志里真正需要盯的三个指标YOLO11训练结束后会输出result.png和result.csv包含box_loss、cls_loss、dfl_loss和mAP50、mAP50-95等指标。我盯的顺序是先看box_loss有没有持续下降到稳定平台再看mAP50有没有超过某个经验阈值单类别夜间行人mAP50 0.8以上算合格最后看验证集上的precision和recall是否均衡。如果recall高但precision低说明模型是宁错杀不放过的风格监控场景下误报会让人工复核那边叫苦反过来precision高但recall低说明模型漏检多安防场景漏检比误报更致命。这个平衡没有统一标准取决于你业务里守夜班的人更烦哪一种。我自己的习惯是优先保证recall因为监控视频丢了目标后续无法回溯误报还能靠后处理过滤。6.2 训练完成后的一次强制自检训练结束后别急着部署按这个顺序做一次完整自检第一从验证集里随机挑30张图用predict模式跑一遍把推理结果画框存下来和真值并排看检查框的贴合度和漏检情况第二找一段没有出现在训练集中的夜间视频流低光照、部分遮挡这些困难场景逐帧跑推理统计单帧推理耗时和漏检率第三用export功能把模型导出成ONNX格式再用ONNX Runtime在CPU上跑一遍相同视频确认导出后精度没有明显掉点。夜间的单帧推理耗时目标不要超过60ms折算下来是16FPS监控场景够用了。如果导出ONNX后精度掉得厉害一般原因有两个模型的iou阈值默认设置导致的NMS差异以及输入图像的归一化方式不一致导出脚本里的反归一化参数要和训练时保持一致。这套自检流程走完模型才算从训练完成变成了可以上线。提示resource里的训练日志记录了博主在GPU上的完整训练过程和最终mAP数据可以作为你自己的模型收敛速度参照。如果你的设备条件相近但mAP差距超过10个点优先检查数据和参数配置是否照搬到位。从那以后我每次拿到新数据集都会先花半小时做标签格式抽检再翻训练日志看前10轮的收敛节奏确认无误才把训练任务挂到后台。这套流程救了我至少三次——有一回就是标签里混了非person类别的框靠抽检拦住了没浪费一整夜训练时间。希望这个习惯对你也有用拿到数据先检查再训练比跑通了再回头找问题高效得多。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进