简介:面向计算机视觉开发者与算法研究者的自制行人检测数据集,基于国内某高铁站监控视角制作,采集真实排队场景,适合用于训练和评估YOLO等目标检测网络。资源包共9864个文件,包含4932张JPEG原始监控截图与4932个XML标注文件,标注由LabelImg完成、类别统一为“行人”,整体约947MB;压缩包内Annotations目录存放逐图标注信息,JPEGImages目录存放原始图像,结构清晰,方便直接接入训练流程。此外附带拍摄原视频,可用于连续帧行为分析和追踪性能验证。目前已有703人学习下载。该数据集样本量充足、场景真实,覆盖复杂背景、遮挡和光照变化等情况,有助于提升模型在智能安防、客流统计等现实任务中的鲁棒性,是算法验证与毕设实践的实用基础资源,可作为算法对比与论文实验的基准数据。 做监控场景下行人排队检测这个方向,最早遇到的坎就是数据集。公开集里行人检测的很多,但专门针对“排队”这一行为的、且是国内监控视角的,几乎找不到现成的。国外那几个经典数据集,要么是水平视角拍的商场走廊,要么是稀疏人流,拿到国内地铁闸机、医院挂号处、景区售票口这种高密度、强遮挡、俯拍角度又刁钻的场景里,模型效果直接打骨折。所以我决定自己动手,从零搭一个“国内监控视角行人排队检测数据集”,这篇文章就把整个制作过程、踩过的坑、以及最终训练验证的完整路径展开聊聊。
这套数据集的定位很明确:用来训练“排队场景下的行人检测+排队状态判断”模型。适合下面几类朋友参考:一是要做智慧零售、客流统计、安防巡检这类项目的工程师;二是想用YOLO、RT-DETR或MMDetection训练自定义检测任务,但还没搞定数据构建的同学;三是对监控视频结构化感兴趣,想研究行人行为分析的研究者。我会尽量把从定义任务、设计标注规范、采集清洗、训练调参到效果评估的每个环节都讲透,保证你看完能直接照着搭一套自己的方案。
1. 内容整体设计与思路拆解
1.1 为什么公开数据集撑不起“国内监控排队”这个场景
先泼一盆冷水:公开数据集不少,但能直接用于国内监控排队检测的几乎没有。最常被引用的行人检测数据集,比如Mall Dataset、UCSD Pedestrian,拍摄角度多是平视或者轻微俯视,场景集中在国外商场、校园步道,人流的密度和排列方式跟国内早高峰地铁站排队完全是两码事。国内监控摄像头的典型架设高度在3到6米,镜头下压角度通常在30度到60度之间,画面里行人呈现的是明显的“头肩视角”,排队时人与人之间的前后间距很小、左右错位严重,遮挡是常态而不是个例。
这就带来三个核心挑战:第一,俯拍角度下人形外观变化大,平视数据集训练出的模型很容易把头顶和肩膀的轮廓错认成背景;第二,密集排队时人挨着人,检测框之间大量重叠,普通的NMS后处理会误删目标;第三,国内场景有大量特定元素,比如隔离带、一米线、闸机口、围栏,这些在公开数据集里几乎没有标注。所以无论从检测还是后续的行为判断来看,自建数据集都是绕不开的一步。
1.2 任务定义:检测排队行人,而不是只框出“人”
项目标题里写的是“行人排队检测”,我把它分成两层任务:第一层是密集行人检测,输出每个人的位置框;第二层是排队状态判断,判断哪些行人处于排队状态、哪些是路过或游离的。前者是目标检测的经典问题,后者我选择在检测结果基础上做规则判定——通过一条预设的“队列线”(从排队起始点到服务窗口)来判断行人是否处于队列区域内。
为什么不在模型里直接加一个“排队/不排队”的分类头?我试过,效果一般。原因是排队状态跟时间顺序强相关,单帧图像里一个人站在队列尾端和站在队伍旁边,视觉特征差异很小,强行用分类头去做,本质上是在让模型“猜”而不是“看”。更稳的做法是:检测框负责空间定位,队列线负责几何判定,两者结合后的状态判断准确率明显更高。这也是很多工业级客流分析系统的通用方案。
1.3 场景覆盖:要让模型见多识广
数据集的泛化能力取决于采集时的场景多样性。我最初只录了一个室内大厅的排队视频,训练出来后在另一个室外售票点测试,mAP直接掉了十几个点。后来重新规划采集,把场景拆成四个维度:室内(医院/银行/食堂)、室外(景区/车站广场)、半封闭(地铁闸机口/安检通道)、以及不同光照时段(白天自然光、傍晚混合光、夜间红外模式)。如果条件有限没法多场地采集,至少也要保证同一场景下覆盖多时段、多机位角度,否则模型很容易把某个场地的背景纹理学进去,换个地方就失灵。
2. 数据采集、清洗与预处理全流程
2.1 采集设备与合规注意点
我用的采集设备其实很普通:海康的4MP网络摄像头,架在三脚架上模拟监控视角,高度大约3.5米,镜头俯角约45度,焦距手动调到能覆盖约8米长的队伍。有条件的可以直接用支持RTSP的摄像头接入NVR录制,能顺便保留时间戳和原始码流,方便后续按时间段分析。没有硬件条件的话,用手机架在高处录制也行,但要注意画面必须稳定、不能用运动镜头,因为监控场景是固定的视角。
这里必须提醒一句合规问题。监控视频涉及个人隐私,自制数据集时如果画面里有清晰的、可识别的人脸,一定要在发布前做人脸模糊处理,或者干脆在采集时让参与人员签署知情同意书。我自己是安排同事、朋友扮演行人,在明确的告知前提下进行录制,这样后期发布、开源都不需要担心肖像权纠纷。另外不要直接去网上抓监控录像来标注,来源不明数据既容易侵权,也可能卷入法律问题。
2.2 抽帧策略:不要按固定间隔无脑抽
很多人做视频数据集时喜欢每10帧抽一帧,但实际效果很差。排队场景的行人运动非常缓慢,连续帧之间几乎没区别,抽出来的是大量近似重复帧,不仅浪费标注时间,还会让训练集高度自相关,造成过拟合。我采用的是“按动作变化抽帧”:先用光流或帧差法算出每帧的运动量,只在运动量超过阈值的帧里随机抽取;对于排队队列基本静止的时段,则每隔3到5秒抽一帧。
具体来说,我写了一段小脚本处理原始录像。核心逻辑是:对视频每15帧计算一次帧间绝对差均值(mean absolute difference),如果大于设定阈值,就把当前帧加入候选池;如果小于阈值,就丢弃。然后从候选池里按目标数量(比如想要2万张有效帧)均匀采样。这样既保留了排队过程中的瞬时遮挡、插队、人员进出等关键变化,又避免了大量剪影完全一样的冗余帧。
2.3 清洗环节:这些帧必须删
抽完帧后不要急着标注,先花半天时间做人工清洗。我总结了几类必须删除的帧:严重过曝或欠曝导致行人轮廓丢失的;镜头被遮挡的(比如有人从摄像头面前走过);画面中排队人数少于3人、完全没有排队结构的长镜头;以及模糊帧——尤其是夜间红外开启时,行人快速移动会产生拖影,这种框就算标了模型也学不到有效特征。清洗后的数据量通常会缩水20%到30%,这是正常的,宁缺毋滥。
清洗时我还会按场景和时间段做一次分层统计,确保每个子目录下的样本数不悬殊。比如上午场景有8000帧,夜间红外只有1500帧,那我会在采样时对夜间段做加权,避免模型在夜间模式上彻底失灵。
2.4 数据增强:合理增强,别把监控视角搞乱
训练阶段的数据增强能明显提升泛化能力,但要结合监控视角做取舍。水平翻转、随机亮度对比度、高斯噪声、随机缩放擦除都是靠谱的增强方式,我每轮训练都会用。但有两个增强要慎用:一个是大幅旋转,监控画面里的地平线、门框、柜台方向是有物理意义的,旋转超过15度会产生不真实视角,模型容易学到奇怪的对齐关系;另一个是任意角度裁剪,建议限制在水平方向做平移或小范围缩放,垂直方向尽量不要动,因为俯拍画面里人形的大小和它在图像中的位置(远处小、近处大)是强相关的,破坏了这种关系反而有害。
3. 标注规范与实操细节
3.1 标注工具选型:从LabelImg到X-AnyLabeling
早期我用LabelImg,它对VOC格式支持好,操作简单,但纯手动标注密集排队场景非常痛苦——队伍里20个人,每个人都要在重叠遮挡的情况下框出准确边界。后来换成X-AnyLabeling,最大优势是支持加载YOLO预标注模型跑“自动标注”,先让模型给出初框,人工只做调整,效率提升了三倍以上。具体做法是:先用一个在COCO上预训练的YOLOv8l行人检测模型跑一遍所有抽帧图像,导出YOLO格式的初标框,再在X-AnyLabeling里加载初标结果逐帧修正,删除误检、补全漏检、调整框的大小和位置。
3.2 类别设计:不是只有“人”一个类
虽然任务名叫“行人排队检测”,但实务里我不建议只标person一个类。我最终的类别体系是三个:
- person:队列中的行人,包括正在排队和短暂停留的人员。
- queue_line:排队路线参考线,从队首服务区延伸到队尾,用折线标出。它是后续判断排队状态的关键几何依据。
- queue_area:排队区域框,标出队伍允许存在的范围。这个类主要用来过滤误检——如果检测框中心落在queue_area之外,就判定为非排队人员。
增加queue_line和queue_area两个类的好处是让几何判定有明确的锚点,而不是靠手工画一条固定直线。不同摄像头架设角度下,同一个排队的“队列线”应当由标注来定义,而不是在代码里硬编码。如果你觉得三个类太繁琐,至少也要保留person和queue_line两个类,否则后续做排队状态判断时会非常被动。
3.3 密集遮挡场景的标注要领
密集排队中标注最大的难点是遮挡。我的标注原则是:只标注可见部分的实际边界,不臆测被遮挡的肢体。比如排在队尾的人半个身体被前面的人挡住,框就应该贴着可见的肩膀和头部区域画,而不是脑补整个人的轮廓。这样训练出来的检测器对遮挡反而更鲁棒,因为在监控俯拍视角下,模型本来能看到的就只是头肩和上半身,强行标全身框只会引入大量噪声。
另一个经验是,距离摄像头远的人(画面顶端)框要尽量精确,哪怕只有几十个像素也要标。很多人在标注时觉得远处小人不容易看清就跳过,结果模型在远端漏检极其严重。这是影响mAP的大坑,务必让标注人员专注把每个小目标标全。
3.4 标注质检与数据统计
标注完成后,我建议做一轮“双人复核”——不是让第二个人从头看一遍,而是只抽查容易出错的帧:比如人数大于15人的密集帧、遮挡超过50%的帧、以及队列线横跨整幅画面的帧。抽查比例在10%到15%之间就行,重点检查漏检和框偏。
最终我的数据集统计大概是这样:有效图像共21240帧,标注person框约173500个,queue_line标注约8240条(每条是连续折线),queue_area约7960个。类别数量上person占绝对多数,所以在训练时我会对queue_line和queue_area适当提高loss权重,或者用过采样来平衡,否则模型会倾向于忽略这两个类。
4. 模型训练与效果验证
4.1 数据格式转换与目录结构
标注工具导出的是JSON格式(X-AnyLabeling默认),我需要转成YOLO训练用的txt格式。转换时要注意坐标归一化:YOLO的每个物体标注行是“class_id x_center y_center width height”,全部用0到1的小数表示。我写了一个简单的转换脚本,核心逻辑如下:
import json import os def convert_labelme_to_yolo(json_path, out_dir, class_map): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w, img_h = data['imageWidth'], data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue class_id = class_map[label] points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) x_center = (x_min + x_max) / 2 / img_w y_center = (y_min + y_max) / 2 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") base = os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base + '.txt'), 'w') as f: f.write('\n'.join(lines))对于queue_line这种折线标注,我会在转换时把整条折线的外接最小矩形(min-area rectangle)转成检测框,保证它也能参与YOLO训练。虽然折线会被近似成矩形,但在实际推理时我的算法会重新读取标注的原始折线坐标,不会影响几何判定的准确性。
数据集目录结构采用最通用的YOLO格式,训练时方便直接套用ultralytics:
datasets/queue/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml划分比例我是8:1:1,但划分时严格按“视频片段”分组,而不是按帧随机划分。否则同一个视频的相邻帧会同时出现在训练集和验证集里,验证指标会虚高,部署后直接露馅。
4.2 训练参数与调优记录
我用的是YOLOv8m轻量版,输入分辨率640×640,batch size 16,初始学习率0.01,cosine衰减,训练150轮。显卡是单张RTX 3090,整个过程大约需要5个小时。这里要强调一个参数:mosaic增强的开启会有帮助,但在密集人群场景下,mosaic拼接产生的图像边界会把行人切成两半,导致部分目标框面积过大或过小,所以我设置mosaic=0.5,随机对一半的样本应用,而不是默认的1.0。
另一个关键参数是NMS的IoU阈值,我从默认的0.45调到了0.3。因为密集排队时两个人靠得太近,检测框的重叠程度很高,默认阈值会把其中一个框抑制掉,导致漏检。调低阈值后,重叠的目标更容易被保留,但随之而来的是误检增加,所以我还加了置信度阈值0.35作为平衡。实测下来mAP@0.5提升约2.3%,F1从0.81升到0.84。
4.3 排队状态判断的逻辑实现
模型输出检测框后,我用这样一个状态判断流程:首先把queue_line从图像坐标转成一条直线方程,然后计算每个person检测框中心点到这条线的垂直距离。如果距离小于设定阈值(我这边取30像素),同时检测框的底部与队首服务窗口的水平距离小于某个值,就判定为“排队中”,否则标记为“非排队”。这套逻辑简单直接,效果稳定,而且不依赖特殊后处理库。
如果场景里有多个服务窗口和多条队列,可以给每条队伍分配独立的queue_line和queue_area,然后逐个计算。只要数据集的标注没有交叉混乱,这个方案做多队列检测也完全可行。后来我还加了时序稳定性约束——用队列序号和最近N帧的平均位置做卡尔曼跟踪,避免单帧抖动造成状态误切,这属于工程优化,可以根据需要再展开。
4.4 实测效果与误差分析
在自己的测试集上(完全未参与训练的独立监控片段,共2600帧),最终效果是:person类的mAP@0.5达到0.872,mAP@0.5:0.95为0.601;queue_line类mAP@0.5为0.794。排队状态判断的准确率在室内固定机位下约91%,在室外多变光线下约83%。有个明显的误差来源是夜间红外场景,人形轮廓和背景对比度低,模型在夜间段的mAP只有白天的七成左右。后来我专门补了一批红外模式的数据,并加入随机亮度扰动加强,夜间段的mAP才拉回到白天的85%以上。
5. 常见问题与排查技巧实录
5.1 密集人群漏检严重
如果发现测试时队伍中部的人总是检测不到,先检查两件事:一是NMS阈值是否过高,优先把NMS IoU阈值从0.45调到0.3附近;二是训练时mosaic增强是否把完整的人形切碎,导致模型对小而完整的行人反而敏感度下降,这种情况把mosaic调到0.5以下或不启用。如果仍然漏检,可以尝试在标注里补充中远景小目标的“头肩区域框”,这类目标完整身体框非常难学,但头肩框特征更清晰、更容易收敛。
5.2 queue_line类训练不收敛
有朋友跑我的方案时反馈queue_line一直学不好。排查下来发现是数据格式的问题:X-AnyLabeling导出的折线,转换为矩形框时因为折线跨度太大,生成的矩形几乎覆盖全图,class imbalance直接爆炸。解决办法是不要把整条队列线转成一个框,而是把折线按断点切分成多段短线段,每段生成一个小矩形框。这样参与训练的queue_line目标数量大幅增加、形状也更规则,收敛速度明显改善。
5.3 跨场景泛化差
训练集和测试集都来自同一个大厅,模型指标很好看;一换到另一个排队场景就拉胯。这种情况说明模型把背景纹理和光照当成了关键特征。应对思路是增强训练数据的场景多样性,如果实在没有条件,可以把原始图片做HSV随机扰动、随机背景替换、以及把高光区域做局部遮挡。我见过有团队用风格迁移生成不同天气模拟图,也能有效提升泛化能力,但训练成本较大,作为后期优化手段更合适。
5.4 标注效率太低,总是来不及
长时间纯手动标注密集人群,速度是每小时200到300框,整个数据集做下来人的精力会被榨干。我的经验是先跑一轮通用行人检测模型做预标注,再人工修正;对于排队这个具体任务,还可以先把queue_area标出来,在这个区域外的行人一律不标,减少无效标注量。如果预算允许,界面化标注工具和预标注流程配合使用,是当前自建数据集性价比较高的方案。
6. 踩坑总结与后续扩展
做这个数据集的过程里,最深的体会是“任务定义先行”。如果一开始没有把“行人检测”和“排队状态判断”这两层任务拆开,我大概率会在标注类别上进行无休止的纠结:是否要把插队的人单独设类,是否要把服务窗口也标成物体,这些问题全被前面的清晰方案解决了。建议所有想自建数据集的朋友,第一周不要打开标注工具,先写清楚任务文档、类别定义和判断逻辑。
另一个心得是所有环节都要留日志。采集视频的时段、抽帧参数、清洗规则、标注版本、训练参数,每一步改动都能回溯。AI项目最怕的不是效果差,而是效果变好了却不知道哪个环节起了作用,效果变差了也不知道改了什么导致的。
这个数据集的后续扩展方向我认为有两个很值得做:一是增加长时间连续片段的标注,让模型结合时序信息直接输出排队时长和队伍长度变化曲线,这对商场、医院管理特别有价值;二是引入多机位协同,把两个相邻摄像头的队列线拼接起来,解决长队超出单一画面视野的问题。数据集的构建不是一个一次性工作,它更像是一个持续迭代的基础设施,越到后期越能看出前期细节打磨的价值。
本文还有配套的精品资源,点击获取