news 2026/9/17 4:13:10

元胞自动机实现人群疏散模型:MATLAB仿真全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
元胞自动机实现人群疏散模型:MATLAB仿真全流程解析

前阵子有个学弟找我问毕业设计,题目是“基于元胞自动机的人口疏散模型MATLAB实现”。他最开始的理解特别乐观:把房间画成网格,人涂成几个格子,设定出口,然后一运行就能看到人流往门口涌,最后做两张曲线图收工。我听完就跟他说,画格子确实不难,但真正的功夫在三个地方:一是网格怎么离散化才合理,二是行人每一步“往哪走”的规则怎么定,三是仿真结束后怎么从数据里读出堵塞、瓶颈和疏散瓶颈。这篇文章我就把自己做这类建模时的完整思路、MATLAB代码骨架、参数实验和踩坑记录摊开讲,适合正在做课程设计、毕业设计,或者准备用MATLAB快速验证疏散方案的读者参考。

1. 为什么选元胞自动机:从宏观排队到微观自组织的思路转变

1.1 三类主流疏散建模方法对比

在做人群疏散建模之前,最好先想清楚一个最基本的问题:为什么大家普遍选择元胞自动机,而不是直接上流体方程或者社会力模型?我用一个简单对比来说明白。

方法空间形式典型特征优势劣势
连续流体模型连续把人流当作流体场,强调密度、速度、流量数学形式成熟,适合分析走廊、大门等大尺度通道个体行为被平均化,看不出排队、冲突、绕行
社会力模型连续每个行人受自驱力、排斥力、墙边界力作用轨迹精细,能模拟拥挤、恐慌、身体接触计算量大,参数多,调参是个体力活
元胞自动机离散空间被均匀网格切分,行人状态离散,按局部规则更新实现简单、速度快,能涌现出拱形堵塞、通道振荡等现象精度有限,个体轨迹不够平滑

我做疏散类的项目,很多时候还要跑不同密度、不同出口宽度、不同人群构成下的几十组对照实验。用社会力模型当然更精细,但每一步都要解微分方程组,扫描参数时计算成本很高。流体模型又抹掉了个体差异,恰恰疏散问题最核心的“个体抢位、拥挤堵塞”就看不见了。元胞自动机把空间和时间都离散化,用非常简单的局部规则就能复现很多宏观现象,是效率和解释力之间比较平衡的选择。

1.2 元胞自动机的三个组成要素

元胞自动机之所以好上手,是因为它只有三个要素需要你定义清楚:格子、状态、规则。

  • 格子(Cell):把二维疏散空间切成方形网格,每一个格子代表一个可以被行人占据的空间单元。
  • 状态(State):每个格子在同一时刻只能处于一种状态。简化版里通常有四种:空地、行人在此、墙/障碍物、出口。
  • 规则(Rule):行人的每一步决策只依赖自己和周围局部邻域内的格子状态,不依赖全局信息。

你可以把整个过程想象成在棋盘上模拟数百枚棋子移动:每个棋子只看得见自己周围两三步的情形,它根据“哪边离出口更近”“旁边有没有人挡路”“哪个方向已经被信息素标记过”这几个信息决定下一步。所有棋子同时决策、同时更新,久而久之,人群的宏观形态就自己长出来了。

这种“局部规则 + 并行更新”的思维方式跟MATLAB特别合拍。MATLAB里二维数组天生就是网格,矩阵操作能高效处理状态更新,imagesc和绘图函数又能快速把仿真过程变成动态画面,很适合做原型验证和教学演示。

2. 场景建模与静态场:把真实空间翻译成矩阵

2.1 网格尺寸与状态编码

建模型的第一步,不是写代码,而是确定“一个格子代表现实里多大一块地”。行人动力学研究里,一个成年人站立时的投影面积大约在0.3~0.5平方米,因此很多文献把网格边长取在0.4米左右。也就是说,一个格子差不多站一个人,密度高时人挨着人,密度低时格子空着,比较符合直觉。

网格尺寸一旦定下来,就要把真实场景翻译成MATLAB能处理的矩阵。我习惯用这几个数值表示状态:

  • 0:空地,行人可以进入;
  • -1或者inf标记:墙/障碍物,行人绝对不可穿越;
  • 1:行人占据;
  • 2:出口格子。

