
最近好几个读研的学弟和转行做安防算法的朋友都问我同一个问题入门视频异常检测到底该拿哪个数据集先跑通市面上的benchmark五花八门但翻来覆去论文里出现频率最高的还是UCSD Ped2、CUHK Avenue和Street Scene这三个。如果凑“5大经典”来算再往后排就是ShanghaiTech Campus和UBnormal这些了。今天就把这三个数据集的实战体验一次性讲透从数据格式、评估协议讲到踩坑记录给准备入坑或者正在被实验结果折磨的人一份可以直接抄作业的参考。先说结论UCSD Ped2最简单适合当第一块敲门砖CUHK Avenue难度中等能考验模型对“人自身行为异常”的建模能力Street Scene最接近真实监控场景挑战最大也是这三个里面最容易让你怀疑人生的。下文会逐个拆解并配上完整的数据加载、评估代码和避坑指南。1. 视频异常检测为什么绕不开这三个数据集1.1 任务定义与数据集的核心作用视频异常检测通俗点说就是在视频流里找出“不太对劲”的事件或目标。和常规目标检测不一样它没有固定类别清单因为异常的定义是开放式的——行人正常走路是正常突然奔跑可疑有人骑车闯进人行道是异常有人徘徊张望也可能是异常。这种开放性决定了所有有监督方法在这里都不太好使主流做法是用正常样本训练模型让模型记住“正常长什么样”测试时凡是偏离正常分布的就算异常。那数据集在这个链路里扮演什么角色它把所有评估边界固定下来场景、相机机位、正常事件集合、异常事件集合、标注粒度帧级还是像素级、训练集和测试集划分。没有这些固定条件算法之间根本没法公平对比。所以你看论文时几乎所有方法都在这几个公开数据集上报AUC就是为了让后来者有据可查。我在实际跑实验时最大的体会是数据集选错了后面再努力也白搭。比如你一开始就上最难的Street Scene代码还没调通就开始怀疑人生反过来如果你只在Ped2上调参训出来的模型拿到真实监控里很可能直接崩。下面把每个数据集的特点拆开讲。1.2 选型逻辑复杂度递进的设计思想这三个数据集不是随便拼在一起的它们代表了一条非常清楚的难度递进线。UCSD Ped2是固定机位、固定视角、背景几乎不变的步行街场景CUHK Avenue是校园主干道人流量更大异常来自人的动作比如奔跑、投掷、徘徊Street Scene则是模拟真实街道监控有相机抖动、树木晃动、光照变化异常类型也更分散比如自行车逆行、机动车闯人行道、动物出没。所以这三个数据集基本覆盖了从“验证模型能不能work”到“验证模型在接近真实场景里有没有可用性”的完整链路。如果你想发论文Ped2和Avenue是必须报的如果你想做落地项目Street Scene的参考价值更大。个人建议无论你最终做什么方向这三个都跑一遍你会对“模型到底学会了什么”有个非常直观的感觉很多论文里没写的细节都是在跨数据集对比时暴露出来的。2. 三大经典数据集逐个拆解场景、异常类型与难点2.1 UCSD Ped2入门首选固定机位的行人场景UCSD Ped2来自加州大学圣迭戈分校是Pedestrian数据集里的第二个子集。分辨率360×240帧率不高总时长也不长但它是最经典的开胃菜。官方划分是训练集16个视频片段、测试集12个片段只包含正常行人行走的视频用于训练测试片段里混入异常事件主要异常类型包括人骑自行车、踩滑板、开小汽车或小卡车穿过、推轮椅等。Ped2的标注分两种帧级标签告诉你这一帧有没有异常像素级mask告诉你异常目标的具体位置。官方给的是.mat格式的ground truth里面每个测试片段对应一个mask矩阵。用的时候需要用scipy.io的loadmat读取再处理成二值mask。踩坑点来了Ped2的mask标注其实有点粗糙边缘不齐有些帧的异常区域还被切了一半。如果你的模型是逐像素出分的直接拿官方mask评估会发现不少误判其实来自标注错位而不是模型问题。我自己的做法是评估前对GT做一次形态学腐蚀/膨胀或者把GT内部的小洞补掉这样AUC能涨不少当然论文里要写清楚你做了预处理不然复现容易对不上。Ped2的另一个隐患是训练集中也存在一些“模糊正常”的片段比如有人推着婴儿车走这在有些论文讨论里被当成正常行为严格说也算一种低速非行人目标。所以不必神化这个数据集它就是帮你快速验证pipeline通不通的。2.2 CUHK Avenue中等难度校园场景的行为异常CUHK Avenue是香港中文大学发布的分辨率640×360训练集16个视频测试集21个视频。异常类型和Ped2最大的区别在于它更多是人的行为异常比如朝镜头奔跑、往地上扔书包、在路边徘徊、反向行走、跳舞等。也就是说模型不能靠“检测到非行人目标”就立功而是要去理解人的动作是否符合正常模式。Avenue的真值也是.mat但结构和Ped2不太一样它是按帧存cell每个cell里面是异常目标的边界框信息。用loadmat读出来后会看到一个cell数组需要自己解析成每帧的异常框。这里有个小坑部分帧的GT框是空的但这一帧实际可能有异常官方标注本来就有一些漏标。做pixel-level评估时最好先补一下这个漏标问题否则模型分打得很高AUC却上不去你还会莫名其妙。我个人觉得Avenue是调参最舒服的数据集它既有足够的异常多样性又不至于像Street Scene那样背景乱得让人崩溃。你在Ped2上跑通baseline之后第二个就该上Avenue它能验证模型是不是只学会了“背景差一截”这种作弊解法。2.3 Street Scene真实街道场景难度直接拉满Street Scene是UCSD后来推出的目的就是真实感。视角模拟街道低机位监控画面里有树叶摇动、旗帜飘动、行人走过商店门口的光影变化有时还有相机微抖动。比起前两个它几乎没有“干净背景”可言对模型的鲁棒性要求高很多。异常方面Street Scene的标定范围更宽包括自行车在行人道上骑行、机动车逆行或闯入人行道、行人骑滑板、动物跑过、物体被抛入画面等。异常的出现频率更低很多时候一个挺长的测试视频里只有少数几帧有异常正负样本比例极其悬殊。标注是pixel-level的mask官方提供的是png序列或者类似形式具体结构因版本不同略有差异下载后建议先看README确认目录。Street Scene是测模型上限的地方。你如果发现模型在异常帧上分数普遍不高先别急着调网络结构多半是正常背景太丰富导致的误报压不下去。我试过直接用Ped2上训好的模型去测Street SceneAUC直接对半砍可见跨数据集泛化有多难。2.4 三张表看懂差异为了让你有个整体印象我把三个数据集的常用公开信息整理成了表格。说明一下因为不同下载源和官方更新版本略有不同具体视频数量以官方文档为准我写的是我实际下载版本的值数据集UCSD Ped2CUHK AvenueStreet Scene发布机构UCSDCUHKUCSD分辨率约360×240640×360约1280×720训练片段数1616约几十段以官方为准测试片段数1221约十几段以官方为准主要异常类型骑车、滑板、车辆穿行奔跑、投掷、徘徊、反向走逆行、骑行、动物、抛落物标注粒度帧级像素mask帧级目标框帧级像素mask场景复杂度低中高适合用途快速验证baseline行为类异常调参接近真实监控的鲁棒性评测再看一眼难度层级维度UCSD Ped2CUHK AvenueStreet Scene背景是否静止基本静止较稳定动态背景明显异常是否依赖行为理解否是是正负样本不平衡程度中中高极高模型容易过拟合的点背景区域特定目标外形背景动态纹理这三张表可以说是我所有数据集选型判断的地基。做实验前先回答三个问题我的异常靠物体出现还是动作改变我的背景是否干净我的正负样本是不是严重失衡答案决定了你怎么设计评价指标和预处理流程。3. 数据集实战下载、结构、加载与评估协议3.1 目录结构与标注格式速查拿到数据集后第一件事不是训练而是先把目录结构摸清楚。不同来源打包方式会不一样但大体是这样Ped2常见结构dataset/ Train/ 001.mpg 002.mpg ... Test/ 001.mpg ... gt/ Test001.mat Test002.mat ...Avenue类似只是视频是.avigt文件夹下每个测试视频一个.matmat里的结构是每帧一个cellcell内是框坐标。Street Scene不同版本差异大有的提供原始mp4有的提供已拆分的jpg帧序列GT多为png格式的mask需要按名字对应。这里强烈建议下载后先写个脚本把数据信息打印出来视频总数、每段帧数、GT矩阵shape、mask值域。别看这是基础操作我群里至少有三次技术咨询是因为GT读出来是cell没解析对导致训练时标签维度对不上。花十分钟看数据结构比报错后排查一小时划算得多。GT读取的通用代码如下适配Ped2和Avenue这类.mat标注import numpy as np from scipy.io import loadmat def load_ped2_gt(mat_path, frame_count): data loadmat(mat_path) print(mat keys:, [k for k in data.keys() if not k.startswith(__)]) # 根据实际key调整 mask data[mask] if mask in data else data[gt] # 转成 T x H x W 的二值mask labels [] for t in range(frame_count): if isinstance(mask[t], np.ndarray): labels.append(mask[t]) else: labels.append(np.zeros((240, 360), dtypenp.uint8)) return np.stack(labels, axis0)注意Ped2的有些mask矩阵不是每帧都有独立矩阵而是把整个视频的像素位置编码成一个数组读取后需要自己映射回帧。我看到好几个开源仓库都有自己做好的读取工具建议直接复用不要重复造轮子。3.2 训练/测试划分与评估指标必须遵守的规则这三个数据集的官方划分是“行业规则”正常情况下不要自己改。原因有两点一是只有用官方划分你的结果才有横向可比性二是异常检测的自监督设定要求训练集不包含任何异常样本自己划分很容易在训练集里混入异常帧导致模型“提前见过”了异常AUC虚高。评估指标方面最常用的三个是frame-level AUC把每帧的异常标签有异常1无异常0和模型输出的异常分数排列起来计算ROC曲线下的面积。这是论文里最常报的指标在Ped2和Avenue上主流模型现在都能报到95%上下Street Scene上能到80%以上已经算很好。pixel-level AUC只针对有人工标注异常像素的那些测试帧比较模型输出的异常mask和GT mask的重合程度。因为要算像素级重合通常会把预测mask二值化后计算IoU再推AUC过程更繁琐分数也会比frame-level低不少。EER等错误率即漏报率等于误报率时对应的值在部分论文里作为补充指标出现。写评估脚本时我习惯把frame-level的标签和分数保存成npy这样每次实验结束直接重放不用重复读视频。最基础的AUC计算就一行from sklearn.metrics import roc_auc_score auc roc_auc_score(frame_labels, frame_scores)不过要注意如果你做pixel-level评估不能直接拿原始分数矩阵去算AUC需要先对每一帧求异常像素占比或最大异常分数作为该帧的异常程度再和GT比对。具体做法很多但核心原则就一个评估方式必须在你写论文的related work部分能讲清楚否则审稿人一问就露馅。3.3 一个通用数据加载类可直接改来用我自己习惯把数据集加载封装成一个PyTorch Dataset统一输出视频帧和对应GT。下面这个类以Ped2为例但改改路径就能用于Avenueimport cv2 import numpy as np from torch.utils.data import Dataset class AnomalyVideoDataset(Dataset): def __init__(self, video_list, gt_dirNone, resize(256, 256), is_trainTrue): self.video_list video_list self.gt_dir gt_dir self.resize resize self.is_train is_train self.frames [] self.masks [] for video_path in video_list: cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_total int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) video_frames [] while True: ret, frame cap.read() if not ret: break frame cv2.resize(frame, resize) video_frames.append(frame) cap.release() self.frames.extend(video_frames) if gt_dir is not None and not is_train: video_name video_path.split(/)[-1].split(.)[0] gt_path f{gt_dir}/Test{video_name}.mat gt loadmat(gt_path) # 这里要看具体mat结构 for t in range(len(video_frames)): self.masks.append(parse_mat_frame(gt, t)) def __len__(self): return len(self.frames) def __getitem__(self, idx): frame self.frames[idx].transpose(2, 0, 1).astype(np.float32) / 255.0 if self.masks: mask self.masks[idx].astype(np.float32) return frame, mask return frame写这个类的时候有个细节容易忽略视频的帧数可能和GT矩阵的帧数不一致原因是有些视频末尾有黑帧或者编码丢帧。加载时最好打印一下对比如果不一致以GT帧数为准视频多出来的尾部帧直接扔掉不然训练时label对不上会一直报错。3.4 评估脚本怎么写才可信一个可信的评估脚本应该包含以下环节对测试集逐帧推理得到异常分数做一次时间维度的平滑比如窗口为5的均值滤波然后计算AUC和EER最后还能输出每个测试视频的异常段定位结果方便可视化检查瓶颈。时间平滑这一步经常被新手忽略。异常检测模型的逐帧分数噪声很大单帧波动可能掩盖真实的异常区间。用一个小滑窗做均值滤波能明显提升frame-level AUC。我测过在Avenue上滑窗3到5帧能涨0.5到1个点。当然如果你用的是Transformer这类强时序模型平滑的必要性会降低。评估代码的核心逻辑如下def evaluate(labels, scores, smooth_window5): scores np.convolve(scores, np.ones(smooth_window)/smooth_window, modesame) auc roc_auc_score(labels, scores) # 还可以算EER fpr, tpr, thresholds roc_curve(labels, scores) fnr 1 - tpr idx np.argmin(np.abs(fpr - fnr)) eer (fpr[idx] fnr[idx]) / 2 return auc, eer不建议在评估前就做幅值归一化之外的后处理比如强行把分数映射到0-1之间后再算AUC其实不影响ROC结果纯粹增加沟通成本。保持原始分数评估debug时更直观。4. 高频踩坑与排查记录4.1 标注文件里的小坑先看再动手Ped2的GT mask我前面提过有形态学噪声。Avenue的GT是框级标注但部分框的label编号不连续存在跳号和重复号。Street Scene的mask是png不过有些序列的mask背景值是0异常是255有些版本则反过来你用OpenCV读的时候一定要先打印一下0和255的分布。我的建议是每一个数据集下载下来以后先人工可视化10到20帧GT把GT叠加在视频帧上看看位置准不准。不要嫌麻烦这一步能帮你省下后面大量无效调参时间。曾经带过一个实习生跑了两个星期实验最后发现GT读出来整体偏移了几十像素相当于模型一直在跟错误的标签做斗争结果自然感人。4.2 评估协议不一致结果复现的隐形雷区同样是Ped2有人报frame-level AUC有人报pixel-level AUC还有人在pixel-level评估里排除了没有异常mask的帧。这些细节直接决定了数字的高低但论文里经常只写一句“pixel-level AUC”实际实现各不相同。复现论文结果对不上时先怀疑协议再怀疑模型。比较稳妥的做法是你在自己项目里固定一套协议并在代码仓库里写明评估细节比如是否排除了GT为空的帧、是否做了形态学处理、是否做了时间平滑。这样即使数字不如别人高至少别人能复现你的结果。我自己的论文里评估部分就专门写了一段“Implementation Details”来说明这些细节审稿人再也没有因为复现问题找过我。4.3 数据预处理与增广的正确姿势视频异常检测的预处理常见的坑有两个一个是直接resize到正方形破坏原始宽高比导致行人被拉伸变形模型学到的是“被拉长的人脸”而不是“走路的行人”另一个是逐帧做标准化时因为视频亮度波动模型会把帧级亮度差当成异常信号。正确的做法是先统一分辨率我一般是把最小边resize到256然后中心裁剪到256×256保持比例不变标准化用的均值和方差最好来自训练集视频而不是随便拿ImageNet的统计值硬套。增广方面随机翻转、随机裁剪、颜色抖动都能用但要注意两点flip之后光流方向会反mask也跟着翻随机裁剪的尺度不能太大否则把正常语义破坏掉模型反而学得更差。4.4 阈值选择和指标解读AUC是一个排序指标它只关心异常分数是否排在正常分数前面不关心绝对分数大小。所以你在部署时不能直接拿AUC当作阈值参考。真实使用时要画出验证集上的PR曲线根据业务能接受的误报率去选阈值。在Street Scene上这个问题特别明显。异常帧真的太少AUC看起来还行但一旦把检测阈值调到业务要求比如每小时最多一次误报你可能会发现异常根本检测不出来。这时候别慌AUC高但部署拉胯并不代表模型坏了只是说明评价指标和真实业务目标之间有差距。可以尝试在训练时加入一些伪异常样本或者调大异常分数输出的对比度缓解阈值难选的问题。4.5 常见问题速查表现象可能原因解决办法训练loss不降GT维度对不上或训练集混入异常帧打印GT shape可视化前20帧GTPed2测试AUC偏低GT标注噪声大或mask未做形态学清洗对GT做膨胀腐蚀或换成框级评估Avenue跑分虚高训练时把测试集视频帧误加进去检查数据加载器的文件列表是否严格按训练/测试划分Street Scene误报率极高背景动态纹理被当成异常增加时间平滑或使用光流/背景建模分支不同论文结果差很多评估协议细节不同统一帧级/像素级、是否排空GT、是否平滑视频读帧数和GT帧数不一致视频编码丢帧或尾部黑帧以GT帧数为准截断视频帧这张表基本覆盖了我被问过的高频问题。你如果遇到表中没列的情况建议先检查“数据读取→GT读取→模型出分→指标计算”四个环节各自输出一个中间npy逐个比对几乎没有定位不了的问题。5. 选型建议与扩展数据集别把鸡蛋放一个篮子里5.1 按研究方向怎么选更合理具体到你自己的场景我可以给出几个直给的建议。如果你的目标是在短时间内跑通一套baseline验证环境没问题那选Ped2它轻、快、结果稳定。如果你在研究行为类异常识别比如袭扰、摔倒、徘徊那Avenue更符合你的问题定义异常更多来自人的动作变化。如果你在做真实监控系统落地或者研究跨域泛化、域适应那Street Scene是更合适的试金石它的背景复杂度能暴露很多实验室数据集里看不到的问题。还有一类情况是你想发论文、刷高指标这也可以理解。那我建议你在Ped2和Avenue上主攻AUC提升但务必在Street Scene上也报一个数字哪怕不好看审稿人反而会觉得你诚实且考虑过泛化问题。只报Ped2高分的论文这几年越来越难发懂的都懂。5.2 5大声里的另外两个ShanghaiTech和UBnormal回到标题里“5大经典”的说法主流对比中除了前面三个最常见的两个补充是ShanghaiTech Campus和UBnormal。ShanghaiTech Campus有437个视频13类异常场景覆盖教学楼、广场、操场训练集包含大量正常行为测试集里异常类型多且复杂是目前大模型和自监督方法最喜欢用来证明效果的数据集。UBnormal则是一个合成数据集场景在虚拟引擎里生成但因为生成引擎可控它的GT标注干净很多很多人拿它做预训练再迁移到真实数据集上微调效果确实好。如果你打算长期深耕这个方向我的建议是Ped2起步Avenue调优Street Scene做鲁棒性测试ShanghaiTech拿来冲论文指标UBnormal辅助预训练。这五个搭配起来基本覆盖了从入门到发论文到落地评估的全链路。5.3 个人实操经验与最后建议做视频异常检测这几年我最大的体会是数据集既是导师也是陷阱。它给你圈定了问题让你能在有限条件下不断迭代方法但如果你只盯着数据集的leaderboard很容易掉进“在这个场景上刷分”的舒适区。跨数据集泛化才是这个方向真正的难点你送进去一个未见过的场景模型还能不能给出合理的异常分数这比Ped2上多0.3个AUC重要得多。最后分享一个实际操作中的小技巧我在做跨数据集测试时会把三个数据集的正常视频混在一起做无监督预训练然后再回到目标数据集上微调。这样模型先见过各种背景纹理到了新场景里就不容易把背景当异常算是一种成本很低的域自适应。具体实现也不复杂就是把UCSD Ped2和Avenue的训练集视频全部读进来按统一分辨率训练一个重建类模型效果往往能带来明显提升而且完全白嫖已有数据。数据集比赛只是起点真正的魔法发生在你迈出数据集去面对真实监控画面的那一刻。希望这篇拆解能让你少走点弯路把时间花在真正有意义的问题上。