☰
夜间行人检测数据集与YOLO11三平台训练脚本实战
2026/9/30 4:10:17 网站建设 项目流程

简介:面向夜间低光环境下的行人检测需求,这份资源整合了5000张真实场景图片的完整数据集,覆盖夜间街景、道路、遮挡及严重遮挡行人等多样情况,既能支撑公共场所监控场景的夜间行人检测项目,也可作为通用行人检测模型的夜间补充数据。资源采用labelimg工具标注,提供VOC(xml)、COCO(json)、YOLO(txt)三种标准格式标签,检测框与类别信息一应俱全,省去格式转换成本,可直接接入YOLO等检测框架训练;同时附赠YOLO11一键训练脚本,支持GPU(GPUs)、CPU、Mac(M芯片)三平台运行,并附带博主训练结果日志供效果参考,便于对照调参。下载包共1个文件,为PDF说明文档,大小6.07MB,内含数据集情况介绍、缩略图、标注截图及百度网盘获取方式,适合先了解数据规模与场景样例再完整下载。目前已有611人学习下载,适合从事目标检测、安防监控或低光视觉研究的开发者快速上手并复用。

1. 夜间行人检测为什么难,这套数据集和脚本到底给了你什么

夜间行人目标检测是目标检测里最容易被低估的场景。白天跑得好好的模型,一放到路灯昏暗、行人只有几十像素的监控画面上,漏检率直接飙升。你要解决的不是“检测”本身,而是低照度下的可见性、小目标分辨率、以及标签格式和训练环境带来的附加成本。这套东西的构成很直接:5000张夜间行人图,每张都带VOC、COCO、YOLO三种格式标签,另外配一个能在GPU(GPUs)、CPU、Mac三平台跑的YOLO11一键训练脚本。它适合安防监控、辅助驾驶和智慧交通方向的工程师,也适合刚入门目标检测但不想把时间耗在标注和格式转换上的人。下面我按“数据怎么组织、脚本怎么跑、参数怎么调、坑在哪”这个顺序,把这套方案拆开讲。

2. 5000张夜间行人图,数据从哪来、怎么组织才不会训练翻车

2.1 数据集目录结构与训练前的检查清单

拿到数据集先别急着训练,先看目录。常见做法是images和annotations分开,训练集、验证集、测试集按比例切好。例如:

night_pedestrian/ ├── images/ │ ├── train/ # 约4000张 │ ├── val/ # 约700张 │ └── test/ # 约300张 ├── annotations/ │ ├── voc/ # 每个图像同名.xml │ ├── coco/ # coco.json │ └── yolo/ # 每个图像同名.txt └── data.yaml # YOLO训练入口配置

这里为什么把annotations分成三种格式?因为YOLO11原生吃YOLO txt,但很多公开标签是VOC XML或COCO JSON。你如果只做YOLO训练,可以只用yolo目录;但如果要跑Faster R-CNN、Mask R-CNN或做对比实验,就要用到VOC和COCO。这三种格式并不是等价货,坐标体系、类别定义、难点都不一样。

训练前我一般会跑一个快速检查脚本,统计每张图的标签数量、标注框面积、是否有空标注文件。这个脚本比训练本身更重要,因为很多翻车都是从这里开始的。比如一个常见问题是训练集里混入了几张没有任何标注的负样本,YOLO的loss会变奇怪;另一个问题是标注框坐标出现负数或超出图像边界,训练时数据增强会崩。

我这里给一个简单的检查脚本框架:

