ManiSkill 自定义机器人开发指南从 URDF/MJCF 导入到控制器、传感器与碰撞建模【免费下载链接】ManiSkillManipulation Skill Framework, an open source GPU parallelized robotics simulator and benchmark项目地址: https://gitcode.com/GitHub_Trending/ma/ManiSkill本篇指南以 ManiSkill 官方自定义机器人教程为主体完整讲解如何在 ManiSkill 中导入 URDF/MJCF 机器人模型、注册自定义 Agent、定义关键帧Keyframe、配置控制器Controller与传感器Sensor、调整链路物理材质并给出四足机器人、移动底座等复杂机型的建模与加速技巧。读完本文你将掌握从零打造一个可在 ManiSkill 仿真中可用、可控、可感知、跑得快的完整机器人并能借助仓库内Panda、ANYmalC、Fetch三个参考实现验证每一步的原理。前置认知自定义机器人在 ManiSkill 中的定位ManiSkill 是一个基于 GPU 并行化的机器人仿真与基准框架。它允许用户通过URDF和MJCF两类模型描述文件导入机器人本体articulation但不会自动为导入的机器人挂载传感器、控制器或调整物理材质——这些都需要由用户在 Agent 类中自行定义。要让一个自定义机器人在 ManiSkill 中真正可用需要依次完成五个步骤创建机器人类继承BaseAgent指定唯一标识uid并声明urdf_path/mjcf_path定义有用的关键帧机器人状态的快照便于测试与其他用户使用定义机器人控制器如 PD 关节位置控制定义挂载在机器人上的传感器如腕部相机为特定 link 建模物理材质如四足移动或物体抓取所需的摩擦系数。本教程将以下文将要实现的MyPanda为主线穿插ANYmalC四足、Fetch移动底座等案例覆盖移动操作机器人、四足机器人等类别的高效建模技巧。1. 机器人类Agent与模型导入1.1 创建并注册机器人类在 ManiSkill 中机器人也被称为Agent。创建自定义机器人需要继承mani_skill.agents.base_agent.BaseAgent为类声明唯一的uid并可选地通过register_agent()装饰器注册。新建一个my_panda.py文件import sapien import numpy as np from mani_skill.agents.base_agent import BaseAgent, Keyframe from mani_skill.agents.controllers import * from mani_skill.agents.registration import register_agent register_agent() class MyPanda(BaseAgent): uid my_panda注册后即可在后续代码中通过字符串uid让环境自动实例化该机器人import mani_skill.envs import my_panda env gym.make(EmptyEnv-v1, robot_uidsmy_panda)从源码看注册机制实现在 mani_skill/agents/registration.py 中register_agent(asset_download_ids[], overrideFalse)会把类以agent_cls.uid为键写入全局字典REGISTERED_AGENTS并同步登记到assets.DATA_GROUPS以便资产缺失时触发下载提示。重复注册同名 uid 时默认跳过传入overrideTrue可强制覆盖。BaseAgent的完整接口包括uid、urdf_path、mjcf_path、keyframes、fix_root_link等类属性以及_controller_configs、_sensor_configs属性定义在 mani_skill/agents/base_agent.py。1.2 导入 URDF 模型在 Agent 类中声明urdf_path指向有效的 URDF 文件即可。仓库自带的 Panda 模型位于mani_skill/assets/robots/panda/panda_v2.urdfclass MyPanda(BaseAgent): uid my_panda urdf_path fpath/to/your/robot.urdfManiSkill 内部会调用 mani_skill/utils/building/urdf_loader.py 中的URDFLoader以及 MJCF 场景下的MJCFLoader自动解析模型文件构建包含文件内定义的 link 与 joint 的 Articulation。需要注意URDF 格式错误或碰撞网格不规范是用户最常见的失败原因具体排查方法见文末的 FAQ / Troubleshooting 章节。1.3 导入 Mujoco MJCF 模型ManiSkill 同样支持导入 Mujoco 的 MJCF 格式mujocoXML但并非所有特性都被支持。典型示例可参考 mani_skill/envs/tasks/control/cartpole.py 中同时加载机器人MJCF与场景的写法。一般而言只需把 URDF 机器人中的urdf_path属性替换为mjcf_pathManiSkill 便会改用 MJCF loader。当前版本对 MJCF 有以下限制mjcf_loader与urdf_loader的差异可从 mani_skill/utils/building/mjcf_loader.py 印证不支持程序化纹理生成不支持导入电机motor与求解器solver配置contype/conaffinity属性不能正确使用contype为 0 表示不添加碰撞网格否则总是添加group属性不能正确使用目前 group 0 和 2 默认可见其他 group 始终隐藏。以上限制可能在后续版本中逐步支持。1.4 机器人加载选项部分机器人需要额外的加载配置例如四足机器人。对于ANYmalC必须保证根 link 不被固定身体才能自由移动class ANYmalC(BaseAgent): # ... fix_root_link False你也可以通过disable_self_collisions True一键关闭机器人自身所有 link 之间的碰撞。但官方不建议这样做disable_self_collisions在 base_agent.py 中明确注明应优先使用 SRDF 排除特定碰撞如果为了提高仿真速度更推荐的做法是简化碰撞网格见下文简化碰撞网格章节。从实现看fix_root_link、load_multiple_collisions、disable_self_collisions会被透传给 loader 并作用到 ArticulationBuilder 上base_agent.py。1.5 测试加载的模型建议编写一个test.py测试脚本导入自定义机器人并复用仓库现成的演示脚本 mani_skill/examples/demo_robot.pyimport my_panda # 导入并注册机器人 import mani_skill.examples.demo_robot as demo_robot_script demo_robot_script.main()然后运行以下命令打开仿真可视化窗口默认不执行动作仅观察机器人python test.py -r my_panda # -r / --robot-uid 传入自定义机器人的 uiddemo_robot.py基于 tyro 解析命令行参数常用参数包括参数别名默认值说明--robot-uid-rpanda要加载的机器人 uid--sim-backend-bauto仿真后端cpu或gpu--control-mode-cpd_joint_pos使用的控制器模式--keyframe-kNone要展示的关键帧名称--keyframe-actions—False用关键帧 qpos 生成动作测试稳定性--random-actions—False采样随机动作驱动控制器--sim-freq/--control-freq—100/20仿真频率与控制频率使用 Panda URDF 时机器人会出现在坐标原点、所有关节置 0 的初始状态。部分机器人如 ANYmal-C 四足看起来会陷进地面这是因为根 link 的初始位姿被设为 0下一步通过定义关键帧即可修正。2. 定义关键帧Keyframes关键帧借鉴自 Mujoco 的 keyframe 概念预先定义一组机器人位姿与关节位置用户可据此快速将机器人初始化到感兴趣的姿态。关键帧可包含pose根 link 位姿并可选的qpos关节位置与qvel关节速度。Keyframe数据结构定义在 mani_skill/agents/base_agent.py。为 Panda 定义一个rest关键帧class MyPanda(BaseAgent): # ... keyframes dict( restKeyframe( qposnp.array( [0.0, np.pi / 8, 0, -np.pi * 5 / 8, 0, np.pi * 3 / 4, np.pi / 4, 0.04, 0.04] ), posesapien.Pose(), ) )为四足机器人定义一个standing关键帧注意把 z 轴高度抬到 0 以上避免与地面相交class ANYmalC(BaseAgent): # ... keyframes dict( standingKeyframe( posesapien.Pose(p[0, 0, 0.545]), qposnp.array( [0.03, -0.03, 0.03, -0.03, 0.4, 0.4, -0.4, -0.4, -0.8, -0.8, 0.8, 0.8] ), ) )运行 demo 脚本时默认展示第一个关键帧也可通过-k指定名称python test.py -r my_panda python test.py -r my_panda -b gpu # 使用 GPU 仿真验证 python test.py -r my_panda -k name_of_keyframe_to_show从 demo_robot.py 的源码可以看到关键帧的应用方式脚本会把kf.qpos写入robot.set_qpos、kf.qvel写入robot.set_qvel、kf.pose写入robot.set_pose并调用controller.reset()随后在 GPU 后端还会执行_gpu_apply_all与gpu_update_articulation_kinematics同步到 GPU 侧。仓库中Panda、ANYmalC、Fetch的keyframes定义分别见 panda.py、anymal_c.py、fetch.py。3. 定义控制器ControllersManiSkill 允许为单个机器人定义多个控制器方便在 sim2real 等研究中快速切换不同控制方案。原则上机器人Articulation的每个**主动关节active joint**都需要被某个控制器覆盖。主动关节即 URDF 中type属性不为fixed的关节。可以用 loader 直接打印主动关节列表进行核对import sapien from mani_skill.envs.scene import ManiSkillScene from mani_skill.utils.building import URDFLoader loader URDFLoader() loader.set_scene(ManiSkillScene()) robot loader.load(path/to/robot.urdf) print(robot.active_joints_map.keys()) # Panda 的输出为 # dict_keys([panda_joint1, ..., panda_joint7, panda_finger_joint1, panda_finger_joint2])3.1 控制器分类与常用配置ManiSkill 开箱即用的控制器主要分为三类PDJointPosControllerPD 关节位置控制、PDEEPoseControllerPD 末端执行器位姿控制内部基于 IK、以及PassiveController被动/自由关节。控制器基类BaseController与组合控制器DictController/CombinedController的实现见 mani_skill/agents/controllers/base_controller.pyPD 关节位置控制器及其 Config 定义见 mani_skill/agents/controllers/pd_joint_pos.py。更详细的控制器原理可参考 controllers 文档。下面以 Panda 为例通过实现_controller_configs属性定义两组控制方案pd_joint_delta_pos臂部增量式关节位置控制与pd_joint_pos臂部绝对关节位置控制夹爪统一使用PDJointPosMimicControllermimic 关节位置控制让两个指节联动class MyPanda(BaseAgent): # ... arm_joint_names [ panda_joint1, panda_joint2, panda_joint3, panda_joint4, panda_joint5, panda_joint6, panda_joint7, ] gripper_joint_names [ panda_finger_joint1, panda_finger_joint2, ] arm_stiffness 1e3 arm_damping 1e2 arm_force_limit 100 gripper_stiffness 1e3 gripper_damping 1e2 gripper_force_limit 100 property def _controller_configs(self): arm_pd_joint_pos PDJointPosControllerConfig( self.arm_joint_names, lowerNone, # None 表示使用关节自身的限位 upperNone, stiffnessself.arm_stiffness, dampingself.arm_damping, force_limitself.arm_force_limit, normalize_actionFalse, ) arm_pd_joint_delta_pos PDJointPosControllerConfig( self.arm_joint_names, lower-0.1, # 增量上限 upper0.1, stiffnessself.arm_stiffness, dampingself.arm_damping, force_limitself.arm_force_limit, use_deltaTrue, # 使用增量式目标 ) gripper_pd_joint_pos PDJointPosMimicControllerConfig( self.gripper_joint_names, lower-0.01, # 技巧下界设负值夹取薄物体时仍能施加力 upper0.04, stiffnessself.gripper_stiffness, dampingself.gripper_damping, force_limitself.gripper_force_limit, ) controller_configs dict( pd_joint_delta_posdict( armarm_pd_joint_delta_pos, grippergripper_pd_joint_pos ), pd_joint_posdict( armarm_pd_joint_pos, grippergripper_pd_joint_pos ), ) # 深拷贝防止用户后续修改污染原始配置 return deepcopy_dict(controller_configs)通过字典可以定义多种控制模式control mode它们对同一组关节互换使用不同的控制器。PDJointPosControllerConfig的关键字段pd_joint_pos.py包括字段默认值含义lower/upperNone动作上下界None 时取关节自身限位stiffness—PD 控制的 P 增益damping—PD 控制的 D 增益force_limit1e10关节驱动力的上限friction0.0关节摩擦use_deltaFalse是否输出相对当前 qpos 的增量目标use_targetFalse增量是否累加到上一目标而非当前 qposinterpolateFalse是否在控制周期内对目标线性插值normalize_actionTrue是否将动作归一化到 [-1, 1]drive_modeforce驱动器模式mimic 控制器通过mimic字典建立联动关系q_mimic q_controlling * multiplier offset默认 multiplier 为 1.0、offset 为 0.0。Panda 实现中显式指定mimic{panda_finger_joint2: {joint: panda_finger_joint1}}panda.py。3.2 被动重力平衡balance_passive_force默认情况下ManiSkill 会自动平衡被动力重力以辅助控制——即对机器人的 link 关闭重力。这一行为对根 link 被固定的机器人如 Panda是合适的但对根 link 不固定的四足机器人、人形机器人则不适用。禁用方式是在控制器字典中显式加入controller_configs dict( pd_joint_delta_posdict( armarm_pd_joint_delta_pos, grippergripper_pd_joint_pos, balance_passive_forceFalse ), # ... )从源码看balance_passive_force的实现在 base_agent.py当为 True 时会对所有 link 调用link.disable_gravity TrueGPU 模式下还会校验是否在 GPU sim 初始化前完成否则给出警告。ANYmalC 的控制器配置即显式设置了balance_passive_forceFalse并在_after_init中仅对非根 link 关闭重力anymal_c.py。3.3 运行与调参运行以下命令测试随机动作先点击 GUI 左上角暂停按钮准备好后再取消暂停python test.py -r my_panda -c pd_joint_delta_pos --random-actions python test.py -r my_panda -c pd_joint_delta_pos -b gpu --random-actions随机动作会采样控制器动作空间中的值你需要调参让机器人动作不过大也不过小。另一种稳定性测试是把所有关节设到关键帧 qpos 并保持python test.py -r my_panda -c pd_joint_pos --keyframe-actions python test.py -r my_panda -c pd_joint_pos -b gpu --keyframe-actions如果机器人在关键帧位姿下保持静止不动说明建模与控制配置工作正常。注意stiffness对应 PD 控制的 P、damping对应 D术语细节见 controllers 文档。这些参数的取值直接影响机器人从仿真到真实的 sim2real 迁移效果官方团队正在完善系统辨识流水线以辅助选择更优的超参数。特别提醒从 Mujoco 等其他仿真器迁移机器人时关节超参数通常不能直接照搬几乎都需要手动重新调参。4. 定义传感器SensorsManiSkill 支持定义挂载在机器人上的传感器以及相对机器人位置固定的传感器方法是在_sensor_configs属性中返回传感器配置列表。以下是在 Panda 手腕上挂载一个 RealSense 风格相机的示例from mani_skill.sensors.camera import CameraConfig class MyPanda(Panda): # ... property def _sensor_configs(self): return [ CameraConfig( uidhand_camera, posesapien.Pose(p[0, 0, 0], q[1, 0, 0, 0]), width128, height128, fovnp.pi / 2, near0.01, far100, mountself.robot.links_map[camera_link], # 挂载到手腕相机 link ) ]CameraConfig的字段定义在 mani_skill/sensors/camera.pyuid为相机唯一标识pose是相对 mount 的位姿width/height为图像分辨率fov或intrinsic二选一指定内参near/far为近远裁剪面mount可以是Actor或Link最终相机全局位姿为mount.pose * local_poseshader_pack支持minimal最快、显存占用最小、default、rt等渲染方案。对于大多数传感器pose被解释为相对 mount 位姿的偏移——上例中 Panda 的camera_link已经朝向正确方向因此位姿取单位变换即可。可以用同一个 demo 脚本打开传感器可视化窗口查看相机图像也可以直接在视口中看到相机视锥仓库中Fetch机器人同时挂载了头部相机与手部相机两个CameraConfig可作为多传感器配置的参考fetch.py。此外 camera.py 还提供了update_sensor_configs_from_dict支持通过 dict 批量覆盖相机参数如 shader_pack、分辨率甚至通过use_stereo_depth把普通相机升级为立体深度相机。5. 为 link 建模物理材质Materials为了让仿真结果正确——尤其是让四足机器人能行走、机械手能抓取物体——通常需要修改机器人局部的物理材质主要是摩擦系数。默认所有物体 static/dynamic friction 均为 0.3这对抓取远远不够物体会从夹爪中滑落。解决办法是定义urdf_config先声明一个默认材质模板gripper 材质再把它应用到指定 link。class MyPanda(BaseAgent): # ... urdf_config dict( _materialsdict( gripperdict(static_friction2.0, dynamic_friction2.0, restitution0.0) ), linkdict( panda_leftfingerdict( materialgripper, patch_radius0.1, min_patch_radius0.1 ), panda_rightfingerdict( materialgripper, patch_radius0.1, min_patch_radius0.1 ), ), ) # ...经验值摩擦系数 2.0 基本是抓取场景所需的上限也是让机器人部件抓住/不滑动的良好起点。patch_radius与min_patch_radius用于控制 SAPIEN PhysX 中接触 patch 的生成粒度。ANYmalC同样为其四个脚部 link 设置了 2.0 的摩擦anymal_c.pyFetch则为两个夹爪手指设置了该材质fetch.py。urdf_config的解析、校验与应用逻辑在 base_agent.py 中通过sapien_utils.parse_urdf_config等函数完成。6. 高级技巧与实战建议6.1 加速仿真的通用原则仿真本质上是接触碰撞求解器加快速度的关键在于两点更少的接触数量以及接触发生在简单图元平面、盒子、圆柱之间。这可以从模型和求解配置两方面入手。6.2 简化碰撞网格为了减少接触并尽量使用基本图元ANYmal-C 的碰撞网格被大幅简化——相比视觉网格碰撞网格明显更稀疏另一种减少接触的方式是删除不太可能与其他物体接近的碰撞形状。四足机器人的碰撞网格如果密不透风部件之间没有间隙物理引擎就必须在相邻部件之间反复做碰撞检查只要在部件之间留出足够大的间隙引擎就不会浪费时间。所需的最小间隙由仿真配置contact_offset决定——它作为第一道过滤器判断两个物体Actor/Link之间的接触是否需要被检查并求解。此外接触数量越少GPU 显存占用也越低。6.3 调整仿真求解配置根据任务类型可以大幅降低solver_position_iterations来提速。经验法则机器人操作类任务通常需要 15 或以上的solver_position_iterations才能准确仿真具体需逐任务验证导航/运动类任务用 4 可能就足够了。仿真配置的完整字段定义可参考 mani_skill/utils/structs/types.py。6.4 精简机器人描述文件WIP机器人的 joint 和 link 越少仿真越快。许多开源 URDF 由工具导出包含大量无用的固定 joint/link。官方团队正在开发自动压缩URDF 的工具以合并并剔除这些冗余节点——该功能目前仍在开发中。6.5 移动底座Mobile Bases建模像 Fetch 这样的移动机器人有可平移、可旋转的移动底座。在仿真中直接模拟轮子与地面的真实物理既困难又缓慢因此与其他仿真器一致ManiSkill 采用假底座方案修改 URDF为 base link 增加允许平移的棱柱关节prismatic和允许旋转的转动关节revolute。Fetch 的 URDF 中root_x_axis_joint、root_y_axis_joint、root_z_rotation_joint三个关节即为此目的而加。修改 URDF 之后需要为底座定义一个独立于机器人其余部分的控制器。Fetch 在 fetch.py 中通过字典把arm、gripper、body、base四组控制器组合起来底座推荐使用PDBaseVelControllerConfig速度控制支持 XY 平移与绕 Z 轴旋转class Fetch(BaseAgent): # ... property def _controller_configs(self): # ... 定义 arm / gripper / body 控制器 base_pd_joint_vel PDBaseVelControllerConfig( self.base_joint_names, lower[-0.5, -0.5, -3.14], upper[0.5, 0.5, 3.14], damping1000, force_limit500, ) controller_configs dict( pd_joint_delta_posdict( armarm_pd_joint_delta_pos, grippergripper_pd_joint_pos, bodybody_pd_joint_delta_pos, basebase_pd_joint_vel, ), # ... ) # ...最后还需要关闭机器人部件与地面之间的碰撞由于底座是悬浮模拟移动的计算它与地面的接触毫无意义只会拖慢仿真。Fetch 只需关闭离地面太近的两个轮子 link 与地面的碰撞。实现方式是重写_after_init并在地面 Actor 上设置相同的 collision group bitclass Fetch(BaseAgent): # ... def _after_init(self): self.l_wheel_link: Link self.robot.links_map[l_wheel_link] self.r_wheel_link: Link self.robot.links_map[r_wheel_link] for link in [self.l_wheel_link, self.r_wheel_link]: link.set_collision_group_bit(group2, bit_idx30, bit1)任务代码中构建地面后同样设置def _load_scene(self, options: dict): # ... self.ground.set_collision_group_bit(group2, bit_idx30, bit1)碰撞屏蔽规则可以这样理解碰撞组 2 中置位相同的对象之间不互相碰撞每个对象最多可设置 32 个 bitbit_idx从 0 到 31。Fetch 实现中把轮子与底座分别放在 bit 30 与 bit 31常量定义见 fetch.py避免影响其他碰撞分组。6.6 触觉传感Tactile Sensing触觉传感功能仍在开发中WIP。现阶段可参考带触觉传感器的 Allegro 手实现 mani_skill/agents/robots/allegro_hand/allegro_touch.py。6.7 四足 / 腿部运动Quadrupeds四足机器人高效仿真的核心同样是简化碰撞网格。以 ANYmal-C 为例ManiSkill 使用的 URDF 相比原始 URDF 明显减少了碰撞定义视觉网格与碰撞网格的差异见上方对比图。调试时可以在 GUI viewer 中点击 Articulation 上任意 link在 Articulation 标签页点击Show collision查看该关节的碰撞网格单个物体则在 Entity 标签页中查看。7. FAQ / 故障排除7.1 URDF 导入常见问题问题加载出的机器人没有正确的渲染材质/颜色。大概率是 URDF 中material标签使用不当。请检查每个材质标签是否具有唯一名称并确认其符合 URDF 规范。问题机器人碰撞表现异常陷进地板、本该碰撞的物体不碰撞等。在 viewer 中点击机器人任意 link在 Articulation 标签页滚动到下方并点击Show collision即可看到实际参与接触仿真的碰撞网格据此修正 URDF 中的collision标签。问题碰撞形状与视觉形状完全不同像是其凸包版本。可能原因有三自定义 Agent 的load_multiple_collisions属性为False。如果你的碰撞网格包含多个可加载的凸形状优先使用.ply或.glb格式把该属性设为True即可该选项由 base_agent.py 的load_multiple_collisions控制并传递给 loader 的load_multiple_collisions_from_file碰撞网格为.stl格式。loader 对.stl的加载偶尔存在问题建议转换为.glb格式系统最容易加载与解析碰撞网格本身不是多个凸形状的组合此时需要自行使用 COACD 之类的工具进行凸分解。结语至此你已掌握在 ManiSkill 中自定义机器人的完整流程继承BaseAgent并注册 uid、声明 URDF/MJCF 路径、定义关键帧、配置多控制器与相机传感器、为抓取/行走部件设置摩擦材质并学会了简化碰撞网格、调整求解迭代、构建假移动底座等加速技巧。仓库中的 Panda、ANYmalC、Fetch 三个实现覆盖了固定臂、四足、移动底座三种典型机型可作为你开发自定义机器人的直接范本配合 demo_robot.py 的-r/-c/-k/-b/--random-actions/--keyframe-actions参数可以随时可视化验证机器人的加载、姿态与控制器表现。【免费下载链接】ManiSkillManipulation Skill Framework, an open source GPU parallelized robotics simulator and benchmark项目地址: https://gitcode.com/GitHub_Trending/ma/ManiSkill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考 SEO 优化官网定制响应式建站教育培训建站