用数值标记而不是建立复杂的对象结构,原因很简单:MATLAB矩阵操作快,而且后面算静态场、检测邻居状态时,直接做逻辑比较就行了。比如判断某个格子能不能走,就要同时满足不在墙内、没有被行人占用、距离场值不是无穷大三个条件,拼成一个逻辑表达式就可以向量化处理。

2.2 静态场必须绕开障碍物

如果说网格是疏散模型的“舞台”,那么静态场就是行人的“指南针”。静态场通常用S(i,j)表示:从每个格子到最近出口的行走距离。出口处的 S 值设为0,离出口越远的格子 S 值越大。行人移动时总倾向于走到 S 值更小的格子,这样就产生了“向出口方向走”的吸引力。

这里有一个特别容易翻车的坑:很多人图省事,直接用欧氏距离算静态场。在一个没有任何隔断的开阔房间里,欧氏距离没问题;但一旦场景里有墙、有拐弯,欧氏距离会直接穿过墙体,把墙另一侧计算成“很近”,行人就会表现出穿墙而过的诡异行为。所以静态场必须考虑障碍物的绕行距离。

最简单的绕障碍物计算方法是广度优先搜索(BFS),从所有出口格子出发,逐层向周围扩展。BFS在离散网格上的语义很直接:步数就是距离,墙不能进入。下面是我常用的函数,输入墙掩膜和出口掩膜,输出每个非墙格子的静态场值:

function S = computeStaticField(M, N, wallMask, exitMask) % 计算带障碍物的静态场 % wallMask: 1表示墙, 0表示可通行 % exitMask: 1表示出口, 0表示非出口 INF_VAL = inf; S = zeros(M, N); S(:) = INF_VAL; queue = zeros(M * N, 2); head = 1; tail = 1; % 多出口同时作为BFS起点 [ex, ey] = find(exitMask); for k = 1:length(ex) S(ex(k), ey(k)) = 0; queue(tail, :) = [ex(k), ey(k)]; tail = tail + 1; end dirs = [1 0; -1 0; 0 1; 0 -1; 1 1; 1 -1; -1 1; -1 -1]; while head < tail pos = queue(head, :); head = head + 1; for d = 1:size(dirs, 1) ni = pos(1) + dirs(d, 1); nj = pos(2) + dirs(d, 2); if ni >= 1 && ni <= M && nj >= 1 && nj <= N ... && ~wallMask(ni, nj) && isinf(S(ni, nj)) S(ni, nj) = S(pos(1), pos(2)) + 1; queue(tail, :) = [ni, nj]; tail = tail + 1; end end end end

注意这里采用的是8邻域扩展,也就是人可以向上下左右和四个对角线方向移动。实际场景中如果只允许4方向行走,把dirs改成上下左右四个方向就行。BFS在多出口场景下特别方便,多个出口同时出发,每个格子记录的是到其中任意一个出口的最近步数。

2.3 一个小房间场景的初始化

写一个最简单的算例:60×60的网格,左墙中央和右墙中央各开一个出口,屋子里随机分布行人。初始化代码大致如下:

M = 60; N = 60; wallMask = zeros(M, N); exitMask = zeros(M, N); % 四周做墙 wallMask(1, :) = 1; wallMask(M, :) = 1; wallMask(:, 1) = 1; wallMask(:, N) = 1; % 左右出口:墙中间各留3个格子 exitMask(30, 1:3) = 1; exitMask(30, N-2:N) = 1;

这里有一件事必须提前说:出口宽度在网格里就是几个格子。3个格子大约对应现实1.2米宽,这比单人通过的窄门要宽。疏散模型最后算出来的“疏散时间”对出口宽度极其敏感,所以出口设置要贴近真实场景,而不是随便填数字。

接下来给区域内随机放行人。放行人的时候一定要避免重叠,我通常会先取出所有可用的空地索引,再从中随机抽取指定数量的位置:

rho = 0.4; % 行人密度 ped = zeros(M, N); canWalk = ~wallMask & ~exitMask & isfinite(computeStaticField(M, N, wallMask, exitMask)); freeIdx = find(canWalk); numPed = round(rho * length(freeIdx)); selIdx = freeIdx(randperm(length(freeIdx), numPed)); ped(selIdx) = 1;

