轨迹优化 | 基于ESDF的共轭梯度优化算法(附ROS C++/Python仿真)

news2024/9/24 11:26:26

目录

  • 0 专栏介绍
  • 1 数值优化:共轭梯度法
  • 2 基于共轭梯度法的轨迹优化
    • 2.1 障碍约束函数
    • 2.2 曲率约束函数
    • 2.3 平滑约束函数
  • 3 算法仿真
    • 3.1 ROS C++实现
    • 3.2 Python实现

0 专栏介绍

🔥课程设计、毕业设计、创新竞赛、学术研究必备!本专栏涉及更高阶的运动规划算法实战:曲线生成与轨迹优化、碰撞模型与检测、多智能体群控、深度强化学习运动规划、社会性导航、全覆盖路径规划等内容,每个模型都包含代码实现加深理解。

🚀详情:运动规划实战进阶


1 数值优化:共轭梯度法

共轭梯度法是一种用于解决大型稀疏线性方程组或无约束优化问题的迭代数值方法。它利用了线性代数中的共轭概念,并结合了梯度下降法的思想,以更有效地找到函数的极小值点。

在这里插入图片描述

形式化地,对于 n n n维二次优化问题

x ∗ = a r g min ⁡ x 1 2 x T Q x + q T x \boldsymbol{x}^*=\mathrm{arg}\min _{\boldsymbol{x}}\frac{1}{2}\boldsymbol{x}^T\boldsymbol{Qx}+\boldsymbol{q}^T\boldsymbol{x} x=argxmin21xTQx+qTx

其中 Q \boldsymbol{Q} Q n n n维对称正定阵, q ∈ R n \boldsymbol{q}\in \mathbb{R} ^n qRn,共轭梯度法既克服了梯度下降法收敛慢的缺点,又避免存储和计算牛顿类算法所需的二阶梯度信息,其核心原理是:求解矩阵 Q \boldsymbol{Q} Q的共轭向量组 d 0 , d 1 , ⋯   , d n \boldsymbol{d}_0,\boldsymbol{d}_1,\cdots ,\boldsymbol{d}_n d0,d1,,dn作为 n n n个优化方向,由于优化方向间彼此正交,故每次迭代只需沿着一个方向 d i \boldsymbol{d}_i di寻优而互不影响。所以理论上最多 n n n次迭代就能找到最优解,收敛速度快,但实际应用中需要视具体情况确定阈值。

2 基于共轭梯度法的轨迹优化

对路径序列 X = { x i = ( x i , y i ) ∣ i ∈ [ 1 , N ] } \mathcal{X} =\left\{ \boldsymbol{x}_i=\left( x_i,y_i \right) |i\in \left[ 1,N \right] \right\} X={xi=(xi,yi)i[1,N]}设计优化目标函数

f ( X ) = w o P o b s ( X ) + w κ P c u r ( X ) + w s P s m o ( X ) f\left( \mathcal{X} \right) =w_oP_{\mathrm{obs}}\left( \mathcal{X} \right) +w_{\kappa}P_{\mathrm{cur}}\left( \mathcal{X} \right) +w_sP_{\mathrm{smo}}\left( \mathcal{X} \right) f(X)=woPobs(X)+wκPcur(X)+wsPsmo(X)

2.1 障碍约束函数

P o b s ( X ) = ∑ x i ∈ X σ o ( ∥ x i − o min ⁡ ∥ 2 − d max ⁡ ) P_{\mathrm{obs}}\left( \mathcal{X} \right) =\sum_{\boldsymbol{x}_i\in \mathcal{X}}^{}{\sigma _o\left( \left\| \boldsymbol{x}_i-\boldsymbol{o}_{\min} \right\| _2-d_{\max} \right)} Pobs(X)=xiXσo(xiomin2dmax)

惩罚机器人与障碍发生碰撞,其中 σ o ( ⋅ ) \sigma _o\left( \cdot \right) σo()是惩罚函数(可选为二次型); o min ⁡ \boldsymbol{o}_{\min} omin是距离 x i \boldsymbol{x}_i xi最近的障碍物; d max ⁡ d_{\max} dmax是距离阈值,节点与最近障碍物的距离超过阈值则不会受到惩罚。以二次型为例,其梯度为

