ROS机械臂开发:MoveIt!中的潜规则

本篇文章介绍MoveIt!在实际开发中需要注意的一些潜规则,官方文档未提到但在实际工作中有用的一些技能。

一、圆弧轨迹规划

上一篇中介绍了直线插补,将waypoints用直线连接,而圆弧轨迹插补API是未提供的,实际中比如焊接是需要这样的轨迹的,我们自己需要实现。方法是用很多直线段模拟圆弧,取点越多越接近实际圆弧:

圆弧生成

直接看代码:

#include <math.h>
#include <ros/ros.h>
#include <moveit/move_group_interface/move_group_interface.h>
#include <moveit/robot_trajectory/robot_trajectory.h>

using namespace std;

int main(int argc, char **argv)
{
    ros::init(argc, argv, "move_with_circle");
    ros::AsyncSpinner spinner(1);
    spinner.start();

    moveit::planning_interface::MoveGroupInterface ur5("manipulator");

    string eef_link = ur5.getEndEffector();
    std::string reference_frame = "base_link";
    ur5.setPoseReferenceFrame(reference_frame);

    ur5.allowReplanning(true);

    ur5.setGoalPositionTolerance(0.001);
    ur5.setGoalOrientationTolerance(0.01);
    ur5.setMaxAccelerationScalingFactor(0.8);
    ur5.setMaxVelocityScalingFactor(0.8);

    // 控制机械臂先回到初始化位置
    ur5.setNamedTarget("home");
    ur5.move();
    sleep(1);

    geometry_msgs::Pose target_pose;
    target_pose.orientation.x = 0.70711;
    target_pose.orientation.y = 0;
    target_pose.orientation.z = 0;
    target_pose.orientation.w = 0.70711;

    target_pose.position.x = 0.070859;
    target_pose.position.y = 0.36739;
    target_pose.position.z = 0.84716;

    ur5.setPoseTarget(target_pose);
    ur5.move();

    vector<geometry_msgs::Pose> waypoints;
    waypoints.push_back(target_pose);

    //在xy平面内生成一个圆周
    double centerA = target_pose.position.x;
    double centerB = target_pose.position.y;
    double radius = 0.15;

    for(double theta = 0.0; theta < M_PI*2; theta += 0.01)
    {
        target_pose.position.x = centerA + radius * cos(theta);
        target_pose.position.y = centerB + radius * sin(theta);
        waypoints.push_back(target_pose);
    }

    // 笛卡尔空间下的路径规划
    moveit_msgs::RobotTrajectory trajectory;
    const double jump_threshold = 0.0;
    const double eef_step = 0.01;
    double fraction = 0.0;
    int maxtries = 100;   //最大尝试规划次数
    int attempts = 0;     //已经尝试规划次数

    while(fraction < 1.0 && attempts < maxtries)
    {
        fraction = ur5.computeCartesianPath(waypoints, eef_step, jump_threshold, trajectory);
        attempts++;

        if(attempts % 10 == 0)
            ROS_INFO("Still trying after %d attempts...", attempts);
    }

    if(fraction == 1)
    {
        ROS_INFO("Path computed successfully. Moving the arm.");

        // 生成机械臂的运动规划数据
        moveit::planning_interface::MoveGroupInterface::Plan plan;
        plan.trajectory_ = trajectory;

        // 执行运动
        ur5.execute(plan);
        sleep(1);
    }
    else
    {
        ROS_INFO("Path planning failed with only %0.6f success after %d attempts.", fraction, maxtries);
    }

    // 控制机械臂先回到初始化位置
    ur5.setNamedTarget("home");
    ur5.move();
    sleep(1);

    ros::shutdown();
    return 0;
}

代码做的工作:在圆弧上均匀采样,将位置依次添加到路点,最后调用直线路径规划API实现圆弧运动。将终端轨迹显示出来如图:

圆弧运动

二、轨迹重定义

如果对规划出来的轨迹不是很满意,我们可以在plan和execute之间对轨迹进行修改。这里以删除轨迹中的中间点为例,对轨迹进行重定义。
首先调用moveit的API规划得到一条路径plan(轨迹规划):

MoveGroupInterface::Plan plan;
MoveItErrorCode success = ur5.plan(plan);

接着调用delete_trajectory(plan, 4);对plan每隔四个点删除一个(轨迹重定义):

void delete_trajectory(MoveGroupInterface::Plan& plan, unsigned gap)
{
    unsigned count = 0;
    auto i = plan.trajectory_.joint_trajectory.points.begin();
    while(i < plan.trajectory_.joint_trajectory.points.end())
    {
        //每gap个元素删除一次,保留首尾
        if(count % gap == 0 && count != 0)
        {
            //尾元素保留
            if(i == plan.trajectory_.joint_trajectory.points.end() - 1)
                break;
            i = plan.trajectory_.joint_trajectory.points.erase(i);
        }
        else
            i++;
        count++;
    }
}

经删除后的轨迹仍旧可以运行:

删除前31个点删除后24个点.png

三、多轨迹连续运动

如何让机械臂在各段轨迹之间不停顿,一气呵成完成所有轨迹的运动?MoveIt本身是没有此功能的,因为MoveIt规划出来的轨迹一定是从速度0到0的轨迹,我们需要重新组合API的调用来实现多轨迹连续运动。实现方法是将规划得到的多条轨迹拼接成一条,然后对这一条轨迹中的速度,加速度进行重新规划,成为一条完整的轨迹,关键就在于如何重规划。本文给出用时间最优算法对拼接的轨迹进行重规划,当然也可以用其它算法实现。