因为静态场里墙的值为inf,isfinite能排除墙和不可达区域。行人密度不要设置得过高,否则初始化阶段就可能大量重叠出错。

3. 行人怎么走:移动规则与冲突消解的细节

3.1 从静态场到转移概率

元胞自动机疏散模型最核心的部分,是决定行人每一步移动到哪个格子。最经典的做法是场域模型:行人处在当前格子 i 时,会对周围可通行的邻居格子 j 计算一个“吸引力权重”,然后按照权重随机选择。权重公式之一如下:

[ P_{ij} = \frac{\exp\left(-\beta (S_j - S_i)\right)}{\sum_{k \in neighbors} \exp\left(-\beta (S_k - S_i)\right)} ]

其中 (S_i)、(S_j) 是静态场值,(\beta) 是灵敏度系数。这个公式的意思很直观:如果邻居格子比当前格子更靠近出口,即 (S_j < S_i),那么指数内部是正的,权重就会大于1,被选中的概率更高。(\beta) 越大,行人越“目标明确”,几乎只会朝出口方向走;(\beta=0) 时,行人完全随机游走,像失去方向感的人一样。

候选邻居集合要满足几个条件:在网格范围内、不是墙、没有其他行人占据、静态场值有限。这里多强调一句:我是一个个条件写逻辑判断的,不要图省事忽略“行人占据”这个条件,否则两个行人在同一时间步会重到同一个格子里。

3.2 并行更新与冲突消解

疏散模拟必须采用并行更新,而不是串行更新。所谓并行更新,是指所有行人在同一时间步先各自想好自己的目标格子,然后统一执行移动。如果按顺序一个一个人更新,前面的人先动了,后面的人看到的世界已经变了,结果就会产生“我先抢到、你被迫等”的人为偏差。

并行更新自然会引出一个问题:两个行人都想进同一个空格子怎么办?这就叫冲突。处理冲突最简单的方法:在每一轮移动时,把行人的处理顺序随机打乱,先处理的人获得该格子,后处理的人发现目标被占就只能留在原地。MATLAB里用randperm打乱索引即可。

更严格的做法是先把所有目标位置相同的行人收集到一个集合里,再让集合内部按概率随机选取一个赢家。我的经验是,如果只是课程设计或方案验证,随机顺序法已经能体现出堵塞涌现;但如果你想发论文,最好做严格版本。

3.3 速度差异与恐慌参数

现实里不是所有人都走得一样快。老人、儿童、行动不便者速度明显慢于普通成年人。在元胞自动机里,表示速度差异有个常用技巧:给每个行人一个移动概率,只有随机数小于这个概率时才尝试移动。普通人取0.9,慢速行人取0.5,这样同样一个时间步内,慢速行人移动次数更少。

恐慌程度怎么体现?可以通过设置不同的 (\beta) 值来模拟。(\beta) 低的人表现得慌乱、乱跑,(\beta) 高的人目标明确。还可以给模型增加一个“从众项”:如果某个邻居格子上有信息素,行人会更倾向于跟着走,这部分属于动态场(Dynamic Floor Field)的范畴。动态场的思路是行人走过的地方留下一定“痕迹”,痕迹随时间挥发,形成对后续行人的吸引。它会自然产生出口前“先到的人走掉、后来的人跟着旧路”的现象。我建议初学者先把静态场模型跑通,再去扩展动态场,否则两个场叠加在一起,出了问题很难定位。

4. 主仿真循环的MATLAB骨架

4.1 参数区与行人初始化

下面是完整的仿真主循环骨架,包含参数设置、初始化和每个时间步的更新。你可以直接复制后根据自己场景改参数。