∂ P o b s ( x i ) ∂ x i = 2 ( ∥ x i − o min ⁡ ∥ 2 − d max ⁡ ) ∂ ∥ x i − o min ⁡ ∥ 2 ∂ x i = 2 ( ∥ x i − o min ⁡ ∥ 2 − d max ⁡ ) x i − o min ⁡ ∥ x i − o min ⁡ ∥ 2 \frac{\partial P_{\mathrm{obs}}\left( \boldsymbol{x}_i \right)}{\partial \boldsymbol{x}_i}=2\left( \left\| \boldsymbol{x}_i-\boldsymbol{o}_{\min} \right\| _2-d_{\max} \right) \frac{\partial \left\| \boldsymbol{x}_i-\boldsymbol{o}_{\min} \right\| _2}{\partial \boldsymbol{x}_i}\\=2\left( \left\| \boldsymbol{x}_i-\boldsymbol{o}_{\min} \right\| _2-d_{\max} \right) \frac{\boldsymbol{x}_i-\boldsymbol{o}_{\min}}{\left\| \boldsymbol{x}_i-\boldsymbol{o}_{\min} \right\| _2} xiPobs(xi)=2(xiomin2dmax)xixiomin2=2(xiomin2dmax)xiomin2xiomin

这里最小障碍通过ESDF获取,可以参考相关文章:

  • ROS2从入门到精通5-1:详解代价地图与costmap插件编写(以距离场ESDF为例)
  • 轨迹优化 | 图解欧氏距离场与梯度场算法(附ROS C++/Python实现)

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toB9MWvL-1721696975312)(https://i-blog.csdnimg.cn/direct/2da70f16131b48e2a2dfb8e1cbf7a89b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATXIuV2ludGVyYA==,size_50,color_FFFFFF,t_30,g_se,x_16#pic_center =650x)]

2.2 曲率约束函数

P c u r ( X ) = ∑ x i ∈ X σ κ ( Δ ϕ i ∥ Δ x i ∥ 2 − κ max ⁡ ) P_{\mathrm{cur}}\left( \mathcal{X} \right) =\sum_{\boldsymbol{x}_i\in \mathcal{X}}^{}{\sigma _{\kappa}\left( \frac{\varDelta \phi _i}{\left\| \varDelta \boldsymbol{x}_i \right\| _2}-\kappa _{\max} \right)} Pcur(X)=xiXσκ(Δxi2Δϕiκmax)

对每个节点轨迹的瞬时曲率进行了上界约束,其中 σ κ ( ⋅ ) \sigma _{\kappa}\left( \cdot \right) σκ()是惩罚函数(可选为二次型); 是路径最大允许曲率——由机器人转向半径约束决定

其梯度为

∂ P c u r ( x i ) ∂ x i = α 1 ∂ κ i ∂ x i − 1 + α 2 ∂ κ i ∂ x i + α 3 ∂ κ i ∂ x i + 1 \frac{\partial P_{\mathrm{cur}}\left( \boldsymbol{x}_i \right)}{\partial \boldsymbol{x}_i}=\alpha _1\frac{\partial \kappa _i}{\partial \boldsymbol{x}_{i-1}}+\alpha _2\frac{\partial \kappa _i}{\partial \boldsymbol{x}_i}+\alpha _3\frac{\partial \kappa _i}{\partial \boldsymbol{x}_{i+1}} xiPcur(xi)=α1xi1κi+α2xiκi+α3xi+1κi

为了求解该梯度,定义向量 a \boldsymbol{a} a在向量 b \boldsymbol{b} b的垂直分量为

a ⊥ b = a − a T b ∣ b ∣ ⋅ b ∣ b ∣ \boldsymbol{a}\bot \boldsymbol{b}=\boldsymbol{a}-\frac{\boldsymbol{a}^T\boldsymbol{b}}{\left| \boldsymbol{b} \right|}\cdot \frac{\boldsymbol{b}}{\left| \boldsymbol{b} \right|} ab=abaTbbb

则令

p 1 = Δ x i ⊥ ( − Δ x i + 1 ) ∥ Δ x i ∥ 2 ∥ Δ x i + 1 ∥ 2 , p 2 = ( − Δ x i + 1 ) ⊥ Δ x i ∥ Δ x i ∥ 2 ∥ Δ x i + 1 ∥ 2 \boldsymbol{p}_1=\frac{\varDelta \boldsymbol{x}_i\bot \left( -\varDelta \boldsymbol{x}_{i+1} \right)}{\left\| \varDelta \boldsymbol{x}_i \right\| _2\left\| \varDelta \boldsymbol{x}_{i+1} \right\| _2}, \boldsymbol{p}_2=\frac{\left( -\varDelta \boldsymbol{x}_{i+1} \right) \bot \varDelta \boldsymbol{x}_i}{\left\| \varDelta \boldsymbol{x}_i \right\| _2\left\| \varDelta \boldsymbol{x}_{i+1} \right\| _2} p1=Δxi2Δxi+12Δxi(Δxi+1),p2=Δxi2Δxi+12(Δxi+1)Δxi

从而

∂ κ i ∂ x i = 1 ∥ Δ x i ∥ 2 − 1 1 − cos ⁡ 2 Δ ϕ ( − p 1 − p 2 ) − Δ ϕ i Δ x i ∥ Δ x i ∥ 2 3 ∂ κ i ∂ x i − 1 = 1 ∥ Δ x i ∥ 2 − 1 1 − cos ⁡ 2 Δ ϕ p 2 + Δ ϕ i Δ x i ∥ Δ x i ∥ 2 3 ∂ κ i ∂ x i + 1 = 1 ∥ Δ x i ∥ 2 − 1 1 − cos ⁡ 2 Δ ϕ p 1 \begin{aligned} \frac{\partial \kappa _i}{\partial \boldsymbol{x}_i}&=\frac{1}{\left\| \varDelta \boldsymbol{x}_i \right\| _2}\frac{-1}{\sqrt{1-\cos ^2\varDelta \phi}}\left( -\boldsymbol{p}_1-\boldsymbol{p}_2 \right) -\frac{\varDelta \phi _i\varDelta \boldsymbol{x}_i}{\left\| \varDelta \boldsymbol{x}_i \right\| _{2}^{3}}\\\frac{\partial \kappa _i}{\partial \boldsymbol{x}_{i-1}}&=\frac{1}{\left\| \varDelta \boldsymbol{x}_i \right\| _2}\frac{-1}{\sqrt{1-\cos ^2\varDelta \phi}}\boldsymbol{p}_2+\frac{\varDelta \phi _i\varDelta \boldsymbol{x}_i}{\left\| \varDelta \boldsymbol{x}_i \right\| _{2}^{3}}\\\frac{\partial \kappa _i}{\partial \boldsymbol{x}_{i+1}}&=\frac{1}{\left\| \varDelta \boldsymbol{x}_i \right\| _2}\frac{-1}{\sqrt{1-\cos ^2\varDelta \phi}}\boldsymbol{p}_1\end{aligned} xiκixi1κixi+1κi=Δxi211cos2Δϕ 1(p1p2)Δxi23ΔϕiΔxi=Δxi211cos2Δϕ 1p2+Δxi23ΔϕiΔxi=Δxi211cos2Δϕ 1p1

2.3 平滑约束函数

P s m o ( X ) = ∑ x i ∈ X ∥ Δ x i − Δ x i + 1 ∥ 2 2 P_{\mathrm{smo}}\left( \mathcal{X} \right) =\sum_{\boldsymbol{x}_i\in \mathcal{X}}^{}{\left\| \varDelta \boldsymbol{x}_i-\varDelta \boldsymbol{x}_{i+1} \right\| _{2}^{2}} Psmo(X)=xiXΔxiΔxi+122

期望每段轨迹的长度近似相等,使整体运动更平坦,其梯度为

∂ P s m o ( x i ) ∂ x i = 2 ( x i − 2 − 4 x i − 1 + 6 x i − 4 x i + 1 + x i + 2 ) \frac{\partial P_{\mathrm{smo}}\left( \boldsymbol{x}_i \right)}{\partial \boldsymbol{x}_i}=2\left( \boldsymbol{x}_{i-2}-4\boldsymbol{x}_{i-1}+6\boldsymbol{x}_i-4\boldsymbol{x}_{i+1}+\boldsymbol{x}_{i+2} \right) xiPsmo(xi)=2(xi24xi1+6xi4xi+1+xi+2)

3 算法仿真

3.1 ROS C++实现

核心算法如下所示:

bool CGOptimizer::optimize(Points2d& path_o)
{
  // distance map update
  boost::shared_ptr<costmap_2d::DistanceLayer> distance_layer;
  bool is_distance_layer_exist = false;
  for (auto layer = costmap_ros_->getLayeredCostmap()->getPlugins()->begin();
       layer != costmap_ros_->getLayeredCostmap()->getPlugins()->end(); ++layer)
  {
    distance_layer = boost::dynamic_pointer_cast<costmap_2d::DistanceLayer>(*layer);
    if (distance_layer)
    {
      is_distance_layer_exist = true;
      break;
    }
  }
  if (!is_distance_layer_exist)
    ROS_ERROR("Failed to get a Distance layer for potentional application.");

  int iter = 0;
  while (iter < max_iter_)
  {
    // choose the first three nodes of the path
    for (int i = 2; i < path_o.size() - 2; ++i)
    {
      Eigen::Vector2d xi_c2(path_o[i - 2].first, path_o[i - 2].second);
      Eigen::Vector2d xi_c1(path_o[i - 1].first, path_o[i - 1].second);
      Eigen::Vector2d xi(path_o[i].first, path_o[i].second);
      Eigen::Vector2d xi_p1(path_o[i + 1].first, path_o[i + 1].second);
      Eigen::Vector2d xi_p2(path_o[i + 2].first, path_o[i + 2].second);

      Eigen::Vector2d correction = Eigen::Vector2d::Zero();
      correction = correction + _calObstacleTerm(xi, distance_layer);
      if (!_insideMap((xi - correction)[0], (xi - correction)[1]))
        continue;

      correction = correction + _calSmoothTerm(xi_c2, xi_c1, xi, xi_p1, xi_p2);
      if (!_insideMap((xi - correction)[0], (xi - correction)[1]))
        continue;

      correction = correction + _calCurvatureTerm(xi_c1, xi, xi_p1);
      if (!_insideMap((xi - correction)[0], (xi - correction)[1]))
        continue;

      Eigen::Vector2d gradient = alpha_ * correction / (w_obstacle_ + w_smooth_ + w_curvature_);
      if (std::isnan(gradient[0]) || std::isnan(gradient[1]))
        gradient = Eigen::Vector2d::Zero();

      xi = xi - gradient;
      path_o[i] = std::make_pair(xi[0], xi[1]);
    }

    iter++;
  }

  return true;
}

在这里插入图片描述

在这里插入图片描述

3.2 Python实现

核心算法如下所示:

while i < self.max_iter:
	for j in range(2, pts_num - 2):
	    xjm2 = np.array([[optimized_path[j - 2][0]], [optimized_path[j - 2][1]]])
	    xjm1 = np.array([[optimized_path[j - 1][0]], [optimized_path[j - 1][1]]])
	    xj   = np.array([[optimized_path[j][0]], [optimized_path[j][1]]])
	    xjp1 = np.array([[optimized_path[j + 1][0]], [optimized_path[j + 1][1]]])
	    xjp2 = np.array([[optimized_path[j + 2][0]], [optimized_path[j + 2][1]]])
	
	    gradient = np.zeros((2, 1))
	
	    # obstacle avoidance
	    gradient = gradient + self.obstacleTerm(xj)
	    if not self.isOnGrid(xj - gradient):
	        continue
	
	    # smooth
	    gradient = gradient + self.smoothTerm(xjm2, xjm1, xj, xjp1, xjp2)
	    if not self.isOnGrid(xj - gradient):
	        continue
	
	    # curvature
	    gradient = gradient + self.curvatureTerm(xjm1, xj, xjp1)
	    if not self.isOnGrid(xj - gradient):
	        continue
	
	    xj = xj - self.alpha * gradient / self.w_total
	    optimized_path[j, :] = xj[:, 0]
	
	i += 1
	
	self.trajectory = optimized_path

在这里插入图片描述

完整工程代码请联系下方博主名片获取


🔥 更多精彩专栏

  • 《ROS从入门到精通》
  • 《Pytorch深度学习实战》
  • 《机器学习强基计划》
  • 《运动规划实战精讲》

👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1942598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Chapter17 表面着色器——Shader入门精要学习

Chapter17 表面着色器 一、编译指令1.表面函数2.光照函数3.其他可选参数 二、两个结构体1.Input 结构体&#xff1a;数据来源2.SurfaceOutput 三、Unity背后做了什么四、表面着色器的缺点 一、编译指令 作用&#xff1a;指明该表面着色器的 表面函数 和 光照函数&#xff0c;并…

LeetCode 58.最后一个单词的长度 C++

LeetCode 58.最后一个单词的长度 C 思路&#x1f914;&#xff1a; 先解决当最后字符为空格的情况&#xff0c;如果最后字符为空格下标就往后移动&#xff0c;直到不为空格才停止&#xff0c;然后用rfind查询空格找到的就是最后一个单词的起始位置&#xff0c;最后相减就是单词…

前台文本直接取数据库值doFieldSQL插入SQL

实现功能&#xff1a;根据选择的车间主任带出角色。 实现步骤&#xff1a;OA的“字段联动”功能下拉选项带不出表“hrmrolemembers”&#xff0c;所以采用此方法。 doFieldSQL("select roleid from HrmResource as a inner join hrmrolemembers as b on a.id b.resource…

AGI 之 【Hugging Face】 的【从零训练Transformer模型】之二 [ 从零训练一个模型 ] 的简单整理

AGI 之 【Hugging Face】 的【从零训练Transformer模型】之二 [ 从零训练一个模型 ] 的简单整理 目录 AGI 之 【Hugging Face】 的【从零训练Transformer模型】之二 [ 从零训练一个模型 ] 的简单整理 一、简单介绍 二、Transformer 1、模型架构 2、应用场景 3、Hugging …

Flink架构底层原理详解:案例解析(43天)

系列文章目录 一、Flink架构&#xff08;掌握&#xff09; 二、Flink代码案例&#xff08;掌握&#xff09; 三、UDF&#xff08;熟悉&#xff09; 四、Flink常见面试题整理 文章目录 系列文章目录前言一、Flink架构&#xff08;掌握&#xff09;1、系统架构1.1 通信&#xff…

海康威视工业相机SDK+Python+PyQt开发数据采集系统(支持软件触发、编码器触发)

海康威视工业相机SDKPythonPyQt开发数据采集系统&#xff08;支持软件触发、编码器触发&#xff09; pythonpyqt开发海康相机数据采集系统 1 开发软件功能&#xff1a; 支持搜索相机&#xff1a;Gige相机设备和USB相机设备支持两种触发模式&#xff1a;软件触发和编码器触发支…

Docker、containerd、CRI-O 和 runc 之间的区别

容器与 Docker 这个名称并不紧密相关。你可以使用其他工具来运行容器 您可以使用 Docker 或一堆非Docker 的其他工具来运行容器。docker只是众多选项之一&#xff0c;Docker&#xff08;公司&#xff09;在生态系统中创建了一些很棒的工具&#xff0c;但不是全部。 容器方面有…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十三章 驱动模块传参

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

【PPT把当前页输出为图片】及【PPT导出图片模糊】的解决方法(sci论文图片清晰度)

【PPT把当前页输出为图片】及【PPT导出图片模糊】的解决方法 内容一&#xff1a;ppt把当前页输出为图片&#xff1a;内容二&#xff1a;ppt导出图片模糊的解决方法&#xff1a;方法&#xff1a;步骤1&#xff1a;打开注册表编辑器步骤2&#xff1a;修改注册表&#xff1a; 该文…

使用jacob文字生成语音文件时遇到的问题及解决方案

使用jacob文字生成语音文件时 出现如下错误 java.lang.UnsatisfiedLinkError: no jacob-1.18-x64 in java.library.path错误表明Java虚拟机无法在其指定的java.library.path路径中找到名为jacob-1.18-x64的本地库文件。这个错误通常发生在尝试通过JNI或者JNA调用本地库时&…

算法学习笔记(Hello算法)—— 初识算法

1、相关链接 Hello算法&#xff1a;Hello 算法 (hello-algo.com) 2、算法是什么 2.1 算法定义 算法是一系列明确、有限且有效的步骤或指令的集合&#xff0c;用于解决特定问题或执行特定任务。 算法具有以下基本特征&#xff1a; 输入&#xff1a;算法至少有一个输入&…

【python】PyQt5中QAbstractButton基类的特性详细分析与实战应用

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

聚水潭·奇门对接打通金蝶云星空订单查询接口与销售退货新增接口

聚水潭奇门对接打通金蝶云星空订单查询接口与销售退货新增接口 对接源平台:聚水潭奇门 聚水潭SaaSERP于2014年4月上线&#xff0c;目前累计超过2.5万商家注册使用&#xff0c;成为淘宝应用服务市场ERP类目商家数和商家月订单增速最快的ERP。2014年及2015年“双十一”当天&#…

TQSDRPI开发板教程:实现PL端的UDP回环与GPSDO

本教程将完成一个全面的UDP运行流程与GPSDO测试&#xff0c;从下载项目的源代码开始&#xff0c;通过编译过程&#xff0c;最终将项目部署到目标板卡上运行演示。此外&#xff0c;我们还介绍如何修改板卡的IP地址&#xff0c;以便更好地适应您的网络环境或项目需求。 首先从Gi…

【Java】:洗牌功能和杨辉三角的实现

洗牌 此操作包含的基本功能有&#xff1a; 组牌&#xff1a;组建 52 张扑克牌 四种花色&#xff1a;“♥️”&#xff0c;“♠️”&#xff0c;“⬛️”&#xff0c;“♣️”每种花色 13 张牌&#xff1a;1~13 洗牌&#xff1a;将 52 张扑克牌打乱顺序发牌&#xff1a;给三个人…

MybatisPlus设置动态表名

对于一些数据量比较大的表&#xff0c;为了提高查询性能&#xff0c;我们一般将表拆分成多张表&#xff0c;常见的是根据数据量&#xff0c;按年分表或者按月分表&#xff1b;分表虽然太高了查询性能&#xff0c;但是在查询的时候&#xff0c;如何才能查询执行分表数据呢&#…

谷粒商城实战笔记-45-商品服务-API-三级分类-查询-递归树形结构数据获取

文章目录 一&#xff0c;准备工作1&#xff0c;启动虚拟机2&#xff0c;启动mysql3&#xff0c;执行MySQL脚本插入分类数据4&#xff0c;关于三级分类 二&#xff0c;Controller层新增接口三&#xff0c;Service层新增接口1&#xff0c;代码实现2&#xff0c;测试 从这一节开始…

海康威视综合安防管理平台 detection 前台RCE漏洞复现

0x01 产品简介 海康威视综合安防管理平台是一套“集成化”、“智能化”的平台,通过接入视频监控、一卡通、停车场、报警检测等系统的设备。海康威视集成化综合管理软件平台,可以对接入的视频监控点集中管理,实现统一部署、统一配置、统一管理和统一调度。 0x02 漏洞概述 海康…

Stateflow中的状态转换表

状态转换表是表达顺序模态逻辑的另一种方式。不要在Stateflow图表中以图形方式绘制状态和转换&#xff0c;而是使用状态转换表以表格格式表示模态逻辑。 使用状态转换表的好处包括&#xff1a; 易于对类列车状态机进行建模&#xff0c;其中模态逻辑涉及从一个状态到其邻居的转换…

【Axure高保真原型】批量增加标签——中继器版

今天和大家分享批量增加标签——中继器版的原型模板&#xff0c;效果包括&#xff1a; 添加标签&#xff1a;在输入框了输入需要添加的标签信息&#xff0c;点击添加标签按钮或者按键盘回车键可以动态添加该标签 批量添加&#xff1a;可以一次性添加多个标签&#xff0c;在输入…