规划[2/5]:位姿规划

  • 对目标状态器中的constraint_samplers::ConstraintSampler采样器对象,在位姿规划创建的派生类是IKConstraintSampler。
  • PositionConstraint封装了某个link上位置(xyz字段)方面的约束。它的configure方法以moveit_msgs::PositionConstraint为参数,设置该对象的各字段。constraint_region_类型是std::vector<bodies::BodyPtr>,bodies::Body是基类,派生类像Sphere。Sphere中的center_、radiusU_分别存储着client要求的在urdf的model_frame_坐标系的位置、误差阀值。
  • OrientationConstraint封装了某个link上角度(rpy)方面的约束。它的configure方法以moveit_msgs::OrientationConstraint为参数,设置该对象的各字段。desired_rotation_matrix_是client要求角度在urdf的model_frame_坐标系的旋转矩阵,像base_link。desired_rotation_matrix_inv_则是对应的逆旋转矩阵。
  • IKConstraintSampler::sample(...)负责采样一个State。分三步:1)调用samplePose(...),从(PositionConstraint, OrientationConstraint)生成一个位姿,它是client要求的位姿加上随机产生的一个误差,这个位姿属于urdf的model_frame_坐标系,记为ikq。2)把ikq变换到ik_frame_坐标系下的位姿,针对示例,就是arm_link坐标系下的位姿,记为ik_query。3)callIK以ik_query为参数,算出对应的各关节状态,当中要使用三大插件中的运行学求解器。
  • 不少运动学求解器会用牛顿迭代法,像KDL、trac_ik,这时需要一个作为迭代初值的初始关节状态seed。这是通过调用getVariableRandomPositions随机产生一个不会超出各关节值范围的关节状态。
arm_joint关节坐标系arm_joint_tf。
<origin xyz="0.11787 0 0.078855" rpy="0.79764 2.5278E-15 1.5708" />
------
-3.67321e-06    -0.698398      0.71571      0.11787
           1 -2.56536e-06  2.62895e-06            0
-2.60902e-15      0.71571     0.698398     0.078855
           0            0            0            1

希望设置到的arm_link坐标系位姿arm_link_pose。
xyz(-0.00028, -0.15590, 0.21609) rpy(-0.98192, -0.61523, 2.78340)
------
 -0.76481 -0.644197 0.0086852  -0.00028
   0.2863 -0.351918 -0.891171   -0.1559
 0.577146  -0.67909  0.453584   0.21609
        0         0         0         1

希望设置到的base_link坐标系位姿。base_link_pose = arm_joint_tf * arm_link_pose
xyz(0.381408, -0.000279032, 0.118193), rpy(1.15452, -2.48807, 1.84256)
------
    0.213121     -0.24025     0.947027     0.381408
   -0.764809    -0.644198   0.00868868 -0.000279032
    0.607985    -0.726147    -0.321038     0.118193
           0            0            0            1
quad: 0.73796i + -0.34048j + 0.52679k + -0.24894

一、ActionServerThread线程:生成IKConstraintSampler类型的constraint_sampler_

1.1 ConstraintSamplerManager::selectDefaultSampler

在“目标状态器”有说过,是在selectDefaultSampler创建constraint_samplers::ConstraintSampler对象,针对位姿规划,创建的派生类是IKConstraintSampler。这函数较复杂,这里只抄和位姿规划相关的。

