
简介面向夜间低光环境下的行人检测任务这份数据集汇集一千张真实场景高质量图片覆盖夜间街景、道路、遮挡及严重遮挡等多种行人形态相比常规通用数据集更贴近安防监控实际可有效弥补监控场景通用行人检测在夜间数据上的不足适合算法研究、模型微调及实际项目落地。资源包以PDF形式交付共含一个文件约6.09MB内部包含数据集整体介绍、标注样例以及百度网盘专属获取指引。所有图片均使用LabelImg工具人工标注同时提供VOCxml、COCOjson、YOLOtxt三种通用格式标签可直接对接主流检测框架免去繁琐的标注格式转换。随附的YOLO11一键训练脚本支持GPUGPUs、CPU及MacM芯片三种平台内置多平台配置并附有博主实际训练结果日志便于快速复现与调参参考。目前已有一千六百四十三人学习下载适合目标检测方向的学生、算法工程师和夜间视觉应用开发者使用。1. 夜间行人目标检测这个数据集和脚本组合解决的是“最后一公里”问题夜间行人检测是目标检测里最不缺论文、最缺可用数据的方向之一。白天模型在Cityscapes上跑得再漂亮到了夜间监控画面里行人往往只剩一小团模糊的亮斑加上路灯色温不均、车灯过曝、暗部噪声漏检率能比白天翻一倍。这1000张夜间行人图的价值不在数量而在它逼着你把训练脚本、标签格式、设备适配一次全跑通。标题里的VOC/COCO/YOLO三种格式对应的是工程里最常见的三种标注交换格式GPU/CPU/Mac三平台脚本则解决了团队里有人用Windows显卡、有人用MacBook、有人只有纯CPU服务器时的协作问题。这套组合适合正在做安防巡检、辅助驾驶、园区机器人避障的工程师——你需要的不只是模型精度而是一套从标注格式到训练命令都能复现的链路。接下来我按自己实际落地这套方案的顺序把数据格式差异、脚本参数、踩坑记录一次讲完。2. 数据集与三种标签格式选型逻辑和转换脚本2.1 夜间行人标注为什么比白天更麻烦白天行人检测的难点在遮挡和尺度夜间则多了一个“目标与背景对比度极低”的维度。低照度下行人轮廓模糊标注员经常要借助连续帧来确认是不是人形这导致夜间数据集的标注一致性普遍不如白天数据集。拿到1000张图后我第一件事不是直接开训而是先抽查标签框和图像内容是否对齐——这类数据集里最容易藏的问题是部分框框住的只是路灯下的光斑不是行人本体。另一个实际问题是夜间图像的噪声分布。ISO拉高带来的彩色噪声、HDR合成产生的鬼影、运动模糊这些都会让训练时模型学到“纹理噪声”而非“行人特征”。所以我在预处理里加了很轻的中值滤波只对训练图生效验证集保持原样。注意滤波核不能大3x3就够否则行人边缘细节会被抹掉模型学到的特征会变钝。这一步不加也能训但加了之后验证集mAP通常能稳定涨1到2个点。2.2 VOC、COCO、YOLO三种格式的字段差异这三种格式本质是同一批框的不同序列化方式。VOC是单张图像一个XML文件用bndbox标签存坐标COCO是全局一个JSON按images、annotations、categories三张表组织YOLO则是每个图像一个TXT文件每行记录“类别 x_center y_center width height”且坐标全部归一化到0到1。很多新手直接拿VOC格式丢给YOLO训练报错后才发现YOLO不认XML得先转换。下表是我按自己使用习惯整理的字段对照。维度VOCCOCOYOLO存储粒度每图一个XML全局一个JSON每图一个TXT坐标表示xmin, ymin, xmax, ymax像素x, y, width, height像素x_center, y_center, width, height归一化类别定义name标签categories数组的id映射每行的第一个整数标注工具兼容LabelImgLabelme / CVAT导出LabelImg / CVAT / Roboflow易错点框超出图像边界没有单独类别文件时id对不上归一化坐标算错导致框偏移COCO和YOLO之间最容易踩坑的是坐标系的起点。COCO的x, y是框左上角YOLO的x_center, y_center是中心点两者转换时必须先加半宽半高再归一化少了任何一步框就会整体偏移。我见过有人用YOLO训练了50个epoch才发现框全往右上角偏了这就是坐标换算少了“加半宽”那一步。2.3 从VOC到YOLO的转换脚本只用标准库就能跑数据集自带三种格式按理说不需要转换。但实际场景里经常会遇到标注同事漏导出了一部分图你只拿到VOC格式的补标数据这时就需要自己转。下面这个脚本用Python标准库就能跑不需要装xmltodict之类的第三方包我用它处理过几万张图速度瓶颈纯粹在磁盘IO。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, class_names, output_path, img_width, img_height): 将单张VOC XML转为YOLO TXT class_names: [person] —— 类别列表顺序决定了YOLO类别id img_width/height: 原图尺寸从XML的size节点读取更稳 tree ET.parse(xml_path) root tree.getroot() # 优先从XML里取图像尺寸避免外部传入的参数不对 size root.find(size) if size is not None: img_width int(size.find(width).text) img_height int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue # 只保留我们关心的类别 class_id class_names.index(name) xmlbox obj.find(bndbox) xmin float(xmlbox.find(xmin).text) ymin float(xmlbox.find(ymin).text) xmax float(xmlbox.find(xmax).text) ymax float(xmlbox.find(ymax).text) # 计算中心点坐标宽高并做归一化 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 越界保护理论上边界框不会越界但夜间模糊标注偶尔会 x_center max(0.0, min(x_center, 1.0)) y_center max(0.0, min(y_center, 1.0)) w max(0.0, min(w, 1.0)) h max(0.0, min(h, 1.0)) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(output_path, w) as f: f.write(\n.join(lines)) # 批量转换示例 if __name__ __main__: xml_dir annotations/voc yolo_dir annotations/yolo os.makedirs(yolo_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue stem xml_file[:-4] voc_to_yolo( os.path.join(xml_dir, xml_file), [person], os.path.join(yolo_dir, stem .txt), img_width0, # 传0没关系脚本会从XML里读 img_height0 )脚本的逻辑核心在x_center (xmin xmax) / 2.0 / img_width这一行。(xminxmax)/2得到的是像素中心坐标再除以图像宽度才得到归一化值。如果你从COCO转记得COCO存的是左上角和宽高中心点得用x w/2来算。脚本里做了越界clip这是夜间标注的真实痛点——标注员在暗光下容易把框拖出图像边缘不做clip的话YOLO训练时偶尔报奇怪的坐标系错误。类别列表的顺序一定要和训练配置里的names保持一致否则会出现“模型以为检测的是person实际类别id对应的是其他类”的错位。3. 三平台GPU/CPU/Mac跑通YOLO11一键训练脚本拆解3.1 设备自动检测让脚本自己决定用CPU还是GPU所谓“一键训练”最核心的不是训练代码本身而是启动前的环境判断。YOLO11的Ultralytics库已经帮你封装了完整的训练流程真正的工程工作在“自动选设备”和“缺什么补什么”这两件事上。我一般会在脚本开头做一次硬件探测判断当前机器是NVIDIA GPU、Apple Silicon还是纯CPU然后据此设置设备参数。import platform import torch def detect_device(): 返回当前最优训练设备cuda - mps - cpu Mac用户别只看torch.backends.mps.is_available()要试跑一次 if torch.cuda.is_available(): return cuda if platform.system() Darwin: try: # 试跑一个极小的矩阵乘法确保MPS后端真的可用 a torch.zeros(8, 8, devicemps) b a 1 _ b.sum().item() return mps except Exception: pass return cpu device detect_device() print(f[INFO] 使用设备: {device})这里有个Mac用户常问的点torch.backends.mps.is_available()返回True不代表MPS一定好用。我遇到过PyTorch版本和macOS版本不匹配MPS后端在训练到第3个epoch时直接崩掉的情况所以脚本里加了一次“试跑”验证。不是每次训练前都跑而是环境变了才跑这个验证的耗时在1秒以内。纯CPU服务器上训练YOLO11不是不行但epoch数和batch size必须降。1000张夜间图如果你只有一颗8核CPUbatch8、imgsz640的情况下一个epoch大概要5到10分钟跑100个epoch就是一整夜。我的建议是CPU上先跑30个epoch验证loss能降再决定要不要租GPU——热词里那个“gpu租用”就是这么来的本地验证代码逻辑、云端跑正式训练这是最省钱的路线。3.2 一键训练脚本从数据集配置到训练启动下面是我常用的最小训练脚本适用于Ultralytics YOLO11的Python API。它做的工作包括加载数据配置、自动跳过一次下载预训练权重、启动训练。from ultralytics import YOLO def train(): device detect_device() # 数据配置文件data.yaml里写train/val路径、类别数、类别名 data_yaml datasets/night_person/data.yaml # 从YOLO11n开始训n是nano最快最省资源 model YOLO(yolo11n.pt) model.train( datadata_yaml, epochs100, batch16 if device cuda else 8, imgsz640, devicedevice, workers4, patience15, # 早停验证集指标15轮不涨就停 save_period10, # 每10轮存一次权重防止中途断掉 projectruns/night_person, nameyolo11n_v1, exist_okTrue, optimizerSGD, # 小数据集用SGD比AdamW更稳 lr00.01, # 初始学习率 mosaic1.0, # 夜间小目标多mosaic增强丰富背景 ) if __name__ __main__: train()逐行解释几个关键参数。patience15是早停轮数训练到第30轮时如果mAP50还不如第15轮的脚本会自动停止并保留最好权重这个参数能避免夜间数据集上常见的“过拟合后指标剧烈抖动”。save_period10是后悔药训练中断时不用从头再来直接加载第N轮的last.pt继续。optimizerSGD是我在千张级小数据集上的固定选择AdamW收敛快但泛化略差SGD在这个规模下更稳。batch参数的设置逻辑显存8GB的显卡用batch16、imgsz640显存4GB就降到8。Mac的MPS后端对batch的容忍度比预想的高M1 Pro 16GB内存跑batch8没有问题但M1基础版最好降到4。CPU上我只建议batch4到batch8之间再高内存带宽会成为瓶颈训练速度反而没有明显提升。3.3 三个必调参数batch、imgsz、epochs的相互制约这三个参数是影响训练结果最直接的因素它们不是孤立调节的。imgsz640是YOLO11的推荐输入尺寸但如果你的夜间图像里行人普遍较小像素高度小于32px可以考虑imgsz960模型会更容易学到小目标特征代价是显存需求按比例上涨。反过来如果标注框有大量越界或错位降低imgsz512反而能用“模糊化”掩盖部分标注噪声。epochs的设定和早停配合才有意义。固定跑200个epoch没有意义——夜间数据集的验证集指标常常在第80到120轮之间反复横跳你需要的是早停而不是硬跑。我给团队的默认值是epochs200、patience20实际上平均第90轮就停了。batch和epochs的关系是batch越小梯度噪声越大需要更多epoch才能收敛到同等水平。所以如果你因为显存限制把batch从16降到4建议epochs同时从100上调到150否则模型欠收敛。4. 夜间训练避坑最容易翻车的5件事与排查方法4.1 现象训练loss不降反升验证集mAP始终为0这是夜间数据集上遇到最多的“玄学”问题。我排查过三次三次都是数据配置的问题。第一次是类别id不连续——数据集的类别是[person, ciclist]但YOLO的类别id按字母序排变成了0: ciclist, 1: person模型学到的是“人”和“自行车”互换了。第二次是data.yaml里的nc类别数写成了2但标注TXT里出现了类别id 2训练时模型直接忽略这部分样本。第三次是标注坐标全部是整数值且未归一化模型看到的“框”全是1.0附近的异常值。解决方法是训练前先写段代码检查每个TXT里的类别id是否在[0, nc-1]范围内以及坐标值是否都在0到1之间。检查代码只用标准库几行就能跑完。这类问题最大的坑在于它不报错loss照样下降只是mAP永远为0——等到训练完推理时才暴露。4.2 现象验证集mAP正常但实际视频里漏检严重这个现象一度让我怀疑是“玄学”后来排查发现是夜间图像的光照分布问题。数据集的1000张图里有800张是路灯照明良好的场景只有200张是暗光场景。训练时模型学会了“有路灯才有人”到真正的暗光监控画面里自然漏检。这不是模型问题是数据集分布问题。解决思路有两个。第一是训练时把mosaic增强提高到1.0让模型在训练中看到更多拼接背景。第二是推理时做分阶段阈值检测置信度阈值从默认的0.25降到0.1再用后续跟踪算法过滤误检。我一般会保留两套权重——一套标准阈值用于白天一套低阈值的用于夜间。这个习惯帮我避开了不少“mAP好看、实战翻车”的尴尬。4.3 现象Mac上CPU训练到一半内存爆掉进程被杀Apple Silicon的Mac用mps后端训练内存占用规律和NVIDIA显卡完全不同。显卡显存不够时会报OOM但Mac是统一内存进程占用过高会触发系统级的强制清理。我遇到过最夸张的一次是batch8, imgsz640在16GB内存的M1上worker8直接吃满内存然后系统把Python进程杀了。原因有两个workers开太多数据加载线程各复制一份图像数据mps后端对内存的管理不如CUDA成熟有些中间张量不会被及时回收。解决办法是workers2、batch4同时设置pin_memoryFalseMPS后端不支持开了反而报警告。如果内存还是紧张把imgsz降到480精度损失在夜间场景下其实不大——因为标注框本身的定位精度也就在10个像素以内。4.4 现象训练时秒报错提示标签文件找不到或格式错误“秒报错”其实是好消息至少问题暴露得早。常见原因有三个data.yaml里的train和val路径是相对路径但脚本的工作目录不在数据集根目录下标签和图片文件名不一致某个JPG没有对应的TXTTXT里的某个坐标算成了负数。这类问题本质是“数据集完整性”问题我建议训练前先跑一遍完整性检查能在30秒内找出所有问题。另一个容易忽略的点是YOLO11的训练脚本会自动过滤掉没有标注文件的图片但这意味着如果你的某张夜间图恰好没有行人它不会贡献任何训练信号。1000张图里如果混了几十张无标注图模型会在这几十张图上随机猜测影响很小但如果占比超过20%验证集指标会明显虚高——因为没行人的图被当成“全背景”预测对了。4.5 现象GPU利用率只有10%训练速度比CPU还慢这通常是“数据加载跟不上GPU”造成的。夜间图像文件普遍偏大夜间监控截图经常是2K甚至4K磁盘读取加解码的时间超过了GPU计算时间GPU只能干等。排查方法是看训练日志里的Rate和time字段如果time远大于计算时间瓶颈就在IO。解决思路很直接workers8用多线程预加载图片尺寸在标注时统一缩放到1280以内再存盘不要用原图4K直接训练把数据集放到NVMe固态盘上。机械硬盘上训练深度学习模型基本是在浪费电这是最不值得的省钱。另一个隐患是Windows平台的num_workers要谨慎Windows的进程创建机制和Linux不同workers8可能导致数据加载反而更慢我一般Windows上只用4。5. 夜间行人检测的进阶优化数据增强、小目标策略与多模态思路5.1 夜间数据增强亮度扰动和噪声注入的边界在哪YOLO11内置了hsv_h、hsv_s、hsv_v这三个HSV抖动参数默认值分别是0.015、0.7、0.4。夜间场景我建议把hsv_v亮度调到0.6以上——夜间不同地段的路灯光照差异极大更强的亮度扰动能让模型对光照不敏感。但注意别调过头hsv_v 0.8时图像会频繁在“过曝”和“全黑”之间横跳模型反而学了噪声。噪声注入方面Ultralytics没有内置高斯噪声参数我一般用一个小技巧在训练脚本里对dataset的load_image函数做monkey patch在返回图像前加一点高斯噪声。这个做法适合对图像质量要求高的场景也可以在推理时做Test-Time AugmentationTTA即把输入图像加噪声后再推理一次两次结果做加权平均。代价是推理时间翻倍但对夜间漏检的改善是立竿见影的。如果你不想改源码直接在训练参数里用erasing0.4做随机擦除也可以这个参数对夜间行人被遮挡的场景非常有效。5.2 小目标检测夜间行人的“矮小”难题夜间行人检测的核心难点之一是小目标。1000张图里的行人框如果大量小于32x32像素YOLO11n的默认anchor设计会显得力不从心。这里有两个工程化手段值得优先尝试。第一个是图像切分。把原图切成四块重叠的patch分别推理后再把框映射回原图坐标。这个方案不需要重新训练推理时直接用就行但小目标漏检率能降低30%以上。切分时注意重叠区域要设置10%到20%的overlap否则目标恰好在切缝上时会直接漏掉。第二个是基于anchor的调整——不过YOLO11取消了手工anchor改用无anchor的检测头所以这条路已经走不通了重点放在切分和增大imgsz上就够了。5.3 针对夜间行人增强的多模态思路红外、热成像与光流融合说实话如果是做安防或车路协同场景纯可见光的夜间行人检测是有物理上限的——低照度下信息确实不足。常见做法是引入红外或热成像或者用视频序列的帧间光流辅助。如果你只有这1000张可见光图可以考虑人造红外效果把图像转到YUV空间后增强Y通道亮度模拟灰度红外图作为第二个输入分支。用YOLO11做多模态融合不需要改模型结构。两种做法一是“决策级融合”两个模型分别跑可见光和红外框做IoU合并二是“数据级融合”把红外图当成第二张输入和可见光图拼接成四通道输入——但YOLO11原生只支持三通道得改源码的ch参数比较麻烦。对新手我更推荐决策级融合简单、可解释、两个模型可以独立迭代。热词里提到“基于yolo目标检测多模态ai分析的交通事故检测系统”思路也是这么落地可见光检测行人夜间的场景再叠加红外或热成像确认最后用规则或跟踪算法输出结果。没必要一开始就上端到端的多模态模型工程化周期会拉长很多。6. 一个验证技巧用夜间视频帧做滑窗实测别只看mAP训练结束后mAP在验证集上再漂亮也替代不了“在真实视频里看效果”这一步。我吃过一次亏模型在验证集上mAP0.5达到了0.85我直接拿去部署结果在现场视频里每隔十几帧就漏掉一个暗处的行人问题出在验证集图像和实际视频的光照、视角度、运动模糊根本没对齐。从那以后我养成了一个习惯——任何模型上线前先拿一段真实视频做滑窗推理。具体做法是用ffmpeg从视频里按固定间隔抽帧比如每秒抽1帧抽200帧够看然后用训练好的模型批量推理把每帧的检测框按置信度阈值画出来最后把“行人被照亮”“行人处于暗角”“行人正走入车灯光柱”三种典型场景的帧挑出来单独看。这一步能同时检验模型在连续场景下的稳定性。# 从视频里按每秒1帧抽帧命名带时间戳方便回溯 ffmpeg -i night_video.mp4 -vf fps1 -q:v 2 frames/frame_%04d.jpg # 用Ultralytics CLI批量推理-S保存每帧的检测结果图 yolo predict modelruns/night_person/yolo11n_v1/weights/best.pt \ sourceframes/ \ conf0.25 \ saveTrue抽帧频频不能太高1秒1帧足够看出趋势抽太多反而淹没在数据里。推理完后重点不是看平均置信度而是看连续几帧的检测框稳定性——同一个行人前后两帧框的位置是否连贯。如果模型在第1帧检测到、第2帧丢失、第3帧又检测到这说明该目标的特征在可见光下不稳定单靠模型本身的改进空间有限需要引入跟踪算法如ByteTrack或上面说的多模态方案。置信度阈值的设定也要基于这段视频实测来做。验证集上0.25的阈值看起来合理但真实视频里夜间误检率会偏高路灯反光的车窗经常被当成行人。我一般在实测时先跑一遍conf0.25的版本统计输出的所有框的置信度分布再结合业务对漏检和误检的容忍度把阈值调到0.35到0.5之间。这是参数调优里最容易被忽略的一步——验证集上的最佳阈值换个场景就得重新标定。最后说一个训练时总结出的习惯每次训练完我会把results.csv里的val_mAP50_95和val_mAP50一起看而不是只看mAP50。夜间小目标多mAP50高、mAP50_95低说明模型对大目标检测好、小目标检测差这对夜间行人来说是致命的。如果这个差值超过0.15我就知道要回头补数据或者调小目标策略了。这套流程走下来我不能保证你的模型一定能达到某个精度指标但能确保它在你自己的夜间场景里不再“翻车”。希望帮到你。本文还有配套的精品资源点击获取