이타인클럽

코스 전체목록

닫기

Wall Following 코드 작성하기

벽 따라 움직이기

토픽 결과물: Mobile 로봇이 벽을 따라갈 수 있어요.

이번 토픽에서는 wall following을 흉내내는 정도로 구현합니다. wall following의 핵심은 로봇의 측면 거리를 벽과 유지하면서 주행하는 것입니다. 이 때, 간단히 측면 거리를 일정 거리로 유지하도록 PD 제어를 하면 됩니다. P, D gain은 적절히 선택합니다. 항상 gain 선택하는 것이 쉽진 않네요. 여기서는 장애물 상태에 따라 wall following 방향을 정하는 부분, PD 제어로 wall following 하는 부분, 장애물 상태에 따라 일반 주행에서 wall following 주행 상태 변이에 대해서 다룹니다.

설명 동영상

Wall Following 방향 정하기

장애물 상태에 따라 wall following 할 방향을 정합니다. 예를 들어 로봇 오른쪽에 장애물이 있다면 로봇 오른쪽 면으로 wall following하고, 왼쪽에 장애물이 있다면 왼쪽 면으로 wall following 합니다. wall following 방향 정하는 것은 주행전략에 따라 변경될 수 있습니다. 예를 들어 모든 영역이 막혀있다면 사용자가 적절히 선택해 주어야 합니다. 여기서는 단순히 장애물 상태에 따라 방향을 정하는 코드를 구현하였습니다.

/*
 * Determine wall following direction based on the obstacle state.
 * It is called when obstacle is detected.
 */
int MRS::determineWFDirection()
{
  // set left as default direction
  int wf_dir= WF_LEFT;
  // right direction cases
  switch( obs_state_ )
  {
  case OBS_R:
  case OBS_FR:
  case OBS_FR_R:
  case OBS_F_R:
    wf_dir= WF_RIGHT;
    break;
  case OBS_F:
    // choose the side having the shorter distance
    if( obs_dist_[FRIGHT] < obs_dist_[FLEFT] )
      wf_dir= WF_RIGHT;
    break;
  case OBS_ALL:
    // choose the direction to the goal
    break;
  case OBS_L_R:
    break;
  default: // wf_dir= WF_LEFT cases: OBS_L, OBS_L_FL, OBS_L_F, OBS_FL, OBS_CLEAR
    break;
  }

  return wf_dir;
}

Wall Following 제어하기

아래 구현된 wall following은 단순히 벽과의 거리가 desired distance가 되도록 PD 제어하는 부분입니다. 즉, 센서로부터 획득한 벽면과의 거리값과 desired distance의 차이, 즉 오차를 계산하여 desired distance를 추종하도록 P, D gain을 곱합니다. 여기서는 선속도는 일정한 값을 사용하고, 각속도만 오차를 줄이도록 변경시켰습니다.

/*
 * Do wall following.
 * @param wf_dir wall following direction
 * @param desired_dist desired wall following distance
 */
void MRS::doWallFollowing( const int _wf_dir, const double _desired_dist )
{
  // current distance to the wall
  double dist= 0.0;

  // initialize the error distance
  double dist_error= _desired_dist;
//  static double old_dist_error= 0.0;
  // left wall following
  if( _wf_dir == WF_LEFT )
  {
    dist= obs_dist_[LEFT];
    // compute the distance error
    if( dist <= _desired_dist )
    {
      dist_error= (_desired_dist - dist );
    }
    else
    {
      dist_error= -(dist - _desired_dist);
      dist_error*= 0.5;
    }
  }
  // right wall following
  else
  {
    dist= obs_dist_[RIGHT];
    if( dist <= _desired_dist )
    {
      dist_error= -(_desired_dist - dist );
    }
    else
    {
      dist_error= (dist - _desired_dist );
      dist_error*= 0.5;
    }
  }
  // @todo reset old error distance if a new wall following starts
  // by counting the dist having over threshold?

  // gain tuning
  double p_gain= params_.wf_slow_pgain * dist_error;
  double d_gain= params_.wf_slow_dgain * (dist_error - old_dist_error_ );
  double angular_vel= p_gain + d_gain;
  angular_vel *= -1;
  double linear_vel= params_.obs_approach_linvel*2;

#if __DEBUG__
  ROS_WARN( "[WF] dist, desired dist, dist_error, old_dist_error: %.4f, %.4f, %.4f, %.4f", dist, _desired_dist, dist_error, old_dist_error_ );
  ROS_WARN( "[WF] p, d gain: %.5f, %.5f", p_gain, d_gain );
  ROS_WARN( "[WF] linear, angular vel: %.4f, %.4f", linear_vel, angular_vel );
#endif

  // publish velocity
  publishVelocity( linear_vel, angular_vel );

  // update the old error distance
  old_dist_error_= dist_error;
}