constraint_samplers::ConstraintSamplerPtr
constraint_samplers::ConstraintSamplerManager::selectDefaultSampler(const planning_scene::PlanningSceneConstPtr& scene,
                                                                    const std::string& group_name,
                                                                    const moveit_msgs::Constraints& constr)
{
  const moveit::core::JointModelGroup* jmg = scene->getRobotModel()->getJointModelGroup(group_name);
  ...
  std::vector<ConstraintSamplerPtr> samplers;
  if (joint_sampler)  // Start making a union of constraint samplers
    samplers.push_back(joint_sampler);

  // read the ik allocators, if any
  const moveit::core::JointModelGroup::KinematicsSolver& ik_alloc = jmg->getGroupKinematics().first;
  const moveit::core::JointModelGroup::KinematicsSolverMap& ik_subgroup_alloc = jmg->getGroupKinematics().second;

  // if we have a means of computing complete states for the group using IK, then we try to see if any IK constraints
  // should be used
  if (ik_alloc)
  {

位姿规划时,ik_alloc是true。

    // keep track of which links we constrained
    std::map<std::string, IKConstraintSamplerPtr> used_l;

    // if we have position and/or orientation constraints on links that we can perform IK for,
    // we will use a sampleable goal region that employs IK to sample goals;
    // if there are multiple constraints for the same link, we keep the one with the smallest
    // volume for sampling
    for (std::size_t p = 0; p < constr.position_constraints.size(); ++p)
      for (std::size_t o = 0; o < constr.orientation_constraints.size(); ++o)
        if (constr.position_constraints[p].link_name == constr.orientation_constraints[o].link_name)
        {
          kinematic_constraints::PositionConstraintPtr pc(
              new kinematic_constraints::PositionConstraint(scene->getRobotModel()));
          kinematic_constraints::OrientationConstraintPtr oc(
              new kinematic_constraints::OrientationConstraint(scene->getRobotModel()));
          if (pc->configure(constr.position_constraints[p], scene->getTransforms()) &&
              oc->configure(constr.orientation_constraints[o], scene->getTransforms()))
          {
            IKConstraintSamplerPtr iks(new IKConstraintSampler(scene, jmg->getName()));
            if (iks->configure(IKSamplingPose(pc, oc)))
            {
              bool use = true;
              // Check if there already is a constraint on this link
              if (used_l.find(constr.position_constraints[p].link_name) != used_l.end())
                // If there is, check if the previous one has a smaller volume for sampling
                if (used_l[constr.position_constraints[p].link_name]->getSamplingVolume() < iks->getSamplingVolume())
                  use = false;  // only use new constraint if it has a smaller sampling volume
              if (use)
              {
                // assign the link to a new constraint sampler
                used_l[constr.position_constraints[p].link_name] = iks;
                ROS_DEBUG_NAMED("constraint_samplers",
                                "Allocated an IK-based sampler for group '%s' "
                                "satisfying position and orientation constraints on link '%s'",
                                jmg->getName().c_str(), constr.position_constraints[p].link_name.c_str());
图1 constr和iks

对示例,used_l只生成一条记录:used_l["grasping_frame"] = iks。

              }
            }
          }
        }

    // keep track of links constrained with a full pose
    std::map<std::string, IKConstraintSamplerPtr> used_l_full_pose = used_l;

    for (const moveit_msgs::PositionConstraint& position_constraint : constr.position_constraints)
    {
      // if we are constraining this link with a full pose, we do not attempt to constrain it with a position constraint
      // only
      if (used_l_full_pose.find(position_constraint.link_name) != used_l_full_pose.end())
        continue;

对大多情况,client会给出配对的位置、角度约束,像示例。上面这个if会返回true,这个for其实没有操作。

以下是这段逻辑,是client给的约束中有“孤立”的位置约束,即该位置约束没有对应的角度约束。这时在生成的位姿约束时,强制给角度部分赋值0。

        ...
      }
    }

    for (const moveit_msgs::OrientationConstraint& orientation_constraint : constr.orientation_constraints)
    {
      // if we are constraining this link with a full pose, we do not attempt to constrain it with an orientation
      // constraint only
      if (used_l_full_pose.find(orientation_constraint.link_name) != used_l_full_pose.end())
        continue;

对大多情况,client会给出配对的位置、角度约束,像示例。上面这个if会返回true,这个for其实没有操作。

以下是这段逻辑,是client给的约束中有“孤立”的角度约束,即该角度约束没有对象的位置约束。这时在生成的位姿约束时,强制给位置部分赋值0。

      ...
    }

    if (used_l.size() == 1)
    {
      if (samplers.empty())
        return used_l.begin()->second;

单目标,而且这个目标就是位姿类型,像示例。在这入口返回。

      else
      {
        samplers.push_back(used_l.begin()->second);
        return ConstraintSamplerPtr(new UnionConstraintSampler(scene, jmg->getName(), samplers));
      }
    }
    ...
  }
  ...
}

IKSamplingPose包含了位置和角度约束,使用在Ik采样期间。

struct IKSamplingPose
{
  // 存储着位置约束数据。
  kinematic_constraints::PositionConstraintPtr position_constraint_;

  // 存储着角度约束数据。
  kinematic_constraints::OrientationConstraintPtr orientation_constraint_;
};

1.2 PositionConstraint

PositionConstraint封装了某个link上位置(xyz字段)方面的约束。位置区域(constraint_region_)被指定为由一个或多个形状(实体基元(solid primitives)或网格(meshes))组成的受限区域块。位姿信息(constraint_region_pose_)将使用pc.constraint_region.primitive_poses进行解释。此外,还可以指定在受约束位置中指定目标偏移量(offset_)。对PositionConstraint,虚操作getType()返回POSITION_CONSTRAINT。