import os import glob img_dir = "images/train" yolo_dir = "annotations/yolo" empty_txt = [] for img_path in glob.glob(os.path.join(img_dir, "*.jpg")): stem = os.path.splitext(os.path.basename(img_path))[0] txt_path = os.path.join(yolo_dir, stem + ".txt") if not os.path.exists(txt_path) or os.path.getsize(txt_path) == 0: empty_txt.append(img_path) continue with open(txt_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: empty_txt.append(img_path) break _, cx, cy, w, h = parts if float(cx) < 0 or float(cx) > 1 or float(w) <= 0: empty_txt.append(img_path) break print("异常文件数:", len(empty_txt)) for p in empty_txt[:10]: print(p)

这段脚本做的事情是按图像文件名找同名txt,读每一行的5个值,判断归一化坐标是否在0到1之间、宽高是否大于0。注意YOLO格式是class_id cx cy w h,cx、cy是中心点归一化坐标,不是左上角坐标。如果你发现很多异常文件,大概率是格式转换时把坐标算错了,而不是数据本身坏了。

说到这里就要提一个关键点:5000张图说多不多,说少不少。夜间行人目标往往很小,标注框可能只有10×20像素。如果imgsz默认640,小目标会被压缩得更小,所以后面我们会主张把imgsz调到960甚至1280。但调高imgsz要看显卡显存,训练时间也会变长,这个在第4章参数表里会再讲。

2.2 VOC/COCO/YOLO三种格式的差异与为什么都要给

VOC格式是每个图像一个XML文件,标签结构大致是object > name, bndbox > xmin, ymin, xmax, ymax,坐标是像素绝对值。COCO格式是所有标注集中在一个JSON文件里,用images、annotations、categories三个数组组织,标注框是[x, y, width, height],左上角加宽高。YOLO格式是每个图像一个txt文件,每行class_id cx cy w h,cx和cy是相对图像宽高的比例,w和h也是相对宽高的比例。

三种格式的转换是最容易埋雷的地方。网上流传的转换脚本很多,但经常忽略“图像实际宽高”和“XML里记录的宽高”不一致的边界情况。比如有些标注工具在XML里写width="1920",但图像实际尺寸是1280×960,转换脚本如果直接读XML里的width,坐标会整体偏移或缩放错误。正确做法是用PIL或cv2重新读取图像尺寸,再拿这个尺寸做归一化。

下面这段是VOC XML转YOLO txt的参考实现:

import os import glob import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_names, out_txt): with Image.open(img_path) as img: img_w, img_h = img.size tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(name) bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 像素坐标转归一化中心坐标 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪到[0,1]防止越界 cx = max(0, min(1, cx)); cy = max(0, min(1, cy)) w = max(0, min(1, w)); h = max(0, min(1, h)) lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_txt, "w") as f: f.write("\n".join(lines))

逻辑说明:先读图像真实宽高img_w, img_h,再解析XML的四个角坐标,用(xmin+xmax)/2和(ymin+ymax)/2算中心点,再用宽高归一化。最后做一次裁剪,防止标注越界导致后续训练数据增强报错。参数说明:class_names是类别列表,要按你的标签名字排序,比如class_names = ["pedestrian"],这个顺序必须和data.yaml里的names一致;out_txt是输出路径。如果你只有单类,上面逻辑没问题;如果有多类,注意类别ID从0开始,不是从1开始。

2.3 一套数据跑三种格式:用COCO JSON统一管理标签的转换思路

上面讲了VOC转YOLO。如果手头是COCO JSON,我一般会先转成COCO的中间结构,再生成YOLO。原因很简单:COCO JSON里包含images的file_name、width、height,还有annotations里的bbox和category_id,信息最全,不容易丢。转换时只要遍历annotation,把[x, y, w, h]改成归一化中心点即可:

