让ABB YuMi机器人跳"蛋仔派对"舞蹈:MoveIt与Gazebo全流程创意开发指南
在机器人编程领域,将枯燥的算法验证变成有趣的视觉表演,是检验技术掌握程度的绝佳方式。今天,我们将使用ROS Noetic下的MoveIt和Gazebo,为ABB YuMi双臂机器人编排一段风靡全球的"蛋仔派对"舞蹈。这个项目不仅适合想要提升ROS实操能力的开发者,也能为机器人教育提供生动的案例。不同于传统工业机器人编程,我们将采用"关键帧动画"的创作思路,通过拖拽示教、轨迹优化和仿真调试,让机器人跳出流畅的舞蹈动作。
1. 环境准备与项目初始化
在开始舞蹈编程前,需要确保开发环境配置正确。推荐使用Ubuntu 20.04 LTS系统,并已安装ROS Noetic完整版。特别需要确认以下软件包已安装:
sudo apt-get install ros-noetic-moveit ros-noetic-gazebo-ros-pkgs ros-noetic-abb-driver为项目创建独立的工作空间是个好习惯:
mkdir -p ~/yumi_dance_ws/src cd ~/yumi_dance_ws catkin_make source devel/setup.bash提示:建议将source命令添加到~/.bashrc文件中,避免每次打开新终端都需要重新设置环境变量。
YuMi机器人模型可以通过ABB官方提供的ROS包获取:
cd ~/yumi_dance_ws/src git clone https://github.com/ros-industrial/abb_robot_driver.git git clone https://github.com/ros-industrial/abb_experimental.git安装依赖并编译工作空间:
rosdep install --from-paths . --ignore-src -y cd ~/yumi_dance_ws catkin_make2. 舞蹈动作设计与关键帧采集
"蛋仔派对"舞蹈的特点是节奏明快、动作夸张,这对机器人运动规划提出了挑战。我们将舞蹈分解为多个关键姿势,通过Rviz的交互功能采集这些关键帧。
启动YuMi在MoveIt中的配置:
roslaunch yumi_moveit_config yumi_moveit_planning_execution.launch在Rviz界面中,可以通过以下方式采集关键帧:
- 使用"Interact"工具拖动机械臂末端到目标位置
- 调整各个关节角度微调姿势
- 通过Python脚本记录当前关节状态:
#!/usr/bin/env python import rospy from moveit_commander import MoveGroupCommander rospy.init_node('yumi_pose_recorder') yumi = MoveGroupCommander('dual_arm') def record_pose(pose_name): current_pose = yumi.get_current_joint_values() rospy.loginfo(f"Pose '{pose_name}': {current_pose}") return current_pose # 示例:记录准备姿势 home_pose = record_pose("home")为"蛋仔派对"舞蹈设计的关键帧包括:
- 准备姿势:双臂自然下垂
- 欢呼姿势:双臂高举呈V字形
- 摇摆姿势:左臂向左伸展,右臂弯曲
- 旋转姿势:双臂交叉于胸前
- 结束姿势:双臂向两侧平展
注意:每个关键帧之间应确保平滑过渡,避免关节角度突变导致机械臂剧烈抖动。
3. 轨迹规划与平滑处理
直接按顺序执行关键帧会导致机械臂在每个姿势间停顿。为了实现流畅的舞蹈效果,需要使用轨迹拼接技术。
创建轨迹规划函数:
from moveit_msgs.msg import RobotTrajectory from trajectory_msgs.msg import JointTrajectoryPoint from copy import deepcopy import rospy def plan_between_poses(group, start_pose, end_pose): group.set_joint_value_target(start_pose) group.set_start_state_to_current_state() plan1 = group.plan() group.set_joint_value_target(end_pose) group.set_start_state(plan1.trajectory.joint_trajectory.points[-1]) plan2 = group.plan() return concatenate_plans(plan1, plan2) def concatenate_plans(plan1, plan2): concatenated = RobotTrajectory() concatenated.joint_trajectory.joint_names = plan1.joint_trajectory.joint_names # 添加第一个轨迹的所有点 for point in plan1.joint_trajectory.points: concatenated.joint_trajectory.points.append(deepcopy(point)) # 计算时间偏移量 time_offset = concatenated.joint_trajectory.points[-1].time_from_start.to_sec() # 添加第二个轨迹的点,并调整时间戳 for point in plan2.joint_trajectory.points: new_point = deepcopy(point) new_time = time_offset + point.time_from_start.to_sec() new_point.time_from_start = rospy.Duration(new_time) concatenated.joint_trajectory.points.append(new_point) return concatenated舞蹈动作序列编排:
def perform_dance_sequence(): dance_moves = [ ("home", home_pose), ("cheer", cheer_pose), ("swing", swing_pose), ("twist", twist_pose), ("finale", finale_pose) ] full_trajectory = None for i in range(len(dance_moves)-1): _, start_pose = dance_moves[i] _, end_pose = dance_moves[i+1] segment = plan_between_poses(yumi, start_pose, end_pose) if full_trajectory is None: full_trajectory = segment else: full_trajectory = concatenate_plans(full_trajectory, segment) yumi.execute(full_trajectory)4. Gazebo仿真与效果优化
在Gazebo中验证舞蹈动作可以避免实际机器人可能出现的碰撞风险。启动Gazebo仿真环境:
roslaunch yumi_gazebo yumi.launch为提高仿真效果,可以调整以下参数:
| 参数名 | 推荐值 | 作用 |
|---|---|---|
| max_velocity_scaling_factor | 0.5 | 降低最大速度使动作更平滑 |
| max_acceleration_scaling_factor | 0.3 | 减小加速度避免抖动 |
| planning_time | 5.0 | 增加规划时间提高成功率 |
| num_planning_attempts | 10 | 增加尝试次数 |
在Python代码中设置这些参数:
yumi.set_max_velocity_scaling_factor(0.5) yumi.set_max_acceleration_scaling_factor(0.3) yumi.set_planning_time(5.0) yumi.set_num_planning_attempts(10)常见问题及解决方案:
动作卡顿:
- 检查轨迹拼接处的时间戳是否连续
- 尝试增加中间过渡姿势
- 调整速度/加速度缩放因子
规划失败:
- 确认关节限制未超出机器人物理约束
- 检查起始状态是否设置正确
- 尝试不同的运动规划算法
仿真不同步:
- 确保Gazebo和MoveIt使用相同的机器人描述
- 检查ROS时间同步状态
- 适当减小仿真步长
5. 音乐同步与完整表演
要让舞蹈与音乐完美配合,需要计算每个动作的时间点。假设"蛋仔派对"音乐节奏为120BPM(每分钟120拍),即每拍0.5秒。
创建时间线表格:
| 时间(秒) | 动作 | 对应音乐节拍 |
|---|---|---|
| 0.0 | 准备姿势 | 前奏开始 |
| 2.0 | 欢呼姿势 | 第一段副歌 |
| 4.5 | 摇摆姿势 | 过渡节拍 |
| 6.0 | 旋转姿势 | 第二段主歌 |
| 8.0 | 结束姿势 | 结尾高潮 |
调整轨迹时间戳:
def adjust_timing(trajectory, target_duration): original_duration = trajectory.joint_trajectory.points[-1].time_from_start.to_sec() scale_factor = target_duration / original_duration for point in trajectory.joint_trajectory.points: new_time = point.time_from_start.to_sec() * scale_factor point.time_from_start = rospy.Duration(new_time) return trajectory完整表演脚本示例:
#!/usr/bin/env python import rospy import actionlib from moveit_msgs.msg import MoveGroupAction, MoveGroupGoal from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint class YuMiDancePerformance: def __init__(self): rospy.init_node('yumi_dance_performance') self.client = actionlib.SimpleActionClient('move_group', MoveGroupAction) self.client.wait_for_server() # 加载预录制的舞蹈姿势 self.load_dance_poses() def load_dance_poses(self): self.poses = { 'home': [0.0, -1.57, 1.57, 0.0, 0.0, 0.0, 0.0, 0.0, -1.57, 1.57, 0.0, 0.0, 0.0, 0.0], 'cheer': [0.0, -0.5, 1.8, 0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 1.8, -0.5, 0.0, -0.5, 0.0], # 其他姿势定义... } def create_trajectory(self, pose_sequence, time_sequence): trajectory = JointTrajectory() trajectory.joint_names = [ 'yumi_joint_1_l', 'yumi_joint_2_l', 'yumi_joint_3_l', 'yumi_joint_4_l', 'yumi_joint_5_l', 'yumi_joint_6_l', 'yumi_joint_7_l', 'yumi_joint_1_r', 'yumi_joint_2_r', 'yumi_joint_3_r', 'yumi_joint_4_r', 'yumi_joint_5_r', 'yumi_joint_6_r', 'yumi_joint_7_r' ] for i, pose_name in enumerate(pose_sequence): point = JointTrajectoryPoint() point.positions = self.poses[pose_name] point.time_from_start = rospy.Duration(time_sequence[i]) trajectory.points.append(point) return trajectory def perform(self): # 定义动作序列和时间点 moves = ['home', 'cheer', 'swing', 'twist', 'finale', 'home'] times = [0.0, 2.0, 4.5, 6.0, 8.0, 10.0] # 创建轨迹 dance_trajectory = self.create_trajectory(moves, times) # 准备并发送动作目标 goal = MoveGroupGoal() goal.request.trajectory = dance_trajectory goal.planning_options.plan_only = False self.client.send_goal(goal) self.client.wait_for_result() return self.client.get_result() if __name__ == '__main__': performance = YuMiDancePerformance() performance.perform()6. 高级技巧与创意扩展
当基础舞蹈动作实现后,可以尝试以下进阶功能:
动作镜像处理:
def mirror_pose(pose): # YuMi有7个关节的双臂,镜像处理左右臂姿势 mirrored = list(pose[7:14]) + list(pose[0:7]) return mirrored循环动作生成:
def generate_wave_motion(base_pose, amplitude, cycles, duration): trajectory = JointTrajectory() steps = 20 * cycles # 每周期20个点 for i in range(steps): point = JointTrajectoryPoint() angle = amplitude * math.sin(2 * math.pi * cycles * i / steps) # 应用波浪运动到特定关节 wave_pose = list(base_pose) wave_pose[2] += angle # 左臂第三关节 wave_pose[9] += angle # 右臂第三关节 point.positions = wave_pose point.time_from_start = rospy.Duration(duration * i / steps) trajectory.points.append(point) return trajectory与ROS系统其他模块集成:
音乐节奏检测:订阅音频处理节点的节奏话题
rospy.Subscriber('/beat_detection', Beat, self.beat_callback)视觉反馈:通过摄像头检测观众反应调整舞蹈强度
rospy.Subscriber('/audience_energy', Float32, self.adjust_intensity)灯光控制:通过ROS服务控制舞台灯光
light_client = rospy.ServiceProxy('/stage_lighting', SetLighting)
实际项目中,我们会将舞蹈动作封装为ROS action,方便与其他系统组件集成:
import actionlib from yumi_dance.msg import PerformDanceAction, PerformDanceResult class DanceServer: def __init__(self): self.server = actionlib.SimpleActionServer( 'perform_dance', PerformDanceAction, execute_cb=self.execute_cb, auto_start=False ) self.server.start() def execute_cb(self, goal): result = PerformDanceResult() try: # 根据goal.dance_name选择不同的舞蹈序列 dance_sequence = self.load_sequence(goal.dance_name) self.perform_sequence(dance_sequence) result.success = True result.message = "Dance performed successfully" except Exception as e: result.success = False result.message = str(e) self.server.set_succeeded(result)