4、行人感知与决策:让虚拟行人“看见”并“思考”
好,我们进入这一章的核心。行人行为模拟里,最难的不是走路动画,而是让行人感知环境并做出决策。说白了,就是给虚拟人装上眼睛和大脑。
我刚开始做这个模块时,犯过一个低级错误——让行人“全知全能”。他能看到360度所有车辆,能预判5秒后的轨迹。结果仿真跑起来,行人个个像开了上帝视角,完全不真实。后来我才明白:真实行人的感知是有局限的,决策是有偏好的。
这一章,我们就来拆解这三个核心模块:感知模型、决策模型、意图预测。
4.1 行人感知模型:视野范围与注意力
行人怎么“看”世界?不是像雷达那样扫描,而是靠眼睛和大脑的协同。我习惯把感知模型拆成两层:物理视野和注意力筛选。
4.1.1 视野范围(Field of View, FoV)
人的视野不是均匀的。中心区域清晰,边缘模糊。在仿真里,我们通常用一个扇形区域来近似:
- 水平视野:约120°(有效视区),中心60°为高分辨率区
- 垂直视野:约60°(上下各30°)
- 最大感知距离:城市环境中约50-100米(受遮挡影响)
举个例子,我在项目中这样实现视野检测:
// 伪代码:行人视野检测
bool IsInFieldOfView(Vehicle vehicle, Pedestrian ped) {
Vector3 dirToVehicle = vehicle.position - ped.position;
float distance = dirToVehicle.magnitude;
// 1. 距离过滤
if (distance > ped.maxPerceptionRange) return false;
// 2. 角度过滤(水平120°)
float angle = Vector3.Angle(ped.forwardDir, dirToVehicle);
if (angle > ped.horizontalFov / 2.0f) return false;
// 3. 遮挡检测(射线投射)
if (Physics.Raycast(ped.eyePos, dirToVehicle, out hit, distance)) {
if (hit.collider.tag != "Vehicle") return false; // 被其他物体挡住
}
return true;
}
4.1.2 注意力模型(Attention Model)
视野里能看到的东西,不代表行人真的注意到了。人的注意力是有限的。你想想看,过马路时你会同时盯着左边来的车、右边来的自行车、脚下的坑、手机上的消息吗?不会的。
我常用的注意力模型分三个层次:
- 焦点注意力:当前最关注的目标(比如最近的车)
- 周边注意力:视野内但非焦点的目标(比如远处的车)
- 忽略目标:视野外或被认为不危险的目标
具体实现时,我会给每个目标算一个注意力权重:
| 影响因素 | 权重系数 | 说明 |
|---|---|---|
| 距离 | 0.4 | 越近越关注 |
| 相对速度 | 0.3 | 速度越快越危险 |
| 运动方向 | 0.2 | 朝向行人的更危险 |
| 目标大小 | 0.1 | 大物体更容易被注意 |
嗯,这里要注意:注意力是会转移的。行人不会一直盯着同一个方向。我习惯加一个扫视机制——每隔2-3秒,行人会随机扫视左右,更新注意力焦点。
4.2 行人决策模型:过马路与避让
感知完了,接下来就是决策。行人决策模型,说白了就是回答两个问题:“现在过马路安全吗?” 和 “如果危险,我该往哪躲?”
4.2.1 过马路决策(Crossing Decision)
过马路决策的核心是间隙接受理论(Gap Acceptance Theory)。行人会判断:下一个车到达我当前位置的时间(Time to Arrival, TTA)是否足够我通过。
我常用的决策流程是这样的:
- 计算安全间隙:行人需要的最小时间 = 道路宽度 / 行人步行速度 + 安全余量
- 评估当前间隙:最近车辆到达的时间 TTA = 距离 / 车速
- 决策规则:如果 TTA > 安全间隙,则过马路;否则等待
代码实现大概这样:
// 过马路决策
bool ShouldCross(Pedestrian ped, Road road, List<Vehicle> vehicles) {
float roadWidth = road.width;
float walkSpeed = ped.walkSpeed; // 约1.2-1.5 m/s
float safetyMargin = 1.5f; // 秒,个人安全偏好
float minTimeNeeded = roadWidth / walkSpeed + safetyMargin;
// 找到最近车辆的TTA
float minTTA = float.MaxValue;
foreach (var v in vehicles) {
float tta = CalculateTTA(v, ped);
if (tta < minTTA) minTTA = tta;
}
// 决策
return minTTA > minTimeNeeded;
}
4.2.2 避让决策(Avoidance Decision)
过马路过程中,如果突然有车冲过来怎么办?这时候就需要避让决策。我把它分成三种策略:
- 加速通过:如果已经走了一半以上,加速跑过去
- 后退返回:如果刚起步,退回路边
- 原地等待:站在路中间的安全岛等待
选择哪种策略,取决于风险等级和当前位置。我习惯用一个简单的状态机:
enum AvoidanceStrategy { Accelerate, Retreat, Wait };
AvoidanceStrategy DecideAvoidance(Pedestrian ped, Vehicle threat) {
float progress = ped.distanceCrossed / roadWidth; // 过马路进度
float ttc = CalculateTTC(threat, ped); // 碰撞时间
if (ttc < 1.0f) {
// 极度危险
if (progress > 0.6f) return Accelerate;
else return Retreat;
} else if (ttc < 2.0f) {
// 中度危险
if (progress > 0.4f) return Accelerate;
else return Wait;
} else {
// 低风险,继续正常行走
return Wait; // 实际上是不需要避让
}
}
4.3 行人意图预测:预判行人的下一步
这一节其实是为自动驾驶车辆服务的。车辆需要预测:这个行人接下来要干嘛? 是继续等,还是突然冲出来?
我常用的意图预测方法有三种:
4.3.1 基于轨迹的预测
最简单的方法。根据行人过去几帧的位置,用卡尔曼滤波或线性外推预测未来轨迹。
// 简单线性外推预测位置
Vector3 PredictPosition(Pedestrian ped, float deltaTime) {
Vector3 velocity = (ped.position - ped.lastPosition) / Time.deltaTime;
return ped.position + velocity * deltaTime;
}
但这个方法有个问题——它假设行人会保持当前运动状态。如果行人突然改变主意(比如本来在等,突然决定过马路),预测就失效了。
4.3.2 基于意图的预测
更高级的方法。我们直接预测行人的决策意图。比如:
- 过马路意图:行人是否在观察车流?身体是否朝向马路?
- 等待意图:行人是否在看手机?是否站在路边不动?
- 犹豫意图:行人是否在左右张望?脚步是否有小幅度移动?
我习惯用隐马尔可夫模型(HMM)来做意图识别。把行人的状态(观察、等待、行走、冲刺)作为隐藏状态,把观测到的行为(头部朝向、脚步移动、身体倾斜)作为观测序列。
4.3.3 混合预测方法
实际项目中,我推荐轨迹预测 + 意图预测结合。先用意图预测判断行人的决策模式,再用轨迹预测计算具体路径。
举个例子:
- 意图预测输出:行人“很可能要过马路”(概率85%)
- 轨迹预测:根据过马路意图,预测行人会沿直线穿过马路,速度1.3m/s
- 最终输出:未来3秒的预测轨迹点
这样既考虑了行人的长期意图,又保留了短期运动细节。
好,这一章就到这里。下一章我们会讲行人群体行为模拟——当一群行人在一起时,他们怎么互相影响?怎么形成人流?那又是另一个有意思的话题了。