class PositionConstraint : public KinematicConstraint
{
protected:
  Eigen::Vector3d offset_;                         /**< \brief The target offset */
  bool has_offset_;                                /**< \brief Whether the offset is substantially different than 0.0 */
  std::vector<bodies::BodyPtr> constraint_region_; /**< \brief The constraint region vector */
  /** \brief The constraint region pose vector. All isometries are guaranteed to be valid. */
  EigenSTL::vector_Isometry3d constraint_region_pose_;
  bool mobile_frame_;                         /**< \brief Whether or not a mobile frame is employed*/
  std::string constraint_frame_id_;           /**< \brief The constraint frame id */
  const moveit::core::LinkModel* link_model_; /**< \brief The link model constraint subject */
};

PositionConstraint会存储在两个地方,一是KinematicConstraintSet中的kinematic_constraints_,以及position_constraints_。KinematicConstraintSet自身存储在ModelBasedPlanningContext中的goal_constraints_。二是IKConstraintSampler中的sampling_pose_.position_constraint_。

bool PositionConstraint::configure(const moveit_msgs::PositionConstraint& pc, const moveit::core::Transforms& tf)
{
  // clearing before we configure to get rid of any old data
  clear();

  link_model_ = robot_model_->getLinkModel(pc.link_name);
  ...

  offset_ = Eigen::Vector3d(pc.target_point_offset.x, pc.target_point_offset.y, pc.target_point_offset.z);
  has_offset_ = offset_.squaredNorm() > std::numeric_limits<double>::epsilon();

  if (tf.isFixedFrame(pc.header.frame_id))
  {

client指定位姿时,会同时指定位姿在哪个坐标系,如果这个坐标系在transforms_map_,isFixedFrame返回true。对base_link,要没意外会返回true。

    constraint_frame_id_ = tf.getTargetFrame();
    mobile_frame_ = false;
  }
  else
  {
    constraint_frame_id_ = pc.header.frame_id;
    mobile_frame_ = true;
  }

  // load primitive shapes, first clearing any we already have
  for (std::size_t i = 0; i < pc.constraint_region.primitives.size(); ++i)
  {
    std::unique_ptr<shapes::Shape> shape(shapes::constructShapeFromMsg(pc.constraint_region.primitives[i]));
    if (shape)
    {
      if (pc.constraint_region.primitive_poses.size() <= i)
      {
        ROS_WARN_NAMED("kinematic_constraints", "Constraint region message does not contain enough primitive poses");
        continue;
      }
      Eigen::Isometry3d t;
      tf2::fromMsg(pc.constraint_region.primitive_poses[i], t);

pc.constraint_region.primitive_poses[i]存储着client要求的位姿,其实有效的只有位置,角度固定是0。参考图2,position是client要求的角度,orientation是0度。tf2::fromMsg把position、orientation给成一个变化矩阵t。

      ASSERT_ISOMETRY(t)  // unsanitized input, could contain a non-isometry
      constraint_region_pose_.push_back(t);
      if (!mobile_frame_)
        tf.transformPose(pc.header.frame_id, constraint_region_pose_.back(), constraint_region_pose_.back());

把frame_id坐标系下的位姿转换到urdf的model_frame_坐标系下,第一个constraint_region_pose_.back()是作为输入,第二个是作为输出。示例中model_frame_就是base_link,因而输入值就是赋值到输出。

      const bodies::BodyPtr body(bodies::createEmptyBodyFromShapeType(shape->type));
      body->setDimensionsDirty(shape.get());
      body->setPoseDirty(constraint_region_pose_.back());
      body->updateInternalData();

setPoseDirty把constraint_region_pose_.back()赋给值成员pose_。>updateInternalData则把pose_求解出center_。

      constraint_region_.push_back(body);
    }
    else
      ROS_WARN_NAMED("kinematic_constraints", "Could not construct primitive shape %zu", i);
  }

  // load meshes
  for (std::size_t i = 0; i < pc.constraint_region.meshes.size(); ++i)
  {

逻辑类似处理pc.constraint_region.primitives,由于meshes较耗资源,实际可能不大使用。

    ...
  }

  if (pc.weight <= std::numeric_limits<double>::epsilon())
  {
    ROS_WARN_NAMED("kinematic_constraints",
                   "The weight on position constraint for link '%s' is near zero.  Setting to 1.0.",
                   pc.link_name.c_str());
    constraint_weight_ = 1.0;
  }
  else
    constraint_weight_ = pc.weight;
图2 PositionConstraint

图2左侧是输入,moveit_msgs::PositionConstraint类型的pc。右侧是PositionConstraint配置后的结果,constraint_region_中的center_类型是Eigen::Vector3d,存储着client要求的位置。

  return !constraint_region_.empty();
}

