ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Gazebo中无人水面车辆动态水流与风力仿真环境构建指南

Gazebo中无人水面车辆动态水流与风力仿真环境构建指南 简介本资源是一个面向机器人仿真学习者的Gazebo无人水面航行器USV综合仿真系统聚焦于真实海洋环境下的动力学建模与控制验证适用于高校自动化、船舶工程及ROS机器人方向的本科生与研究生开展算法开发与数字孪生实验。压缩包共773个文件涵盖125个zbak备份文件、59个launch启动脚本、46个Python控制模块、37个YAML参数配置、71个XML模型定义及5个world仿真场景辅以大量3D模型.obj/.dae/.blend/.3ds、传感器配置.urdf/.xacro和可视化资源.png/.rviz整体大小为145.84MB。已有84人学习下载资源结构高度模块化支持快速切换不同船型如sailboat、rudderboat、airboat等与环境工况含风场谱、流场扰动配置内嵌自适应PID与滑模控制示例并提供ROS集成接口与蒙特卡洛鲁棒性测试框架可直接用于路径跟踪、抗扰控制等课题的仿真验证与性能分析。1. 项目概述为什么要在Gazebo里“造浪”如果你正在研究无人水面车辆无论是做学术、搞竞赛还是产品开发你肯定知道一个道理在真实水域里测试成本高、风险大、变量多还受天气影响。所以仿真成了必经之路。Gazebo作为一款强大的机器人仿真平台能提供逼真的物理引擎和传感器模型是机器人开发者的“数字沙盘”。但默认的Gazebo世界水面往往平静得像游泳池这显然不符合USV无人水面车辆的实际作业环境——真实的水域有水流、有风浪这些环境因素会直接影响USV的航行控制、路径规划和能源消耗。这个项目的核心就是要在Gazebo这个“数字沙盘”里构建一个动态、可配置的水流和风力环境让USV的仿真测试更贴近现实。我们选择用Python和Ruby来实现背后有明确的考量Python是机器人领域尤其是ROS的“通用语”生态丰富数据处理和算法验证方便而Ruby则是因为Gazebo的插件系统World Plugin和模型生成脚本SDF的某些高级定制用Ruby来写有时比Python更简洁高效尤其是在处理XML结构和快速原型验证时。这不是一个简单的“开箱即用”功能而是需要深入Gazebo底层通过编写插件来“欺骗”物理引擎模拟出流体动力学和空气动力学效应的过程。简单来说这个项目适合三类人一是机器人或船舶工程专业的学生和研究者需要一个可控的、可重复的水面环境来验证算法二是参加RoboBoat、RoboSub等国际水下/水面机器人竞赛的团队需要在赛前进行大量仿真训练三是从事无人船、水上清洁机器人等产品开发的工程师需要在虚拟环境中进行可靠性测试和参数调优降低实船测试的成本和风险。接下来我会拆解整个系统的设计思路、实现细节并分享我在搭建过程中踩过的坑和总结的经验。2. 系统整体设计与思路拆解2.1 核心需求与目标定义在动手写代码之前我们必须明确要模拟什么以及模拟到什么程度。对于USV仿真水流和风力不是简单的“推力”它们具有方向、强度并且会对船体产生复杂的力矩效应。水流模拟的核心需求方向性水流需要有全局方向如从北向南也可以模拟局部涡流。分层性真实水域可能存在表层流和深层流速度不同。虽然Gazebo物理引擎ODE/Bullet不直接支持流体分层但我们可以通过为船体不同部位施加不同力来近似模拟。时变性水流速度可以恒定也可以按一定规律如正弦波变化模拟潮汐或周期性水流。区域化不是整个水域都是同一流速。可以设计特定区域如河道中央流速快岸边流速慢。风力模拟的核心需求矢量性风有方向和大小通常用一个三维矢量表示但水面主要考虑水平方向X, Y。阵风与湍流风不是恒定的存在阵风Gust和湍流Turbulence这会对USV的稳定性控制提出挑战。与船体相互作用风力作用点通常在船体的风压中心这个点与重心不重合因此会产生使船体偏转的力矩。我们的目标是开发一个Gazebo World Plugin世界插件它能在仿真运行时动态地计算并施加水流力和风力到指定的USV模型上。同时我们希望通过Python脚本和Ruby脚本来分别实现环境配置和模型预处理形成一个灵活的工作流。2.2 技术方案选型为什么是Python Ruby Gazebo Plugin这是一个混合技术栈每个部分都承担着明确的职责。Gazebo World Plugin (C)这是系统的核心。所有物理计算和力/力矩的施加都必须发生在Gazebo仿真循环内部用C编写插件是性能最高、与Gazebo物理引擎交互最直接的方式。我们将在这里实现核心算法计算每一仿真步长simulation step中水流和风力对USV模型的作用力和力矩。Python脚本作为“控制台”和“数据分析端”。Python的优势在于其强大的科学计算库如NumPy, SciPy和便捷的ROS通信能力ROS1/ROS2。我们将用Python来配置环境参数编写一个YAML或JSON配置文件定义水流速度、方向、风力矢量、变化模式等然后用Python脚本解析并生成插件所需的初始化参数甚至通过ROS服务动态修改这些参数。启动与管理仿真编写launch文件一键启动Gazebo、加载带有我们插件的世界文件、生成USV模型。数据记录与可视化订阅Gazebo或ROS发布的USV位姿、速度话题用Matplotlib或Rviz进行实时或事后分析评估环境干扰下的控制效果。Ruby脚本作为“模型与场景构建器”。Ruby在处理文本、生成结构化文件如XML方面非常高效。Gazebo的模型和世界文件本质上是SDFSimulation Description Format格式的XML文件。我们将用Ruby来批量生成或修改SDF模型例如快速为USV模型添加多个用于计算受力的链接Link和碰撞体Collision或者生成包含复杂水流区域标记的世界文件。预处理与模板渲染使用ERBEmbedded Ruby模板可以轻松地将环境参数如流速、风向动态填充到SDF世界文件的插件配置部分避免手动编辑XML的繁琐和错误。这个分工体现了“专业的人做专业的事”C负责高性能仿真内核Python负责灵活的控制与评估Ruby负责高效的场景描述与构建。三者通过文件SDF, 配置文件和进程间通信如ROS话题协同工作。3. 核心细节解析与实操要点3.1 Gazebo插件开发力与力矩的计算模型这是整个系统最硬核的部分。我们需要在插件的OnUpdate回调函数中该函数在每个仿真步长被调用完成以下计算水流力计算简化模型 对于USV水流力主要考虑粘性阻力和兴波阻力但在仿真中为了实时性常采用非常简化的模型。一个常用且有效的模型是与相对速度平方成正比的阻力模型。假设USV的某个水下部分如前体、后体可以简化为一个具有有效面积 $A$ 和阻力系数 $C_d$ 的板块。水流速度矢量为 $\vec{v}_c$USV该部分在水中的速度矢量为 $\vec{v}_v$那么相对水流速度 $\vec{v}_r \vec{v}_c - \vec{v}_v$。该部分受到的水流力 $\vec{F}_h$ 可以近似为 $\vec{F}_h -\frac{1}{2} \rho C_d A |\vec{v}_r| \vec{v}_r$ 其中 $\rho$ 是水密度。这个力方向与相对速度方向相反。我们需要将这个力施加到该水下部分的中心通过Gazebo的Link::AddForce函数。注意这是一个极度简化的模型忽略了流体惯性力、升力等。但对于验证控制器在恒定流中的抗干扰能力以及进行对比性实验这个模型已经足够。更复杂的模型如基于MMG模型需要大量船体参数且计算量巨大不适合高实时性仿真。风力计算简化模型 风力计算类似但作用在船体水上部分。风力 $\vec{F}_w$ 的计算公式相似 $\vec{F}_w \frac{1}{2} \rho_a C_w A_w |\vec{v}_w| \vec{v}_w$ 其中 $\rho_a$ 是空气密度$C_w$ 是风阻力系数$A_w$ 是受风面积$\vec{v}_w$ 是相对风速风速减去船速在水平面的分量。关键点力矩的计算。 仅仅施加力是不够的力作用点不通过重心就会产生力矩 $\vec{\tau} \vec{r} \times \vec{F}$其中 $\vec{r}$ 是从重心指向力作用点的矢量。这个力矩会导致船体旋转偏航、横摇。在Gazebo中我们可以使用Link::AddForceAtRelativePosition函数直接指定在链接局部坐标系下的力作用点插件会自动计算合力矩。因此在建模时我们必须准确测量或估算USV模型的重心位置、水下部分中心和水上部分中心风压中心并在SDF模型中通过pose标签或插件参数正确设置。3.2 环境建模在SDF中定义作用区域如何在Gazebo世界中定义“这片区域有水流”一个巧妙的方法是使用透明碰撞体作为触发器。我们可以在世界文件中添加一些不可见的立方体或圆柱体模型为其定义一个特定的link并赋予一个特殊的collision元素。在我们的插件中我们持续检测USV的link是否与这些特殊的collision发生了接触通过Gazebo的接触传感器或直接计算包围盒重叠。一旦检测到接触就根据该触发区域预设的水流参数速度、方向来计算力。例如定义一个从(0, -10, -1)到(20, 10, -1)的矩形水流区域model namewater_current_zone pose10 0 -0.5 0 0 0/pose statictrue/static link namelink collision namecollision geometry box size20 20 1/size /box /geometry surface contact !-- 禁用该碰撞体的物理响应仅作为触发器 -- ode max_vel0/max_vel /ode /contact /surface /collision visual namevisual!-- 可设置为透明用于调试 --/visual /link /model在插件中我们解析这个模型的位姿和尺寸就能确定其作用范围。风力的区域定义可以类似但通常作用于整个世界或者通过高度来分层。3.3 Python与Ruby的分工实践Python端环境控制与数据分析 创建一个environment_manager.py脚本其主要功能如下import yaml import rospy from std_srvs.srv import Empty import numpy as np class EnvironmentManager: def __init__(self, config_path): with open(config_path, r) as f: self.config yaml.safe_load(f) # 解析配置例如 self.current_velocity np.array(self.config[current][velocity]) # [vx, vy, vz] self.wind_velocity np.array(self.config[wind][velocity]) self.wind_gust_params self.config[wind].get(gust, None) # 可能通过ROS服务动态更新插件参数 rospy.wait_for_service(/gazebo/set_model_configuration) # 示例服务 self.update_srv rospy.ServiceProxy(/gazebo/set_plugin_parameter, YourCustomService) def apply_environment(self): # 将配置参数发送给Gazebo插件假设通过ROS服务实现 try: resp self.update_srv(self.current_velocity, self.wind_velocity) print(Environment applied successfully.) except rospy.ServiceException as e: print(fService call failed: {e}) def generate_sinusoidal_current(self, amplitude, frequency, duration): # 生成时变水流数据可用于动态测试 t np.linspace(0, duration, numint(duration/0.1)) # 假设0.1s一个点 vx amplitude * np.sin(2 * np.pi * frequency * t) # ... 可以将这个速度序列通过ROS话题持续发送给插件这个Python管理器让你可以在仿真运行时动态改变风浪条件模拟USV从平静水域突然进入湍流区的场景。Ruby端SDF模板生成 创建一个generate_world.rb脚本使用ERB模板来生成最终的世界文件dynamic_ocean.world。require erb # 环境参数 current_config { zone1: { velocity: [0.5, 0.0, 0.0], pos: [5, 0, -1], size: [10, 20, 1] }, zone2: { velocity: [0.0, -0.3, 0.0], pos: [25, 10, -1], size: [15, 10, 1] } } wind_config { velocity: [2.0, 1.0, 0.0], gust_enabled: true } # 读取模板 template File.read(world_template.erb) renderer ERB.new(template) # 渲染并写入文件 File.open(generated_worlds/dynamic_ocean.world, w) do |f| f.write(renderer.result(binding)) # binding将当前变量传递给模板 end puts World file generated successfully.对应的world_template.erb模板文件片段?xml version1.0? sdf version1.6 world namedynamic_ocean !-- 物理引擎和光照等基础设置 -- includeurimodel://sun/uri/include includeurimodel://ground_plane/uri/include !-- 通过Ruby循环生成多个水流区域 -- % current_config.each do |name, zone| % model namecurrent_zone_% name % pose% zone[:pos].join( ) % 0 0 0/pose statictrue/static link namelink collision namecollision geometryboxsize% zone[:size].join( ) %/size/box/geometry surfacecontactodemax_vel0/max_vel/ode/contact/surface /collision /link /model % end % !-- 加载我们的核心插件 -- plugin namehydro_wind_plugin filenamelibhydro_wind_plugin.so !-- 插件参数可以从Ruby变量中注入 -- wind_velocity% wind_config[:velocity].join( ) %/wind_velocity enable_gust% wind_config[:gust_enabled] %/enable_gust !-- 水流区域参数可以以字符串形式传入由插件解析 -- current_zones % current_config.each do |name, zone| % zone center% zone[:pos][0] % % zone[:pos][1] % % zone[:pos][2] %/center size% zone[:size][0] % % zone[:size][1] % % zone[:size][2] %/size velocity% zone[:velocity].join( ) %/velocity /zone % end % /current_zones /plugin !-- 最后放入USV模型 -- include urimodel://my_usv/uri pose0 0 0.1 0 0 0/pose /include /world /sdf这种方式的优势是你只需修改Ruby脚本中的配置哈希表就能快速生成具有不同风浪场景的多个世界文件极大地提升了测试效率。4. 实操过程与核心环节实现4.1 开发环境搭建与Gazebo插件编译系统与依赖 我是在Ubuntu 22.04 LTS上进行的开发Gazebo 11版本。理论上也支持Ubuntu 20.04/Gazebo 9和Ubuntu 24.04/Gazebo 13。首先确保基础环境sudo apt-get update sudo apt-get install gazebo11 libgazebo11-dev ros-your-ros-distro-gazebo-ros-pkgs # 例如ROS Noetic: ros-noetic-gazebo-ros-pkgs sudo apt-get install cmake build-essential pkg-config创建插件工程mkdir -p ~/usv_sim_ws/src cd ~/usv_sim_ws/src catkin_create_pkg hydro_wind_plugin gazebo_ros roscpp std_msgs geometry_msgs cd hydro_wind_plugin mkdir include src在src/目录下创建核心插件文件hydro_wind_plugin.cpp。代码结构骨架如下#include gazebo/gazebo.hh #include gazebo/physics/physics.hh #include gazebo/common/common.hh #include ignition/math/Vector3.hh #include ros/ros.h #include geometry_msgs/Vector3.h namespace gazebo { class HydroWindPlugin : public WorldPlugin { public: HydroWindPlugin() : WorldPlugin() {} void Load(physics::WorldPtr _world, sdf::ElementPtr _sdf) override { // 1. 保存世界指针 this-world _world; // 2. 从_sdf中解析插件参数风速、水流区域等 this-ParseSdfParameters(_sdf); // 3. 初始化ROS如果需要动态配置 if (!ros::isInitialized()) { int argc 0; char **argv NULL; ros::init(argc, argv, hydro_wind_plugin_node, ros::init_options::NoSigintHandler); } this-rosNode.reset(new ros::NodeHandle(hydro_wind_plugin)); // 4. 订阅或发布ROS话题/服务用于接收Python管理器发来的更新指令 this-windSub this-rosNode-subscribe(/wind_velocity, 10, HydroWindPlugin::OnWindMsg, this); // 5. 连接到仿真更新事件 this-updateConnection event::Events::ConnectWorldUpdateBegin( std::bind(HydroWindPlugin::OnUpdate, this)); } private: void ParseSdfParameters(sdf::ElementPtr _sdf); void OnWindMsg(const geometry_msgs::Vector3::ConstPtr _msg); void OnUpdate(); void ApplyHydroForce(physics::LinkPtr _link, const ignition::math::Vector3d _rel_vel, const ignition::math::Vector3d _force_center); void ApplyWindForce(physics::LinkPtr _link, const ignition::math::Vector3d _wind_vel); physics::WorldPtr world; event::ConnectionPtr updateConnection; std::unique_ptrros::NodeHandle rosNode; ros::Subscriber windSub; // 存储环境参数 ignition::math::Vector3d globalWindVel; std::vectorCurrentZone currentZones; // ... 其他成员变量 }; // 必须注册插件 GZ_REGISTER_WORLD_PLUGIN(HydroWindPlugin) }ParseSdfParameters函数负责解析我们在Ruby模板中注入的XML参数。OnUpdate是核心它在每个仿真步长被调用遍历世界中所有模型找到我们的USV可以通过模型名称或标签识别然后对其各个Link调用ApplyHydroForce和ApplyWindForce函数。编译插件 修改CMakeLists.txt添加Gazebo和ROS的依赖并编译成动态库libhydro_wind_plugin.so。find_package(gazebo REQUIRED) find_package(roscpp REQUIRED) include_directories(${GAZEBO_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS}) link_directories(${GAZEBO_LIBRARY_DIRS} ${catkin_LIBRARY_DIRS}) add_library(hydro_wind_plugin src/hydro_wind_plugin.cpp) target_link_libraries(hydro_wind_plugin ${GAZEBO_LIBRARIES} ${catkin_LIBRARIES})编译成功后将生成的.so文件路径通常是devel/lib/添加到GAZEBO_PLUGIN_PATH环境变量中Gazebo启动时才能找到它。4.2 USV模型准备与参数标定一个粗糙的模型会导致仿真结果毫无意义。你需要一个至少包含以下部分的USV的SDF或URDF模型船体主链接base_link包含船体的视觉、碰撞和惯性属性。惯性矩inertial一定要尽可能准确可以用CAD软件导出或根据粗略几何体估算。水下受力链接可以将船体水下部分拆分为前、中、后几个独立的link分别指定其在水中的位置pose。这些链接通过固定关节joint typefixed连接到base_link。这样我们就可以分别计算它们受到的水流力。水上受风链接类似地创建一个代表上层建筑或风压中心的链接。传感器与执行器根据需要添加GPS、IMU、摄像头、推进器通过gazebo标签添加libgazebo_ros_force.so插件控制等。参数标定技巧阻力系数 $C_d$这是一个调参难点。对于简单长方体船体可以粗略设为1.0-1.2。更准确的做法是查阅流体力学手册中类似船型的系数或者通过CFD仿真获得一个近似值。在项目中可以将其作为一个可调参数通过对比仿真船速与理论估算或简单实验来校准。受风/水面积 $A$在SDF中每个collision几何体都有一个投影面积。可以编写一个简单的Ruby或Python脚本解析SDF中的box或cylinder尺寸自动计算其在受力方向上的投影面积并作为插件参数传入。这比手动计算更可靠。重心与力作用点务必在三维建模软件或SDF中确认这些点的坐标。一个常见的错误是忽略了Z轴高度方向上的差异导致产生不正确的横摇或纵倾力矩。4.3 集成与启动一键化仿真工作流将所有部分串联起来形成一个高效的工作流配置阶段编辑Ruby脚本中的config哈希表定义今天要测试的风浪场景。生成阶段运行ruby generate_world.rb生成定制化的dynamic_ocean.world文件。准备阶段运行Python脚本python environment_manager.py --config current_test.yaml该脚本可以启动一个ROS节点提供参数服务。启动阶段使用一个ROS launch文件或shell脚本一次性完成!-- launch_simulation.launch -- launch !-- 启动Gazebo服务器和客户端并加载生成的世界文件 -- include file$(find gazebo_ros)/launch/empty_world.launch arg nameworld_name value$(find hydro_wind_plugin)/worlds/generated/dynamic_ocean.world/ arg namepaused valuefalse/ /include !-- 启动环境管理节点 -- node nameenv_manager pkghydro_wind_plugin typeenvironment_manager.py outputscreen/ !-- 启动你的USV控制节点 -- node nameusv_controller pkgmy_usv_control typecontroller_node outputscreen/ /launch控制与观测仿真启动后你可以通过ROS话题或服务动态调整风浪参数并通过Rviz或Gazebo的GUI观察USV的响应。5. 常见问题与排查技巧实录在实际搭建过程中我遇到了不少坑这里总结出来希望能帮你节省时间。5.1 Gazebo插件加载失败问题Gazebo启动时提示Failed to load plugin libhydro_wind_plugin.so。排查路径问题首先检查GAZEBO_PLUGIN_PATH环境变量是否包含了你的.so文件所在目录。可以用echo $GAZEBO_PLUGIN_PATH查看并在shell配置文件中添加export GAZEBO_PLUGIN_PATH$GAZEBO_PLUGIN_PATH:/path/to/your/plugin。依赖缺失使用ldd libhydro_wind_plugin.so命令检查动态库依赖是否全部满足。常见问题是链接了错误版本的Gazebo库。确保编译时的find_package(gazebo)找到的版本与你运行的Gazebo版本一致。符号未定义如果ldd没问题但加载仍失败查看Gazebo的终端输出可能有更详细的错误。可能是你的插件类没有正确定义或GZ_REGISTER_WORLD_PLUGIN宏使用有误。5.2 力/力矩效果不明显或方向错误问题USV在水流或风力中几乎没有反应或者朝着奇怪的方向运动。排查单位制Gazebo内部使用SI单位制米、千克、秒。检查你计算力时使用的密度、面积、速度单位是否一致。水的密度是$1000 kg/m^3$空气密度约$1.225 kg/m^3$。坐标系这是最容易出错的地方。Gazebo中力的施加有世界坐标系和链接局部坐标系之分。AddForce默认在世界坐标系下施加力。如果你计算出的力矢量是基于局部坐标系的需要转换到世界坐标系。使用Link::WorldPose()和Link::WorldCoGLinearVel()等函数来获取正确的世界坐标系下的位姿和速度。作用点AddForceAtRelativePosition函数中的位置矢量是相对于链接原点的通常是其重心除非在SDF中指定了pose偏移。确保你传入的位置矢量正确指向风压中心或水下部分中心。模型质量与惯性如果你的USV模型质量mass设置得过大比如默认的1kg那么微小的力根本无法推动它。检查SDF中inertial标签下的质量值是否合理一个几米长的小艇至少几十公斤。打印调试在插件的OnUpdate函数中使用gzmsg或ROS_INFO打印出计算出的力矢量、相对速度、作用点坐标。与你的预期进行对比。5.3 性能问题与仿真抖动问题仿真运行很慢或者USV出现不正常的抖动、跳跃。排查仿真步长在physics标签中默认的max_step_size最大步长是0.001秒。如果你的OnUpdate计算非常复杂可以适当调大步长如0.005但可能会降低精度。需要在精度和性能间权衡。力施加频率确保力的计算和施加是在每个仿真步长进行的但避免在OnUpdate中进行过于复杂的循环或查找操作。例如通过模型名称查找USV链接的操作应该在Load函数中完成并缓存指针而不是在每次OnUpdate中都遍历全世界模型。接触检测开销如果你使用碰撞接触检测来判定是否进入水流区域当区域很多或模型很多时计算开销会很大。考虑使用更轻量的方法如直接计算USV位置是否在预定义的几何体内如轴对齐包围盒AABB。物理引擎尝试切换物理引擎。ODE可能在某些接触计算上比Bullet更耗资源。在世界文件中将physics typeode改为physics typebullet试试。5.4 Ruby模板生成文件格式错误问题Gazebo无法加载Ruby生成的世界文件报XML格式错误。排查缩进与标签闭合ERB模板中混合了Ruby代码和XML很容易导致缩进混乱标签不闭合。生成后先用xmllint --format generated_world.world命令格式化并检查XML文件看是否有语法错误。特殊字符转义如果Ruby变量中包含XML特殊字符如,,需要在ERB模板中使用% h(zone[:name]) %如果h是转义函数或确保变量内容安全。数值格式确保Ruby中数值变量在嵌入XML时被正确转换为字符串且不包含多余的空格或换行。使用% zone[:velocity].join( ) %而不是% zone[:velocity] %后者会输出数组的默认字符串表示如[0.5, 0.0, 0.0]。5.5 Python与Gazebo/ROS通信失败问题Python管理器发送的指令无法改变仿真中的环境参数。排查ROS Master确保roscore已经启动并且Gazebo插件和Python脚本连接到了同一个ROS Master。检查环境变量ROS_MASTER_URI。话题/服务名称在插件和Python脚本中订阅和发布的话题名称、服务名称必须完全一致。使用rostopic list和rosservice list命令来确认服务是否已正确发布。消息类型确保发送和接收的消息类型geometry_msgs/Vector3等一致。在Python中使用from geometry_msgs.msg import Vector3来创建正确的消息对象。时序问题Gazebo插件可能在Python脚本准备好之前就完成了加载和初始化。在Python脚本中使用rospy.wait_for_service(service_name)来等待插件服务可用再进行调用。搭建这样一个仿真系统就像在数字世界里为USV建造一个“风洞”和“水槽”。它允许你在安全、可控、可重复的环境中以极低的成本进行大量的算法测试和参数整定。从简单的定常流到复杂的动态风浪场你可以一步步增加环境的复杂性来考验你的控制器的鲁棒性。虽然初始的搭建和调试需要投入不少精力但一旦这套流程跑通它将成为你研发过程中不可或缺的利器。本文还有配套的精品资源点击获取
返回列表