import json import os def coco_to_yolo(coco_json, out_dir): with open(coco_json) as f: data = json.load(f) id_to_name = {cat["id"]: cat["name"] for cat in data["categories"]} for img in data["images"]: img_id = img["id"] img_w = img["width"] img_h = img["height"] lines = [] for ann in data["annotations"]: if ann["image_id"] != img_id: continue x, y, w, h = ann["bbox"] cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h nw = w / img_w nh = h / img_h cat_id = ann["category_id"] lines.append(f"{cat_id - 1} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") with open(os.path.join(out_dir, img["file_name"] + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:这段代码的关键是cat_id - 1,因为COCO的类别ID一般从1开始(比如人=1),而YOLO要求从0开始。如果你的COCO JSON里类别ID已经是从0开始,就别减。参数说明:coco_json是输入文件,out_dir是输出目录,输出文件名用图像的file_name茎干名。这个脚本只做了单向转换,实际项目中我建议保留COCO JSON作为主标注,VOC和YOLO都从它生成,这样改标签时不用同步改三份。

还有一个容易被忽略的是数据划分。很多数据集给的是train/val/test已经分好的目录,但如果标签格式是你自己转换的,你重新划分数据时一定要同步移动同名文件,不能只移动图片不移动标签。我见过有人用脚本随机抽20%图片做验证集,结果没把对应txt移过去,训练时YOLO自动忽略没有标签的图片,导致训练集变小、验证集没有标签可评估。这种翻车非常隐蔽,因为YOLO训练日志里不会主动报错,只会打印“Scanning labels”时跳过。

如果你要自己划分,最简单的做法是生成一个data.yaml,指向train、val、test的图片目录,YOLO会自动去同名目录下找标签。Ultralytics的约定是:图片在images/train/xxx.jpg,标签就在labels/train/xxx.txt。如果Annotations和Images分开放,你需要在训练脚本里把标签路径也映射好,或者干脆把标签文件复制到labels目录。我们这套数据集里面已经按YOLO习惯放好了,data.yaml里只需要写:

path: /path/to/night_pedestrian train: images/train val: images/val test: images/test names: 0: pedestrian

注意path最好写绝对路径,因为一键脚本在Mac和服务器之间切换时,相对路径容易因为工作目录不同而找不到数据。这一点在跨平台训练时特别重要,Mac上路径是/Users/xxx/...,服务器上是/home/ubuntu/...,写死在data.yaml里会好排查。

以上,我们把这套数据的目录结构、三种格式差异和转换脚本都讲了。下一章进入YOLO11训练脚本本身。

3. 用YOLO11训练夜间行人模型:一键脚本背后的参数与硬件调度

3.1 YOLO11模型选型与网络结构里的关键点

YOLO11是Ultralytics系列的目标检测模型,延续了C3K2、SPPF、C2PSA这类结构,适合在中小数据集上快速出效果。对于夜间行人这种任务,我一般不会上来就选最大的YOLO11x,而是先在YOLO11n和YOLO11s之间试一轮。原因很简单:夜间行人目标小,模型容量大不一定能提升小目标召回,反而更容易过拟合到噪声背景。YOLO11n只有几百万参数,跑一轮很快,能摸清数据baseline;YOLO11s在精度上有小幅提升,但训练时间会变成两倍左右。

如果你想把小目标检测做得更细,可以关注YOLO11的检测头输出层数和特征融合。默认YOLO11从8倍、16倍、32倍下采样特征图输出三个尺度。夜间行人目标往往在8倍特征图上才能保留足够信息,如果目标太小,三个尺度都不够用,就需要加一个4倍下采样的P2检测头,或者用一些基于YOLO11的小目标增强模块。这属于模型改进,不是必选,但对做比赛或论文复现的读者可能是刚需。训练脚本里我们先用官方结构跑通,你想要加P2头,改动点在模型配置文件的head部分,后面可以单独写一篇。

3.2 一键训练脚本:自动识别平台并调用YOLO11

标题里说支持GPU(GPUs)/CPU/Mac三平台,真正实现起来不是单靠Ultralytics就能自动选设备的。Ultralytics默认device=0表示用第一张CUDA显卡,device='cpu'用CPU,device='mps'用Apple Silicon的Metal加速。我们要做的训练脚本,核心是让用户不需要关心该填哪个值,脚本自己判断。

这里给一份我常用的训练启动脚本,基于Python编写,既能在Windows直接在命令行运行,也能在Linux服务器和macOS上跑:

import platform import torch from ultralytics import YOLO def detect_device(): if torch.cuda.is_available(): return "0" # 使用第一张/主GPU elif platform.system() == "Darwin" and torch.backends.mps.is_available(): return "mps" else: return "cpu" if __name__ == "__main__": device = detect_device() print(f"[NightPedestrian] 当前训练设备: {device}") model = YOLO("yolo11n.pt") # 可以换成 yolo11s.pt model.train( data="data.yaml", epochs=100, imgsz=960, batch=16, device=device, project="runs/night_pedestrian", name="yolo11n_960", amp=True, patience=20, save_period=10, )

逻辑说明:detect_device里第一优先级是CUDA,第二是苹果的MPS,第三是CPU。torch.backends.mps.is_available()只会返回True,前提是你在Mac上装了支持MPS的PyTorch。训练参数里data="data.yaml"指向上一章的数据配置文件;imgsz=960是针对小目标调的输入尺寸;patience=20是早停轮数,模型连续20轮没提升就停,防止把时间耗在过拟合阶段;save_period=10表示每10轮存一次中间权重,虽然占磁盘,但对夜间场景的调参很有用,可以回看哪一轮的置信度更稳定。

这里要解释为什么用yolo11n.pt而不是直接用模型配置文件。用预训练权重做初始模型,可以继承COCO上学习到的通用特征,对夜间行人这种下游任务来说收敛更快。如果你的GPU显存够大,想冲更高精度,把yolo11n.pt换成yolo11s.pt或yolo11m.pt即可。注意第一次运行会从Ultralytics拉取权重,如果你在离线环境,需要提前手动下载权重文件放到项目目录,脚本里改成YOLO("weights/yolo11n.pt")。

3.3 GPU/CPU/Mac三平台的依赖安装与运行条件

三平台训练最麻烦的不是训练本身,而是环境配置。GPU(GPUs)平台要求NVIDIA显卡、CUDA驱动、PyTorch的CUDA版本匹配;CPU和Mac平台主要是慢,但能跑,YOLO11的依赖也一样。常见做法是用pip install ultralytics,它会自动安装一个CPU版或当前环境的PyTorch。注意Mac上如果直接pip安装,默认可能是CPU版PyTorch,MPS后端不生效,训练起来比CPU快不了多少。你需要在安装PyTorch时选择MPS支持的版本。

我给一个最简单的环境确认命令:

python -c "import torch; print(torch.__version__); print(torch.backends.mps.is_available())"

在Mac上如果输出True,说明MPS可用;在Linux上检查CUDA用:

python -c "import torch; print(torch.cuda.is_available())"

如果这条命令输出False,说明PyTorch装成了CPU版,需要到官网选对应的CUDA版本重装。这个过程是yolo11环境配置里最常见的坑,很多人训练时报错“Torch not compiled with CUDA enabled”,然后以为是显卡坏了,其实是驱动和torch版本不匹配。

CPU平台训练不是不能跑,而是要认命。同样5000张图、imgsz=960、epochs=100,在RTX 4090上大约几小时,CPU可能跑一天一夜。我的建议是CPU只做调试,把epochs设成10,imgsz设成640,先确认数据管道跑通。如果数据没问题再切到GPU上做正式训练。Mac的MPS速度介于CPU和主流GPU之间,小模型完全够用,但有些YOLO11运算在MPS后端还没有全支持,遇到不支持的算子,Ultralytics会报错。我的经验是安装Ultralytics版本越新,MPS兼容性越好,但训练时最好也用amp=False,因为AMP和MPS混合精度在某些torch版本上会不稳定。

在多卡GPU服务器上,Ultralytics也支持device="0,1",但一键脚本默认只写第一张卡。如果你有多卡,可以手动改成device="0,1"。这时batch size要相应放大,因为两张卡并行处理。脚本里的batch=16是每张卡的batch,总数等于16×卡数,注意显存是否够用。常见的坑是用户以为batch=16是全局batch,结果单卡显存不够却不知道把batch调小。我在脚本里特意用batch=16而不是更大的值,就是为了兼容6GB以上显存的大部分消费卡。如果你的卡只有4GB,把batch降到8,imgsz降到640,或者开启梯度累积。Ultralytics本身的batch参数可以传-1让它自动检测最优batch,但对新手我不推荐,因为自动检测会占用很多显存做测试,有时意外被杀进程。

训练完成后,权重保存在runs/night_pedestrian/yolo11n_960/weights/best.pt和last.pt。best.pt是按验证集指标挑出来的,last.pt是最后一轮的状态。如果你要做严谨实验,别直接用last.pt作为最终产物,一定用best.pt。后面第6章的推理我会用best.pt。

4. 夜间行人训练的参数边界:batch、imgsz、epochs与数据增强怎么调

4.1 必调参数速查表(附夜场景建议)

夜间行人检测和白天任务最大的区别是输入图像亮度低、目标小、容易漏检。经过前面用ImageNet/COCO预训练模型跑一版baseline之后,我一般会按下面这张表去调参:

参数默认值(官方)夜间行人建议理由
imgsz640960 或 1280小目标在640下只有十几个像素,检测头难以响应
batch168~16 视显存batch太大会显存溢出,太小则BN统计不准
epochs100100~200夜间数据多样性不高,但小目标收敛慢
lr00.010.005~0.01预训练模型收敛快,学习率过大容易震荡
patience2020~50夜间小目标mAP提升是爬坡式的,太早停会错过
mosaic1.00.5~0.8夜间目标数量少,过度mosaic会生成大量无意义组合
hsv_v0.40.2~0.3夜间亮度本身低,过强亮度扰动会破坏真实分布
degrees0.010~20监控视角下行人姿态多变,轻微旋转可打数据增强

这张表不是拍脑袋。imgsz=960意味着输入分辨率提高50%,显存占用大约升高到原来的2.25倍,所以batch要从16降到8才能不溢出。如果你用GPU(GPUs)训练,8G显存可以先试imgsz=960, batch=8;如果显存不够,可以把rect=True打开,让YOLO按图像长宽比分批计算输入尺寸,而不是全部resize到正方形,能省很多显存。

这里有三个容易误设的参数说明:第一是mosaic。Ultralytics默认前10轮会关闭mosaic,因为mosaic生成的拼接图会让模型在早期看到过多“假”目标。夜间数据本身图像整体偏暗,mosaic比例调小到0.5,可以减少把行人切碎变成噪声的概率。第二是hsv_v。默认的HSV亮度扰动范围是0.4,对夜间数据来说太大了,一个本来就不亮的目标可能被增强成纯黑或者过曝,反而让模型学不到稳定的亮度特征,我一般调到0.2。第三是patience。很多新看到loss不再下降就手动停了,但夜间小目标的mAP在60轮以后仍然可能缓慢上升,因为小目标位置回归的难度大,需要更多轮次微调。建议设成30以上,让它自己早停。

4.2 夜间数据增强策略:亮度、噪声、模糊与mixup

除了上面几个参数,我还建议在训练脚本里显式配置数据增强。Ultralytics的model.train()接受augment相关参数,夜间场景我常用的组合是:

model.train( data="data.yaml", imgsz=960, batch=8, device=device, hsv_h=0.015, hsv_s=0.5, hsv_v=0.25, degrees=10, translate=0.1, scale=0.5, fliplr=0.5, mosaic=0.6, mixup=0.2, copy_paste=0.1, erasing=0.2, )

逻辑说明:hsv_h、hsv_s、hsv_v控制HSV空间的色相、饱和度和亮度扰动。夜间图像饱和度本来就低,hsv_s我反而调高到0.5,让模型不去依赖颜色,更多依赖形状和边缘。degrees=10允许行人轻微旋转,模拟不同监控角度。translate=0.1允许目标在图像内平移10%,这比翻转更接近夜间行人从画面边缘走入的情形。copy_paste和erasing是Ultralytics较新版本支持的数据增强,前者把目标复制粘贴到其他位置,后者随机擦除部分区域,模拟行人被遮挡的情况。

参数说明:mixup=0.2表示有20%概率将两张训练图混合,这对夜间行人样本少的情况有正向帮助。但如果你发现验证集mAP不升反降,先把erasing和copy_paste关掉,因为它们会改变目标的上下文,而夜间目标在上下文极度缺失时很容易被当成背景。数据增强不是越多越好,我在夜间场景吃过亏:为了提升泛化开满了所有增强,结果模型在真实监控视频里把路灯杆识别成人,因为它学会了把“亮斑+细长形状”当成人,增强过度导致目标语义被破坏。

如果你用的是YOLO11的训练命令行而非Python API,也可以把上面的参数写进一个yaml文件,用data参数里附带的augment配置。Python API更容易排查,因为报错会直接告诉你哪个参数名不合法。注意不同Ultralytics版本对数据增强参数的默认值和拼写有差异,以你安装版本的官方文档为准,不要照抄旧博客。

4.3 评价指标怎么读:mAP@0.5与mAP@0.5:0.95的取舍

训练日志最后会打印一张指标表,包括mAP50(B)、mAP50-95(B)等。很多人只看mAP50,这在夜间行人任务上是危险的。mAP50只看IoU阈值0.5,只要框大概压在行人身上就算对。夜间小目标本身框小,IoU稍微偏一点可能就超过0.5,所以mAP50会显得很好看,但真实部署时框的抖动很大。我会更看重mAP@0.5:0.95,它把IoU从0.5到0.95每隔0.05算一次平均,对框的精度更敏感。

夜间行人还有一个指标被忽略:小目标AP。Ultralytics的训练日志不会直接给你分尺寸AP,但可以额外写一段验证脚本,用COCO的评估工具pycocotools计算AP_small。如果AP_small很低但整体mAP还行,说明模型漏掉的主要是那些距离远、像素小的行人。这种场景下盲目调参不如提高输入分辨率或加P2检测头。我之前做过一个对比:在同样的数据集上,把imgsz从640升到1280,mAP@0.5:0.95提升了接近20个百分点,而mAP50只提升了不到5个点。原因是mAP50对框位置不敏感,而小目标在1024以上分辨率下才能被正确回归。所以你在评估自己模型有没有进步时,别只看一张图表,要分开看。

关于置信度阈值,训练时YOLO默认用conf=0.001做mAP计算,这是为了画出完整PR曲线。推理时你会自己设conf=0.25或更高。调conf会直接改变precision和recall的平衡,但要记住mAP是模型能力的体现,conf只是你查输出的尺度,不能通过调高conf去刷mAP。这是新手常见误区,统一在这里说明。

5. 避坑清单:夜间行人数据集翻车的五个现场与排查方法

5.1 现象:loss不断下降,但mAP一直贴着0.1不动

这是夜间数据集训练里最常见的怪事。训练日志里box_loss、cls_loss都在降,看起来一切正常,但验证集mAP就是上不去。原因有三个层面:一是标签格式转换把类别ID写错了,所有目标都被当作背景,YOLO的cls_loss虽然降了但学不到任何有效位置;二是图像和标签整体没对齐,YOLO扫描标签时生成了空cache,训练时没有真实目标参与;三是数据集里存在大量难例,比如几十像素的行人,模型把这类目标当作背景拟合了。

解决方法是先回到第2章的检查脚本,随机抽20张训练图,把YOLO txt画到图上,肉眼看一下框是不是对准了行人。如果框全部偏移,检查转换脚本里的坐标归一化是否用了图像真实尺寸,而不是XML里的声明尺寸。如果框是对的,再检查data.yaml的names列表和txt里的class_id是否一致。很多时候是类别ID从1开始,YOLO按0开始,导致所有目标被判为“非目标”。这一步排查比调参重要得多,夜间行人数据集的标签一旦错位,后面所有参数都白调。

5.2 现象:单卡训练到一半爆显存,报CUDA out of memory

训练到第30轮突然报RuntimeError: CUDA out of memory,看起来莫名其妙。原因多半不是初始显存不够,而是训练过程中图像随机resize和mosaic产生了一张超大组合图,加上batch中每张图尺寸不一,显存峰值瞬间抬高。解决方法是不要只减小batch,也要检查cache参数。Ultralytics默认cache=False,但如果开了cache=True,会把数据集完整放进RAM,RAM不足时也报类似错误。我建议cache=True只用于小数据集,5000张图在imgsz=960时大概要占几十GB内存,不是每台机器都吃得消。如果你需要稳定训练,把batch=8、imgsz=960、cache=False,同时加一行workers=4来限制数据加载进程数,减少内存突发。

还有一种情况是AMP混合精度打开但显卡太老,导致某一轮loss变为NaN,后续数值溢出占满显存。这种报错前面会跟着loss: nan,解决方法是设置amp=False,或者更新显卡驱动。我遇到过同一台服务器上换了新驱动之后,原来稳定的训练突然爆显存,最后发现是驱动版本和CUDA库不一致导致显存分配异常。

5.3 现象:Mac上训练时提示MPS后端不可用或者算得非常慢

在Mac上跑标题里的一键脚本,最常见的报错是torch.backends.mps.is_available()返回 False,脚本就乖乖落到CPU,然后训练慢到怀疑人生。原因一般是你通过pip安装的torch是CPU版本,没有编译MPS支持。解决方法是重装PyTorch,选macOS/Apple Silicon对应的安装命令。装好后再确认python -c "import torch; print(torch.backends.mps.is_available())"输出True。另一个问题是MPS虽然可用,但Ultralytics在训练某些模块时会报“operation not implemented for MPS”,那是PyTorch还没有覆盖全部算子。我的做法是在训练脚本里加一个异常回退:检测到MPS训练失败时自动改回CPU,并在日志里提示。这种回退虽然牺牲速度,但至少不让训练中断。

try: model.train(..., device="mps") except NotImplementedError: print("[NightPedestrian] MPS算子不支持,自动回退到CPU训练") model.train(..., device="cpu")

逻辑说明:NotImplementedError是PyTorch在不支持的操作上会抛出的异常。参数说明:这个try包住整个train调用,但要注意如果训练中断在某个epoch之后,模型权重在内存里可能已经更新,回退到CPU会从当前状态继续,不会完全从头开始。不过Ultralytics内部可能有缓存,最保险的是重新加载best.pt再继续。

5.4 现象:验证时把人影识别成人,路灯和树干全是误检

这是一个经典的夜间行人误检。训练集里都是有人标注框的图,模型学会了“暗背景下一块亮斑”就是人,结果部署到真实夜间视频里,路灯、树干、汽车后视镜反光全亮了。原因有两点:第一是负样本不足。目标检测模型在正样本上过拟合后,会把大量背景误检。解决方法是给数据集加一批“没有人”的夜间背景图,标注文件为空,重新训练。第二是推理置信度阈值太低。训练时mAP按conf=0.001计算,推理时如果也用默认conf=0.25,误检会很多。夜间场景我建议推理阈值调到0.4到0.5,宁漏勿错。如果你的场景是安防报警,漏检和误检的代价不一样,阈值要响应用户风险偏好,不能一个值走天下。

注意空标签文件在YOLO里不能直接丢弃,因为负样本帮助模型学习“这里没有目标”。如果训练时用过滤空标签的脚本把空标注图片删了,等于放弃了负样本。第2章检查脚本里我把空txt标记为异常,但在负样本场景里,空txt是合理的,要区别对待。另外,我还习惯在训练前看一遍验证集里误检最多的图片,保存下来拼接成一张大图,快速判断是目标太小、太暗还是背景干扰,这比只盯mAP数字有效得多。

5.5 现象:使用不同版本Ultralytics后,同样参数训练结果差距很大

YOLO11还在快速迭代,Ultralytics的几个版本之间默认增强、坐标损失定义都有变化。你在项目里跑通的参数,升级到新版本后可能mAP掉点或者直接报参数名错误。原因就是训练配置没有锁定。解决方法是把环境依赖版本固定,建议在你的训练环境里用requirements.txt固定ultralytics版本,并记录pip安装时的torch版本。这样以后复现实验时不会出现“昨天跑0.2,今天只有0.15”的尴尬。如果你在多个服务器上跑同一个项目,记得先pip install -r requirements.txt,再跑一键脚本,不要自信到认为最新版一定更好。新特性比如后续目标检测版本里的结构变化,只在新版本支持,但稳定性不如老版本,做生产环境时不要追新。

另外,当你把训练日志里的mAP和其他人报告的数字对比时,要确认你们用的是同一版本、同一种数据划分。我见过有人把验证集和测试集混着评估,报告出来的分数虚高,等上了生产才发现泛化不够。所以我在项目里习惯把data.yaml的test目录固定住,验证只用val,只有全部训练完才用test做一次最终评估。这样能避免数据泄露和“指标好看但部署翻车”的尴尬。

6. 模型没白练:从测试集评估到视频推理的实际效果

6.1 用val模式替代predict做效果评估

训练结束看到best.pt之后,先别急着跑视频。我建议先用val模式在测试集上做一次定量评估:

yolo val model=runs/night_pedestrian/yolo11n_960/weights/best.pt \ data=data.yaml split=test imgsz=960 device=0

逻辑说明:split=test是Ultralytics新版支持的参数,老版本需要自己把数据目录换成test图片目录。device=0在Mac上可以改成mps,但val过程很快,用CPU也没关系。这条命令会输出mAP50、mAP50-95等指标,同时保存PR曲线到结果目录。参数说明:imgsz=960要和训练时一致,如果训练用1280验证用960,指标会失真,因为小目标在不同分辨率下的难度不一样。这一步是模型效果的客观基准,后续调推理阈值都在这个基准上做。

6.2 夜间视频推理与置信度阈值调试

评估通过后,我一般直接用best.pt跑一段真实监控视频。如果视频太长,先取前500帧做快速验证:

yolo predict model=runs/night_pedestrian/yolo11n_960/weights/best.pt \ source=night_walk.mp4 device=0 conf=0.4 save_txt=True

这里conf=0.4是夜间场景我比较常用的起点。如果误检多就往上调到0.5,如果漏检多就往下调到0.3。调的时候不要只看单帧,要看连续帧的稳定性。夜间画面噪声大,同一个行人在某一帧被检出、下一帧消失,通常是置信度刚好卡在阈值边缘,可以适当降低conf并配合IoU NMS的调整来提升时序稳定。如果你发现路灯反光被稳定识别成人,那多半不是阈值问题,而要回到第5章加负样本重新训练。

6.3 导出ONNX/TorchScript做跨平台部署

训练和验证完成后,如果要部署到Jetson、Windows服务端或者云端函数,我习惯先导出ONNX:

from ultralytics import YOLO model = YOLO("runs/night_pedestrian/yolo11n_960/weights/best.pt") model.export(format="onnx", imgsz=960, dynamic=True)

逻辑说明:dynamic=True允许动态输入尺寸,部署时灵活性更高,但推理速度会比固定尺寸慢一点。如果追求速度,可以把dynamic=False,输入尺寸固定为960。参数说明:导出后的ONNX文件可以用ONNX Runtime或TensorRT加载,Mac上也能用CoreML继续导出。我自己的一个教训是,导出ONNX时imgsz必须和训练时一致,否则你拿到线上跑出来的效果会和训练时差一大截,那时的“玄学掉点”多半不是模型问题,而是尺寸没对齐。

最后我想说一个习惯:夜间行人模型不能只看mAP,我会额外拿一段从未出现在数据集里的夜间视频,按时间段切成凌晨、黄昏、深夜三段分别统计漏检数。只有这三段的数字都稳定,我才敢把模型交出去。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询