namespace bodies

该命名空间内的类允许快速判断给定点是否在对象内部。当从机器人内部移除点时(例如,当机器人看到其手臂时),此功能非常有用。在PositionConstraint,要用bodies中的Sphere(从Body派生),调用它的samplePointInside(...)生成一个随机数,随机数限制在一个以center_为球心、radiusU_为半径的球体内。

1.3 OrientationConstraint

OrientationConstraint封装了某个link上角度(rpy)方面的约束。约束以四元数的形式指定,还有一个X、Y和Z轴的误差阀值(absolute_x/y/z_axis_tolerance_)。旋转差基于XYZ欧拉角公式(固有旋转)或作为旋转向量计算。这取决于“参数化”(Parameterization_type_)类型。对OrientationConstraint,虚操作getType()返回ORIENTATION_CONSTRAINT。

class OrientationConstraint : public KinematicConstraint
{
protected:
  const moveit::core::LinkModel* link_model_;   /**< \brief The target link model */
  // 希望的旋转矩阵。getDesiredRotationMatrix()返回该值。
  Eigen::Matrix3d desired_rotation_matrix_;     /**< \brief The desired rotation matrix in the tf frame. Guaranteed to
                                                 * be valid rotation matrix. */
  Eigen::Matrix3d desired_rotation_matrix_inv_; /**< \brief The inverse of the desired rotation matrix, precomputed for
                                                 * efficiency. Guaranteed to be valid rotation matrix. */
  std::string desired_rotation_frame_id_;       /**< \brief The target frame of the transform tree */
  bool mobile_frame_;                           /**< \brief Whether or not the header frame is mobile or fixed */
  int parameterization_type_;                   /**< \brief Parameterization type for orientation tolerance. */
  // absolute_x/y/z_axis_tolerance_:0.001。误差。
  double absolute_x_axis_tolerance_, absolute_y_axis_tolerance_,
      absolute_z_axis_tolerance_; /**< \brief Storage for the tolerances */
};

OrientationConstraint会存储在两个地方,一是KinematicConstraintSet中的kinematic_constraints_,以及orientation_constraints_。KinematicConstraintSet自身存储在ModelBasedPlanningContext中的goal_constraints_。二是IKConstraintSampler中的sampling_pose_.orientation_constraint_。

bool OrientationConstraint::configure(const moveit_msgs::OrientationConstraint& oc, const moveit::core::Transforms& tf)
{
  // clearing out any old data
  clear();

  link_model_ = robot_model_->getLinkModel(oc.link_name);
  ...
  Eigen::Quaterniond q;
  tf2::fromMsg(oc.orientation, q);

geometry_msgs::Quaternion类型的四元数oc.orientation转换成Eigen::Quaterniond类型的四元数。q = 0.737963i + -0.340484j + 0.526792k + -0.24894

  if (fabs(q.norm() - 1.0) > 1e-3)
  {
    ROS_WARN_NAMED("kinematic_constraints",
                   "Orientation constraint for link '%s' is probably incorrect: %f, %f, %f, "
                   "%f. Assuming identity instead.",
                   oc.link_name.c_str(), oc.orientation.x, oc.orientation.y, oc.orientation.z, oc.orientation.w);
    q = Eigen::Quaterniond(1.0, 0.0, 0.0, 0.0);
  }

  if (oc.header.frame_id.empty())
    ROS_WARN_NAMED("kinematic_constraints", "No frame specified for position constraint on link '%s'!",
                   oc.link_name.c_str());

  if (tf.isFixedFrame(oc.header.frame_id))
  {
    tf.transformQuaternion(oc.header.frame_id, q, q);
    desired_rotation_frame_id_ = tf.getTargetFrame();
    desired_rotation_matrix_ = Eigen::Matrix3d(q);
    desired_rotation_matrix_inv_ = desired_rotation_matrix_.transpose();
    mobile_frame_ = false;

把四元数q转换到urdf的model_frame_坐标系。oc.header.frame_id是base_link,model_frame_坐标系也是base_link,既然是同一个坐标系,q值不会变。

desired_rotation_matrix_ = 

  0.213121   -0.24025   0.947027

 -0.764809  -0.644198 0.00868868

  0.607985  -0.726147  -0.321038

q = 0.737963i + -0.340484j + 0.526792k + -0.24894

desired_rotation_matrix_就是base_link坐标系下的旋转矩阵,desired_rotation_matrix_inv_则是desired_rotation_matrix_的转置矩阵。(疑问:旋转矩阵的转置矩阵一定等于逆矩阵吗?——好像是)

desired_rotation_matrix_inv_ = 

  0.213121  -0.764809   0.607985

  -0.24025  -0.644198  -0.726147

  0.947027 0.00868868  -0.321038

  }
  else
  {
    desired_rotation_frame_id_ = oc.header.frame_id;
    desired_rotation_matrix_ = Eigen::Matrix3d(q);
    mobile_frame_ = true;
  }
  std::stringstream matrix_str;
  matrix_str << desired_rotation_matrix_;
  ROS_DEBUG_NAMED("kinematic_constraints", "The desired rotation matrix for link '%s' in frame %s is:\n%s",
                  oc.link_name.c_str(), desired_rotation_frame_id_.c_str(), matrix_str.str().c_str());

  if (oc.weight <= std::numeric_limits<double>::epsilon())
  {
    ROS_WARN_NAMED("kinematic_constraints",
                   "The weight on position constraint for link '%s' is near zero.  Setting to 1.0.",
                   oc.link_name.c_str());
    constraint_weight_ = 1.0;
  }
  else
  {
    constraint_weight_ = oc.weight;
  }

  parameterization_type_ = oc.parameterization;

后面代码:1)检查parameterization_type_有效性。2)给三个误差阀值赋值:absolute_x/y/z_axis_tolerance_

  ...

