一、核心原理(一句话懂)
moveit2_servo 不吃 "轨迹",只吃 "速度增量指令" 相机 → 算出目标偏移 → 转成 TwistStamped(速度) → 发给 /servo_demo_node/delta_twist_cmds → moveit2_servo 自动 IK + 避障 → 输出 /arm_controller/joint_trajectory
和你键盘控制完全同一路线,只是把键盘换成相机。
二、moveit2_servo 固定输入话题(必须记住)
ROS2 Humble + moveit2_servo 默认输入:
-
笛卡尔(末端)指令:
/servo_demo_node/delta_twist_cmds消息:geometry_msgs/TwistStamped -
关节指令:
/servo_demo_node/delta_joint_cmds消息:moveit_msgs/JointJog
相机必须发 TwistStamped 到上面这个 topic!
三、相机 → moveit2_servo 完整流程(4 步)
1)相机输出:目标在相机坐标系的 3D 位姿
相机节点输出:
- TF:
camera_link → target - 或者话题:
/target_pose(PoseStamped)
2)手眼标定(关键!)
必须知道:
base_link ↔ camera_link的 TF(固定)end_effector ↔ camera_link(眼在手上)
没有标定,坐标乱飘。
3)视觉 PID(把偏移 → 速度指令)
相机算出:
plaintext
error_x, error_y, error_z (m)
error_rx, error_ry, error_rz (rad)
用简单 PID 转成 TwistStamped(速度):
plaintext
linear.x = Kp * error_x
linear.y = Kp * error_y
linear.z = Kp * error_z
angular.x = Kp * error_rx
...
4)发给 moveit2_servo(和你键盘完全一样)
ROS2 发布到:
plaintext
/servo_demo_node/delta_twist_cmds
moveit2_servo 收到后:
- 内部 IK
- 碰撞检测
- 限速
- 输出:
/arm_controller/joint_trajectory
四、直接可用的 ROS2 相机→servo 代码(极简)
python
运行
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import TwistStamped, PoseStamped
from tf2_ros import TransformListener, Buffer
import math
class CameraToServo(Node):
def __init__(self):
super().__init__('camera_to_servo')
# 发布给 servo
self.pub = self.create_publisher(TwistStamped, '/servo_demo_node/delta_twist_cmds', 10)
# 订阅相机目标位姿
self.create_subscription(PoseStamped, '/target_pose', self.pose_cb, 10)
self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)
self.Kp = 0.5 # 速度增益,调这个
def pose_cb(self, msg):
try:
# 把目标转到 base_link
tf = self.tf_buffer.lookup_transform(
'base_link', msg.header.frame_id, rclpy.time.Time()
)
except:
return
# 简化:直接拿误差做速度(PID你再加)
err_x = msg.pose.position.x - tf.transform.translation.x
err_y = msg.pose.position.y - tf.transform.translation.y
err_z = msg.pose.position.z - tf.transform.translation.z
twist = TwistStamped()
twist.header.stamp = self.get_clock().now().to_msg()
twist.header.frame_id = 'base_link'
twist.twist.linear.x = self.Kp * err_x
twist.twist.linear.y = self.Kp * err_y
twist.twist.linear.z = self.Kp * err_z
# 旋转同理,这里省略
self.pub.publish(twist)
def main():
rclpy.init()
Node = CameraToServo()
rclpy.spin(Node)
Node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
五、servo 参数必须开对(非常关键)
servo_params.yaml 确保:
yaml
moveit_servo:
ros__parameters:
command_in_type: "twist_units" # 接收Twist
robot_link_command_frame: "base_link" # 和相机统一
scale:
linear: 0.2 # 最大速度 m/s
六、最终数据流(你一眼看懂)
plaintext
相机 → 目标位姿 → 视觉PID → TwistStamped
↓
/servo_demo_node/delta_twist_cmds
↓
moveit2_servo(IK+避障+限速)
↓
/arm_controller/joint_trajectory
↓
机械臂动