视频讲解
ROS2 机械臂 MoveIt 开发必看!用 MTC 实现抓取任务规划的完整流程(附代码解析)
内容:定义一个机械臂从抓取物体到放置物体,最后返回初始位置的完整任务流程
1. 在规划场景中添加一个圆柱形的碰撞物体,模拟待抓取的目标物体
2. 创建任务:使用 MoveIt2 的任务构造器(MoveIt Task Constructor,MTC)构建一个复杂的任务,该任务包含多个阶段,如打开手爪、移动到抓取位置、抓取物体、移动到放置位置、放置物体和返回初始位置等
依赖 MTC 包,如果没有moveit task constructor,进入src,进行clone
cd ~/ws_moveit2/src
git clone https://github.com/ros-planning/moveit_task_constructor.git -b ros2
同时创建mtc_tutorial的package
ros2 pkg create --build-type ament_cmake --node-name mtc_tutorial mtc_tutorial
修改package.xml如下:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>mtc_tutorial</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="youremail@domain.com">user</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>moveit_task_constructor_core</depend>
<depend>rclcpp</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
修改CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.8)
project(mtc_tutorial)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(moveit_task_constructor_core REQUIRED)
find_package(rclcpp REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
add_executable(mtc_tutorial src/mtc_tutorial.cpp)
ament_target_dependencies(mtc_tutorial moveit_task_constructor_core rclcpp)
target_include_directories(mtc_tutorial PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_features(mtc_tutorial PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
install(TARGETS mtc_tutorial
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
修改src/mtc_tutorial.cpp
#include <rclcpp/rclcpp.hpp>
#include <moveit/planning_scene/planning_scene.h>
#include <moveit/planning_scene_interface/planning_scene_interface.h>
#include <moveit/task_constructor/task.h>
#include <moveit/task_constructor/solvers.h>
#include <moveit/task_constructor/stages.h>
#if __has_include(<tf2_geometry_msgs/tf2_geometry_msgs.hpp>)
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
#else
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#endif
#if __has_include(<tf2_eigen/tf2_eigen.hpp>)
#include <tf2_eigen/tf2_eigen.hpp>
#else
#include <tf2_eigen/tf2_eigen.h>
#endif
static const rclcpp::Logger LOGGER = rclcpp::get_logger("mtc_tutorial");
namespace mtc = moveit::task_constructor;
class MTCTaskNode
{
public:
MTCTaskNode(const rclcpp::NodeOptions& options);
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr getNodeBaseInterface();
void doTask();
void setupPlanningScene();
private:
// Compose an MTC task from a series of stages.
mtc::Task createTask();
mtc::Task task_;
rclcpp::Node::SharedPtr node_;
};
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr MTCTaskNode::getNodeBaseInterface()
{
return node_->get_node_base_interface();
}
MTCTaskNode::MTCTaskNode(const rclcpp::NodeOptions& options)
: node_{ std::make_shared<rclcpp::Node>("mtc_node", options) }
{
}
void MTCTaskNode::setupPlanningScene()
{
moveit_msgs::msg::CollisionObject object;
object.id = "object";
// 参考坐标系 world
object.header.frame_id = "world";
object.primitives.resize(1);
object.primitives[0].type = shape_msgs::msg::SolidPrimitive::CYLINDER;
object.primitives[0].dimensions = { 0.1, 0.02 };
geometry_msgs::msg::Pose pose;
pose.position.x = 0.5;
pose.position.y = -0.25;
pose.orientation.w = 1.0;
object.pose = pose;
moveit::planning_interface::PlanningSceneInterface psi;
psi.applyCollisionObject(object);
}
void MTCTaskNode::doTask()
{
task_ = createTask();
try
{
task_.init();
}
catch (mtc::InitStageException& e)
{
RCLCPP_ERROR_STREAM(LOGGER, e);
return;
}
if (!task_.plan(5))
{
RCLCPP_ERROR_STREAM(LOGGER, "Task planning failed");
return;
}
task_.introspection().publishSolution(*task_.solutions().front());
auto result = task_.execute(*task_.solutions().front());
if (result.val != moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
{
RCLCPP_ERROR_STREAM(LOGGER, "Task execution failed");
return;
}
return;
}
mtc::Task MTCTaskNode::createTask()
{
mtc::Task task;
// 设置任务名称和加载机器人模型
task.stages()->setName("demo task");
task.loadRobotModel(node_);
const auto& arm_group_name = "panda_arm";
const auto& hand_group_name = "hand";
const auto& hand_frame = "panda_hand";
// 这些属性可以在任务的各个阶段中共享和使用
// 设置机械臂的关节组名称
task.setProperty("group", arm_group_name);
// 设置末端执行器的关节组名称
task.setProperty("eef", hand_group_name);
// 会以这个坐标系为参考来求解关节角度
task.setProperty("ik_frame", hand_frame);
// Disable warnings for this line, as it's a variable that's set but not used in this example
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
mtc::Stage* current_state_ptr = nullptr; // Forward current_state on to grasp pose generator
#pragma GCC diagnostic pop
// 添加一个CurrentState阶段
auto stage_state_current = std::make_unique<mtc::stages::CurrentState>("current");
// 获取当前机器人的状态
current_state_ptr = stage_state_current.get();
task.add(std::move(stage_state_current));
// 创建采样规划器、插值规划器和笛卡尔路径规划器
auto sampling_planner = std::make_shared<mtc::solvers::PipelinePlanner>(node_);
auto interpolation_planner = std::make_shared<mtc::solvers::JointInterpolationPlanner>();
auto cartesian_planner = std::make_shared<mtc::solvers::CartesianPath>();
cartesian_planner->setMaxVelocityScalingFactor(1.0);
cartesian_planner->setMaxAccelerationScalingFactor(1.0);
cartesian_planner->setStepSize(.01);
auto stage_open_hand = std::make_unique<mtc::stages::MoveTo>("open hand", interpolation_planner);
stage_open_hand->setGroup(hand_group_name);
stage_open_hand->setGoal("open");
task.add(std::move(stage_open_hand));
//添加一个Connect阶段,将机械臂从当前位置移动到抓取位置
auto stage_move_to_pick = std::make_unique<mtc::stages::Connect>(
"move to pick",
mtc::stages::Connect::GroupPlannerVector{ { arm_group_name, sampling_planner } });
stage_move_to_pick->setTimeout(5.0);
stage_move_to_pick->properties().configureInitFrom(mtc::Stage::PARENT);
task.add(std::move(stage_move_to_pick));
// 使用SerialContainer创建一个抓取物体的子任务,包含接近物体、生成抓取姿态、碰撞、关闭手爪、抓取物体和提升物体等阶段
mtc::Stage* attach_object_stage = nullptr; // Forward attach_object_stage to place pose generator
{
auto grasp = std::make_unique<mtc::SerialContainer>("pick object");
task.properties().exposeTo(grasp->properties(), { "eef", "group", "ik_frame" });
grasp->properties().configureInitFrom(mtc::Stage::PARENT,
{ "eef", "group", "ik_frame" });
{
auto stage =
std::make_unique<mtc::stages::MoveRelative>("approach object", cartesian_planner);
stage->properties().set("marker_ns", "approach_object");
stage->properties().set("link", hand_frame);
stage->properties().configureInitFrom(mtc::Stage::PARENT, { "group" });
stage->setMinMaxDistance(0.1, 0.15);
// Set hand forward direction
geometry_msgs::msg::Vector3Stamped vec;
vec.header.frame_id = hand_frame;
vec.vector.z = 1.0;
stage->setDirection(vec);
grasp->insert(std::move(stage));
}
{
// Sample grasp pose
auto stage = std::make_unique<mtc::stages::GenerateGraspPose>("generate grasp pose");
stage->properties().configureInitFrom(mtc::Stage::PARENT);
stage->properties().set("marker_ns", "grasp_pose");
stage->setPreGraspPose("open");
stage->setObject("object");
stage->setAngleDelta(M_PI / 12);
stage->setMonitoredStage(current_state_ptr); // Hook into current state
Eigen::Isometry3d grasp_frame_transform;
Eigen::Quaterniond q = Eigen::AngleAxisd(M_PI / 2, Eigen::Vector3d::UnitX()) *
Eigen::AngleAxisd(M_PI / 2, Eigen::Vector3d::UnitY()) *
Eigen::AngleAxisd(M_PI / 2, Eigen::Vector3d::UnitZ());
grasp_frame_transform.linear() = q.matrix();
grasp_frame_transform.translation().z() = 0.1;
// Compute IK
auto wrapper =
std::make_unique<mtc::stages::ComputeIK>("grasp pose IK", std::move(stage));
wrapper->setMaxIKSolutions(8);
wrapper->setMinSolutionDistance(1.0);
wrapper->setIKFrame(grasp_frame_transform, hand_frame);
wrapper->properties().configureInitFrom(mtc::Stage::PARENT, { "eef", "group" });
wrapper->properties().configureInitFrom(mtc::Stage::INTERFACE, { "target_pose" });
grasp->insert(std::move(wrapper));
}
{
auto stage =
std::make_unique<mtc::stages::ModifyPlanningScene>("allow collision (hand,object)");
stage->allowCollisions("object",
task.getRobotModel()
->getJointModelGroup(hand_group_name)
->getLinkModelNamesWithCollisionGeometry(),
true);
grasp->insert(std::move(stage));
}
{
auto stage = std::make_unique<mtc::stages::MoveTo>("close hand", interpolation_planner);
stage->setGroup(hand_group_name);
stage->setGoal("close");
grasp->insert(std::move(stage));
}
{
auto stage = std::make_unique<mtc::stages::ModifyPlanningScene>("attach object");
stage->attachObject("object", hand_frame);
attach_object_stage = stage.get();
grasp->insert(std::move(stage));
}
{
auto stage =
std::make_unique<mtc::stages::MoveRelative>("lift object", cartesian_planner);
stage->properties().configureInitFrom(mtc::Stage::PARENT, { "group" });
stage->setMinMaxDistance(0.1, 0.3);
stage->setIKFrame(hand_frame);
stage->properties().set("marker_ns", "lift_object");
// Set upward direction
geometry_msgs::msg::Vector3Stamped vec;
vec.header.frame_id = "world";
vec.vector.z = 1.0;
stage->setDirection(vec);
grasp->insert(std::move(stage));
}
task.add(std::move(grasp));
}
// 添加一个Connect阶段,将机械臂从抓取位置移动到放置位置
{
auto stage_move_to_place = std::make_unique<mtc::stages::Connect>(
"move to place",
mtc::stages::Connect::GroupPlannerVector{ { arm_group_name, sampling_planner },
{ hand_group_name, sampling_planner } });
stage_move_to_place->setTimeout(5.0);
stage_move_to_place->properties().configureInitFrom(mtc::Stage::PARENT);
task.add(std::move(stage_move_to_place));
}
// 使用SerialContainer创建一个放置物体的子任务,包含生成放置姿态、打开手爪、碰撞、分离物体和回撤等阶段
{
auto place = std::make_unique<mtc::SerialContainer>("place object");
task.properties().exposeTo(place->properties(), { "eef", "group", "ik_frame" });
place->properties().configureInitFrom(mtc::Stage::PARENT,
{ "eef", "group", "ik_frame" });
{
// Sample place pose
auto stage = std::make_unique<mtc::stages::GeneratePlacePose>("generate place pose");
stage->properties().configureInitFrom(mtc::Stage::PARENT);
stage->properties().set("marker_ns", "place_pose");
stage->setObject("object");
geometry_msgs::msg::PoseStamped target_pose_msg;
target_pose_msg.header.frame_id = "object";
target_pose_msg.pose.position.y = 0.5;
target_pose_msg.pose.orientation.w = 1.0;
stage->setPose(target_pose_msg);
stage->setMonitoredStage(attach_object_stage); // Hook into attach_object_stage
// Compute IK
auto wrapper =
std::make_unique<mtc::stages::ComputeIK>("place pose IK", std::move(stage));
wrapper->setMaxIKSolutions(2);
wrapper->setMinSolutionDistance(1.0);
wrapper->setIKFrame("object");
wrapper->properties().configureInitFrom(mtc::Stage::PARENT, { "eef", "group" });
wrapper->properties().configureInitFrom(mtc::Stage::INTERFACE, { "target_pose" });
place->insert(std::move(wrapper));
}
//添加一个MoveTo阶段,打开夹爪
{
auto stage = std::make_unique<mtc::stages::MoveTo>("open hand", interpolation_planner);
stage->setGroup(hand_group_name);
stage->setGoal("open");
place->insert(std::move(stage));
}
// 防止碰撞
{
auto stage =
std::make_unique<mtc::stages::ModifyPlanningScene>("forbid collision (hand,object)");
stage->allowCollisions("object",
task.getRobotModel()
->getJointModelGroup(hand_group_name)
->getLinkModelNamesWithCollisionGeometry(),
false);
place->insert(std::move(stage));
}
// 分离物体
{
auto stage = std::make_unique<mtc::stages::ModifyPlanningScene>("detach object");
stage->detachObject("object", hand_frame);
place->insert(std::move(stage));
}
// 回撤
{
auto stage = std::make_unique<mtc::stages::MoveRelative>("retreat", cartesian_planner);
stage->properties().configureInitFrom(mtc::Stage::PARENT, { "group" });
stage->setMinMaxDistance(0.1, 0.3);
stage->setIKFrame(hand_frame);
stage->properties().set("marker_ns", "retreat");
// Set retreat direction
geometry_msgs::msg::Vector3Stamped vec;
vec.header.frame_id = "world";
vec.vector.x = -0.5;
stage->setDirection(vec);
place->insert(std::move(stage));
}
task.add(std::move(place));
}
// 添加一个MoveTo阶段,将机械臂移动到初始位置
{
auto stage = std::make_unique<mtc::stages::MoveTo>("return home", interpolation_planner);
stage->properties().configureInitFrom(mtc::Stage::PARENT, { "group" });
stage->setGoal("ready");
task.add(std::move(stage));
}
return task;
}
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
rclcpp::NodeOptions options;
options.automatically_declare_parameters_from_overrides(true);
// 创建MTCTaskNode对象和多线程执行器
auto mtc_task_node = std::make_shared<MTCTaskNode>(options);
rclcpp::executors::MultiThreadedExecutor executor;
auto spin_thread = std::make_unique<std::thread>([&executor, &mtc_task_node]() {
executor.add_node(mtc_task_node->getNodeBaseInterface());
executor.spin();
executor.remove_node(mtc_task_node->getNodeBaseInterface());
});
mtc_task_node->setupPlanningScene();
while(true){
mtc_task_node->doTask();
usleep(1000000);
}
// mtc_task_node->doTask();
spin_thread->join();
rclcpp::shutdown();
return 0;
}
新增launch文件
mkdir lanuch && cd launch
mtc_demo.launch.py
import os
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
from moveit_configs_utils import MoveItConfigsBuilder
def generate_launch_description():
# planning_context
moveit_config = (
MoveItConfigsBuilder("moveit_resources_panda")
.robot_description(file_path="config/panda.urdf.xacro")
.trajectory_execution(file_path="config/gripper_moveit_controllers.yaml")
.planning_pipelines(
pipelines=["ompl", "chomp", "pilz_industrial_motion_planner"]
)
.to_moveit_configs()
)
# Load ExecuteTaskSolutionCapability so we can execute found solutions in simulation
move_group_capabilities = {
"capabilities": "move_group/ExecuteTaskSolutionCapability"
}
# Start the actual move_group node/action server
run_move_group_node = Node(
package="moveit_ros_move_group",
executable="move_group",
output="screen",
parameters=[
moveit_config.to_dict(),
move_group_capabilities,
],
)
# RViz
rviz_config_file = (
get_package_share_directory("moveit2_tutorials") + "/launch/mtc.rviz"
)
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log",
arguments=["-d", rviz_config_file],
parameters=[
moveit_config.robot_description,
moveit_config.robot_description_semantic,
moveit_config.robot_description_kinematics,
],
)
# Static TF
static_tf = Node(
package="tf2_ros",
executable="static_transform_publisher",
name="static_transform_publisher",
output="log",
arguments=["--frame-id", "world", "--child-frame-id", "panda_link0"],
)
# Publish TF
robot_state_publisher = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
name="robot_state_publisher",
output="both",
parameters=[
moveit_config.robot_description,
],
)
# ros2_control using FakeSystem as hardware
ros2_controllers_path = os.path.join(
get_package_share_directory("moveit_resources_panda_moveit_config"),
"config",
"ros2_controllers.yaml",
)
ros2_control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[ros2_controllers_path],
remappings=[
("/controller_manager/robot_description", "/robot_description"),
],
output="both",
)
# Load controllers
load_controllers = []
for controller in [
"panda_arm_controller",
"panda_hand_controller",
"joint_state_broadcaster",
]:
load_controllers += [
ExecuteProcess(
cmd=["ros2 run controller_manager spawner {}".format(controller)],
shell=True,
output="screen",
)
]
return LaunchDescription(
[
rviz_node,
static_tf,
robot_state_publisher,
run_move_group_node,
ros2_control_node,
]
+ load_controllers
)
pick_place_demo.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from moveit_configs_utils import MoveItConfigsBuilder
def generate_launch_description():
moveit_config = MoveItConfigsBuilder("moveit_resources_panda").to_dict()
# MTC Demo node
pick_place_demo = Node(
package="mtc_tutorial",
executable="mtc_tutorial",
output="screen",
parameters=[
moveit_config,
],
)
return LaunchDescription([pick_place_demo])
编译
colcon build --mixin debug --packages-select mtc_tutorial
运行
source install/setup.bash
ros2 launch moveit2_tutorials mtc_demo.launch.py
ros2 launch mtc_tutorial pick_place_demo.launch.py