图3 OrientationConstraint

图3左侧是输入,moveit_msgs::OrientationConstraint类型的oc。右侧是OrientationConstraint配置后的结果,desired_rotation_matrix_是client要求角度在urdf的model_frame_坐标系的旋转矩阵,像base_link。desired_rotation_matrix_inv_则是对应的逆旋转矩阵。

  return link_model_ != nullptr;
}

 

二、goalSamplingThread线程:采样出State,添加到States_

之前已说,goalSamplingThread线程采样State可分为两个步骤。

  1. 私有采样器constraint_sampler_调用sample(...)采样出一个状态,放在work_state.position_。
  2. checkStateValidity把work_state.position_更新到new_goal。于是sample(...)生成采样值就是要放入目标状态器的状态。

针对位姿规划,私有采样器是IKConstraintSampler,第一步就是调用IKConstraintSampler::sample。 

bool IKConstraintSampler::sample(moveit::core::RobotState& state, const moveit::core::RobotState& reference_state,
                                 unsigned int max_attempts)
{
  return sampleHelper(state, reference_state, max_attempts, false);
}

sample逻辑很简单,直调用调用sampleHelper。

2.1 IKConstraintSampler::sampleHelper

要是成功,一次sample会采出一个State,该值会到state.position_。

bool IKConstraintSampler::sampleHelper(moveit::core::RobotState& state, const moveit::core::RobotState& reference_state,
                                       unsigned int max_attempts, bool project)
{
  ...
  for (unsigned int a = 0; a < max_attempts; ++a) {

牛顿迭代初值是随机的,不排除这一次不能成功逆解,下一次却可以。max_attemps值是4。

    // sample a point in the constraint region
    Eigen::Vector3d point;
    Eigen::Quaterniond quat;  // quat is normalized by contract
    if (!samplePose(point, quat, reference_state, max_attempts))
    {

samplePos采样出一个在base_link坐标系下的位姿(point+quat)。这个位姿是client希望的位姿,加上一个在误差范围内的随机值。

point = (0.381426, -0.000289836, 0.118188)。增加的位置随机值:(0.000018, -0.000010804, -0.000005)

quat = 0.738112i + -0.340157j + 0.526907k + -0.248703。增加的角度随机值:(-0.000001906, 0.0000106588, -0.000891617)

      if (verbose_)
        ROS_INFO_NAMED("constraint_samplers", "IK constraint sampler was unable to produce a pose to run IK for");
      return false;
    }

    // we now have the transform we wish to perform IK for, in the planning frame
    if (transform_ik_)
    {
      // we need to convert this transform to the frame expected by the IK solver
      // both the planning frame and the frame for the IK are assumed to be robot links
      Eigen::Isometry3d ikq(Eigen::Translation3d(point) * quat);  // valid isometry by construction
      // getFrameTransform() returns a valid isometry by contract
      ikq = reference_state.getFrameTransform(ik_frame_).inverse() * ikq;  // valid isometry * valid isometry
      point = ikq.translation();
      quat = Eigen::Quaterniond(ikq.linear());  // ikq is isometry, so quat is normalized

第一次ikq。把samplePose生成的point、quat组成一个变化矩阵。

ikq = 

    0.213325    -0.240062     0.947028     0.381426

   -0.764235     -0.64488    0.0086793 -0.000289836

    0.608636    -0.725604    -0.321033     0.118188

           0            0            0            1

第二次ikq。第一次ikq是在base_link坐标系下位姿,运动学求解器要求的是ik_frame_下的位姿,此时ik_frame_值是arm_link,也就说第二次ikq是arm_link坐标系下的位姿。由于reference_state.getFrameTransform(ik_frame_)得到的位姿是arm_link到base_link坐标系,所以从base_link算到arm_link,要用它的逆。

ik_frame_: arm_Link, reference_state.getFrameTransform(ik_frame_) = 

-3.67321e-06    -0.698398      0.71571      0.11787

           1 -2.56536e-06  2.62895e-06            0

-2.60902e-15      0.71571     0.698398     0.078855

           0            0            0            1

reference_state.getFrameTransform(ik_frame_).inverse() = 

-3.67321e-06            1 -2.60902e-15  4.32961e-07

   -0.698398 -2.56536e-06      0.71571    0.0258828

     0.71571  2.62895e-06     0.698398    -0.139433

           0            0            0            1

第二次ikq。同是arm_link坐标系,它的值就是arm_link_pose加上随机偏差。

   -0.764236    -0.644879   0.00867582 -0.000290804

    0.286623    -0.351661    -0.891169    -0.155916

    0.577747    -0.678576     0.453589       0.2161

           0            0            0            1

quat = 0.182918i + -0.489639j + 0.801481k + 0.290556

此时的point是client希望的在arm_link坐标系位姿的位置部分,quat则是角度。

    }

    if (need_eef_to_ik_tip_transform_)
    {

need_eef_to_ik_tip_transform_默认false。

      // After sampling the pose needs to be transformed to the ik chain tip
      Eigen::Isometry3d ikq(Eigen::Translation3d(point) * quat);  // valid isometry by construction
      ikq = ikq * eef_to_ik_tip_transform_;  // eef_to_ik_tip_transform_ is valid isometry (checked in loadIKSolver())
      point = ikq.translation();
      quat = Eigen::Quaterniond(ikq.linear());  // ikq is isometry, so quat is normalized
    }

    geometry_msgs::Pose ik_query;
    ik_query.position.x = point.x();
    ik_query.position.y = point.y();
    ik_query.position.z = point.z();
    ik_query.orientation.x = quat.x();
    ik_query.orientation.y = quat.y();
    ik_query.orientation.z = quat.z();
    ik_query.orientation.w = quat.w();

    if (callIK(ik_query, adapted_ik_validity_callback, ik_timeout_, state, project && a == 0))
      return true;

ik_query是client希望的在arm_link坐标系位姿。callIK以该位姿为参数,算出对应的各关节状态,当中要使用三大插件中的运行学求解器。对第5个参数,由于参数project是false,callIK得到的会总是false。

  }
  return false;
}

 

