单车模型(Bicycle Model)实际上是对阿克曼转向几何的一个简化,使用单车模型需做如下假设:
采用单车模型的一大好处就在于它简化了前轮转向角与后轴将遵循的曲率之间的几何关系,其关系如下式所示: t a n ( δ ) = L R tan(δ)=\frac{L}{R} tan(δ)=RL 其中 δ δ δ表示前轮的转角, L L L为轴距, R R R则为在给定的转向角下后轴遵循着的圆的半径。
从自行车模型出发,纯跟踪算法以车后轴为切点,车辆纵向车身为切线,通过控制前轮转角,使车辆可以沿着一条经过目标路点(goal point)的圆弧行驶,如下图所示:
图中 ( g x , g y ) (g_x,g_y) (gx,gy)是我们下一个要追踪的路点,它位于我们已经规划好的全局路径上,现在需要控制车辆的后轴经过该路点, l d l_d ld表示车辆当前位置(即后轴位置)到目标路点的距离, α α α表示目前车身姿态和目标路点的夹角,那么根据正弦定理我们可以推导出如下转换式: l d s i n ( 2 α ) = R s i n ( π 2 − α ) \frac{l_d}{sin(2α)}=\frac{R}{sin(\frac{\pi}{2}-α)} sin(2α)ld=sin(2π−α)R l d 2 s i n α c o s α = R c o s α \frac{l_d}{2sinαcosα}=\frac{R}{cosα} 2sinαcosαld=cosαR l d s i n α = 2 R \frac{l_d}{sin\alpha}=2R sinαld=2R因为道路的曲率 κ = 1 R {κ}=\frac{1}{R} κ=R1上式也可以表示为 κ = 2 s i n ( α ) l d κ=\frac{2sin(α)}{l_d} κ=ld2sin(α)则由式子 t a n ( δ ) = L R tan(δ)=\frac{L}{R} tan(δ)=RL可得 δ = a r c t a n ( L R ) = a r c t a n ( κ L ) = a r c t a n ( 2 L s i n ( α ) l d ) δ=arctan(\frac{L}{R})=arctan(κL)=arctan(\frac{2Lsin(α)}{l_d}) δ=arctan(RL)=arctan(κL)=arctan(ld2Lsin(α))
(1) 初始化函数
def __init__(self):
rospy.init_node('pure_persuit', log_level=rospy.DEBUG)
rospy.Subscriber('/rear_pose', PoseStamped, self.pose_cb, queue_size = 1)
rospy.Subscriber('/velocity', TwistStamped, self.vel_cb, queue_size = 1)
rospy.Subscriber('/final_waypoints', Lane, self.lane_cb, queue_size = 1)
self.left_vel_pub =rospy.Publisher('/rear_left_velocity_controller/command', Float64, queue_size = 10)
self.right_vel_pub = rospy.Publisher('/rear_right_velocity_controller/command', Float64, queue_size = 10)
self.left_steer_pub = rospy.Publisher('/left_bridge_position_controller/command', Float64, queue_size = 10)
self.right_steer_pub = rospy.Publisher('/right_bridge_position_controller/command', Float64, queue_size = 10)
self.currentPose = None
self.currentVelocity = None
self.currentWaypoints = None
self.loop()
说明:
rospy.Subscriber('/rear_pose', PoseStamped, self.pose_cb, queue_size = 1)
rospy.Subscriber('/velocity', TwistStamped, self.vel_cb, queue_size = 1)
rospy.Subscriber('/final_waypoints', Lane, self.lane_cb, queue_size = 1)
self.left_vel_pub =rospy.Publisher('/rear_left_velocity_controller/command', Float64, queue_size = 10)
self.right_vel_pub = rospy.Publisher('/rear_right_velocity_controller/command', Float64, queue_size = 10)
self.left_steer_pub = rospy.Publisher('/left_bridge_position_controller/command', Float64, queue_size = 10)
self.right_steer_pub = rospy.Publisher('/right_bridge_position_controller/command', Float64, queue_size = 10)
(2) loop()函数
def loop(self):
rate = rospy.Rate(20)
rospy.logwarn("pure persuit starts")
while not rospy.is_shutdown():
if self.currentPose and self.currentVelocity and self.currentWaypoints:
self.calculateTwistCommand()
rate.sleep()
说明:
(3) 回调函数
def pose_cb(self,data):
self.currentPose = data
def vel_cb(self,data):
self.currentVelocity = data
def lane_cb(self,data):
self.currentWaypoints = data
说明:
(4) 阿克曼(Ackermann)转向函数
def ackermann_steering_control(self, velocity, radian):
global left_angle, left_speed, right_angle, right_speed
if radian > 0:
inside_radius = L / math.tan(radian) - T / 2
outside_radius = L / math.tan(radian) + T / 2
else:
outside_radius = L / math.tan(radian) - T / 2
inside_radius = L / math.tan(radian) + T / 2
outside_speed = velocity * ( 1 + T * math.tan(abs(radian)) / ( 2 * L ) )
inside_speed = velocity * ( 1 - T * math.tan(abs(radian)) / ( 2 * L ) )
inside_angle = math.atan( L / inside_radius )
outside_angle = math.atan( L / outside_radius )
if radian > 0:
left_angle = outside_angle
left_speed = outside_speed
right_angle = inside_angle
right_speed = inside_speed
else:
right_angle = outside_angle
right_speed = outside_speed
left_angle = inside_angle
left_speed = inside_speed
说明:
(5) calculateTwistCommand()函数part.1
def calculateTwistCommand(self):
lad = 0.0 #look ahead distance accumulator
targetIndex = len(self.currentWaypoints.waypoints) - 1
for i in range(len(self.currentWaypoints.waypoints)):
if((i+1) < len(self.currentWaypoints.waypoints)):
this_x = self.currentWaypoints.waypoints[i].pose.pose.position.x
this_y = self.currentWaypoints.waypoints[i].pose.pose.position.y
next_x = self.currentWaypoints.waypoints[i+1].pose.pose.position.x
next_y = self.currentWaypoints.waypoints[i+1].pose.pose.position.y
lad = lad + math.hypot(next_x - this_x, next_y - this_y)
if(lad > HORIZON):
targetIndex = i+1
break
targetWaypoint = self.currentWaypoints.waypoints[targetIndex]
targetSpeed = self.currentWaypoints.waypoints[0].twist.twist.linear.x
说明:
(6) calculateTwistCommand()函数part.2
targetX = targetWaypoint.pose.pose.position.x
targetY = targetWaypoint.pose.pose.position.y
currentX = self.currentPose.pose.position.x
currentY = self.currentPose.pose.position.y
说明:
(7) calculateTwistCommand()函数part.3
#get vehicle yaw angle
quanternion = (self.currentPose.pose.orientation.x, self.currentPose.pose.orientation.y, self.currentPose.pose.orientation.z, self.currentPose.pose.orientation.w)
euler = tf.transformations.euler_from_quaternion(quanternion)
yaw = euler[2]
说明:
(8) calculateTwistCommand()函数part.4
#get angle difference
alpha = math.atan2(targetY - currentY, targetX - currentX) - yaw
l = math.sqrt(math.pow(currentX - targetX, 2) + math.pow(currentY - targetY, 2))
if(l > 0.5):
theta = math.atan(2 * 0.16 * math.sin(alpha) / l)
self.ackermann_steering_control(targetSpeed, -theta)
#get twist command
left_steer = Float64()
right_steer = Float64()
left_vel = Float64()
right_vel = Float64()
left_steer.data = left_angle
right_steer.data = right_angle
left_vel.data = left_speed
right_vel.data = right_speed
else:
left_steer = Float64()
right_steer = Float64()
left_vel = Float64()
right_vel = Float64()
left_steer.data = 0
right_steer.data = 0
left_vel.data = 0
right_vel.data = 0
self.left_vel_pub.publish(left_vel)
self.right_vel_pub.publish(right_vel)
self.left_steer_pub.publish(left_steer)
self.right_steer_pub.publish(right_steer)
说明:
ROS|平平无奇路径跟踪仿真(Pure Persuit)