ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多智能体协同搜救:从A*路径规划到贝叶斯目标检测的Matlab仿真

多智能体协同搜救:从A*路径规划到贝叶斯目标检测的Matlab仿真 1. 先把这个题目的底细摸清楚做过几年数学建模的人看到这类题目应该会有一种熟悉感给一个真实场景让你把感知、决策、行动串成一个闭环系统。消防搜救这个题目本质上是“多智能体协同搜索”问题形式上很像数学建模竞赛里的D题或者E题要求参赛者完成建模、算法设计、仿真验证、报告撰写一整条链路。题目拆开看有三个关键词智能体、路径规划、目标概率检测。这三个词对应三个子问题。智能体建模解决的是“搜救队员是什么能做什么”。这里的搜救队员不是真的人而是抽象的Agent——可以是地面机器人、无人机甚至是一个携带着传感器的决策单元。每个智能体有自己的位置、速度、感知范围、剩余能量还能根据环境信息改变行为。路径规划解决的是“怎么走到该去的地方”。火场里不是所有地方都能踩墙要绕火源要避传感器能覆盖的区域有限所以搜索路径不能瞎走得有策略。目标概率检测解决的是“被困的人可能在哪”。搜救最大的困难在于信息不完全你知道这里有火、有烟、有障碍但不知道被困人员藏在哪个格子。这时候就要用概率的方法把“不知道”变成“一个概率分布”随着搜索的推进不断更新这个分布让搜救越来越有针对性。整个项目最后的交付物是两部分一套Matlab仿真代码一份完整的建模报告。这份博文想把这条从零到一的完整链路讲清楚尤其是那些写报告时容易忽略、写代码时容易踩坑的地方。适合正在备赛的建模选手也适合做路径规划相关课程设计的同学参考。2. 环境建模与智能体个体模型2.1 栅格地图把混乱的火场变成一张可计算的表消防搜救的第一件事是把现实世界的火场抽象成计算机能处理的形式。市面上主流的选择是栅格地图Grid Map也就是把区域划分成若干大小相同的方格每个格子记录一种状态。这张图就是整个仿真的“棋盘”所有智能体都在棋盘上移动。我常用的编码方式是这样的0表示空地智能体可以通行1表示障碍物比如墙体、倒塌的钢筋不可通行2表示火源点或者高温危险区域智能体尽量不要进入3表示被困目标也就是要搜救的对象在Matlab里一张50×50的地图就是一个50×50的矩阵生成起来非常方便。障碍物可以手动布置比如用矩形区域模拟房间墙壁也支持随机生成用rand函数加阈值判断方便做多实验对比。% 生成一个50x50的地图 map zeros(50, 50); % 模拟墙体第20列的1到40行放置障碍 map(1:40, 20) 1; map(10:45, 35) 1; % 随机生成小块障碍物 rand_map rand(50, 50); map(rand_map 0.92) 1; % 放置火源区域 map(15:25, 40:48) 2; % 放置被困目标 targets [8, 8; 30, 45; 42, 25; 6, 33];需要说明的是栅格尺寸直接影响计算量和精度。格子太小地图精细但路径规划的计算量大仿真跑起来很慢格子太大路径粗糙可能穿过现实中无法通行的窄道。在实际项目里50×50到100×100的栅格规模是性能和效果比较均衡的选择。2.2 智能体的运动模型走一步算一步的代价每个智能体在棋盘上都是一个移动节点。我在项目里给智能体定义了这样一组状态位置坐标 (x, y)单位是栅格索引速度 v表示每个时间步能走的格子数朝向 angle用于路径平滑和运动约束感知半径 R表示能探测多大范围的区域剩余电量 e模拟搜救机器人的续航限制运动模型上我允许智能体向8个邻域方向移动上下左右加四个对角每移动一步消耗一定的电量。对角移动的代价取欧氏距离 √2直线移动代价取1这样做最直观的好处是路径规划里计算的路径长度和真实移动距离是一致的。有一个一开始容易忽略的问题智能体转弯不是无代价的。如果这个模型要更贴近实际可以给转弯动作加一个额外代价比如转弯消耗的能耗是直线移动的1.5倍。这个细节放到报告里体现的是建模者对现实约束的思考放在模型的“改进方向”或者“约束条件”部分会加分。2.3 感知模型看得见不等于探测得到搜救智能体不是透视眼它探测目标的能力受距离、烟雾、障碍物遮挡等因素影响。这就是“目标概率检测”的物理基础——传感器不是100%可靠发现概率是距离和环境的函数。我在项目里采用的探测概率模型是P_detect(d) exp(-d^2 / (2σ^2)) × occluded_factor其中 d 是智能体与目标之间的欧氏距离σ 是感知能力的衰减系数取值越大感知半径越远。occluded_factor 是遮挡因子如果目标在智能体和某个障碍物连线上也就是视线被挡这个因子取0.3否则取1。有人可能会问为什么要用高斯形式的衰减而不是线性的因为真实传感器的探测能力在近距离内几乎恒定超过一定距离后迅速衰减高斯曲线恰好符合这个特征。这个细节在报告里值得写一段评阅人看到你能给出“为什么选这个函数”而不是“随便捏一个”印象分会明显不同。2.4 火势的动态扩散让环境“活”起来如果火源是静态的搜救就退化成普通的静态环境路径规划问题区分度不高。所以我给模型加了一个简单的火势动态扩散过程。火势扩散的基本规则是每个时间步火源格子以一定概率向四邻域空地蔓延。这个概率受“等效可燃物密度”影响我用一个参数 p_spread 控制通常取0.1到0.3。火源周围的格子被标记为“危险区”智能体路径规划时要在代价函数里给这些格子加上惩罚项。这套设计和真实的元胞自动机Cellular Automata火势模型同源只是做了极大简化。在Matlab里实现它只需要几行代码配合imagesc可视化能明显提升报告的观赏性。3. 路径规划从最短路到搜救策略3.1 为什么选A*算法路径规划算法选型是个避不开的问题。这个项目里我最终选择了A*算法作为核心对比的算法有Dijkstra和BFS。Dijkstra可以找到最短路径但它没有任何“方向感”搜索时向四周均匀扩散效率低。BFS只适合无权图在带权重的火场场景里不适用。A*在Dijkstra的基础上加了启发式函数通俗地说就是“大概往哪个方向走更有可能接近目标”。A*的核心公式是f(n) g(n) h(n)其中 g(n) 是从起点到当前格子的实际代价h(n) 是从当前格子到目标格子的估计代价启发函数。启发函数的选择直接影响搜索效率。我用的是曼哈顿距离h(n) |x_target - x_n| |y_target - y_n|原因是在栅格地图中如果允许四方向移动曼哈顿距离是理论最优的启发函数它永远不大于真实路径代价保证了A*找到的路径是最优的。火源要避开所以我把危险区的通行代价设为空地的5倍而不是直接设为不可通行。这样设计的好处是如果没有更安全的路智能体会冒险穿过火源边缘但优先级会排在后面如果火势完全封路代价无穷大时自然会绕道。这是一个比“一刀切不可通行”更贴合现实的建模思路。3.2 A*算法的Matlab实现要点A*的实现关键在“开列表”和“关列表”的管理。开列表存放待考察的格子关列表存放已考察过的格子。每次从开列表中取f值最小的格子扩展直到到达目标。function path a_star_search(map, start, goal) [rows, cols] size(map); open java.util.PriorityQueue(); open.add(Node(start(1), start(2), 0, h_func(start, goal))); came_from containers.Map(); g_score inf(rows, cols); g_score(start(1), start(2)) 0; while ~open.isEmpty() current open.poll(); if current.x goal(1) current.y goal(2) path reconstruct_path(came_from, current); return; end neighbors get_valid_neighbors(map, current.x, current.y); for i 1:size(neighbors, 1) nb neighbors(i, :); tentative_g g_score(current.x, current.y) cost(current, nb, map); if tentative_g g_score(nb(1), nb(2)) came_from(num2str([nb(1), nb(2)])) [current.x, current.y]; g_score(nb(1), nb(2)) tentative_g; f_score tentative_g h_func(nb, goal); open.add(Node(nb(1), nb(2), f_score, 0)); end end end path []; end这里面有几个容易踩的坑第一个是java.util.PriorityQueue使用的问题。Matlab自带的优先队列不是原生的需要用Java对象但长度过大时性能很差而且元素不能重复入队。更稳妥的做法是维护一个普通数组每次从中取f值最小的节点50×50的地图规模下完全跑得动。第二个是containers.Map的key类型。Matlab的Map对象key不支持数组直接作为索引我在这上面吃过亏解决办法是用num2str把坐标转成字符串存取的时候再解析回来算是比较土但稳的方案。第三个是邻居节点的生成。边界格子容易越界所以get_valid_neighbors里要先判断坐标在1到rows/cols范围内再去查map里的障碍物值。这段逻辑看着简单但写不好就是程序报错的重灾区。3.3 多智能体协同分区比扎堆更高效单智能体搜索的缺点是效率太低。你让一个智能体在不小于50×50的地图里找未知目标光走完所有空地就要几百步更不用说每个格子的探测和计算。我采用的策略是“分区块搜索 边界协调”。先把地图按面积均分成N个子区域N等于智能体数量每个智能体负责一个区域。区域内部用A*规划覆盖路径覆盖完自己区域后再判断相邻区域是否已有人去过如果没人去就填补过去。在实际仿真中这种策略的覆盖率明显高于所有智能体一窝蜂挤在同一个区域。原因很好理解搜索的核心是信息覆盖每个智能体的感知范围有限分散开才能最大化单位时间内的信息获取量。多智能体之间还要处理“碰撞避让”。我的做法比较简单把其他智能体的当前位置临时标记为障碍把它们的下一步位置标记为高代价区域。因为智能体数量不多通常2到4个这种临时标记法完全够用。3.4 动态重规划环境变了原来的路就作废了火势扩散以后原本安全的路径可能变得危险或者干脆被封死。路径规划不能只做一次。我设置的触发机制是每个时间步结束后重新检查当前所在格子的4邻域如果原本规划的路径上有格子变成了危险区就触发重规划重规划的目标点不变起点设为当前格子重新跑一次A*重规划有一个性能陷阱如果每个时间步都全量重算计算量太大。实际测试下来纯路径规划在50×50地图上单次耗时约0.3秒每步都跑也能接受但更科学的做法是设置“只有当路径上障碍数据发生变化时才重算”这是学术上说的增量式规划性能最优。4. 目标概率检测贝叶斯更新的核心是个“猜”字4.1 为什么要用概率而不是直接“探测到/没探测到”搜救问题的本质不是“找东西”而是“在不确定中最大化找到东西的概率”。被困者在哪你不知道传感器探测可不可靠也不确定。如果你把探测结果当作确定事实来做决策一次误判就会把搜索带偏方向。概率的方法是把每一个格子的“存在目标”信念记录为一个0到1之间的数值。搜索开始时每个格子有一个先验概率随着智能体不断探测后验概率不断更新。这样即便某一次探测没有结果系统也能知道“这个区域的信息已经确认过了”从而引导智能体去更不确定的区域。4.2 贝叶斯更新的公式和落地实现我把目标存在看作是二元变量 X取值1表示该格子有目标0表示没有。给定一次探测结果 Z根据贝叶斯公式P(X1|Z) P(Z|X1) * P(X1) / P(Z)其中 P(Z|X1) 是“如果目标存在被探测到的概率”这在第2.3节的感知模型里已经定义了P(X1) 是当前格子的目标存在概率即先验P(Z) 是全概率等于探测到目标的总概率可以通过遍历所有格子求和得到。实际代码里我用的是对数几率形式更新避免数值下溢也避免概率反复乘除导致的值域问题% Log-odds update for target existence probability prob_map 0.5 * ones(size(map)); % 初始均匀分布 log_odds log(prob_map ./ (1 - prob_map)); for agent 1:num_agents visible_cells get_cells_in_range(agents(agent).pos, agents(agent).sensor_range, map); for i 1:size(visible_cells, 1) cell visible_cells(i, :); dist norm(agents(agent).pos - cell); detect_prob exp(-dist^2 / (2 * sigma^2)); if is_occluded(agents(agent).pos, cell, map) detect_prob detect_prob * 0.3; end % 如果探测结果为目标存在更新似然比 if agents(agent).detected_target_cell cell likelihood_ratio detect_prob / 0.1; else likelihood_ratio (1 - detect_prob) / 0.9; end log_odds(cell(1), cell(2)) log_odds(cell(1), cell(2)) log(likelihood_ratio); end end prob_map 1 ./ (1 exp(-log_odds));这段代码最核心的注释是第10行的判断逻辑如果探测到了目标说明这个格子有目标的可能性大幅上升如果没探测到可能性略微下降。这里特别注意一点没探测到不意味着没目标——所以likelihood_ratio的分子用的是(1-detect_prob)而不是0。这就是概率更新和布尔判断的本质区别。4.3 先验分布怎么设初始先验分布决定了搜索的起点方向。有两种常见设法第一种是均匀分布。完全没有任何信息时每个格子存在目标的概率都设为0.5或者按目标数量估计的平均密度。这种设法的优点是公平不引入额外偏见缺点是前期要大面积盲搜。第二种是根据已知信息设置非均匀先验。比如报警信息提到最后看见被困人员是在某个区域附近那就在这个区域加一个高斯峰离该区域越远先验概率越低。这种设法的搜索效率高很多但前提是信息可信如果信息是错的反而会误导。我在项目里做了一个折中如果有“最后确认位置”信息用高斯先验如果没有就用均匀先验。两种方法的对比实验写在报告的结果分析部分用来验证“信息对搜索效率的贡献”效果很好。4.4 概率地图和路径规划怎么联动概率检测不是终点它要驱动智能体的行动。否则智能体到处乱跑路线规划和概率更新就是两套割裂的模块。我采用的联动方式是“引导式目标驱动”在A*的代价函数里加入一个惩罚项。对于概率地图中目标存在概率特别高的格子把它的通行代价设为原来的0.5倍吸引智能体靠近对于概率已经降到很低的格子比如经过多次探测都没发现目标把代价上调让智能体尽快离开。实际效果是搜索前期的路径偏重“全面覆盖”因为所有格子概率相同中后期路径会越来越偏向概率高的区域形成一种“先广搜、后精搜”的自适应行为。这个联动机制在报告里是重要的模型亮点评测角度也比较新颖。5. 仿真主循环与结果分析5.1 仿真程序主循环设计整个Simulation主循环的骨架大概长这样% 初始化地图、智能体、概率地图 map generate_map(50, 50); agents initialize_agents(3); prob_map 0.5 * ones(50, 50); for t 1:max_steps % 1. 感知阶段每个智能体获取感知范围内的探测结果 for a 1:num_agents agents(a).sensor_data sense(agents(a), map, targets); end % 2. 概率更新根据探测结果更新全局概率地图 prob_map update_probability_map(prob_map, agents); % 3. 路径规划每个智能体根据当前概率地图规划下一步路径 for a 1:num_agents goal select_goal(agents(a), prob_map, map); path a_star_search(map, agents(a).pos, goal); agents(a).path path; end % 4. 移动更新所有智能体沿路径移动一步 for a 1:num_agents agents(a) move_agent(agents(a), map); end % 5. 火势扩散更新危险区域 map update_fire(map, t); % 6. 终止条件所有目标均已被发现 或 达到最大时间步 if all_targets_found(agents, targets) break; end end这个循环体现的正是智能体系统的经典框架感知-决策-行动Sense-Plan-Act。代码顺序看起来简单但每一步都有隐藏的耦合细节。比如概率更新和路径规划都依赖感知结果感知模型的可信度决定了后面所有决策的质量所以感知模型里的参数sigma设置直接决定整个算法表现是“极度激进”还是“极度保守”。5.2 参数设置与实验设计我这里给出一组可直接复现的基准参数参数取值说明地图尺寸50×50栅格地图规模智能体数量3多智能体协同感知半径8格传感器有效探测距离衰减系数σ6探测概率函数陡度初始先验概率0.5均匀分布火势扩散概率0.15每步向邻域蔓延概率危险区代价倍数5路径规划惩罚权重概率高区域代价增益0.5引导式搜索牵引强度最大时间步200仿真总时长实验可以分三组做对照第一组是“智能体数量对搜索效率的影响”分别跑1个、3个、5个智能体记录发现全部目标所需的时间步数和总路程消耗。第二组是“感知半径对搜索效率的影响”分别设置R5、R8、R12看覆盖率曲线的变化。第三组是“先验分布对搜索效率的影响”对比均匀先验和高斯先验。这三组对照实验是报告中最有价值的部分因为它们直接回答了“模型参数变了会怎么样”这个问题比光跑一个算例然后展示结果有说服力得多。我在实际跑实验中三组实验的数据都记录在Excel表格里用plot和errorbar画成对比图配上简要分析这块在评阅人眼里属于直接加分项。5.3 结果指标怎么算图表怎么画我用的四个核心指标是一是搜索覆盖率。覆盖率 已被至少一个智能体感知过的非障碍格数 / 全部非障碍格数。覆盖率曲线应该随时间步单调上升最终逼近一个上限。上限取决于感知半径和地图中不可通行区域的比例感知半径越大覆盖率越快接近100%。二是平均目标发现时间。把每个目标被发现的时间步记录成向量求平均值。这个指标对参数变化最敏感先验分布和智能体数量对它的影响最明显。三是平均路径长度。统计每个智能体从起点到发现第一个目标期间的平均移动距离衡量“跑路效率”。四是能量消耗。统计智能体剩余电量防止出现“还没搜完就没电了”的情况。Matlab里画图推荐三件套% 覆盖率随时间变化 plot(1:T, coverage_history, LineWidth, 2); xlabel(时间步); ylabel(搜索覆盖率); grid on; % 概率热力图 imagesc(prob_map); colormap(hot); colorbar; title(目标存在概率地图); % 搜救轨迹可视化 hold on; for a 1:num_agents plot(agent_history{a}(:, 2), agent_history{a}(:, 1), LineWidth, 1.5); end plot(targets(:, 2), targets(:, 1), ro, MarkerSize, 10);提醒一句imagesc画概率地图时颜色映射要用hot或者jet这样高概率区域一眼就能看出来。搜救轨迹叠加在同一张图上能直观展示智能体是否在概率高的区域反复搜索。5.4 配套报告的结构与写法一份能拿高分的建模报告不是把公式堆在一起就行它要有清晰的叙事逻辑。我采用的报告结构是这样的问题重述与分析用自己的话把题目要求重新组织把“搜救行动”拆成“环境建模”“路径规划”“目标检测”三个子问题模型假设列出8条左右假设比如栅格尺寸远小于建筑尺寸、传感器探测相互独立、智能体之间通信无延迟。每条假设都应该对应一个现实依据不要写废话符号说明用表格列出所有变量、含义、单位方便评阅人快速查阅模型建立分别写环境模型、智能体运动模型、感知模型、目标概率检测模型、路径规划模型。每个模型先说物理过程再给数学表达式算法设计给出A*的伪代码、贝叶斯更新的伪代码、主循环伪代码仿真与结果验证展示基准参数的仿真结果然后是对比实验、灵敏度分析模型评价与改进总结模型的优缺点优点要具体缺点要说清楚原因不要用“由于时间限制”这种套话报告正文控制在15到20页左右比较合适附录放核心代码。6. 常见问题与排查技巧实录6.1 Matlab实现中的典型坑路径规划部分的报错最常见的是“索引超出矩阵维度”。这类报错90%出在边界格子和障碍物边界上排查方法是在get_valid_neighbors函数里对搜索范围内的每个邻居做一次坐标合法性检查。还有一种是open列表无限增长A*算法陷入了死区多半是启发函数不可接受高估了距离导致无法找到最优路径检查启发函数是否满足可采纳性。我最初用Java优先队列模拟堆的时候频繁出现元素重复和排序错乱的问题。换成手动数组遍历后反而更稳定。50×50的地图手动取最小值的时间开销完全可以接受写起来也简单直观建议初学者先用这种“笨办法”跑通流程再说优化。6.2 概率更新中的数值问题贝叶斯更新最怕两类问题。一类是概率越界更新成超过1或小于0原因是在连续的探测中某次探测的似然比异常大直接推翻了合理的先验。解决办法是用对数几率形式更新这个我在前面的代码里已经体现它的天然性质就是把整个实数轴映射到(0,1)区间不会越界。另一类是除以零。P(Z)作为分母理论上不为零但浮点数运算时可能非常接近零。稳妥做法是给P(Z)加一个极小值eps比如eps 1e-10或者用对数空间的加法绕开除法。这些都是写代码时容易忽略、跑起来才会发现的边界问题。6.3 多智能体重叠探测的重复计算多个智能体的感知范围重叠时同一格子在同一个时间步被多个智能体探测到。如果每个智能体都独立更新概率会把这格子的“没发现目标”证据重复累计导致目标存在概率被过分压低或抬高。解决办法是在同一时间步内对同一个格子把多个智能体的探测结果合并只做一次贝叶斯更新。实现方法是引入一个“已更新格子”集合每个时间步开始时清空更新时先判断这个格子是否在集合里。这个细节对结果的影响不可小觑我对比过不合并的时候地图中央区域的概率会系统性偏低因为多个智能体经常在那里交汇。6.4 仿真卡顿和性能优化Matlab的循环是出了名的慢。如果在主循环里写双层for遍历所有格子做概率更新50×50的地图还好但如果把地图放大到200×200运行时间会指数级上升。优化思路有两个方向。第一个是把内层循环向量化。概率更新本身是对矩阵的操作完全可以一次性算完所有格子的探测结果再用矩阵更新对数几率。第二个是只更新“感知范围内的格子”而不是整个地图。每个智能体的感知半径只有8格涉及到的格子不到地图面积的5%全图更新纯属浪费。我实测过优化前后的耗时对比同样的200×200地图优化前跑完200个时间步需要6分钟优化后只要40秒差距接近10倍。这个经验在真正做仿真时非常实用。6.5 报告答辩时最容易被追问的三个问题第一你划分栅格的依据是什么如果只回答“为了计算方便”等于给评阅人送分。正确思路是结合建筑结构的实际尺寸和传感器分辨率给出一个几何意义上的理由。我在项目里设置每格代表0.5米×0.5米感知半径8格就是4米正好和常用消防巡逻机器人的传感器覆盖范围匹配。第二你的启发函数为什么选曼哈顿距离而不是欧氏距离回答是地图允许四方向移动时曼哈顿距离是最佳可采纳启发能保证找到最优路径如果地图允许对角移动则应改用切比雪夫距离。这个问题考察的是算法理论基础。第三火势模型是否真实可信诚实的回答是它高度简化远达不到真实火灾数值模拟的精度但作为路径规划的约束条件它抓住了“危险区域动态变化”这个核心矛盾这在工程上是可接受的近似。这三问基本能满足评阅人最在意的模型合理性、算法选型、近似误差三个维度。另外提醒一句代码里的注释和模块划分要清晰。交代码之前把每个文件头部都加上功能说明和作者信息主函数要能一键运行到底。很多人觉得代码能跑就行但一份结构清晰、注释完整的代码在评分时和人品一样重要这类体验非常直观打开文件夹扫一眼就知道你是有组织地写代码还是临时拼了一堆脚本。7. 我做完这个项目之后的几点真实体会第一模型不是越复杂越好。这个项目里最容易犯的毛病是把火势扩散做成元胞自动机、把感知模型做到三维声场仿真听起来很高级但一旦参数设置不当整个仿真就崩了。建模比赛也好课程设计也好核心是把一个闭环系统跑通把结果讲明白在这个基础上再谈模型的扩展性。第二概率检测模块是整个系统的“灵魂”。A*算法到处都有现成代码但“用概率地图引导搜索目标”这个设计是从问题本身长出来的不是所有队伍都愿意在这方面下功夫。建模时把感知的不确定性写清楚代码里把概率更新的逻辑写干净整篇文章马上就立起来了。第三宁可少听几节课也要把三组对比实验做扎实。我自己跑过的所有参数组合里“有先验信息vs无先验信息”“3个智能体vs1个智能体”这两组结果最直观画成图放在报告里几乎不需要过多解释评阅人一眼就能看出建模的价值。最后分享一个非常实用的小技巧仿真过程中把每一帧的画面用exportgraphics保存为gif插入到报告里。评审看过静态图和表格之后看到动态过程会有一个非常直观的感受这个环节几乎不需要额外工作但效果比任何文字描述都强。
返回列表