ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Vadere行人仿真数据收集与分析:密度、流量、速度与可视化全攻略

Vadere行人仿真数据收集与分析:密度、流量、速度与可视化全攻略 做人群仿真这几年我最大的感受是模型只是前半段后半段全是数据。Vadere这款开源的微观行人仿真软件跑起来并不难真正让人头疼的是跑完之后那一堆输出文件——trajectories.csv、density_xx.csv、flow_xx.csv到底该怎么看、该算哪个数、该信哪个结果。这篇内容我打算把Vadere里的数据收集与分析整个链路从头到尾捋一遍包括测量区域怎么画、密度流量速度三个探测器怎么配、结果文件怎么清洗以及做基本图和热力图时常用的那几招。适合已经把场景跑通、但还不知道怎么把仿真结果变成可信数据的研究生、工程师和数据分析师。1. Vadere的数据体系拆解先搞清楚你收集的是什么1.1 行人是“粒子”处理器是“计数器”人群仿真从本质上说就是把每个行人当作一个有目标、有速度、有尺寸的智能体在二维空间中随时间运动。Vadere的科学内核是微观模型每一步仿真都会更新每个行人的位置和速度。因此整个数据体系的最底层是一条按时间排序的行人轨迹记录——你可以把它想象成监控录像的原始帧。任何宏观结论比如“这个瓶颈区域的平均密度到了3人/平方米”“通过这个出口的流量是每分钟35人”都只能从这些轨迹记录中通过统计得到而不是像某些软件那样你在界面上看到一个数字就以为它是唯一的答案。Vadere的原则是数据透明、可追溯它把统计工作交给“数据处理器”Data Processor来做。我习惯把这些处理器理解成站到通道边上的“统计员”一个统计员手里握着一块测量区域每隔固定时间上报一次区域内的行人数量另一个统计员盯着一根虚拟线记录谁从左边跨到了右边。这些统计员可以同时在场互不干扰结果统一写到输出文件里。理解了这个模型你就知道后续所有配置都是在安排“谁在哪里统计什么”。1.2 微观轨迹与宏观指标的关系轨迹文件里每个时刻一行记录仿真时间、行人编号、坐标。可能还包含速度、目标信息等扩展字段。这套微观数据有两个特点。第一数据量和行人数量、仿真时长、输出频率成正比一场600秒、200人的仿真输出频率0.05秒时轨迹文件轻松达到百万行量级用Excel直接打开很吃力所以Python和pandas基本是标配。第二数据是离散的两个相邻时刻之间发生了什么只能靠插值和事件推断也就是说处理数据时必须考虑时间步长的影响。宏观指标就是从微观轨迹上聚合出来的。密度是区域内人数与面积的比值流量是单位时间通过某一断面的净人数速度是区域内行人运动的平均快慢。它们的统计口径不同得到的数值也不同。所以做数据分析前第一件事不是打开文件而是想清楚你要回答什么问题是评估服务水平还是验证瓶颈容量不同问题决定了测量区域放哪、用什么算法、输出什么指标。这一步想清楚了后面所有配置都有方向。1.3 一条完整的数据流水线完整的数据收集链路是这样的场景文件中先定义测量区域和处理器参数仿真运行时Vadere将每个时间步的行人位置与测量区域做空间关系判断匹配到的行人数据交给处理器做统计仿真结束后以CSV或VTK格式落到结果目录最后你用Python、Excel或postVis提取、清洗、可视化。这个链路里最容易出错的是第一步和第二步的衔接。很多人只绘制了测量区域却没有给处理器指定这个区域结果处理器运行了但输出为空还有人给处理器配置了输出却发现时间间隔和仿真步长不对齐最终得到的曲线锯齿状严重。后面我会专门把这些坑列出来讲清楚为什么会出现以及怎么排查。2. 测量区域与三大探测器数据从图纸上生长出来2.1 测量区域所有指标的几何底座在Vadere的场景绘制中测量区域是一个独立的图层对象。它可以是矩形、圆形或者其他任意多边形只要顶点落在可通行区域里就行。点击工具栏的“Measurement Area”工具在场景中依次点出顶点就会生成一个半透明的高亮区域。支持任意多边形这一点很实用因为现实里的走廊弯道、楼梯口、安检通道很少是规整的矩形用多边形才能贴合实际边界。放置测量区域时有三个原则都是我实际踩过坑之后总结的。第一和障碍物保持距离。贴着墙或者护栏的测量区域会把墙壁附近几乎没有行人的空区域也计入面积造成密度被稀释。我一般在边界留出至少0.3米特别是在研究瓶颈时测量区域千万不要跨越障碍物否则行人在障碍物另一侧的轨迹会被错误计入。第二大小要匹配你所用的密度算法。经典密度算法在面积过小的区域里噪声非常大面积过大的区域又会把拥堵信息磨平。经验上做局部拥堵分析用0.5到2平方米的小区域做疏散场景整体分析用5到10平方米的大区域这样密度曲线才不会变成一条无意义的直线。第三同一个测量区域可以被多个处理器共用。这很关键如果我要同时算密度、速度、流量可以画一个测量区域然后挂三个处理器而不是画三个区域。这样各个指标在空间上是严格对应的后面做基本图的散点就不会因为区域位置不同而失真。如果你打算后续做密度-流量关系分析这一步一定要从一开始就规划好。2.2 密度探测器三种算法怎么选Vadere的密度处理器提供多种密度估计方式我最常用的是三种经典密度、Voronoi密度和高斯密度。这三种算法背后的思路差别很大选错了结果会差一个量级所以值得仔细说。经典密度的算法思路是统计某一圆形邻域内的行人数再除以圆形面积。参数是半径R默认通常是1米。它实现简单、计算快但边界效应非常明显在测量区域边缘行人被计算进密度但圆面积被墙壁截断密度值会比实际偏高。做快速探测时可以用它但结果要谨慎解读特别是在贴墙的位置。Voronoi密度的思路是给每个行人划分一块Voronoi单元用1除以单元面积作为这个行人贡献的局部密度再在测量区域内做加权平均。它更符合“个人空间”的直觉对边界处理也更好是研究瓶颈拥堵时的首选。Vadere在计算时会对边界外的单元做裁剪所以结果相对可信。缺点是计算开销稍大行人密集时Voronoi图更新频繁但以现在电脑的性能来说基本可以忽略。高斯密度本质上就是核密度估计。每个行人对周围空间产生一个高斯衰减的密度贡献带宽参数控制衰减速度。它特别适合生成热力图因为输出平滑美观。但带宽设置不当会得到完全不同的结论太小则热点碎成颗粒太大则热点融为一体没法定位具体拥堵位置。我个人建议带宽取0.4到0.8米之间然后根据热力图效果微调。三者的选择没有绝对对错但必须在报告里写清楚你用的是哪一种、参数是什么。我之前审过一些报告只写“密度为2.3人/平方米”却不说用哪把尺子量的这种数据根本无法复现。这是人群仿真数据分析里最基础也最容易被忽视的规范。2.3 流量探测器站在断面数人头流量是人群仿真里最常用的“结果指标”之一疏散时间、排队人数、通行能力背后都要用到流量。Vadere中流量测量通常配合虚拟测量线或狭窄测量带使用处理器统计的是“穿过该测量断面的净行人数量”。配置流量测量时测量线的长度要覆盖主要通行宽度。比如一个4米宽的走廊如果你只画了1米长的线行人会从线两端绕过去计数结果会严重偏小。更合理的做法是画一条垂直于主要流动方向的线长度覆盖走廊宽度的85%以上两侧留一点余量以免贴墙。这条线不能太短也不能跨越不可通行区域否则计数逻辑会混乱。Vadere的流量处理器可以区分穿越方向所以输出的结果里通常能看到正向和反向两个计数。分析出口流量时只取正向穿过方向分析双向通道时两个方向都要保留。如果不加筛选把来回穿行的重复计数全部累加流量数值会直接失真。我在做地铁站通道的双向流量分析时就吃过这个亏后来在处理器配置里加了方向筛选才把数据修正过来。做流量统计还要注意如果你把测量区域画成一条带而不是一根线那么行人可能在某一步进入这条带后再退出导致计数重复。Vadere的流量测量是基于“跨越边界”事件来判断的它比基于位置的简单判断要准确但前提是你必须正确设置那条测量边界的位置而不是随便画一个区域就说它是流量测量区。2.4 速度探测器量化“走多快”速度探测器的基本逻辑是在测量区域内对每一时间步或每一个时间窗口中的行人瞬时速度做平均输出一条平均速度时间序列。它看起来简单但参数选择很考验经验。这里有一个参数需要特别注意——输出时间窗口。如果每个仿真步都输出一个平均速度那条曲线的抖动会非常剧烈因为总是存在几个行人正在减速或者转向避让如果窗口设置太长比如30秒一个平均又会把拥堵的形成和消散过程磨平。我用下来做瓶颈分析时0.5到2秒的窗口比较均衡输出间隔设置为0.5秒左右既平滑又保留变化趋势。速度探测器和密度探测器共用同一个测量区域时还能顺便得到速度-密度散点。这一步是后面画基本图的重要数据来源。如果你需要分析不同人群的步行速度差异还可以按行人类型过滤Vadere的处理器支持按属性筛选行人这个功能在混合人群场景里特别有用。一个常被忽略的细节是速度探测器的输出默认是所有行人的算术平均但如果你关心的是“群体速度”有时候需要用流量加权平均因为慢速行人在队列中的占比较多时简单算术平均会低估拥堵程度。具体用哪种取决于你的研究问题没有统一答案。3. 跑完仿真后结果目录、字段解读与数据清洗3.1 结果目录里有什么仿真结束之后项目的输出目录下通常会有几个文件和文件夹。第一类是轨迹数据最常见的名字是trajectories.csv。第二类是各个数据处理器导出的结果文件按处理器类型命名比如density_1.csv、flow_2.csv。第三类是后处理可视化所需的中间文件postVis或ParaView可以打开。我一般不会直接打开CSV就开始分析而是先用postVis回放一遍轨迹。回放能看到拥堵从哪里开始、有没有异常穿墙、测量区域的位置是否合理。这个检查步骤比任何数据预处理都重要因为如果仿真本身有问题后面的数据再漂亮也是垃圾。回放时我会叠加密度热力图一眼就能看出数据异常值出在哪个时间段、哪个空间位置。有些时候你会在结果目录里发现某个处理器的输出文件明显比其他文件小很多这时候要警惕可能不是数据量少而是处理器在某些时间步根本没拿到数据。具体原因可能是行人还没走到测量区域附近或者测量区域设置的位置有问题。先回放确认再做统计可以少走很多弯路。3.2 剖析trajectories.csv和处理器输出trajectories.csv通常用分号或逗号分隔字段至少包含仿真时间、行人编号、x坐标、y坐标。如果配置了输出速度还会有瞬时速度列。坐标单位默认是米时间单位默认是秒。拿到文件后第一件事是确认分隔符和字段名不同版本可能有细微差异我见过好几个人拿错分隔符解析出全是NaN的惨案。处理器输出的CSV格式相对规律通常每一行包含simTime和对应的测量值。密度处理器的值是密度流量处理器的值是累计人数或瞬时流量速度处理器的值是平均速度。如果有多个测量区域文件名或列中会带区域标识分析时注意一一对应。我把最常见的几类输出文件整理成了下面的表方便拿到新项目时快速对照。文件典型列单位主要用途trajectories.csvsimTime, pedestrianId, x, y秒, -, 米, 米轨迹回放、个体行为分析density_*.csvsimTime, density秒, 人/平方米密度时间序列、热力图flow_*.csvsimTime, cumulatedNumber秒, 人通过人数、流量计算speed_*.csvsimTime, meanSpeed秒, 米/秒速度时间序列、基本图3.3 用Python做清洗和指标重算拿到CSV后最稳妥的方式是用pandas做一个标准化的预处理脚本。先把列名统一再做去重、排序、缺失值处理。轨迹数据里有一种常见脏数据同一个行人ID在同一时刻出现多行这通常是处理器输出重复或文件拼接问题需要用drop_duplicates处理。另外如果连续两个时间步之间行人坐标出现了巨大位移多半是记录异常我会先标记出来再看是不是穿模。流量计数不能简单数“某时刻区域内的人数”。以计算“通过某条线的净人数”为例需要判断每个行人在相邻两个时间步是否跨越了测量线的位置阈值。以一条垂直于x轴、位于x3.0处的测量线为例代码可以这样写import pandas as pd df pd.read_csv(trajectories.csv, sep;) df.columns [simTime, pedestrianId, x, y] df df.drop_duplicates(subset[simTime, pedestrianId]) df df.sort_values([pedestrianId, simTime]) line_x 3.0 df[prev_x] df.groupby(pedestrianId)[x].shift(1) crossed df[(df[x] line_x) (df[prev_x] line_x)] print(crossed[pedestrianId].nunique())这个思路的核心是“前一个时刻在这边后一个时刻在那边”才算一次真正穿越。只判断“当前坐标在线的另一侧”会把本来就是从那侧走来的行人也算进去导致计数偏高。如果轨迹文件里没有速度列也可以用位置差分估算速度但要注意差分带来的噪声会被放大。相邻时间步的位移除以时间步长就是瞬时速度实际算出来抖动很大建议再做一次移动平均df[dx] df.groupby(pedestrianId)[x].diff() df[dy] df.groupby(pedestrianId)[y].diff() df[dt] df.groupby(pedestrianId)[simTime].diff() df[speed] (df[dx] ** 2 df[dy] ** 2).pow(0.5) / df[dt] df[speed_smooth] df.groupby(pedestrianId)[speed].rolling(10, min_periods1).mean().reset_index(level0, dropTrue)记得删掉每一组的第一行NaN否则后面聚合时会把空值带进去。如果你只是想看区域平均速度用速度探测器直接输出会更干净位置差分适合做个体层面的补充分析。4. 数据怎么用基本图、热力图与轨迹回放4.1 用密度和流量画一张基本图基本图Fundamental Diagram是人群动力学里的核心经验曲线横轴是密度纵轴是流量或速度它刻画了“越挤越慢”这一宏观规律。Vadere跑出来的数据完全可以用来画基本图关键是密度和流量要在同一个时间段、同一组测量区域上采集否则两个指标对不上画出来的点就是乱序的。具体做法是在瓶颈上游放一个长方形测量区域记录每个时间窗口的平均密度同时在瓶颈断面的测量线记录每个时间窗口的通过人数除以窗口长度再除以断面宽度得到单宽流量。这样每个时间窗口就得到一对密度-流量数据点把所有窗口的点画在散点图上就是实测基本图。流量和密度的关系式很简单流量约等于密度乘以速度所以速度-密度图与流量-密度图本质上是一体两面。时间窗口的选取很关键。用2秒窗口数据点多但散用10秒窗口曲线平滑但丢失细节。我常用5秒作为默认值做完之后再比较2秒和10秒的结果如果趋势一致就说明结论稳定。计算单宽流量时宽度取测量线有效覆盖宽度不是整个走廊宽度否则会系统性偏低。我自己第一次画这张图时就犯了同类错误后来把宽度校准到0.8倍走廊宽才拿到了合理区间。4.2 热力图把空间拥挤程度摊开看密度热力图是最直观的数据可视化形式。postVis里可以直接显示而用Python二次处理时通常需要把非均匀的密度数据转换成规则网格。如果处理器已经输出了密度值你可以把测量区域按网格划分每个格子取该处的密度用imshow或pcolormesh画出来。简单的绘图流程是这样从密度文件中读取时间和位置信息把空间坐标离散化按网格聚合然后绘图。代码可以很短import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(density_area1.csv, sep;) # 假设列包含x_bin, y_bin, density pivot df.pivot_table(indexy_bin, columnsx_bin, valuesdensity, aggfuncmean) plt.figure(figsize(8, 6)) plt.imshow(pivot, originlower, cmaphot_r, aspectequal) plt.colorbar(labeldensity (1/m^2)) plt.xlabel(x) plt.ylabel(y) plt.show()在真实项目里我更倾向于输出postVis自带的密度图层再导出为图片因为postVis的网格划分和密度算法是经过验证的出图也快。脚本绘图主要用于批量处理或自定义配色。一组好的热力图往往比一整段文字更有说服力给审阅者展示疏散场景时我通常会把自由流、排队形成、瓶颈堵塞三个时间节点的热力图并排放一眼就能看出问题区域在哪里。4.3 轨迹回放从整体到个体的降维观察轨迹数据还有一个不可替代的用途个体级别的行为分析。比如几个人同时涌向出口时的侧向避让、某个行人绕开障碍物的路径选择、队伍尾部的犹豫和折返这些微观现象只有回放轨迹才看得清楚。我会在postVis里把轨迹按时间步播放同时打开密度图层观察拥堵热点何时形成哪些行人的轨迹出现明显绕行。如果对可视化要求更高可以把轨迹导出为VTK格式用ParaView打开。ParaView支持时间轴动画、任意视角旋转和截面切面适合做高质量汇报图。平时做快速分析用postVis就够了出正式报告时我才会花时间导到ParaView里精修。回放轨迹时如果发现某个行人的轨迹出现大段折返不要急着认为是仿真bug很可能是他的局部密度太高在绕行避让这一类行为恰恰是微观模型有价值的地方。5. 常见问题与排查技巧实录5.1 常见问题速查表我把这几年用Vadere踩过的、以及帮别人排查过的问题整理成了一张表保存下来可以省很多时间。表格不能穷尽所有情况但覆盖了绝大多数场景会遇到的雷区。问题可能原因解决建议处理器输出文件为空处理器没有绑定测量区域仿真时间太短输出间隔设置过大检查处理器的Measurement Area设置延长仿真时间调小输出间隔密度值高得离谱测量区域贴墙或太小经典密度半径设置过小短暂聚集被当成稳态重画测量区域并留边界换Voronoi或高斯密度对时间序列做平滑流量计数偏大测量线太短导致行人多次穿越被重复计数没有筛选穿越方向加长测量线至通行宽度85%以上按穿越方向过滤同场景两次结果不一致随机种子未固定在仿真参数中固定random seed结果文件过大、仿真变卡输出频率设成了每个仿真步将输出时间间隔设为0.5秒或1秒只输出所需处理器轨迹回放中出现异常穿墙障碍物后处理有问题或碰撞算法参数不合适检查场景几何调大行人身体半径缩短时间步长5.2 容易被忽略的三个细节第一仿真前几秒的数据是暂态数据。行人从初始生成到进入正常行走状态通常需要几秒甚至更长这段时间速度低、密度波动大。统计平均指标时一定要把这段暂态窗口切掉否则你会发现平均速度被明显拉低密度曲线出现虚假尖峰。我在做疏散场景时一般删掉前5秒到10秒的数据再画曲线。不同场景的稳定时间不一样可以先跑一次预览仿真从密度曲线里观察它什么时候进入平稳段。第二不要把不同密度算法的结果混在同一张图里。经典密度和Voronoi密度的数值可能存在系统性偏差混在一起画基本图会让数据点分成两团看起来像两条曲线。要么统一用一种算法要么在不同图上明确标注算法和参数。这个习惯看起来微不足道但能避免很多“分析完了才发现数据不可比”的尴尬。第三修改场景时只改需要研究的参数。如果同时修改了行人数量又改了测量区域位置基本图上多出来的差异就说不清来自哪个变量。固定种子后逐项做参数扫描每次只动一个变量这样结果才能归因。这也是很多论文附录里会写详细参数矩阵的原因。批量仿真时我习惯把每次运行的scenario文件保存快照确保事后能追溯到底改了哪些参数。最后再分享一个小习惯我每次跑批量的仿真前都会先花五分钟在postVis里跑一个低精度预览把测量区域和处理器配置都确认一遍然后再投入正式的长时间仿真。这个步骤最多浪费五分钟但能省下来的重跑时间通常是半小时起步。数据处理也一样先回放、再统计这个顺序最好不要反。我个人在这些项目里的体会是Vadere的数据收集与分析能力完全不依赖任何外部商业工具它已经内建了从微观轨迹到宏观指标的数据链。但工具只是工具会不会被测度方案绑架才是仿真数据能否站得住脚的关键。我会习惯把每个项目的dataProcessor配置导出成模板换场景只改测量区域坐标处理器设置整体复用这样文件名统一、参数可追溯后续做批量参数扫描时会特别省心。建议你也可以试试这套工作流先把一个场景的数据链完整跑通再复制到其他场景里效率会高很多。
RELATED READING

延伸阅读

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