尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

人工势场算法动态避障演示:Python+Tkinter交互式路径规划实战

人工势场算法动态避障演示:Python+Tkinter交互式路径规划实战 简介本资源是一套基于人工势场法APF的动态路径规划教学演示系统面向机器人学、智能控制与路径规划方向的本科生及入门研究者解决静态/动态障碍物环境下移动机器人实时避障与目标跟踪问题。压缩包共8个文件6个MATLAB源码文件、1个GUI界面.fig文件、1个操作演示.avi视频总大小333KB结构精炼核心算法模块如吸引力/斥力计算、合力合成、可达性判断独立封装主程序Runme.m统一调度配合GUI可交互式添加障碍物、拖拽移动目标点直观呈现机器人动态重规划过程。已有1141人学习下载配套高清操作录像详细展示环境搭建、参数调整与运行逻辑帮助初学者快速理解势场法原理、MATLAB GUI开发要点及动态场景下的算法鲁棒性表现。 一直觉得学习路径规划算法最枯燥的时刻就是只看到公式和静态仿真曲线。尤其人工势场算法APF——它的核心魅力在于机器人对动态环境的实时响应但如果没有一个能随手“扔”障碍物、拖拽目标点、看机器人当场重新规划路线的交互界面很多直觉根本练不出来。这个项目就是为此做的一个带GUI界面的人工势场算法路径规划演示工具障碍物可以动态放置目标点可以当成移动点拖着走机器人会实时重新计算路线并完成动态避障。配合代码操作演示视频一起看从原理到落地一条线不需要翻论文也能把APF吃透。这个Demo适合三类人正在复习或讲授路径规划算法的学生和老师刚接触机器人局部避障、想知道这套“引力-斥力”思想到底怎么落地成代码的入门开发者以及想快速验证APF参数行为、在工程选型时做方案对比的机器人从业者。下面我把原理、GUI设计、核心代码、以及实测中反复踩过的坑全部拆开讲。1. 人工势场算法的核心思想与数学基础1.1 引力场与斥力场的本质人工势场算法的出发点非常直觉化把整个二维平面想象成一个高低起伏的“地形”。目标点是一个低洼的谷底障碍物是一座座凸起的小山机器人则像一个从高处自然滚落的小球顺着“坡度”往谷底移动同时避开山峰。整套机制由两个势场叠加而成引力势场Attractive Potential机器人离目标越远受到的“拉向目标”的力越大机器人被目标点吸引。斥力势场Repulsive Potential机器人靠近障碍物时障碍物会把它往外“推”距离越近推力越大超出影响范围后斥力归零。对势场求负梯度就得到力。这个操作在物理上等价于“力总是指向势能下降最快的方向”在算法上则是让机器人沿合力方向移动一步步逼近目标。引力势场最常见的定义是二次函数形式U_att(q) (1/2) * ξ * d(q, q_target)²其中ξ是引力增益系数d(q, q_target)表示机器人当前位置和目标点之间的欧氏距离。对q求梯度再取负号得到引力F_att(q) -∇U_att(q) -ξ * (q - q_target)这个表达式说明一个关键特性如果直接用梯度引力大小会随距离线性增大。也就是说机器人离目标越远拉力越大这在长距离路径规划中会表现出“机器人横冲直撞”的观感。实际写代码时我习惯对引力做一个“距离截断”超过阈值后改为恒定速度逼近避免初始速度过大导致越过目标点。斥力势场相对复杂一些。常见定义是U_rep(q) (1/2) * η * (1/d(q, q_obs) - 1/d0)²当 d(q, q_obs) ≤ d0 时成立否则 U_rep 0其中η是斥力增益系数d(q, q_obs)是机器人到障碍物表面的距离d0是斥力影响半径。对应的斥力为F_rep(q) η * (1/d - 1/d0) * (1/d²) * ∇(d)这个公式可以分三段理解当距离很远d d0时障碍物完全不参与计算斥力为0。当距离逐渐逼近d0时斥力从0开始增加。当距离不断缩小时(1/d - 1/d0)和(1/d²)两项同时增大导致斥力急剧上升形成一道“软墙”。我在实操中会把斥力再做一层上限保护因为当机器人几乎贴在障碍物表面时浮点除法会产生接近无穷大的值直接导致机器人“弹飞”到画布外面。1.2 合力计算与运动决策机器人最终受到的合力是引力和所有障碍物斥力的矢量和F_total F_att(q) Σ F_rep_i(q)每一步迭代机器人按单位合力方向移动固定步长step_sizeq_next q_current step_size * (F_total / |F_total|)这里有几个隐藏的工程决策第一为什么要用“固定步长”而不是“合力大小直接决定移动距离”因为APF本质上是一个迭代数值方法如果步长和力的大小挂钩很容易产生振荡。固定步长把机器人约束成“匀速巡线”模式视觉上更像真实的轮式机器人运动算法稳定性也好很多。第二合力的单位化会丢失力的幅值信息但这点在路径规划层面是可接受的。APF解决的是“往哪个方向走”而不是“以多大速度走”真实机器人的速度控制通常由底层的速度规划器负责上层只传方向。第三所有障碍物的斥力需要累加。每帧循环遍历障碍物列表逐个计算斥力复杂度是O(n)n是障碍物数量。在几十个障碍物以内完全无压力。1.3 势场参数的物理意义APF参数不多但每个参数的物理意义都很值得研究参数符号典型作用调节倾向引力增益ξ控制机器人向目标的“拽劲”偏大会导致路径贴障碍物过近斥力增益η控制障碍物排斥强度偏大会导致路径绕远甚至无法靠近目标斥力影响半径d0控制机器人提前多远感知障碍物偏大会在窄通道中过度避让迭代步长step_size控制机器人每次移动的距离偏大会振荡偏小会拖慢收敛要记住这四个参数不是独立的。ξ和η的比例关系决定了机器人在目标与障碍物之间的“偏好”。我在调参时一般先固定d0为障碍物直径的1.5倍step_size固定在5像素再调整ξ和η的比例看机器人的路径曲率是否符合直觉。2. 动态交互场景下的算法设计选型2.1 为什么选择Python Tkinter作为GUI方案这个Demo的核心诉求是“动态交互演示”而不是“产品级部署”。我对比过三套GUI方案最终选了TkinterTkinter的优势在于Python标准库自带无需安装任何第三方依赖拿到代码就能跑。对于算法演示类小工具Canvas画布足够完成动态绘制root.after()能方便地实现定时刷新。它虽然看起来朴素但在这个场景下完全是优点——项目中根本没有复杂的表单、表格、图表需求核心逻辑是视觉反馈和鼠标交互Tkinter的Canvas事件系统完全覆盖。如果换成PyQt/PySide界面会更精致但引入的依赖、编译配置、事件循环复杂度都大得多。对一个以算法演示为主的项目来说属于杀鸡用牛刀。Matplotlib的方案我也试过它的交互响应和实时绘制体验不如Canvas轻量尤其是拖拽目标点时会有明显延迟。2.2 动态障碍物与移动目标点的实时性挑战“动态”是这个项目区别于普通APF静态Demo的核心。动态体现在三个层面第一层是障碍物动态增加。用户在画布上任意位置点击障碍物立即出现在该位置。它在点击发生的下一帧就参与斥力计算需要事件回调里即时修改障碍物列表。第二层是目标点动态移动。这不是简单的点移动而是目标点始终跟随鼠标位置每帧都在变化。机器人不是“规划一条到固定目标的完整路径”而是不断根据当前目标位置重新计算下一步方向。这正好体现了APF作为局部规划器“边感知边移动”的特性。第三层是机器人动态避障。因为障碍物和目标点都在变机器人不能提前预计算整条路径只能每帧根据当前势场决定下一步移动方向。为了支撑这种实时性我采用了一个主循环驱动架构程序一启动就进入循环每隔30毫秒执行一次“感知-计算-移动-绘制”流程。所有鼠标事件不直接修改机器人状态而是先修改障碍物列表或目标点坐标让主循环在下一帧自然感知到变化。这种解耦方式避免了多线程竞争也让整个程序的逻辑链路非常清晰。2.3 帧循环机制的实现思路帧循环的核心是Tkinter的after方法。它向Tkinter事件循环注册一个延迟回调每次回调执行完业务逻辑后再次注册自己形成永不停歇的循环def update_loop(): # 1. 读取当前目标点和障碍物状态 # 2. 计算机器人当前受力 # 3. 更新机器人位置 # 4. 重绘画布 root.after(30, update_loop)30毫秒的间隔对应约33FPS的刷新率在这个刷新率下机器人移动看起来足够平滑。值得注意的是after的延迟时间不能设得太短否则在低配置机器上会出现事件积压鼠标点击响应滞后。我实测过10ms间隔视觉效果和30ms差别不大但CPU占用明显升高所以最终锁定30ms。这种基于定时器的循环模式本质上和游戏引擎的主循环是同一个思路渲染发生在固定的帧节奏中外部事件通过共享数据结构影响下一帧的计算。我们不需要在事件回调里即时绘图那样反而会造成大量的重复绘制调用拖慢界面。3. 从零搭建Demo环境准备与核心代码拆解3.1 环境依赖与整体框架这个项目只需要Python 3.8及以上版本以及Tkinter标准库。不需要安装numpy所有向量运算用math和简单的元组拆解实现因为场景中涉及的计算量并不大纯Python完全可以实时跑完。整个项目拆成四个模块方便后续扩展apf_demo/ ├── main.py # 程序入口初始化Tk窗口 ├── apf_core.py # 势场计算核心算法不依赖GUI ├── gui_app.py # Tkinter界面与交互事件 └── config.py # 可调参数集中管理把核心算法和GUI分离是很重要的一步。这样如果之后想把这个算法移植到ROS节点里可以直接复用apf_core.py而不需要动任何界面代码。3.2 机器人、障碍物、目标点的数据结构这个Demo不需要引入类继承体系用简单字典或namedtuple就够了。但为了让代码更贴近真实项目的组织方式我用三个轻量类class Robot: def __init__(self, x, y): self.x x self.y y class Obstacle: def __init__(self, x, y, radius25): self.x x self.y y self.radius radius class Target: def __init__(self, x, y): self.x x self.y y这里有一个需要提前处理的单位问题Tkinter的Canvas坐标系中y轴向下为正。数学推导时y轴向上为正的公式在画布上直接使用时机器人会“往反方向跑”。解决办法有两种要么在读取坐标时做一次矩阵变换要么在计算势场时沿用屏幕坐标、不额外做数学坐标变换。我选择了后者——既然所有几何运算都在屏幕坐标中进行就干脆把y轴向下当成本问题的事实标准公式中不改变符号这样最不容易出错。障碍物的半径在演示中统一设为30像素避免用户在界面上放置一个透明不可见的小点。实际应用中障碍物半径对应真实机器人的安全膨胀半径一般设置为“车身最大尺寸的一半 安全余量”。3.3 势场计算核心函数实现这一节是算法的核心。引力计算函数import math def attractive_force(robot_x, robot_y, target_x, target_y, xi8.0): dx target_x - robot_x dy target_y - robot_y dist math.hypot(dx, dy) if dist 1e-6: return 0.0, 0.0 # 引力大小与距离成正比这是二次势场求导后的结果 force_magnitude xi * dist fx force_magnitude * dx / dist fy force_magnitude * dy / dist return fx, fy这段代码的写法背后对应的是物理学中“力是势场的负梯度”这一条规则。如果直接写成fx xi * dx你会发现大小自动是xi * dist因为dx本身就是带方向的距离差值再除以dist归一化乘上force_magnitude得到向量。所以实际上代码可以简化为fx xi * dx fy xi * dy但保留“显式计算大小”的写法在调试时更有帮助可以打印出当前受力大小来定位问题。斥力计算函数def repulsive_force(robot_x, robot_y, obstacle_x, obstacle_y, obstacle_radius, eta2000.0, d0100.0): dx robot_x - obstacle_x dy robot_y - obstacle_y dist_to_center math.hypot(dx, dy) # 到障碍物表面的距离 dist_to_surface dist_to_center - obstacle_radius if dist_to_surface d0: return 0.0, 0.0 if dist_to_surface 1e-6: # 防止除零给一个紧急的小位移方向 dist_to_surface 1e-6 # 斥力大小 eta * (1/d - 1/d0) / d^2 magnitude eta * (1.0 / dist_to_surface - 1.0 / d0) / (dist_to_surface * dist_to_surface) fx magnitude * dx / dist_to_center fy magnitude * dy / dist_to_center return fx, fy这段代码有一个我特别提醒的细节距离用的是“机器人到障碍物表面”的距离即dist_to_center减去obstacle_radius。如果直接用圆心距离机器人的视觉半径会被忽略看起来像是机器人已经“压进”障碍物内部才感受到斥力。第一次跑Demo时我也踩了这个坑机器人总是先撞上障碍物再被弹开非常不真实。3.4 动态避障逻辑与路径更新在每一帧更新中机器人需要重新计算合力归一化后移动一个步长def compute_total_force(robot, target, obstacles, config): fx_total, fy_total 0.0, 0.0 # 引力部分 att_fx, att_fy attractive_force( robot.x, robot.y, target.x, target.y, config.XI_ATT ) fx_total att_fx fy_total att_fy # 斥力部分遍历所有障碍物 for obs in obstacles: rep_fx, rep_fy repulsive_force( robot.x, robot.y, obs.x, obs.y, obs.radius, config.ETA_REP, config.D0 ) fx_total rep_fx fy_total rep_fy return fx_total, fy_total主循环更新逻辑def update_loop(): # 计算合力 fx, fy compute_total_force(robot, target, obstacles, config) magnitude math.hypot(fx, fy) if magnitude 1e-6: # 归一化到单位方向乘以固定步长 step_x config.STEP_SIZE * fx / magnitude step_y config.STEP_SIZE * fy / magnitude robot.x step_x robot.y step_y # 记录路径点画折线 path_points.append((robot.x, robot.y)) if len(path_points) 2000: path_points.pop(0) # 到达目标附近则自动暂停或重置 dist_to_target math.hypot(target.x - robot.x, target.y - robot.y) if dist_to_target 15: robot.x, robot.y start_pos draw_canvas() root.after(30, update_loop)我想重点说明“归一化再乘固定步长”这个做法。如果不归一化直接把fx和fy当作位移增量那么当机器人远离目标时引力很大机器人的单帧位移会非常大出现“瞬移”的效果当机器人靠近障碍物时斥力又可能突然猛增产生抖动。固定步长本质上把APF变成了一个离散时间步的常速运动模型保留了动态避障的“形”又保证了视觉上的连续感。到达目标附近后自动重置这是为了演示可循环性。我实测了很多次如果不重置机器人会一直围着目标点绕小圈——这是局部极小值的一种表现后面会专门讲。3.5 GUI交互层鼠标放置障碍物、拖拽目标点Tkinter的交互层由三个事件组成左键点击画布在点击位置创建一个新障碍物。左键拖拽如果按住的是目标点目标点跟随鼠标移动。右键点击/拖拽直接移动机器人的起始位置方便快速测试新场景。实现代码def on_canvas_left_click(event): # 判断是否点击在目标点附近 if math.hypot(event.x - target.x, event.y - target.y) 50: dragging_target True target.x, target.y event.x, event.y else: obstacles.append(Obstacle(event.x, event.y)) def on_canvas_drag(event): if dragging_target: target.x, target.y event.x, event.y def on_canvas_right_click(event): robot.x, robot.y event.x, event.y path_points.clear()这里有一个交互设计上的细节如果用户想放置障碍物但恰好点击位置离目标点比较近会被误判为拖拽目标点。我加了一个距离阈值判断50像素并且把目标点画成一个带外圈的大圆让用户在视觉上能意识到“这个区域是可以拖动目标的”。如果点的是目标点外圈更远的位置就正常放障碍物。界面布局这块我用Frame切分左右区域左侧是Canvas画布右侧是控制面板。控制面板放五个参数输入框引力增益、斥力增益、影响半径、步长、障碍物半径和两个按钮清空障碍物、重置机器人。参数输入的即时生效是我特别要求的——修改参数后点击“应用参数”按钮再重新计算而不是每次修改都重新启动程序。4. 实测运行中的常见坑与调参经验4.1 局部极小值问题机器人卡住怎么办APF最出名的缺陷就是局部极小值local minima。在一个U形障碍物中间机器人受到的引力被周围障碍物的斥力抵消合力接近零于是它停在原地轻轻颤动永远走不出来。我复现这个现象非常简单在目标点前方放两个紧挨着的障碍物留出一条很窄的缝机器人会卡在缝口处。在Demo中你会看到机器人来回小幅抖动路径轨迹在某个区域画出密集的小圈。处理这个问题有几种常见思路添加随机扰动当检测到合力接近零且未到达目标时给机器人加一个随机方向的微小偏移打破力平衡。设置虚拟目标点当检测到卡住超过N帧在垂直于引力方向的平面上设置一个临时子目标引导机器人绕开局部极小区。判断“合力连续多帧保持同一位置”后强制切换到全局路径规划器给出的绕行路径。在我的Demo里最简单也最直观的方法是第一种因为视觉上能看到机器人“抖了几下然后挣脱”很适合演示教学。实现方式是维护一个stuck_count计数器如果连续20帧机器人的位置变化小于0.5像素而且还没到目标点就在当前合力方向上叠加一个随机偏转角度。4.2 目标点不可达问题斥力与引力的平衡目标点不可达GNRONGoal Nonreachable with Obstacles Nearby是APF的另一个经典问题。现象是目标点紧挨着障碍物机器人靠近目标时受到的斥力远大于引力机器人永远无法到达目标点只会停在目标点外面一圈。我一开始的斥力公式没有考虑目标距离修正项结果测试时把目标点拖到障碍物旁边机器人始终在目标点外围绕来绕去看起来非常傻。后来在斥力公式中加入了一个目标距离因子F_rep(G) η * (1/d - 1/d0) * (1/d²) * d_target^n其中d_target是机器人到目标点的距离n通常取2。这样当机器人靠近目标时引力和斥力会同步衰减解决目标点被障碍物“屏蔽”的问题。如果只做演示也可以采取一个取巧方案把目标点渲染为一个大圆只要机器人进入目标点半径范围内就算“到达”然后自动重置。这样虽然不解决GNRON但至少Demo不会卡在目标点旁演示效果更流畅。我做项目时保留了两种模式默认开启目标距离修正项方便展示算法原理时切换到原始公式展示GNRON效应。4.3 抖动/震荡问题步长与迭代频率的配合机器人在狭窄通道里特别容易发生左右抖动。原因有两个一是步长太大导致机器人每次移动都“跨过”势场中心线二是斥力影响半径设置过小机器人只有到了障碍物很近的地方才感知到斥力等感知到的时候已经晚了于是猛烈反弹。排查抖动问题时我建议先做减法定位把所有障碍物移走只留两个对称障碍物观察机器人路径。如果左右对称的波动仍然明显基本可以确定是步长问题。我把步长从8像素降到5像素后抖动明显缓解。如果步长已经很小仍然抖动可以考虑给机器人位置加一阶低通滤波robot.x 0.7 * robot.x 0.3 * raw_force_new_x注意这里的滤波是在算法计算出的位置基础上做平滑不是直接对速度做平滑两者效果有本质区别。位置滤波能显著降低路径的毛刺感但会让机器人对突然出现的障碍物反应“钝”一些。在Demo的帧率下0.7/0.3的系数已经足够不需要调得更极端。4.4 动态场景下的性能优化这个Demo的障碍物数量一般不会超过50个每帧计算一次势场的耗时在毫秒级别完全不会有性能问题。但如果你把障碍物数量加到500个或者把步长缩小到1像素让迭代次数暴增就会遇到一些性能瓶颈。我在优化时做了三个动作第一把Canvas的重绘逻辑改为“只更新有变化的部分”。机器人每帧移动但障碍物和目标点不一定每帧都变。我维护了一个dirty_flag只有障碍物列表或目标点坐标发生变化时才重绘障碍物和目标点机器人本身则单独用canvas.coords()更新而不是整幅画布delete再重画。第二斥力计算中先做粗一步的“距离筛选”。如果障碍物距离机器人超过d0 机器人安全半径就直接跳过斥力计算。这相当于给O(n)加了一个提前剪枝省下了很多浮点开方运算。第三路径点记录做了最大长度限制。2000个点画成折线后Canvas的绘制负担会逐渐增加。超过限制后从头弹出旧点既能保证轨迹的延续感又不会让画布元素无限增长。在我实际测试中障碍物数量在200个以内、刷新率33FPS时CPU占用稳定在15%左右这已经能满足演示工具的交互流畅度要求。5. 从Demo到真实机器人的扩展思考5.1 与ROS2、MoveIt等框架的衔接这个Demo在仿真层面验证了APF的动态避障能力但把它移植到真实机器人系统时需要把定时器、Canvas绘制和鼠标事件替换成对应框架的机制。在ROS2环境中最直接的方式是把apf_core.py里的compute_total_force和update函数封装成一个ROS节点。节点订阅三个话题/goal_pose接收目标点位置、/obstacle_cloud接收障碍物点云或占据栅格、/odom接收机器人当前位姿。计算出的合力方向发布到/cmd_vel话题由底层的差速驱动控制器转换为线速度和角速度。MoveIt用于机械臂避障是另一条路线。机械臂的关节空间和笛卡尔空间转换比移动机器人复杂很多但APF思想仍然可以用于机械臂末端的局部避障把机械臂末端当作“机器人”把规划的关节轨迹作为“目标点”把工作空间中的动态障碍物作为斥力源。MoveIt的柔性规划框架里也能自定义运动学约束APF可以作为局部避障层嵌入。不过我要提醒一句ROS2和MoveIt实际部署时APF一般不会单独作为唯一规划器因为它天然缺乏全局最优性。更常见的组合是全局层用A*、Dijkstra或RRT*生成一条无碰撞路径局部层用APF或其变种做动态避障。5.2 从2D仿真到真实物理世界的差距2D仿真里机器人是一个没有惯性的点但在真实机器人上运动学模型、传感器噪声、控制延迟都会改变算法行为。以差速轮式机器人为例它能执行的运动不是任意方向的平移而是线速度加角速度的组合。要把APF计算出的合力方向转成运动指令需要先计算机器人当前朝向和合力方向的夹角desired_heading math.atan2(fy, fx) heading_error desired_heading - current_heading # 把角度差映射到[-pi, pi] heading_error math.atan2(math.sin(heading_error), math.cos(heading_error)) angular_velocity Kp_angular * heading_error linear_velocity Kp_linear * math.cos(heading_error)这个转换在仿真Demo中完全看不到但在真实硬件上至关重要。如果直接把合力方向当作线速度方向差速机器人会原地打转或产生侧滑。传感器噪声是另一个差距。仿真中障碍物坐标是精确值真实场景中无论是激光雷达还是深度相机都有测量噪声和延迟。APF对噪声比较敏感尤其是斥力项中1/d²的放大效应会让一个噪声尖峰变成巨大的虚假斥力。工程上的做法是对传感器数据做时间平滑或者用占据栅格地图替代原始点云参与势场计算。5.3 算法改进方向与Dijkstra、A*、RRT的对比做这个Demo之前我一度以为APF的短板可以通过调参弥补但接触了更多路径规划算法后我对APF在算法谱系中的位置有了更清晰的认识算法完备性最优性实时性适用场景Dijkstra完备最优慢静态地图全局规划A*完备最优启发式一致时中静态地图全局规划RRT/RRT*概率完备概率最优中高维空间搜索人工势场不完备不保证快局部动态避障这张表的核心结论是APF的优势不在“找到最短路径”而在“实时响应动态环境变化”的极低计算开销。它和全局规划器其实是互补关系不是替代关系。我在做这个Demo时尝试过一个混合方案先用A*在静态栅格图上规划一条全局参考路径然后以参考路径上的当前目标点作为APF的临时目标点APF只负责避开参考路径上发现的动态障碍物。这个方案兼顾了全局最优性和局部实时性是我个人最推荐的实践方向。从更远的视角看近年比较流行的DWA动态窗口法、TEB时间弹性带等局部规划器虽然具体机制和APF不一样但它们都在解决同一个问题如何在动态环境中快速找到一条无碰撞且符合运动学约束的局部路径。理解了APF的“感知-计算-执行”循环再去看DWA的“速度采样-轨迹预测-评价选择”循环很多思路是可以平移的。最后分享两个我在实际操作中总结的细节关于这个Demo我最想说的是做这类可视化工具最大的收获往往不是算法本身而是“如何把算法变成可见的运动”。每当你看到一个反直觉的现象——比如机器人明明看到了目标却被障碍物“卡死”或者路径来回绕一个大圈——那都是理解算法边界条件的最好机会。一个具体小技巧在调试参数时别只盯着最终路径把机器人的“受力向量”画出来用一条从机器人位置出发的小箭头表示合力方向。我在画布上增加了这个调试模式后走一次就能看清是引力还是斥力占主导比闷头调参快得多。具体实现是在绘制函数里根据合力方向画一条长度和力大小成正比的线段。另一个细节为了让演示视频的观感更好我在画布底部加了一个“路径长度”实时统计。每当机器人到达目标点或重置都会显示本次总路径长度。这样在视频里观众能直观看到“参数调好后路径变短了”也可以用来对比不同参数组合的效果。如果你只是想把Demo跑起来照着代码敲一遍基本半小时就能看到效果但如果你想真正搞懂APF的脾气建议自己动手改一改参数、拖一拖目标点、故意制造几个局部极值看它怎么挣扎。等到你能预测“机器人下一步会往哪走”这门算法才算真正学进去了。本文还有配套的精品资源点击获取
返回列表