2.2 IKConstraintSampler::samplePose

samplePos采样出一个在base_link坐标系下的位姿(point+quat)。这个位姿是client希望的位姿,加上一个在误差范围内的随机值。算出来后,位置部分放在参数pos,角度部分放在quat。

  • @pos[OUT]:存储位姿中的位置。
  • @quat[OUT]:存储位姿中的旋转。
bool IKConstraintSampler::samplePose(Eigen::Vector3d& pos, Eigen::Quaterniond& quat, const moveit::core::RobotState& ks,
                                     unsigned int max_attempts)
{
  ...
  if (sampling_pose_.position_constraint_)
  {
    const std::vector<bodies::BodyPtr>& b = sampling_pose_.position_constraint_->getConstraintRegions();
    if (!b.empty())
    {
      bool found = false;
      std::size_t k = random_number_generator_.uniformInteger(0, b.size() - 1);
      for (std::size_t i = 0; i < b.size(); ++i)
        if (b[(i + k) % b.size()]->samplePointInside(random_number_generator_, max_attempts, pos))
        {

计算位置pos。这个位置是用samplePointInside生成的一个随机数,随机数限制在一个以center_为球心、radiusU_为半径的球体内。结果存放在result。

          found = true;
          break;
        }
      ...
    }
    ...

    // if this constraint is with respect a mobile frame, we need to convert this rotation to the root frame of the
    // model
    if (sampling_pose_.position_constraint_->mobileReferenceFrame())
      pos = ks.getFrameTransform(sampling_pose_.position_constraint_->getReferenceFrame()) * pos;
  }
  ...

  if (sampling_pose_.orientation_constraint_)
  {
    // sample a rotation matrix within the allowed bounds
    double angle_x =
        2.0 * (random_number_generator_.uniform01() - 0.5) *
        (sampling_pose_.orientation_constraint_->getXAxisTolerance() - std::numeric_limits<double>::epsilon());
    double angle_y =
        2.0 * (random_number_generator_.uniform01() - 0.5) *
        (sampling_pose_.orientation_constraint_->getYAxisTolerance() - std::numeric_limits<double>::epsilon());
    double angle_z =
        2.0 * (random_number_generator_.uniform01() - 0.5) *
        (sampling_pose_.orientation_constraint_->getZAxisTolerance() - std::numeric_limits<double>::epsilon());

angle_x、angle_y、angle_z是随机产生的值,值限制在范围absolute_x/y/z_axis_tolerance_。举个例子,absolute_x_axis_tolerance_是0.001,那angle_x是范围(-0.001, 0.001)中的某个值。angle_x、angle_y、angle_z:(-0.00023890645336354434, 0.00021065886132414432, -0.00089161701500396003)

    Eigen::Isometry3d diff;
    if (sampling_pose_.orientation_constraint_->getParameterizationType() ==
        moveit_msgs::OrientationConstraint::XYZ_EULER_ANGLES)
    {
      diff = Eigen::Isometry3d(Eigen::AngleAxisd(angle_x, Eigen::Vector3d::UnitX()) *
                               Eigen::AngleAxisd(angle_y, Eigen::Vector3d::UnitY()) *
                               Eigen::AngleAxisd(angle_z, Eigen::Vector3d::UnitZ()));

类似rpy="0.79764 2.5278E-15 1.5708"定义的“origin”,type类型是XYZ_EULER_ANGLES。生成的diff是误差角度。

    }
    else if (sampling_pose_.orientation_constraint_->getParameterizationType() ==
             moveit_msgs::OrientationConstraint::ROTATION_VECTOR)
    {
      Eigen::Vector3d rotation_vector(angle_x, angle_y, angle_z);
      diff = Eigen::Isometry3d(Eigen::AngleAxisd(rotation_vector.norm(), rotation_vector.normalized()));
    }
    else
    {
      /* The parameterization type should be validated in configure, so this should never happen. */
      ROS_ERROR_STREAM_NAMED("default_constraint_samplers",
                             "The parameterization type for the orientation constraints is invalid.");
    }

    // diff is isometry by construction
    // getDesiredRotationMatrix() returns a valid rotation matrix by contract
    // reqr has thus to be a valid isometry
    Eigen::Isometry3d reqr(sampling_pose_.orientation_constraint_->getDesiredRotationMatrix() * diff.linear());
    quat = Eigen::Quaterniond(reqr.linear());  // reqr is isometry, so quat has to be normalized

在上面乘法中,getDesiredRotationMatrix()值是desired_rotation_matrix_,就是client设置的角度;diff.linear()返回随机误差中左上角的旋转矩阵。它们相乘等价两个角度“相加”。也就是说,reqr存储着client设置角度“加上”随机误差角度,格式是变换矩阵。reqr.linear()得到旋转矩阵,quat是以这旋转矩阵得到的四元数,也就是说quat就是以元四数表格表示的client希望角度“加上”随机误差角度。

偏差旋转矩阵diff.linear() =

           1  0.000891617  1.06588e-05

-0.000891617            1    1.906e-06

-1.06571e-05  -1.9155e-06            1

(base_link坐标系)期望位姿旋转矩阵getDesiredRotationMatrix() =

  0.213121   -0.24025   0.947027

 -0.764809  -0.644198 0.00868868

  0.607985  -0.726147  -0.321038

期望位姿+偏差旋转矩阵reqr.linear() =

 0.213325 -0.240062  0.947028

-0.764235  -0.64488 0.0086793

 0.608636 -0.725604 -0.321033

quat = 0.738112i + -0.340157j + 0.526907k + -0.248703

    // if this constraint is with respect a mobile frame, we need to convert this rotation to the root frame of the
    // model
    if (sampling_pose_.orientation_constraint_->mobileReferenceFrame())
    {
      // getFrameTransform() returns a valid isometry by contract
      const Eigen::Isometry3d& t = ks.getFrameTransform(sampling_pose_.orientation_constraint_->getReferenceFrame());
      // rt is isometry by construction
      Eigen::Isometry3d rt(t.linear() * quat);
      quat = Eigen::Quaterniond(rt.linear());  // rt is isometry, so quat has to be normalized
    }
  }
  ...

  // if there is an offset, we need to undo the induced rotation in the sampled transform origin (point)
  if (sampling_pose_.position_constraint_ && sampling_pose_.position_constraint_->hasLinkOffset())
    // the rotation matrix that corresponds to the desired orientation
    pos = pos - quat * sampling_pose_.position_constraint_->getLinkOffset();
 
  return true;
}

