Ros中的轨迹规划模块

一、Moveit!运动规划模块

Ros中的运动规划是由Moveit!支撑的,Moveit内部分为路径规划模块和轨迹生成模块,路径规划默认使用的是open motion plan library(OMPL)方法,轨迹生成部分实现了Ruckig时间最优轨迹生成算法。(想了解Ruckig算法原理的朋友,也可以去看看原作者提供的算法实现,开源项目仓库地址为https://github.com/pantor/ruckig,对应的论文为https://arxiv.org/abs/2105.04830。)其中OMPL主要解决路径搜索和避障,相关资料较多,此处不再讨论。下面主要讨论Ruckig的轨迹生成方法。

Ruckig解决了非零急动度(Jerk)约束下有限加速度、速度的时间最优在线轨迹生成问题。它将起点到终点之间的运动划分为7个阶段,讨论了7个阶段所有可能的配置组合方案,从合理的组合中选出了时间最优方案,从而给定了起点到终点之间任意时刻的运动学状态的数值计算方法,在最优时间内,各轴恰好到达终点。

Ruckig算法鲁棒性分析:

随机生成1000000000组起始点和终止点运动学状态[(;),(),(),...],其中:

(注:Normal为正太分布,Gamma为伽马分布)

去掉终点加速度不合理的情况,实现了百分百的轨迹生成。具体来说,终点加速度由于受到急动度约束,应该满足下列不等式:

当生成的1000000000组运动学状态中有不满足上述不等式的情况,则直接丢弃。

需要说明的是,Ruckig对动作持续时间较为敏感,如果从起点运行到终点,需要很长的时间,则由于积分误差,可能导致Ruckig计算得到轨迹终点和设定的轨迹终点之间的误差会超过阈值,此时认为轨迹生成失败。根据论文设定的阈值推算,动作持续时间不能超过16分钟。

此外,Ruckig对各个自由度表示的物理含义是无感的,意味着该轨迹生成方法即适用于轴关节空间,也适用于笛卡尔工具空间。当使用笛卡尔工具空间时,六个自由度通常是(位置+姿态,其中姿态用一组欧拉角表示)。如果期望得到笛卡尔空间的直线轨迹,则要求起始点和终止点的位置、位置速度和位置加速度都满足相应的轨迹方程。

假如空间直线轨迹由下面的两个平面方程确定:

则速度需要满足:

加速度需要满足:

至于姿态的插值,如果直接对RPY角或欧拉角进行插值,可能存在万向节死锁的问题,所以通常会用四元数表示姿态,然后在四元数上进行插值,一般使用正则化线性插值或球面线性插值。

关于圆弧轨迹,由于Ruckig是以时间最优进行优化的,所以无法在两点之间插出指定的圆弧轨迹。鉴于此,提供一个通用的空间圆弧插值方法(理论部分参考https://blog.csdn.net/Kalenee/article/details/86698110)。给定任意三点,如果不共线则得到圆弧轨迹,否则将得到直线轨迹:


#include <iostream>
#include <vector>
#include <Eigen>
#include <math.h>
#include <fstream>
#define Pi 3.14159265358979323846

struct CircleInterploateCoffecients
{
    double nx, ny, nz;
    double ox, oy, oz;
    double theta;
};

bool CalculateCircleCenterRadius(float x_0, float y_0, float z_0, float x_1, float y_1, float z_1, float x_2, float y_2, float z_2, float& c_x, float& c_y, float& c_z, float& r)
{
    Eigen::Vector3d x0, x1, x2;
    x0 << x_0, y_0, z_0;
    x1 << x_1, y_1, z_1;
    x2 << x_2, y_2, z_2;
    Eigen::Vector3d v1 = x1 - x0;
    Eigen::Vector3d v2 = x2 - x0;
    if (v1.norm() < 1e-5 || v2.norm() < 1e-5)
        return false;//三个点不能重复
    Eigen::Vector3d v1norm = v1 / v1.norm();
    Eigen::Vector3d v2norm = v2 / v2.norm();
    Eigen::Vector3d co = v1norm.cross(v2norm);
    if ((co.x() > -1e-5 && co.x() < 1e-5) &&
        (co.y() > -1e-5 && co.y() < 1e-5) &&
        (co.z() > -1e-5 && co.z() < 1e-5)/*三点共线*/ || 
        (abs(co.x()) + abs(co.y()) + abs(co.z()) < 1e-5/*三点趋近于直线*/))
    {
        v2.normalize();
        c_x = v2.x();
        c_y = v2.y();
        c_z = v2.z();
        r = -1.0;
        return true;
    }
    Eigen::Vector3d u = v1norm;
    Eigen::Vector3d w = v2.cross(v1);
    w.normalize();
    Eigen::Vector3d v = w.cross(u);
    double bx = v1.dot(u);
    double cx = v2.dot(u);
    double cy = v2.dot(v);
    double h = (pow((cx - 0.5*bx), 2) + pow(cy, 2) - pow(0.5*bx, 2)) / (2 * cy);
    Eigen::Vector3d center = x0 + 0.5*bx * u + h * v;
    c_x = center.x();
    c_y = center.y();
    c_z = center.z();
    r = sqrt(pow(c_x - x0.x(), 2) + pow(c_y - x0.y(), 2) + pow(c_z - x0.z(), 2));
    return true;
}

CircleInterploateCoffecients GetCircleInterploateParameters(float x_0, float y_0, float z_0, float x_1, float y_1, float z_1, float x_2, float y_2, float z_2, float c_x,float c_y,float c_z,float radius)
{
    Eigen::Vector3d p0, p1, p2, pc;
    p0 << x_0, y_0, z_0;
    p1 << x_1, y_1, z_1;
    p2 << x_2, y_2, z_2;
    pc << c_x, c_y, c_z;
    double A = (y_1 - y_0) * (z_2 - z_1) - (z_1 - z_0) * (y_2 - y_1);
    double B = (z_1 - z_0) * (x_2 - x_1) - (x_1 - x_0) * (z_2 - z_1);
    double C = (x_1 - x_0) * (y_2 - y_1) - (y_1 - y_0) * (x_2 - x_1);
    double K = sqrt(pow(A,2)+pow(B,2)+pow(C,2));
    Eigen::Vector3d co = p0 - pc;
    double nx, ny, nz;
    double ox, oy, oz;
    double ax, ay, az;
    nx = (x_0 - c_x) / co.norm(), ny = (y_0 - c_y) / co.norm(), nz = (z_0 - c_z) / co.norm();
    ax = A / K, ay = B / K, az = C / K;
    ox = ay * nz - az * ny, oy = az * nx - ax * nz, oz = ax * ny - ay * nx;
    Eigen::Matrix4d T;
    T << nx, ox, ax, c_x, ny, oy, ay, c_y, nz, oz, az, c_z, 0.0, 0.0, 0.0, 1.0;
    Eigen::Matrix<double,4,3> Q;
    Q << x_0, x_1, x_2, y_0, y_1, y_2, z_0, z_1, z_2, 1.0, 1.0, 1.0;
    Eigen::Matrix4d invT = T.inverse();
    Eigen::MatrixXd q1 = invT * Q.col(0);
    Eigen::MatrixXd q2 = invT * Q.col(1);
    Eigen::MatrixXd q3 = invT * Q.col(2);
    double thetaRange13 = 0.0, thetaRange12 = 0.0;
    if (q3(1, 0) < 0)
        thetaRange13 = atan2(q3(1, 0), q3(0, 0)) + 2 * Pi;
    else
        thetaRange13 = atan2(q3(1, 0), q3(0, 0));
    if (q2(1, 0) < 0)
        thetaRange12 = atan2(q2(1, 0), q2(0, 0)) + 2 * Pi;
    else
        thetaRange12 = atan2(q2(1, 0), q2(0, 0));
    CircleInterploateCoffecients coffe;
    coffe.theta = thetaRange13;
    coffe.nx = nx, coffe.ny = ny,  coffe.nz = nz;
    coffe.ox = ox, coffe.oy = oy, coffe.oz = oz;
    return coffe;
}

void GetStrightLinePointCase1(float x_start, float y_start,float z_start, float delta,float x0,float y0,float z0, float& x_end,float& y_end,float& z_end)
{
    x_end = x_start;
    y_end = y_start;
    z_end = z_start + delta;
}

void GetStrightLinePointCase2(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start;
    y_end = y_start + (y0/z0)* delta;
    z_end = z_start+delta;
}

void GetStrightLinePointCase3(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start;
    y_end = y_start+delta;
    z_end = z_start;
}

void GetStrightLinePointCase4(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start+delta;
    y_end = y_start;
    z_end = z_start;
}

void GetStrightLinePointCase5(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start + (x0/z0)*delta;
    y_end = y_start;
    z_end = z_start+delta;
}

void GetStrightLinePointCase6(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start + delta;
    y_end = y_start + (y0/x0)*delta;
    z_end = z_start + (z0/x0)*delta;
}

void GetStrightLinePointCase7(float x_start, float y_start, float z_start, float delta, float x0, float y0, float z0, float& x_end, float& y_end, float& z_end)
{
    x_end = x_start + (x0/y0)*delta;
    y_end = y_start + delta;
    z_end = z_start;
}

typedef void(*InterploateLineFunc)(float,float,float,float, float, float, float, float&,float&,float&);
InterploateLineFunc GetLineInterploateFunc(float x0,float y0,float z0)
{
    // [x0,y0,z0] 为直线的方向向量
    if ((x0<1e-5 && x0>-1e-5) && (y0<1e-5 && y0>-1e-5) && (z0 > 1e-5 || z0 < -1e-5))//m==0,n==0,p!=0
    {
        // x=p1[0], y=p1[1], z = p1[2] + delta
        return GetStrightLinePointCase1;
    }
    else if ((x0<1e-5 && x0>-1e-5) && (y0 > 1e-5 || y0 < -1e-5) && (z0 > 1e-5 || z0 < -1e-5))//m==0,n!=0,p!=0
    {
        // x = p1[0], y=p1[1]+(y0/z0)*(z-p1[2]), z = p1[2] + delta
        return GetStrightLinePointCase2;
    }
    else if ((x0<1e-5 && x0>-1e-5) && (y0 > 1e-5 || y0 < -1e-5) && (z0 < 1e-5 && z0 > -1e-5))//m==0,n!=0,p==0
    {
        // x = p1[0], y = p1[1] + delta, z = p1[2]
        return GetStrightLinePointCase3;
    }
    else if ((x0 > 1e-5 || x0 < -1e-5) && (y0 < 1e-5 && y0 > -1e-5) && (z0 < 1e-5 && z0 > -1e-5))//m!=0,n==0,p==0
    {
        //x = p1[0] + delta, y = p1[1], z = p1[2]
        return GetStrightLinePointCase4;
    }
    else if ((x0 > 1e-5 || x0 < -1e-5) && (y0 < 1e-5 && y0 > -1e-5) && (z0 > 1e-5 || z0 < -1e-5))//m!=0,n==0,p!=0
    {
        // x = p1[0]+(x0/z0)*(z-p1[2]), y = p1[1], z = p1[2] + delta
        return GetStrightLinePointCase5;
    }
    else if ((x0 > 1e-5 || x0 < -1e-5) && (y0 > 1e-5 || y0 < -1e-5) && (z0 > 1e-5 || z0 < -1e-5))//m!=0,n!=0,p!=0
    {
        //x = p1[0]+delta, y = p1[1] + (y0/x0)*(x-p1[0]), z = p1[2] + (z0/x0)(x-p1[0])
        return GetStrightLinePointCase6;
    }
    else if ((x0 > 1e-5 || x0 < -1e-5) && (y0 > 1e-5 || y0 < -1e-5) && (z0 < 1e-5 && z0 > -1e-5))//m!=0,n!=0,p==0
    {
        // x = p1[0]+(x0/y0)(y-p1[1]), y = p1[1] + delta, z = p1[2]
        return GetStrightLinePointCase7;
    }
    else
        return nullptr;
}

int main()
{ 
    float x0=-0.1, y0=-0.1, z0=-0.1,radius;
    //float p1[] = { 0.3790,0.4368,-0.81581 };
    //float p2[] = { -0.3990,0.8164,-0.41741 };
    //float p3[] = { -0.6984,0.7155,-0.01701 };

    float p1[] = { 0.0,0.0,0.0 };
    float p2[] = { 1.0,1.0,1.0 };
    float p3[] = { 2.0,2.0,2.0 };

    bool res = CalculateCircleCenterRadius(p1[0],p1[1],p1[2],p2[0],p2[1],p2[2],p3[0],p3[1],p3[2], x0, y0, z0, radius);
    if (!res)
        return -1;
    std::ofstream in;
    in.open("zz-com.txt", std::ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
    std::vector<std::vector<float>> pathPoints;
    double interploateRange = 0.0;
    if (radius > 0)
    {
        CircleInterploateCoffecients param = GetCircleInterploateParameters(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2], p3[0], p3[1], p3[2], x0, y0, z0, radius);
        
        interploateRange = param.theta;
        double step = interploateRange / 200;
        int cnt = 0;
        double accumalate = 0.0;
        for (; abs(accumlate-interploateRange)>1e-5; )
        {
            double coeff1, coeff2;
            coeff1 = cos(accumalate);
            coeff2 = sin(accumalate);
            accumalate += step;
            float xt, yt, zt;
            xt = param.nx * radius * coeff1 + param.ox * radius * coeff2 + x0;
            yt = param.ny * radius * coeff1 + param.oy * radius * coeff2 + y0;
            zt = param.nz * radius * coeff1 + param.oz * radius * coeff2 + z0;
            pathPoints.push_back({ xt,yt,zt });
            in << cnt++ << "," << xt << "," << yt << "," << zt << "\n";
        }
    }
    else
    {
        InterploateLineFunc method = GetLineInterploateFunc(x0, y0, z0);
        if (method != nullptr)
        {
            float xt, yt, zt;
            if (method == GetStrightLinePointCase1)
                interploateRange = p3[2] - p1[2];
            else if (method == GetStrightLinePointCase2)
                interploateRange = p3[2] - p1[2];
            else if (method == GetStrightLinePointCase3)
                interploateRange = p3[1] - p1[1];
            else if (method == GetStrightLinePointCase4)
                interploateRange = p3[0] - p1[0];
            else  if (method == GetStrightLinePointCase5)
                interploateRange = p3[2] - p1[2];
            else if(method == GetStrightLinePointCase6)
                interploateRange = p3[0] - p1[0];
            else if(method == GetStrightLinePointCase7)
                interploateRange = p3[1] - p1[1];
            float accumlate = 0.0;
            float step = interploateRange /200;
            int cnt = 0;
            for (; abs(accumlate-interploateRange)>1e-5;)
            {
                method(p1[0], p1[1], p1[2], accumlate, x0,y0, z0, xt, yt, zt);
                accumlate += step;
                in << cnt++ << "," << xt << "," << yt << "," << zt << "\n";
                pathPoints.push_back({ xt,yt,zt });
            }
        }
    }
    in.close();
    return 0;
}

需要注意的是,该插值完全没有考虑速度条件,默认轨迹是匀速的,轨迹点数量默认是200,可以根据需要进行调整。

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ROS系统是一种常用于机器人操作系统的开源平台,它提供了丰富的工具和功能,用于设计、构建和控制各类机器人。水滴机器人是一种球形移动机器人,可以在平面上自由移动,因此需要进行路径规划和轨迹规划来实现自动化控制。 首先,路径规划是指确定机器人从起点到终点的最佳路径。在ROS,可以使用导航堆栈(navigation stack)来实现路径规划。导航堆栈包括了一系列的节点,如地图构建、定位、路径规划等。其,路径规划模块可以使用ROS导航包的全局规划器(global planner)和局部规划器(local planner)。全局规划器主要负责在整个地图上搜索最佳路径,通常使用A*算法或Dijkstra算法等;而局部规划器则负责实时避障和执行轨迹跟踪。 其次,轨迹规划是指根据路径规划结果生成机器人运动的实际轨迹。在ROS,可以使用MoveIt软件包来实现轨迹规划。MoveIt是一个用于机器人运动规划的高级软件框架,提供了一组功能强大的工具和算法。通过使用MoveIt的运动规划器(motion planner),可以将路径规划结果转化为机器人的运动轨迹,考虑到机器人的运动学约束和物体遮挡等因素。 在设计基于ROS系统的水滴机器人的路径规划与轨迹规划时,首先需要构建环境地图,并利用地图构建节点将环境信息传输到导航堆栈。然后,利用全局规划器进行路径规划,得到机器人的最佳路径。接着,通过局部规划器生成实际运动轨迹,并考虑机器人动力学和障碍物避障。最后,利用运动规划器将轨迹规划结果转化为机器人的运动控制指令,实现水滴机器人的自动化控制。 综上所述,基于ROS系统的水滴机器人设计路径规划与轨迹规划需要利用导航堆栈进行路径规划,使用MoveIt进行轨迹规划,同时考虑机器人的动力学约束和障碍物避障,以实现机器人的自动化控制。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值