% ============ 参数设置 ============ M = 60; % 网格行数 N = 60; % 网格列数 beta = 3; % 场域灵敏度 p_move = 0.9; % 正常行人移动概率 p_slow = 0.5; % 慢速行人移动概率 slowRatio = 0.2; % 慢速行人比例 simTime = 500; % 最大仿真步数 visStep = 5; % 每隔几帧绘制一次 rng(42); % 保证可复现 % 墙、出口、静态场 wallMask = zeros(M, N); exitMask = zeros(M, N); wallMask(1, :) = 1; wallMask(M, :) = 1; wallMask(:, 1) = 1; wallMask(:, N) = 1; exitMask(30, 1:3) = 1; exitMask(30, N-2:N) = 1; S = computeStaticField(M, N, wallMask, exitMask); % 行人状态 ped = zeros(M, N); canWalk = ~wallMask & ~exitMask & isfinite(S); freeIdx = find(canWalk); rho = 0.4; numPed = round(rho * length(freeIdx)); selIdx = freeIdx(randperm(length(freeIdx), numPed)); ped(selIdx) = 1;

这里有几个细节值得解释。rng(42)是为了固定随机种子,否则每次运行结果都不一样,后续很难对比参数。行人初始位置不仅避开墙,还要避开出口格子,避免一开始就站在出口上导致疏散时间被低估。慢速行人的标记可以用一个同尺寸的矩阵来存,但要注意它只对“当前时刻是行人”的格子有效:

slowFlag = zeros(M, N); slowFlag(selIdx(rand(length(selIdx), 1) < slowRatio)) = 1;

随机的慢速标记只给行人的初始位置赋值,后面行人移动时,得把这个标记同步搬到新位置。处理方式是在每次移动时,把slowFlag里旧位置的标记一起搬到新位置,再清空旧位置。

4.2 主循环与移动逻辑

history = zeros(1, simTime); nb = [0 0; 1 0; -1 0; 0 1; 0 -1; 1 1; 1 -1; -1 1; -1 -1]; for t = 1:simTime % 移除已经到达出口的行人 arrived = exitMask & (ped == 1); evacuated = sum(arrived(:)); ped(arrived) = 0; history(t) = sum(ped(:) > 0); if history(t) == 0 && t > 2 break; end % 找到当前所有行人 [pr, pc] = find(ped == 1); n = length(pr); goalr = zeros(n, 1); goalc = zeros(n, 1); for k = 1:n i = pr(k); j = pc(k); candR = []; candC = []; candP = []; for d = 1:9 ni = i + nb(d, 1); nj = j + nb(d, 2); if ni >= 1 && ni <= M && nj >= 1 && nj <= N ... && ~wallMask(ni, nj) && ped(ni, nj) == 0 ... && isfinite(S(ni, nj)) candR(end + 1) = ni; candC(end + 1) = nj; candP(end + 1) = exp(-beta * (S(ni, nj) - S(i, j))); end end if isempty(candR) continue; end % 按概率采样 candP = candP / sum(candP); cdf = cumsum(candP); sel = find(rand <= cdf, 1, 'first'); goalr(k) = candR(sel); goalc(k) = candC(sel); end % 并行更新与冲突消解 occupied = ped > 0; order = randperm(n); for k = order if goalr(k) == 0 continue; end gi = goalr(k); gj = goalc(k); if ~occupied(gi, gj) ped(pr(k), pc(k)) = 0; ped(gi, gj) = 1; occupied(gi, gj) = true; % 同步慢速标记 if slowFlag(pr(k), pc(k)) > 0 slowFlag(gi, gj) = 1; slowFlag(pr(k), pc(k)) = 0; end end end % 可视化 if mod(t, visStep) == 0 imagesc(ped + 2 * wallMask + 3 * exitMask); colormap([1 1 1; 0 0 0; 1 0 0; 0 1 0]); % 空地白, 行人黑, 墙红, 出口绿 axis equal tight; title(sprintf('t = %d, 剩余 %d 人', t, sum(ped(:) > 0))); drawnow; end end

主循环里nb的第一项是[0 0],代表“停留在原地”。这样即使周围没有合适的目标,候选集合里也至少有一个选项,不会出现概率和为零的边界情况。如果你希望行人必须移动,就把这一项去掉。

还有一点容易忽略:出口格子的静态场值为0,所以行人会先走到出口格子上,然后在下一轮开始时被移除。因此疏散曲线里所有逃出人数都有约一个时间步的滞后,这种滞后在宏观统计上基本可以忽略。

4.3 可视化的一些心得