2.3 Sphere::samplePointInside

samplePointInside生成一个随机数,随机数限制在一个以center_为球心、radiusU_为半径的球体内。结果存放在result。

@result[OUT]:存储着随机生成的位置。

bool bodies::Sphere::samplePointInside(random_numbers::RandomNumberGenerator& rng, unsigned int max_attempts,
                                       Eigen::Vector3d& result) const
{
  for (unsigned int i = 0; i < max_attempts; ++i)
  {
    const double minX = center_.x() - radiusU_;
    const double maxX = center_.x() + radiusU_;
    const double minY = center_.y() - radiusU_;
    const double maxY = center_.y() + radiusU_;
    const double minZ = center_.z() - radiusU_;
    const double maxZ = center_.z() + radiusU_;
    // we are sampling in a box; the probability of success after 20 attempts is 99.99996% given the ratio of box volume
    // to sphere volume
    for (int j = 0; j < 20; ++j)
    {
      result = Eigen::Vector3d(rng.uniformReal(minX, maxX), rng.uniformReal(minY, maxY), rng.uniformReal(minZ, maxZ));
      if (containsPoint(result))
        return true;

containsPoint逻辑就是“(center_ - result).squaredNorm() <= radiusU_*radiusU_”。

    }
  }
  return false;
}