ScanCB() 함수 수정

이미 작성된 scanCB 함수 끝부분에 아래의 내용을 추가합니다. 아래 구현된 부분은 로봇이 일반주행하다가 장애물을 만났을 때, wall following으로 주행을 변경하는 부분에 대한 것입니다. 대략적인 주행 변이는 아래와 같습니다.

  • 일반 주행(drive)중 전방에 장애물 만나면 감속하며 접근(approach) 한다.
  • 일반 주행(drive)중 측면에 장애물 감지되면 wall following 한다.
  • 접근(approach)중에 로봇 전방에 장애물 감지되면 회피(avoid) 한다.
  • wall following 중에 장애물 감지 안되면 일반 주행(drive)로 변경한다.
/*
 * scan callback
 */
void MRS::scanCB( const sensor_msgs::LaserScanConstPtr& _scan )
{
  ...

  // determine wall following direction or choose one
  int wf_dir= determineWFDirection();
  // jump to a branch based on the driving state
  switch( driving_state_ )
  {
  case DRIVE:
    // obstacle detected?
    if( detected )
    {
      // check if either side of the robot is close to a wall
      if( obs_dist_[LEFT] <= params_.desired_wf_dist || obs_dist_[RIGHT] <= params_.desired_wf_dist )
      {
        // transit to wall following driving
        driving_state_= DRIVE_WF;
#if __DEBUG__
      ROS_WARN( "[DRIVE] --> [DRIVE_WF]" );
#endif
        break;
      }
      // front obstacle?
      if( obs_state_ > OBS_FRONT_SECT )
      {
        // transit to approaching driving
        driving_state_= DRIVE_APPROACH;
        // approach slowly
        publishVelocity( params_.obs_approach_linvel, 0.0 );
#if __DEBUG__
      ROS_WARN( "[DRIVE] --> [DRIVE_APPROACH]" );
#endif

      }
      // side obstacle
      else
      {
        // transit to wall following driving
        driving_state_= DRIVE_WF;
#if __DEBUG__
      ROS_WARN( "[DRIVE] --> [DRIVE_WF]" );
#endif
      }
    }
    else
    {
      // drive normally
      publishVelocity( params_.drive_linvel, 0.0 );
    }
    break;
  case DRIVE_APPROACH:
#if __DEBUG__
    ROS_INFO( "[approach] param thresh: %.2f, obs_dist: %.2f", params_.obs_dist_threshs[OBS_SECTION_SIZE], obs_dist_[FLEFT] );
#endif
    // check if either side of the robot is close to a wall
    if( obs_dist_[LEFT] <= params_.desired_wf_dist || obs_dist_[RIGHT] <= params_.desired_wf_dist )
    {
      // transit to avoiding driving
      driving_state_= DRIVE_AVOID;
      // avoid obstacle by rotating the robot
      publishVelocity( 0., wf_dir*params_.obs_avoid_angvel );
#if __DEBUG__
      ROS_WARN( "[DRIVE_APPROACH] --> [DRIVE_AVOID]" );
#endif
      break;
    }
    // approaching done? checking the front sections
    if( obs_dist_[FLEFT] <= params_.obs_dist_threshs[OBS_SECTION_SIZE] ||
        obs_dist_[FRIGHT] <= params_.obs_dist_threshs[OBS_SECTION_SIZE] )
    {
      // transit to avoiding driving
      driving_state_= DRIVE_AVOID;
      // let the robot rotate
      publishVelocity( 0., wf_dir*params_.obs_avoid_angvel );
#if __DEBUG__
      ROS_WARN( "[DRIVE_APPROACH] --> [DRIVE_AVOID]" );
#endif
    }
    else
    {
      // approach slowly
      publishVelocity( params_.obs_approach_linvel, 0.0 );
    }
    break;
    // rotate until no front obstacle is detected
  case DRIVE_AVOID:
    // keep rotating while front obstacle exists
    if( obs_dist_[FLEFT] <= params_.obs_dist_threshs[FLEFT] ||
        obs_dist_[FRIGHT] <= params_.obs_dist_threshs[FRIGHT] )
    {
      // rotate the robot just in case ( robot is already rotating)
      publishVelocity( 0., wf_dir*params_.obs_avoid_angvel );
      break;
    }
    // now no front obstacle
    // transit to wf
    driving_state_= DRIVE_WF;
    // clear the error distance
    old_dist_error_= 0.0;
#if __DEBUG__
      ROS_WARN( "[DRIVE_AVOID] --> [DRIVE_WF]" );
#endif
    break;
  case DRIVE_WF:
    // no obstacle?
    if( !detected )
    {
      // transit to normal driving state
      driving_state_= DRIVE;
#if __DEBUG__
      ROS_WARN( "[DRIVE_WF] --> [DRIVE]" );
#endif
      // exit the wall following state
      break;
    }
    else
    {
      // check if either side of the robot is close to a wall.
      if( obs_dist_[LEFT] <= params_.desired_wf_dist-0.2 || obs_dist_[RIGHT] <= params_.desired_wf_dist-0.2 )
      {
        doWallFollowing( wf_dir, params_.desired_wf_dist );
        break;
      }

      // front obstacle?
      if( obs_dist_[FLEFT] <= params_.obs_dist_threshs[FLEFT] ||
          obs_dist_[FRIGHT] <= params_.obs_dist_threshs[FRIGHT] )
      {
        // transit to approaching driving
        driving_state_= DRIVE_APPROACH;
        // approach slowly
        publishVelocity( params_.obs_approach_linvel, 0.0 );
#if __DEBUG__
      ROS_WARN( "[DRIVE_WF] --> [DRIVE_APPROACH]" );
#endif
        break;
      }
      // keep wall following
      doWallFollowing( wf_dir, params_.desired_wf_dist );
    }
    break;
  default:
    ROS_WARN( "Driving state is undefined" );
    break;
  }  // end of switch( driving_state_ )
} // end of scanCB()

노드 실행

노드를 실행하기 전에 catkin_make하여 소스를 빌드합니다. 아래와 같이 Gazebo 시뮬레이션 노드를 실행하면 로봇이 스스로 앞으로 이동하며 벽을 만나면 wall following을 하게 됩니다. 본 토픽에서는 정밀하게 wall following구현 하지 않아서, 로봇이 벽면 모서리에서 wall following을 유지하지 않고, 앞으로 나아가게 됩니다. wall following 모션이 제대로 동작 안한다면 mrs.launch.xml 파일에서 P, D gain 파라미터값 변경과 desired distance 변경 등을 해봅니다. 또는 보다 정교하게 wall following 전략을 코딩해 보는 것도 좋습니다. wall following이 어느 정도 되면 zigzag 모션 등을 구현해 볼 수 있습니다.

$ roslaunch mobile_robot_sim turtlebot_hokuyo.launch

댓글

댓글 본문
graphittie 자세히 보기