imagesc是最快的可视化方式,但图像比较粗糙。如果想做更直观的动态图,可以在imagesc的基础上叠加plot画出墙体,或者改用scatter画行人点,并按墙体颜色填充背景。做演示时,我会在每帧把剩余人数写到标题里,这样仿真过程本身就可以当一张“疏散曲线实时图”。

另外一个建议:不要在循环里每帧都重算整个imagesc,否则高密度场景会卡到没法看。设置每隔5步或10步绘制一次,速度就能接受。如果想让输出更顺滑,可以配合VideoWriter把每一帧写入视频文件,最终生成一段“疏散过程.mp4”,这是答辩时最能直观展示建模效果的材料。

5. 参数调优与疏散瓶颈:实测中看到的那些现象

5.1 密度升高后疏散时间的变化

把仿真跑起来之后,第一个应该做的事情不是去调参数,而是做几组密度扫描。我建议固定出口宽度、固定 (\beta),只改变初始行人密度,从0.1一直试到0.8。你会看到疏散时间并不是从低到高线性增加的,而是在某个临界密度附近开始快速上升。

原因是高密度下出口前会出现“拱形结构”:大量行人同时涌向一个出口,大家都想挤进去,结果谁也走不快,反而在出口外形成半圆形堵塞。元胞自动机虽然把人和空间都离散化了,但只要冲突消解规则存在,这个现象就会自然涌现出来。能涌现,才说明模型具备解释力,而不是简单叠加。

如果画出密度-疏散时间曲线,你会发现密度增高到某个阈值时曲线会明显变陡。这个阈值对应的就是现实场景里“出口通行能力达到上限”的时刻。做建筑设计疏散分析时,这个阈值就很有参考价值。

5.2 出口宽度是真正的瓶颈

出口宽度的灵敏度往往比我预想的还要高。把出口从1个格子扩大到3个格子,疏散时间可能会缩短一半以上;从3个格子扩大到5个格子,改善幅度就明显减小。这说明疏散瓶颈并不完全取决于出口面积,而跟行人的排队机制有关。

我在仿真里实测过,当出口只有一个格子时,行人目标选择几乎变成“单行道”,出口前方会形成明显的排队;当出口宽度增加到3格以上后,行人就能并行通过,堵塞消失。做设计时,这个现象提醒我们:单纯加宽出口在一定范围内效果显著,但超过阈值后边际收益会下降。

5.3 灵敏度系数与人员构成的影响

(\beta) 参数对疏散时间的影响有些反直觉。在低密度场景中,(\beta) 从1增加到8,疏散速度提升并不大,因为行人本来就很容易找到出口;在高密度场景中,(\beta) 太高反而会让大家认为“所有方向都必须朝着出口”,在出口前形成严重挤压,堵塞加剧。

慢速行人的比例也值得测一测。我试过把20%的行人设为慢速,初始离散位置随机分布,最后疏散时间比全正常速度时多出15%到25%。如果慢速人恰好分布在出口附近,影响会更明显。这说明模型里的人员构成对结果有很大影响,做报告时应该把慢速人员比例作为不确定度的一部分展示出来,而不是只给一条确定曲线。

6. 结果怎么看:疏散曲线、热力图以及避坑清单

6.1 疏散曲线和平均疏散时间

仿真结束后,最直接的输出是“剩余人数随时间变化”的疏散曲线。前面的代码里我把每步剩余人数存到了history向量里,画出来即可:

figure; plot(1:simTime, history, 'LineWidth', 1.5); xlabel('时间步'); ylabel('剩余人数'); title('疏散曲线'); grid on;

观察疏散曲线时,通常会出现两个阶段:前段曲线下降较快,因为人群分散,大家都能顺利走到出口;后段曲线变得平缓,因为出口已经满了,排队成了主导因素。这两个阶段的分界点,对应的就是出口开始形成稳定队列的时刻。

单纯看一条疏散曲线还不能下结论,因为随机种子不同,结果会波动。我建议对同一组参数跑20次,取平均疏散时间和标准差。平均疏散时间比单次结果可靠得多,标准差也能反映人流组织的稳定性。比如某些参数下20次结果的标准差很大,说明系统对初始位置敏感,这时方案就不够稳健。

6.2 用热力图找“堵点”

