用Python和ROS 2 Humble手把手实现一个2自由度机械臂的逆运动学(附完整代码)

张开发
2026/4/17 12:53:30 15 分钟阅读

分享文章

用Python和ROS 2 Humble手把手实现一个2自由度机械臂的逆运动学(附完整代码)
用Python和ROS 2 Humble实现2自由度机械臂逆运动学的实战指南机械臂控制是机器人开发中最具挑战性也最令人着迷的领域之一。想象一下当你告诉机械臂去拿那个杯子时它需要自动计算出每个关节应该转动多少角度——这正是逆运动学要解决的问题。本文将带你用Python和ROS 2 Humble从零构建一个实用的2自由度机械臂逆运动学求解器完全面向实际应用而非理论推导。1. 环境准备与基础概念在开始编码之前我们需要确保开发环境就绪。ROS 2 Humble是最新的长期支持版本提供了完善的机械臂控制工具链。安装基础环境只需以下命令sudo apt install ros-humble-desktop sudo apt install ros-humble-joint-state-publisher-gui2自由度机械臂是最简单的运动学模型但包含了所有核心概念。我们的机械臂由两个旋转关节和两个连杆组成基座关节固定在基座上可水平旋转肘部关节连接两个连杆决定肘部位置连杆1长度L1连接基座和肘部连杆2长度L2连接肘部和末端执行器正运动学是从关节角度计算末端位置而逆运动学则是相反的过程——给定末端目标位置(x,y)求出对应的关节角度θ₁和θ₂。这在实践中极为重要因为我们通常知道想让机械臂到达哪里而不知道需要怎样的关节角度。2. 构建机械臂URDF模型在ROS中我们使用URDF(统一机器人描述格式)定义机械臂。创建一个simple_arm.urdf文件?xml version1.0? robot namesimple_2dof_arm link namebase_link visual geometrybox size0.1 0.1 0.1//geometry /visual /link joint namejoint1 typerevolute parent linkbase_link/ child linklink1/ axis xyz0 0 1/ limit lower-3.14 upper3.14/ /joint link namelink1 visual geometrycylinder length0.3 radius0.02//geometry /visual /link joint namejoint2 typerevolute parent linklink1/ child linklink2/ origin xyz0.3 0 0/ axis xyz0 0 1/ limit lower-3.14 upper3.14/ /joint link namelink2 visual geometrycylinder length0.3 radius0.02//geometry /visual /link /robot这个模型定义了两个0.3米长的连杆可以在XY平面内自由旋转。要可视化模型运行ros2 launch urdf_tutorial display.launch.py model:path/to/simple_arm.urdf3. 实现逆运动学求解器2自由度机械臂的逆运动学有解析解这意味着我们可以直接通过数学公式计算关节角度而不需要迭代逼近。创建inverse_kinematics.pyimport numpy as np import math class TwoDOFKinematics: def __init__(self, L10.3, L20.3): self.L1 L1 # 第一连杆长度 self.L2 L2 # 第二连杆长度 def forward_kinematics(self, theta1, theta2): 正运动学从关节角度计算末端位置 x self.L1 * math.cos(theta1) self.L2 * math.cos(theta1 theta2) y self.L1 * math.sin(theta1) self.L2 * math.sin(theta1 theta2) return x, y def inverse_kinematics(self, x, y, elbow_upTrue): 逆运动学从末端位置计算关节角度 elbow_up: True为肘部向上配置False为向下 返回: (theta1, theta2) 或 None(如果不可达) # 计算到目标的距离 d math.sqrt(x**2 y**2) # 检查可达性 if d self.L1 self.L2 or d abs(self.L1 - self.L2): print(f目标位置({x:.2f}, {y:.2f})不可达!) return None # 计算theta2 (余弦定理) cos_theta2 (x**2 y**2 - self.L1**2 - self.L2**2) / (2 * self.L1 * self.L2) cos_theta2 np.clip(cos_theta2, -1, 1) # 避免数值误差 theta2 math.acos(cos_theta2) if elbow_up else -math.acos(cos_theta2) # 计算theta1 alpha math.atan2(y, x) beta_numerator self.L2 * math.sin(theta2) beta_denominator self.L1 self.L2 * math.cos(theta2) beta math.atan2(beta_numerator, beta_denominator) theta1 alpha - beta return theta1, theta2 def jacobian(self, theta1, theta2): 计算雅可比矩阵用于速度控制 J11 -self.L1 * math.sin(theta1) - self.L2 * math.sin(theta1 theta2) J12 -self.L2 * math.sin(theta1 theta2) J21 self.L1 * math.cos(theta1) self.L2 * math.cos(theta1 theta2) J22 self.L2 * math.cos(theta1 theta2) return np.array([[J11, J12], [J21, J22]])这个类实现了核心的逆运动学算法。关键点在于首先检查目标位置是否在机械臂工作空间内使用余弦定理计算第二个关节角度θ₂通过几何关系计算第一个关节角度θ₁elbow_up参数让我们可以选择肘部向上或肘部向下两种配置4. 与ROS 2集成控制现在我们将逆运动学求解器集成到ROS 2节点中实现真实的机械臂控制。创建arm_controller.pyimport rclpy from rclpy.node import Node from sensor_msgs.msg import JointState from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint from inverse_kinematics import TwoDOFKinematics import math class ArmController(Node): def __init__(self): super().__init__(arm_controller) self.kinematics TwoDOFKinematics() # 发布关节轨迹命令 self.trajectory_pub self.create_publisher( JointTrajectory, /joint_trajectory_controller/command, 10) # 订阅关节状态 self.joint_state_sub self.create_subscription( JointState, /joint_states, self.joint_state_callback, 10) self.current_position [0.0, 0.0] # 当前关节角度 def joint_state_callback(self, msg): 更新当前关节状态 if len(msg.position) 2: self.current_position [msg.position[0], msg.position[1]] def move_to_position(self, x, y, duration2.0): 移动机械臂到指定位置 # 计算逆运动学 result self.kinematics.inverse_kinematics(x, y) if result is None: self.get_logger().error(f无法到达位置({x}, {y})) return False theta1, theta2 result # 创建轨迹消息 trajectory JointTrajectory() trajectory.joint_names [joint1, joint2] # 创建轨迹点 point JointTrajectoryPoint() point.positions [theta1, theta2] point.time_from_start.sec int(duration) trajectory.points.append(point) # 发布轨迹 self.trajectory_pub.publish(trajectory) self.get_logger().info(f移动到关节角度: θ1{theta1:.2f}, θ2{theta2:.2f}) return True def draw_circle(self, center_x0.3, center_y0.0, radius0.1, points20): 让机械臂末端画圆 trajectory JointTrajectory() trajectory.joint_names [joint1, joint2] for i in range(points): angle 2 * math.pi * i / points x center_x radius * math.cos(angle) y center_y radius * math.sin(angle) result self.kinematics.inverse_kinematics(x, y) if result is None: continue theta1, theta2 result point JointTrajectoryPoint() point.positions [theta1, theta2] point.time_from_start.sec int(i * 0.2) trajectory.points.append(point) self.trajectory_pub.publish(trajectory) self.get_logger().info(开始画圆轨迹) def main(argsNone): rclpy.init(argsargs) controller ArmController() # 示例移动到几个位置 controller.move_to_position(0.4, 0.2) rclpy.spin_once(controller, timeout_sec2.0) controller.move_to_position(0.3, 0.3) rclpy.spin_once(controller, timeout_sec2.0) # 画圆演示 controller.draw_circle() rclpy.spin(controller) controller.destroy_node() rclpy.shutdown() if __name__ __main__: main()这个控制器实现了订阅关节状态以获取当前位置使用逆运动学计算目标关节角度发布关节轨迹命令使机械臂平滑移动高级功能如画圆轨迹生成5. 处理实际问题与优化在实际应用中我们会遇到几个关键问题需要处理5.1 奇异位形与雅可比矩阵当机械臂完全伸直或完全折叠时会进入奇异位形此时失去某些方向的运动能力。我们可以通过雅可比矩阵的行列式来检测def is_singular(self, theta1, theta2): 检查是否处于奇异位形 J self.jacobian(theta1, theta2) det np.linalg.det(J) return abs(det) 1e-6 # 接近零时为奇异5.2 多解选择与关节限位2自由度机械臂通常有两种解肘部向上/向下我们需要根据实际情况选择def get_all_solutions(self, x, y): 获取所有可能的逆运动学解 solutions [] # 肘部向上解 up_solution self.inverse_kinematics(x, y, elbow_upTrue) if up_solution is not None: solutions.append((elbow_up, *up_solution)) # 肘部向下解 down_solution self.inverse_kinematics(x, y, elbow_upFalse) if down_solution is not None: solutions.append((elbow_down, *down_solution)) return solutions5.3 轨迹插值与平滑为了让机械臂运动更平滑我们需要在关节空间进行插值def interpolate_joints(self, start_angles, target_angles, steps10): 生成关节空间的插值轨迹 trajectory [] for i in range(steps): t i / (steps - 1) interpolated [ start_angles[0] t * (target_angles[0] - start_angles[0]), start_angles[1] t * (target_angles[1] - start_angles[1]) ] trajectory.append(interpolated) return trajectory6. 可视化与调试技巧良好的可视化能极大帮助调试。我们可以使用Matplotlib绘制机械臂和工作空间import matplotlib.pyplot as plt def plot_arm(self, theta1, theta2, axNone): 绘制机械臂当前状态 if ax is None: fig, ax plt.subplots(figsize(8, 8)) # 计算关节位置 x0, y0 0, 0 x1 self.L1 * math.cos(theta1) y1 self.L1 * math.sin(theta1) x2 x1 self.L2 * math.cos(theta1 theta2) y2 y1 self.L2 * math.sin(theta1 theta2) # 绘制连杆 ax.plot([x0, x1], [y0, y1], b-, linewidth3) ax.plot([x1, x2], [y1, y2], r-, linewidth3) # 绘制关节 ax.plot(x0, y0, ko, markersize10) # 基座 ax.plot(x1, y1, ko, markersize8) # 肘部 ax.plot(x2, y2, go, markersize6) # 末端 # 设置图形范围 max_len self.L1 self.L2 ax.set_xlim(-max_len, max_len) ax.set_ylim(-max_len, max_len) ax.set_aspect(equal) ax.grid(True) return ax def plot_workspace(self): 绘制机械臂工作空间 fig, ax plt.subplots(figsize(8, 8)) # 生成工作空间边界点 theta np.linspace(0, 2*np.pi, 100) r_outer self.L1 self.L2 r_inner abs(self.L1 - self.L2) # 绘制工作空间 ax.plot(r_outer * np.cos(theta), r_outer * np.sin(theta), b--) ax.plot(r_inner * np.cos(theta), r_inner * np.sin(theta), r--) ax.fill_between(r_outer * np.cos(theta), r_outer * np.sin(theta), r_inner * np.cos(theta), r_inner * np.sin(theta), colorgray, alpha0.2) ax.set_aspect(equal) ax.grid(True) ax.set_title(机械臂工作空间) return ax7. 进阶应用力控制与阻抗控制了解逆运动学后我们可以进一步实现力控制和阻抗控制。这需要结合力传感器数据和雅可比矩阵转置def compute_torques(self, theta1, theta2, desired_force): 计算产生期望末端力所需的关节力矩 desired_force: [Fx, Fy] 期望的末端力 返回: [τ1, τ2] 关节力矩 J self.jacobian(theta1, theta2) J_transpose J.T # 雅可比矩阵转置 return np.dot(J_transpose, desired_force)阻抗控制则模拟弹簧-质量-阻尼系统行为def impedance_control(self, theta1, theta2, desired_pos, actual_pos, actual_vel, K, D): 阻抗控制计算 K: 刚度矩阵 D: 阻尼矩阵 返回: 关节力矩 error np.array(desired_pos) - np.array(actual_pos) J self.jacobian(theta1, theta2) J_transpose J.T # 计算虚拟力 virtual_force np.dot(K, error) - np.dot(D, actual_vel) # 转换为关节力矩 return np.dot(J_transpose, virtual_force)

更多文章