利用时间最优算法重规划

#include <ros/ros.h>
#include <moveit/move_group_interface/move_group_interface.h>
#include <moveit/robot_trajectory/robot_trajectory.h>
#include <moveit/trajectory_processing/iterative_time_parameterization.h>
#include <moveit_msgs/OrientationConstraint.h>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "moveit_continue_trajectory_demo");
    ros::NodeHandle node_handle;
    ros::AsyncSpinner spinner(1);
    spinner.start();

    moveit::planning_interface::MoveGroupInterface ur5("manipulator");

    double accScale = 0.8;
    double velScale = 0.8;
    ur5.setMaxAccelerationScalingFactor(accScale);
    ur5.setMaxVelocityScalingFactor(velScale);
    ur5.setGoalPositionTolerance(0.001);
    ur5.setGoalOrientationTolerance(0.01);

    // 控制机械臂先回到初始化位置
    ur5.setNamedTarget("home");
    ur5.move();
    sleep(1);

    std::string end_effector_link = ur5.getEndEffectorLink();

    //设置目标位置所使用的参考坐标系
    std::string reference_frame = "base_link";
    ur5.setPoseReferenceFrame(reference_frame);

    //当运动规划失败后,允许重新规划
    ur5.allowReplanning(true);

    geometry_msgs::Pose target_pose;
    target_pose.position.x = -0.093;
    target_pose.position.y = 0.46713;
    target_pose.position.z = 0.84616;
    target_pose.orientation.x = 0.7071;
    target_pose.orientation.y = 0;
    target_pose.orientation.z = 0;
    target_pose.orientation.w = 0.7071;

    ur5.setPoseTarget(target_pose);

    moveit::planning_interface::MoveGroupInterface::Plan plan1;
    moveit::planning_interface::MoveItErrorCode success1 = ur5.plan(plan1);

    ROS_INFO("Plan1 (pose goal) %s", success1 ? "SUCCESS":"FAILED");

    if(success1)
      ur5.execute(plan1);
    sleep(1);

    target_pose.position.x = -0.13995;
    target_pose.position.y = 0.68947;
    target_pose.position.z = 0.64173;
    ur5.setPoseTarget(target_pose);

    moveit::planning_interface::MoveGroupInterface::Plan plan2;
    moveit::planning_interface::MoveItErrorCode success2 = ur5.plan(plan2);

    ROS_INFO("Plan2 (pose goal) %s", success2 ? "SUCCESS":"FAILED");

    if(success2)
      ur5.execute(plan2);
    sleep(1);

    // 控制机械臂回到初始化位置
    ur5.setNamedTarget("home");
    ur5.move();
    sleep(1);

    //连接两条轨迹
    moveit_msgs::RobotTrajectory trajectory;
    trajectory.joint_trajectory.joint_names = plan1.trajectory_.joint_trajectory.joint_names;
    trajectory.joint_trajectory.points = plan1.trajectory_.joint_trajectory.points;
    for(unsigned i = 0; i < plan2.trajectory_.joint_trajectory.points.size(); i++)
    {
        trajectory.joint_trajectory.points.push_back(plan2.trajectory_.joint_trajectory.points[i]);
    }

    //采用时间最优算法对轨迹重规划
    moveit::planning_interface::MoveGroupInterface::Plan joinedPlan;
    robot_trajectory::RobotTrajectory rt(ur5.getCurrentState()->getRobotModel(), "manipulator");
    rt.setRobotTrajectoryMsg(*ur5.getCurrentState(), trajectory);
    trajectory_processing::IterativeParabolicTimeParameterization iptp;
    iptp.computeTimeStamps(rt, velScale, accScale);

    rt.getRobotTrajectoryMsg(joinedPlan.trajectory_);

    if (!ur5.execute(joinedPlan))
    {
        ROS_ERROR("Failed to execute plan");
        return false;
    }

    sleep(1);

    ROS_INFO("Finished");

    ros::shutdown(); 

    return 0;
}

运行效果如图,可以看出两条轨迹之间的停顿已经消除,机械臂一气呵成完成整个动作。


轨迹连接

四、更换运动学插件

通过一系列运动学插件实现正逆运动学计算,常用的有三种:

  • KDL求解器
    来自于orocos框架,数值解,能求解封闭情况下的逆运动学,但速度慢,失败率高

  • Track-IK
    也是数值解,但做了一些优化,速度和成功率比KDL高

安装

i5@i5-ThinkPad-T460p:~$ sudo apt-get install ros-kinetic-trac-ik-kinematics-plugin

配置
修改kinematics.yaml,将solver改为track_ik_kinematics_plugin/TRAC_IKKinematicsPlugin

kinematics.yaml

测试

i5@i5-ThinkPad-T460p:~/ws_moveit/src/ur5_moveit_config$ roslaunch probot_anno_moveit_config demo.launch

启动过程中看到kinematics_solver加载成功


image.png
  • IKFast
    由Rosen编写,可求解任意复杂运动链的运动学方程解析解,产生特定语言(如C++)文件供使用,稳定,速度快,最新处理器可在5us完成计算。但得到的解并非唯一,需要自己写选解的算法,一般选取与当前位置最接近的解。由于IKFast配置过程比较复杂,将在另一片文章中介绍。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343