
简介smarc_stonefish_sims 是一套基于 Python 的石鱼生态仿真资源压缩包以 zip 格式提供面向海洋生物学、机器人学以及仿真开发相关研究人员特别适合对机器人操作系统和生态模拟交叉方向感兴趣的进阶学习者。资源共包含 223 个文件主体为 205 个 obj 三维模型涵盖锚链、场景物体等几何资源同时搭配 py 脚本、scn 场景文件、jpg 贴图、yaml 与 launch 配置、csv 藻类数据以及 README、LICENSE 等说明文档整体大小约 1.7MB体积精简却覆盖了模型、纹理、配置和数据多个层面。项目依托 ROS Noetic 构建在环境建模、物理模拟、生物行为建模、事件驱动编程、多线程并发处理、日志记录与数据可视化等关键知识点上均有体现并展示了 PyBullet、Pygame 等工具在生态模拟中的实际运用方式。已有 179 人浏览学习通过该资源可以对照藻类信息与锚链模型理解石鱼伪装、捕食、繁殖等行为规则的设计思路也可在后续扩展仿真场景、调试 ROS 节点、验证多学科算法时作为轻量级参考框架。1. 石鱼的世界模拟把真机验证成本压到零的虚拟海水下机器人调试有个共识真机下水一次很贵一根密封圈老化就可能让半天工作白费。smarc_stonefish_sims 这份资源把 Stonefish 模拟器变成了一片可以反复折腾的虚拟海——里面预置了水下世界、载具模型和传感器配置你在 Python 里订阅话题就能拿到声呐、IMU、深度计数据发布推进器指令就能让虚拟载具动起来。它适合两类人做水下视觉与路径规划算法的开发者以及想先跑通闭环再碰真机的 ROS 工程向同学。下面从环境搭建开始把这份资源拆开讲透。2. 环境搭建与技术选型从依赖安装到编译通过2.1 Stonefish 的架构物理引擎、渲染器与传感器仿真Stonefish 的架构可以切成三层看。最底层是物理层刚体动力学交给 Bullet Physics 处理载具与海底、障碍物的碰撞接触都发生在这里中间层是仿真核心负责实现浮力、附加质量、流体阻力这类水动力效应并根据海流和海浪模型实时更新载具受力最上层是传感器与渲染OpenGL 负责出图摄像头、声呐、IMU、DVL 这些传感器从当前仿真状态里采集数据。这套分层和游戏引擎很像但差别在水动力层。普通引擎里的浮力只是一个简单的阿基米德公式而 Stonefish 会按载具的排水体积、浸没深度、表面朝向逐面计算压力甚至能模拟接近水面时的兴波阻力。我一般会分开理解「物理步长」和「渲染帧率」物理步长决定动力学精度渲染帧率决定画面跟手程度。调试时载具动作异常抖动先看物理步长只是画面卡那是渲染层的事别混着调。Python 侧的边界非常清楚能读写传感器数据和载具状态不能直接碰物理引擎内部。脚本写久了容易产生一种错觉以为自己是在控制真实载具其实只是通过 ROS 话题在跟仿真核心通信。保持这层认知出问题时排查方向会清晰很多——是算法的问题、通信的问题还是仿真模型的问题先归因再动手。GPU 方面Stonefish 的渲染依赖 OpenGL集成显卡也能跑但场景复杂时明显掉帧。我一般建议至少有一张支持 OpenGL 3.3 以上的显卡硬件妥协时把窗口分辨率调低或者关掉水面反射能救回不少帧率代价只是画面丑一点数据链路完全不受影响。2.2 目录结构clone 之后先认路拿到资源包先别急着编译用 tree 看一眼布局。这套包的目录结构大致是这样的smarc_stonefish_sims/ ├── worlds/ # .scn 场景定义文件 │ ├── sam_coastal.scn │ └── sam_harbor.scn ├── launch/ # ROS launch 启动脚本 │ ├── sam_sim.launch │ └── sam_sim_rviz.launch ├── robots/ # 载具模型描述 │ ├── sam_vehicle.urdf │ └── sam_vehicle.sdf ├── data/ # 网格、纹理与水动力参数 │ ├── meshes/ │ ├── textures/ │ └── hydro/ ├── config/ # 传感器与控制器参数 │ └── sam_sensors.yaml └── scripts/ # Python 辅助脚本 ├── check_topic_rate.py └── set_thruster.pyworlds 目录是入口一个 .scn 文件对应一整个仿真场景海洋参数、载具、传感器全写在里面。launch 目录是 ROS 侧的门脸日常跑仿真基本都从这里进。robots 目录放 URDF 和 SDFURDF 管运动学树载具的质量、浮力、阻力这些动力学属性则在 .scn 的 BODY 段和 data/hydro 参数文件里定义。config 目录值得单独说。里面的 yaml 会被 Python 节点和桥接节点读取常见内容是传感器话题名、采样率、噪声参数。改这些参数不需要重新编译改完重启 launch 就生效做参数扫描时非常方便。scripts 目录通常备着几个辅助脚本比如检查话题频率、手动发推进器指令先跑一遍脚本再写自己的逻辑能省不少事。2.3 依赖安装与编译三步走装依赖时我习惯把包都列在一个命令里减少反复输入 sudo 的次数sudo apt install build-essential cmake libboost-all-dev \ libeigen3-dev libyaml-cpp-dev libopencv-dev \ libglew-dev libbullet-dev python3-pip这里有个高频翻车点如果机器之前装过别的机器人仿真环境apt 里可能已经有一份 libbullet-dev版本往往偏旧。Stonefish 对 Bullet 的接口版本有要求旧版本会在编译中段报出一堆找不到符号的错误。所以装完先查一遍版本dpkg -l | grep bullet pkg-config --modversion bullet版本明显偏旧的话建议直接从源码编译新版 Bullet 装到 /usr/local再回来编工作空间。血泪经验是不要在一个缺依赖的环境里反复重跑 catkin_make报错信息会叠加到让你怀疑人生——先花十分钟把依赖清干净比反复重编一小时划算得多。依赖就位后编译整个工作空间cd ~/ros_ws catkin_make source devel/setup.bash编译输出重点看两个包stonefish 核心库和 stonefish_ros 桥接包。前者是 C 仿真库后者负责把传感器数据映射成 ROS 话题。如果桥接包编译时报找不到 stonefish 头文件多半是 CMake 没找到库路径把环境变量里源码目录相关路径导一遍再重编。我一般单独编这两个包而不是一次性编全工作空间定位报错会快很多。3. 核心模块拆解世界文件、载具模型与传感器链路3.1 .scn 世界文件场景的一站式入口Stonefish 用文本描述整个场景所有要素——海洋、大气、光照、载具、传感器——都写进一个 .scn 文件。拿一个海岸场景举例核心段落长这样OCEAN { SIZE_X 500.0 SIZE_Y 500.0 DEPTH 30.0 WAVE_HEIGHT 0.3 WAVE_LENGTH 2.0 CURRENT_VEL 0.15 CURRENT_DIR 45.0 } BODY { NAME sam_vehicle MESH data/meshes/sam_hull.obj POS 0.0 0.0 -1.5 RPY 0.0 0.0 0.0 DENSITY 940.0 HYDRO data/hydro/sam.hydro } SENSOR { TYPE CAMERA NAME forward_cam PARENT sam_vehicle POS 1.2 0.0 0.35 RPY 0.0 0.0 0.0 RESOLUTION 640 480 FOV_H 60.0 }不用死记字段名理解三件事就行。第一OCEAN 段决定环境难度浪高、波长、海流速度和方向直接影响载具漂移。做路径规划算法验证时把 CURRENT_VEL 从 0 调到 0.5相当于从泳池换到开放海域难度直接拉高一个档位。第二BODY 段决定载具动力学DENSITY 比水略小才能半浮在水面调太小载具会一直飘在水上下不去调太大直接沉底——这是调载具手感的第一入口。第三SENSOR 段决定数据源PARENT 指定挂在哪艘载具上POS/RPY 是安装位置和姿态RESOLUTION 和 FOV_H 决定图像分辨率与视野。改完 .scn 不用重新编译重启 launch 就重新加载。这个「文本即场景」的设计让迭代特别快——我调视觉算法时经常三个终端来回切一个改传感器安装角度一个重启仿真一个看算法输出。3.2 载具模型与参数运动学、动力学各管各的载具描述分两层。URDF 文件描述运动学——连杆、关节、坐标系ROS 里的 tf 树由它生成供给导航和可视化使用。动力学信息在 .scn 的 BODY 段和水动力参数文件里质量分布、浮力中心、各个自由度的阻力系数。两者靠名称对齐.scn 里叫 sam_vehicle 的刚体对应 URDF 里的 base_link。水动力参数文件是纯数据按六自由度给出阻力系数和附加质量系数。一个合格的水下仿真不能只给浮力——旋转阻尼和附加质量必须给否则载具在水里的动作像在真空中一样飘算法验证完全没有参考价值。常见做法是把船模实验数据或 CFD 结果换算成系数填进去没有实测数据就先给经验值再看仿真里载具对阶跃指令的响应曲线手调阻尼项直到姿态变化符合直觉。这个环节是仿真「像不像」的分水岭也是之后二次开发最值得投入的地方。还有一个容易被忽略的点载具重心和浮心的高度差。重心低、浮心高载具天然自稳反过来就会像倒扣的不倒翁稍微给个力就翻转。调姿态控制算法之前先确认这两个点的相对位置——很多「控制发散」其实是模型本身就扶不正换什么控制器都白搭。3.3 传感器到 ROS 话题的映射链路stonefish_ros 桥接包把每个传感器映射成固定命名的话题。摄像头出图像话题声呐出点云或声呐图像IMU 出 sensor_msgs/Imu 消息内含三轴线加速度与角速度深度计出单值 Float64DVL 出速度矢量。映射关系写在桥接配置里改话题名前缀不影响仿真器本体只需要同步改订阅方代码。验证链路通没通我习惯用一条命令rostopic hz /sam/imu/data能稳定输出 50Hz 以上说明传感器链路是通的0Hz 或断续输出问题大概率出在桥接配置或场景文件里传感器没挂对。第四章就沿这条链路走一遍完整的启动、订阅与控制流程把数据从仿真器一路送到 Python 回调函数里。4. 跑通完整链路启动仿真世界并用 Python 操作载具4.1 用 launch 文件把世界拉起来启动仿真的标准姿势是走 launch 文件。一个典型的 launch 会同时拉起三件事Stonefish 仿真主节点、stonefish_ros 桥接节点、Rviz 可视化窗口。命令行长这样roslaunch smarc_stonefish_sims sam_sim.launch world:sam_coastal.scnlaunch 文件本身不长核心就三块加载场景文件路径、启动仿真主节点、启动桥接节点。自己写 launch 的话可以参考这个简化骨架launch arg nameworld defaultsam_coastal.scn/ node namestonefish_sim pkgstonefish_ros typestonefish_ros_node outputscreen param nameworld_file value$(find smarc_stonefish_sims)/worlds/$(arg world)/ /node /launch实际资源包里的 launch 会比这个复杂会带上 Rviz 配置和话题重映射但理解这三块就够了。日志里出现 Simulation initialized 字样后主循环就起来了此时仿真窗口里应该能看到载具浮在海面上周围有波浪。看不到就先回第五章的渲染排查别带着一个黑屏窗口往下走。如果是在无显示器的服务器上跑也可以在 launch 里关掉可视化窗口只留仿真主循环和桥接节点。这样做的缺点是没法直观确认场景长什么样但好处是稳定——我在调参期间基本都这么跑最后需要出图时才把可视化打开。4.2 Python 节点订阅传感器、发布推进器指令跑通链路的核心是「读数据」和「发指令」两个方向都打通。下面这段 Python 脚本做的事很直接订阅 IMU 和深度计同时发布推进器指令让载具保持在一个目标深度附近。#!/usr/bin/env python3 # depth_hold.py —— 订阅传感器并发送推进器指令的示例节点 import rospy from sensor_msgs.msg import Imu from std_msgs.msg import Float64, Float32MultiArray target_depth 2.0 # 目标深度单位米 def imu_cb(msg): # IMU 数据包含三轴线加速度和角速度这里取 x/z 分量做示例 ax msg.linear_acceleration.x az msg.linear_acceleration.z rospy.loginfo_throttle(2.0, faccel: x{ax:.2f} z{az:.2f}) def depth_cb(msg): # 深度计返回单值单位是米 depth msg.data # 简单比例控制误差越大垂直推进器出力越大 err target_depth - depth thrust_cmd max(-100.0, min(100.0, err * 12.0)) cmd_msg Float32MultiArray() cmd_msg.data [thrust_cmd, thrust_cmd] # 左右垂直推进器 pub.publish(cmd_msg) rospy.init_node(depth_hold_demo) rospy.Subscriber(/sam/imu/data, Imu, imu_cb) rospy.Subscriber(/sam/depth, Float64, depth_cb) pub rospy.Publisher(/sam/thruster_setpoints, Float32MultiArray, queue_size1) rospy.spin()这段代码有三个值得注意的细节。第一订阅前最好先用rostopic type /sam/imu/data确认消息类型类型不匹配时订阅会静默失败节点日志没有任何提示非常坑。第二推进器话题的消息类型和数组长度必须跟载具模型一致——两个垂直推进器就发长度为 2 的数组多一个少一个都会被桥接层丢弃而且同样是静默失败。第三高频传感器话题的日志一定要限频loginfo_throttle的第二个参数是秒数这里设 2 秒打印一次否则终端会被 IMU 消息刷爆连 CtrlC 都要等好几秒才能响应。比例控制系数err * 12.0也不是随便写的。系数太小载具会慢慢悠悠很久才到目标深度系数太大垂直推进器会反复过冲深度曲线变成锯齿状来回震荡。我一般先给一个保守值跑起来观察深度曲线再逐步加大直到出现临界震荡再退回 80%这个经验值基本能覆盖大部分仿真场景。4.3 闭环验证从数据曲线看仿真有没有跑对脚本跑起来后验证闭环的方式是看深度是否收敛。Rviz 里加一个 Plot 面板订阅 /sam/depth 话题如果曲线在 2 米附近小范围波动说明订阅、发布、控制链路全通。如果深度纹丝不动先停掉脚本手动发布一个大的推进器指令看载具位置话题有没有反应——这一步能区分是「控制没生效」还是「载具根本没动」。提示Stonefish 默认尽量跑实时机器性能不够时仿真时间会变慢IMU 话题频率也会跟着掉。算法在仿真里正常、真机上完全不对的时候先查物理时间与真实时间的比例这是最容易被忽略的一个变量。位置话题建议同时订阅一下观察载具坐标是否随时间连续变化。连续变化说明动力学在正常推进数值跳变则说明物理稳定性有问题——最常见原因是物理步长设得太大把步长调小一个量级再看。还有个小技巧把深度计数据和位置话题的 z 轴放在同一张图里对比能直接看出深度传感器是跟随真实位置还是存在固定偏差这对后面做传感器模型校准特别有用。5. 常见问题与避坑编译失败、黑屏和话题静默的排查记录下面四条是这份资源最常见的翻车现场每一条我都踩过不止一次按「现象 → 原因 → 解决」的顺序写方便你直接对照。5.1 编译报错Bullet 版本不一致现象catkin_make 编到一半报一串找不到 btMultiBodyConstraint 之类的头文件或链接错误报错位置全在外层模板代码里读起来很痛苦。原因系统里的 libbullet-dev 版本与 Stonefish 源码期望的版本不匹配。发行版仓库里的 Bullet 往往停留在较老版本而仿真核心用了较新的多体约束接口。解决从 Bullet 源码编译新版并安装到 /usr/local然后确认pkg-config --modversion bullet输出新版本号。编译前先看版本不对就先处理依赖再回来编不要在错误叠加的状态下反复重跑。这条经验帮我省掉了至少一下午的无效排查时间。5.2 渲染窗口黑屏或白屏现象launch 正常启动节点日志全绿但仿真窗口一片黑或一片白载具完全看不到。原因八成出在 OpenGL 上下文上。两种情况最常见一是 SSH 远程跑在没有显示器的机器上二是显卡驱动不支持 Stonefish 所需的 OpenGL 版本。解决远程场景改用虚拟显示或者直接关掉可视化窗口跑 headless 模式数据链路不受影响本地场景先执行glxinfo | grep OpenGL version确认驱动版本。注意区分光照参数没调好时画面通常还能看到物体轮廓只是偏暗或过曝驱动问题则是连轮廓都没有表现完全不同别误判成场景配置问题。5.3 传感器话题一直不出数据现象rostopic list里能看到话题名但rostopic hz输出 0Hz订阅节点收不到任何消息。原因三种可能性——场景文件里传感器 PARENT 写错、桥接配置里话题名和代码对不上、传感器坐标超出仿真范围比如把相机挂在水下 100 米而场景水深只有 30 米。解决先在 launch 日志里搜传感器初始化信息确认传感器是否成功挂载再用rostopic echo看话题上有没有零散消息最后回头查 .scn 里传感器的 PARENT 和坐标。我遇到最多的是 PARENT 名称不匹配——ROS 侧完全不报错桥接层直接跳过无效传感器从现象上很难猜原因。后来我把「查日志」列成了排查第一步就不再靠玄学猜了。5.4 仿真越跑越慢物理时间跟不上现象一开始流畅运行几分钟后载具动作变成慢镜头IMU 话题频率掉到 10Hz 以下。原因通常是场景里物体数量过多或网格面数过高物理层每一步的碰撞检测都在变重另一个隐藏原因是日志文件越写越大机械盘 IO 拖慢了主循环。解决如果只是调算法把场景里纯装饰的静态物体删掉必须保留则优先精简单个高面数网格。日志问题换 SSD 或者降低记录频率。判断技巧是看 CPU 和 IO 占用CPU 吃满说明碰撞计算太重IO 等待高则是磁盘问题对症下药才有用。6. 二次开发往石鱼世界里加自己的载具与传感器6.1 新增载具的步骤先放网格文件和水动力参数再在 .scn 里加 BODY 段最后在 URDF 里补对应的 base_link。顺序不能反——先有动力学实体才有运动学描述否则 tf 树缺根节点导航栈直接罢工。新载具的 DENSITY 建议从 900 开始试90% 的异常漂浮问题都能靠这个参数拉回来。6.2 自定义传感器输出的常见做法内置传感器不够用时先别急着改仿真器源码。常见做法是订阅现有话题在 Python 节点里做后处理把声呐点云转栅格占用图把相机图像送进视觉模型推理把 DVL 速度融合进状态估计。收益是仿真器保持稳定算法逻辑全部留在 Python 侧迁移真机时只需替换话题来源。6.3 改完之后的验证顺序我一般按固定顺序过一遍编译不报错、launch 能起、rostopic list 能看到新话题、rostopic hz 频率稳定、物理时间接近实时、跑一段闭环看数据曲线。任何一步不过就停在那里排查不带着疑问往下走。从那以后我每次改完场景都强制走一遍「改配置 → 重启 launch → 查话题 → 看频率 → 跑闭环」的流程这套习惯让后续算法验证省掉了大量返工。希望帮到你。本文还有配套的精品资源点击获取