2.4 IKConstraintSampler::callIK

要是成功,由位姿ik_query反向求解出的关节状态会更新到state.position_。

bool IKConstraintSampler::callIK(const geometry_msgs::Pose& ik_query,
                                 const kinematics::KinematicsBase::IKCallbackFn& adapted_ik_validity_callback,
                                 double timeout, moveit::core::RobotState& state, bool use_as_seed)
{
  const std::vector<unsigned int>& ik_joint_bijection = jmg_->getKinematicsSolverJointBijection();
  std::vector<double> seed(ik_joint_bijection.size(), 0.0);
  std::vector<double> vals;

  if (use_as_seed)
    state.copyJointGroupPositions(jmg_, vals);
  else
    // sample a seed value
    jmg_->getVariableRandomPositions(random_number_generator_, vals);

不少运动学求解器会用牛顿迭代法,像KDL、trac_ik,这时需要一个作为迭代初值的初始关节状态,vals就是这个值。由于传给sampleHelper的project固定是false,所以use_as_seed值是false,是调用getVariableRandomPositions随机产生一个关节状态,当然它会保证不会超出([-1.57, 1.57], [0, 3.14], [-1.57, 1.57])范围,像(0.27, 0.72, 0.25)。顺便说下,如果use_as_seed=true,那会把机器人目前的关节状态作为迭代法的初始状态。

  assert(vals.size() == ik_joint_bijection.size());
  for (std::size_t i = 0; i < ik_joint_bijection.size(); ++i)
    seed[i] = vals[ik_joint_bijection[i]];

ik_joint_bijection大都就是(0, 1, 2),因而seed就是vals的原样复制。

  std::vector<double> ik_sol;
  moveit_msgs::MoveItErrorCodes error;
 
  if (adapted_ik_validity_callback ?
          kb_->searchPositionIK(ik_query, seed, timeout, ik_sol, adapted_ik_validity_callback, error) :
          kb_->searchPositionIK(ik_query, seed, timeout, ik_sol, error))
  {

kb_就是运动学求解器插件,调用它的searchPositionIK由(ik_query, seed)求解出关节状态,结果存放在ik_sol。

    assert(ik_sol.size() == ik_joint_bijection.size());
    std::vector<double> solution(ik_joint_bijection.size());
    for (std::size_t i = 0; i < ik_joint_bijection.size(); ++i)
      solution[ik_joint_bijection[i]] = ik_sol[i];
    state.setJointGroupPositions(jmg_, solution);

ik_sol存储着由位姿求解出的关节状态,把它更新到state.position_。更新方法和关节规划时一样,参考“JointConstraintSampler::sample”,也用state.setJointGroupPositions。

    return validate(state);
  }
  ...
  return false;
}

至此一个和client要求有偏差的目标位姿被反向求解到了“同值”关节状态。和关节规划一样,目标关节状态更新到了state.position_。

全部评论: 0

    写评论: