Pure Pursuit算法
Pure pursuit算法是一种基于几何方式的基本路径追踪算法,下面将从:
RTK与imu融合
车辆运动学模型
ROS实现进行讲解
imu_gps_localization包
该项目(ROS包)是:利用EKF(扩展卡尔曼滤波)实现IMU与GPS信号的融合。
为什么要进行融合呢?
IMU数据具有:高频率、灵敏,能有效提供姿态变化,但存在漂移等问题。
GPS数据具有:位置精度高(RTK 厘米级误差)、可靠,但存在更新频率慢。
综上所述,仅仅使用GPS进行对车辆的完全控制显然是不可靠的。
下载包后,分析项目结构有:
├─doc ## 存放地图信息 ├─imu_gps_localizer ## imu、GPS融合算法 │ ├─include │ │ └─imu_gps_localizer │ ├─src │ └─third_party │ └─GeographicLib ## 几何库 │ ├─include │ └─src └─ros_wrapper ## 封装 ├─include ├─launch ├─rviz └─src
进入ros_wrapper
目录中,首先观察启动文件的设计:
<launch> <param name="acc_noise" type="double" value="1e-2" /> <param name="gyro_noise" type="double" value="1e-4" /> <param name="acc_bias_noise" type="double" value="1e-6" /> <param name="gyro_bias_noise" type="double" value="1e-8" /> // imu噪声 <param name="I_p_Gps_x" type="double" value="0.0" /> <param name="I_p_Gps_y" type="double" value="0.0" /> <param name="I_p_Gps_z" type="double" value="0.0" /> // GPS在IMU坐标系下的位置 <param name="log_folder" type="string" value="$(find imu_gps_localization)" /> // 日志目录 <node name="nmea_topic_driver" pkg="nmea_navsat_driver" type="nmea_topic_driver" output="screen" /> // nmea驱动 <node name="imu_gps_localization_node" pkg="imu_gps_localization" type="imu_gps_localization_node" output="screen" /> // 融合 <node pkg="rviz" type="rviz" name="rviz" output="screen" args="-d $(find imu_gps_localization)/ros_wrapper/rviz/default.rviz" required="true"> // 打开rviz配置,失败关闭整个进程 </node> </launch>
综上所述,只需在新建的work_ws/src
下,添加imu_gps_localization
和nmea_navsat_driver
包进行编译即可。
而对于nmea_navsat_driver
包的使用,其launch文件:
<launch> <arg name="port" default="/dev/ttyUSB0" /> <arg name="baud" default="4800" /> <arg name="frame_id" default="gps" /> <arg name="use_GNSS_time" default="False" /> <arg name="time_ref_source" default="gps" /> <arg name="useRMC" default="False" /> <node name="nmea_serial_driver_node" pkg="nmea_navsat_driver" type="nmea_serial_driver" output="screen"> <param name="port" value="$(arg port)"/> <param name="baud" value="$(arg baud)" /> <param name="frame_id" value="$(arg frame_id)" /> <param name="use_GNSS_time" value="$(arg use_GNSS_time)" /> <param name="time_ref_source" value="$(arg time_ref_source)" /> <param name="useRMC" value="$(arg useRMC)" /> </node> </launch>
发布的ROS话题:启动 nmea_navsat_driver
后,它会解析 NMEA 数据并发布以下 ROS 话题:
/fix
:标准的 sensor_msgs/NavSatFix
消息,包含位置信息。
/vel
:标准的 geometry_msgs/TwistStamped
消息,包含速度信息(如果可用)。
/time_reference
:标准的 sensor_msgs/TimeReference
消息,包含时间参考信息(如果可用)。
则通过以上文件的配置,我们就可以实现imu与GPS信号的融合,完整完整操作为:
sudo chmod 666 /dev/ttyUSB0 roscore roslaunch imu_gps_localization imu_gps_localization.launch
值得注意的是:该项目需要自己设计imu话题发布对象,话题名:/imu/data,为标准sensor_msgs格式。
当我们得到理想的IMU与GPS融合数据后,现在需要进行控制车辆对目标点的追踪。
阿克曼运动学模型
阿克曼模型即四轮模型,可简化成自行车(二轮模型)进行运动学分析。
Pure Pursuit是一种几何跟踪控制算法,也被称为纯跟踪控制算法。该算法最早由R. Wallace在1985年提出,其思想是基于当前车辆的后轮中心位置(车辆质心),在参考路径上向l d l_d l d (称为前视距离)的距离匹配一个预瞄点,假设车辆后轮中心可以按照一定的转弯半径R R R 行驶至该预瞄点,然后根据前视距离l d l_d l d 、转弯半径R R R 、车辆坐标系下预瞄点的朝向角𝛼之间的几何关系来计算前轮转角δ \delta δ 。
l d sin ( 2 α ) = R sin ( π 2 − α ) ⇒ R = l d 2 sin α \frac{l_d}{\sin(2\alpha)}=\frac{R}{\sin(\frac{\pi}{2}-\alpha)}\Rightarrow R=\frac{l_d}{2\sin\alpha} s i n ( 2 α ) l d = s i n ( 2 π − α ) R ⇒ R = 2 s i n α l d
δ = arctan L R \delta=\arctan\frac LR δ = arctan R L
δ = arctan 2 L sin α l d \delta=\arctan\frac{2L\sin\alpha}{l_d} δ = arctan l d 2 L s i n α
e y = l d sin α e_y=l_d\sin\alpha e y = l d sin α
当前轮转角很小时(<π 3 \frac{\pi}{3} 3 π ),横向误差可以近似为:
e y = l d sin α ≈ l d 2 δ 2 L ⇒ δ = 2 L l d 2 e y e_{y}=l_{d}\sin\alpha\approx\frac{l_{d}^{ 2}\delta}{2L}\Rightarrow\delta=\frac{2L}{l_{d}^{ 2}}e_{y} e y = l d sin α ≈ 2 L l d 2 δ ⇒ δ = l d 2 2 L e y
所以,该算法近似于一个P控制器,跟踪效果由前视距离l d l_d l d 决定。
纯追踪ROS实现
获取小车的当前坐标(订阅odom获取或者map到base_link坐标变换获取)
void PurePursuit::odomCallback (const nav_msgs::Odometry::ConstPtr &odomMsg) { this ->odom_ = *odomMsg; if (this ->goal_received_) { double car2goal_x = this ->goal_pos_.x - odomMsg->pose.pose.position.x; double car2goal_y = this ->goal_pos_.y - odomMsg->pose.pose.position.y; double dist2goal = sqrt (car2goal_x*car2goal_x + car2goal_y*car2goal_y); } }
寻找与小车最近的路径点
int PurePursuit::minIndex (const geometry_msgs::Point& carPose) { int index_min = 0 ; int d_min = INT16_MAX; for (int i =0 ; i< map_path_.poses.size (); i++) { geometry_msgs::PoseStamped map_path_pose = map_path_.poses[i]; double d_temp = PointDistanceSquare (map_path_pose,carPose); if (d_temp < d_min) { d_min = d_temp; index_min = i; } } return index_min; }
从该路径点出发,沿着路径寻找大于前视距离的第一个点作为目标点
bool PurePursuit::isWayPtAwayFromLfwDist (const geometry_msgs::Point& wayPt, const geometry_msgs::Point& car_pos) { double dx = wayPt.x - car_pos.x; double dy = wayPt.y - car_pos.y; double dist = sqrt (dx*dx + dy*dy); if (dist < Lfw) return false ; else if (dist >= Lfw) return true ; }
将路径点转到小车坐标系
odom_car2WayPtVec.x = cos (carPose_yaw)*(forwardPt.x - carPose_pos.x) + sin (carPose_yaw)*(forwardPt.y - carPose_pos.y); odom_car2WayPtVec.y = -sin (carPose_yaw)*(forwardPt.x - carPose_pos.x) + cos (carPose_yaw)*(forwardPt.y - carPose_pos.y);
计算α或e(航偏角或横向误差)
double PurePursuit::getEta (const geometry_msgs::Pose& carPose) { geometry_msgs::Point odom_car2WayPtVec = get_odom_car2WayPtVec (carPose); return atan2 (odom_car2WayPtVec.y,odom_car2WayPtVec.x); }
计算δ
double PurePursuit::getSteering (double eta) { return atan2 (2 *(this ->base_shape_L*sin (eta)),(this ->Lfw)); }
发布角度,速度控制
void PurePursuit::controlLoopCallback (const ros::TimerEvent&) { geometry_msgs::Pose carPose = this ->odom_.pose.pose; geometry_msgs::Twist carVel = this ->odom_.twist.twist; if (this ->goal_received_) { double eta = getEta (carPose); if (foundForwardPt_) { this ->steering_ = this ->base_angle_ + getSteering (eta); if (!this ->goal_reached_) { this ->velocity = this ->reference_v; } } } if (goal_reached_) { velocity = 0.0 ; steering_ = 0.0 ; } this ->ackermann_cmd_.drive.steering_angle = this ->steering_; this ->ackermann_cmd_.drive.speed = this ->velocity; this ->ackermann_pub_.publish (this ->ackermann_cmd_); }
版权声明: 此文章版权归lancit所有,如有转载,请注明来自原作者。