想要知道疏散过程中哪里拥堵最严重,有一个非常直观的技巧:额外维护一个累积计数器visitCount,每轮更新时,对当前所有行人所在的格子加1。仿真结束后把visitCount画成热力图,就能看到哪些区域被频繁踩踏。

出口前方通常是热力图最亮的区域,因为所有行人都会汇聚过来;如果场景里有走廊拐角,拐角内侧也会非常亮。热力图的价值在于快速定位设计缺陷,比如某个角落明明有大量行人经过,却没有足够的疏散通道,那说明通道设置可能有问题。

imagesc(visitCount); colorbar; colormap(flipud(hot)); axis equal tight;

6.3 仿真前后的几个常见错误

最后列几个我踩过的坑,每一条都对应真实调试时间。

第一个坑是初始行人重叠。随机生成行人位置时如果没有做好去重,会导致多个行人在同一格子里,后续移动逻辑会出各种诡异bug。用randperm从可用空地索引中一次性抽取,是最简单的防重叠方案。

第二个坑是墙边行人被“困住”。当行人站在紧贴墙壁的位置时,它的候选邻居里可能有一部分落在墙内,剩余可行方向又全被占用,最后它哪儿也去不了。这不算模型bug,但会让仿真图像看起来像有人“卡死”在墙边。解决方法是在候选集合里始终保留“原地等待”这一项,也就是前面代码里nb包含[0 0]的原因。

第三个坑是静态场穿墙。场景复杂时,用欧氏距离算出来的静态场会让行人直接穿过隔墙。一定要用绕障碍物距离,哪怕场景里只有一道矮墙,也要在初始化阶段就处理好。

第四个坑是时间步与真实时间的对应关系。元胞自动机本身只输出时间步,不是秒数。要跟真实时间对应,通常需要根据行人平均速度反推一个时间步对应的秒数。比如设定0.4米一格、正常步行速度1.2米每秒,那么一个时间步大约对应0.33秒。这个换算关系要在报告里写清楚,否则结果没有实际参考价值。

第五个坑是只跑一次就下结论。元胞自动机里的随机性很强,初始位置、冲突消解顺序都会影响结果。参数对比时要多跑几组取均值,不然很可能会被单次实验的偶然性带偏。

我在实际做这类项目时,最大的感受是不要把精力全花在花哨的动态画面上,模型规则本身的合理性才是灵魂。先跑通一个最简单场景,再逐步加出口、加障碍、加慢速行人,每加一个因素就做一轮对比实验,这样出来的结果既有层次,也经得起问。如果你按照上面的框架把代码跑通,再往里面加入自己的场景约束,就能很快得到一个可复现、可解释的疏散分析工具。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 4:13:06

涨紧芯轴硬度检测:选对方法比选对设备更重要

1. 项目概述&#xff1a;为什么涨紧芯轴的硬度检测不能“差不多就行”工装涨紧芯轴——这玩意儿听着冷门&#xff0c;但在机加工、齿轮制造、轴承装配、汽车变速箱壳体镗孔这些现场&#xff0c;它就是夹具系统的“心脏”。它不是普通轴&#xff0c;而是靠弹性变形产生径向涨紧力…

作者头像 李华
网站建设 2026/9/17 4:12:33

FPGA积分赛备赛实战:从数电基础到上板调试全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 4:12:32

G-Helper 调校指南:ROG Keris II Ace 无线鼠标设置完整教程

G-Helper 调校指南&#xff1a;ROG Keris II Ace 无线鼠标设置完整教程 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenboo…

作者头像 李华
网站建设 2026/9/17 4:11:54

CSK5062离线语音红绿灯系统实战:工业级状态机与抗干扰设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 4:10:22

从零开发MCP Server:用Python实现AI Agent工具调用与知识库接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 4:08:58

MATLAB二维频谱分析实战:从fft2到f-k谱与相速度提取

简介&#xff1a;面向需要分析二维波形数据的Matlab使用者&#xff0c;如地震、雷达、超声波等复杂信号处理场景&#xff0c;这份rar压缩包提供了一套从频谱基础到频率-波数谱、能量谱绘制的完整讲解与可直接运行的示例代码&#xff0c;重点解决如何将时间域或空间域信号转换到…

作者头像 李华