dwb local planner 2

dwb local plannerの実装を理解していきます。

自分で考える

source code解析をする前に自分で作ることを考えるとします。

前回、詳解確率ロボティクスのPythonをお手本として回転のみでgoalを目指す実装をしました。
障害物回避を実装します。

詳解確率ロボティクスのPythonはrobotの動作がわかりやすく拡張もやりやすいので利用させていただきます。

整理する

遠くにある障害物は無視します。
通ろうとしていた経路の近くの衝突してしまう障害物に対して少し余裕を持って避ける動作を実装します。

output

障害物を回避します。
goalに到着の条件を実装していないためgoalを目指し続けます。
自分の大きさ、形状を考慮していません。次の記事からはnav2の解析をします。その後に自分の大きさを考慮した実装にします。

sensorでobstacleの位置を把握するところまで実装すればobstacleの位置をworld座標系からrobot座標系にするという変換を削除できます。そこまでやれる時間あるかなー。

実装

実装です。

dwb_local_planner_002.cpp

  1. import matplotlib
  2. # matplotlib.use('nbagg')
  3. import matplotlib.animation as anm
  4. import matplotlib.pyplot as plt
  5. import math
  6. from math import sin, cos, degrees, sqrt
  7. import matplotlib.patches as patches
  8. import numpy as np
  9. import random
  10. import time
  11. # ------------------------------------------------------------------------------
  12. PI = 3.141592
  13. DEGREE_1 = PI/180
  14. DEGREE_179 = DEGREE_1*179
  15. ANGULAR_VELOCITY_MAX = 0.1
  16. COEFF = 0.2
  17. COEFF_OB = 1.0
  18. # ------------------------------------------------------------------------------
  19. ## x : robot position x
  20. ## y : robot position y
  21. ## yaw : robot position yaw
  22. class Robot:
  23. def __init__( self, pose, nu, omega, goal, obstacles, color="black" ):
  24. self.pose = pose
  25. self.r = 0.2
  26. self.color = color
  27. self.poses = [pose]
  28. self.nu = nu
  29. self.omega = omega
  30. self.goal = goal
  31. self.angle = 0.0
  32. self.diff_angle = 0.0
  33. self.g_x_in_robot_flame = 0.0
  34. self.g_y_in_robot_flame = 0.0
  35. self.tmp_x = 0.0
  36. self.tmp_y = 0.0
  37. self.obstacles = obstacles
  38. def draw(self, ax, elems):
  39. x, y, yaw = self.pose
  40. xn = x + self.r * math.cos(yaw)
  41. yn = y + self.r * math.sin(yaw)
  42. ##
  43. elems += ax.plot([x,xn], [y,yn], color=self.color)
  44. ## robot's orientation
  45. ax.plot([x,xn], [y,yn], color=self.color)
  46. c = patches.Circle(xy=(x, y), radius=self.r, fill=False, color=self.color)
  47. elems.append(ax.add_patch(c))
  48. elems.append(ax.text( 2, -2, f'g_x - x: {self.tmp_x:.3f}', size=15 ))
  49. elems.append(ax.text( 2, -3, f'g_y - y: {self.tmp_y:.3f}', size=15 ))
  50. elems.append(ax.text( 2, -4, f'g_x_in_robot_flame: {self.g_x_in_robot_flame:.3f}', size=15 ))
  51. elems.append(ax.text( 2, -5, f'g_y_in_robot_flame: {self.g_y_in_robot_flame:.3f}', size=15 ))
  52. elems.append(ax.text( 2, -6, f'angle: {degrees(self.angle):.3f}', size=15 ))
  53. elems.append(ax.text( 2, -7, f'yaw: {degrees(self.pose[2]):.3f}', size=15 ))
  54. elems.append(ax.text( 2, -8, f'diff_angle: {degrees(self.diff_angle):.3f}', size=15 ))
  55. elems.append(ax.text( 2, -9, f'omega: {self.omega:.3f}', size=15 ))
  56. self.poses.append(self.pose)
  57. elems += ax.plot([e[0] for e in self.poses], [e[1] for e in self.poses], linewidth=0.5, color="black")
  58. @classmethod
  59. def state_transition(cls, nu, omega, time, pose):
  60. t0 = pose[2]
  61. if math.fabs(omega) < 1e-10:
  62. return pose + np.array( [nu*math.cos(t0),
  63. nu*math.sin(t0),
  64. omega ] ) * time
  65. else:
  66. return pose + np.array( [nu/omega*(math.sin(t0 + omega*time) - math.sin(t0)),
  67. nu/omega*(-math.cos(t0 + omega*time) + math.cos(t0)),
  68. omega*time ] )
  69. def one_step(self, time_interval):
  70. # nu, omega = self.run()
  71. nu, omega = self.dwa()
  72. self.pose = self.state_transition( nu, omega, time_interval, self.pose )
  73. def run( self ):
  74. return self.nu, self.omega
  75. def go_stragiht( self ):
  76. return self.nu, self.omega
  77. def dwa( self ):
  78. # ------------------------------------------------------------------------------
  79. # robot pose in world flame
  80. x, y, yaw = self.pose
  81. ## 0(deg) <= yaw < 360(deg)
  82. if (2*PI) <= yaw:
  83. yaw = yaw - 2*PI
  84. if yaw < 0:
  85. yaw = yaw + 2*PI
  86. self.pose[2] = yaw
  87. # ------------------------------------------------------------------------------
  88. # goal in world flame
  89. goal = self.goal
  90. g_x = goal.pos[0]
  91. g_y = goal.pos[1]
  92. print(f'x: {g_x},y: {g_y}')
  93. ## gradient = slope
  94. angle = math.atan2( g_y - y, g_x - x )
  95. ## 0(deg) <= angle < 360(deg)
  96. ## atan2 return value: -PI < angle <= PI
  97. # if (2*PI) <= angle:
  98. # angle = angle - 2*PI
  99. if angle < 0:
  100. angle = angle + 2*PI
  101. self.angle = angle
  102. print(f'robot yaw: {yaw}')
  103. print(f'angle (rad): {angle}')
  104. print(f'angle (deg): {math.degrees(angle)}')
  105. print(f'robot yaw - angle difference: {math.degrees(yaw - angle)}')
  106. # ------------------------------------------------------------------------------
  107. # goal in robot flame
  108. tmp_x = g_x - x
  109. tmp_y = g_y - y
  110. g_x_in_robot_flame = tmp_x * cos(-yaw) - tmp_y * sin(-yaw)
  111. g_y_in_robot_flame = tmp_x * sin(-yaw) + tmp_y * cos(-yaw)
  112. ## for debug
  113. self.g_x_in_robot_flame = g_x_in_robot_flame
  114. self.g_y_in_robot_flame = g_y_in_robot_flame
  115. self.tmp_x = tmp_x
  116. self.tmp_y = tmp_y
  117. # ------------------------------------------------------------------------------
  118. # obstacles in world flame
  119. obstacles = self.obstacles
  120. # ------------------------------------------------------------------------------
  121. # determine omega towards the goal
  122. diff_angle = angle - yaw
  123. self.diff_angle = diff_angle
  124. ## the goal is on the left
  125. if 0 < g_y_in_robot_flame:
  126. if abs(diff_angle) < DEGREE_1:
  127. ## move forward
  128. change_omega = 0
  129. else:
  130. change_omega = abs(diff_angle) * COEFF
  131. ## the goal is on the right
  132. if g_y_in_robot_flame < 0:
  133. if DEGREE_179 < abs(diff_angle) and abs(diff_angle) < DEGREE_181:
  134. ## force clockwise
  135. change_omega = ANGULAR_VELOCITY_MAX
  136. else:
  137. change_omega = - abs(diff_angle) * COEFF
  138. # ------------------------------------------------------------------------------
  139. # move around obstacle
  140. # closest ranking
  141. dist_ob = []
  142. ob_in_robot_flame = []
  143. for obstacle in obstacles:
  144. # obstacles in robot flame
  145. tmp_x = obstacle.pos[0] - x
  146. tmp_y = obstacle.pos[1] - y
  147. dist = sqrt( tmp_x * tmp_x + tmp_y * tmp_y )
  148. ob_x_in_robot_flame = tmp_x * cos(-yaw) - tmp_y * sin(-yaw)
  149. ob_y_in_robot_flame = tmp_x * sin(-yaw) + tmp_y * cos(-yaw)
  150. dist_ob.append( (dist, ob_x_in_robot_flame, ob_y_in_robot_flame) )
  151. # print( dist_ob )
  152. dist_ob.sort(key=lambda x: x[0])
  153. # print( dist_ob )
  154. print(f'robot yaw: {yaw}')
  155. print( f'dist: {dist_ob[0][0]}' )
  156. print( f'ob_y_in_robot_flame: {dist_ob[0][2]}' )
  157. # distance between robot and obstacle
  158. if dist_ob[0][0] < 1.0:
  159. if 0 < dist_ob[0][2]:
  160. change_omega = - (1 / dist + 0.1) * COEFF_OB
  161. if dist_ob[0][2] < 0:
  162. change_omega = + (1 / dist + 0.1) * COEFF_OB
  163. self.omega = change_omega
  164. return self.nu, self.omega
  165. # ------------------------------------------------------------------------------
  166. class World:
  167. def __init__( self, time_span, time_interval ):
  168. self.objects = []
  169. self.time_span = time_span
  170. self.time_interval = time_interval
  171. self.file_name_counter = 0
  172. def append( self,obj ):
  173. self.objects.append(obj)
  174. def draw(self):
  175. fig = plt.figure(figsize=(8,8)) # fig = plt.figure(figsize=(4,4))
  176. ## make axes
  177. ax = fig.add_subplot(111)
  178. ax.set_aspect('equal')
  179. ax.set_xlim(-10,10)
  180. ax.set_ylim(-10,10)
  181. ax.set_xlabel("X",fontsize=10)
  182. ax.set_ylabel("Y",fontsize=10)
  183. elems = []
  184. tmp_1 = int( self.time_span/self.time_interval ) + 1
  185. tmp_2 = int( self.time_interval )
  186. self.ani = anm.FuncAnimation(fig, self.one_step, fargs=(elems, ax), frames=tmp_1, interval=tmp_2, repeat=False)
  187. plt.show()
  188. def one_step(self, i, elems, ax):
  189. while elems: elems.pop().remove()
  190. time_str = "t = %.2f[s]" % (self.time_interval*i)
  191. elems.append(ax.text(-4.4, 4.5, time_str, fontsize=10))
  192. ##
  193. elems.append(ax.text(-4.4, 4.5, time_str, fontsize=10))
  194. for obj in self.objects:
  195. obj.draw(ax, elems)
  196. if hasattr(obj, "one_step"): obj.one_step(self.time_interval)
  197. # ------------------------------------------------------------------------------
  198. class Map:
  199. def __init__(self):
  200. self.landmarks = []
  201. self.goal = []
  202. self.obstacles = []
  203. def append_landmark(self, landmark):
  204. landmark.id = len(self.landmarks)
  205. self.landmarks.append(landmark)
  206. def append_goal( self, goal ):
  207. self.goal.append( goal )
  208. def append_obstacle( self, obstacle ):
  209. self.obstacles.append( obstacle )
  210. def draw( self, ax, elems ):
  211. for goal in self.goal: goal.draw(ax, elems)
  212. for obstacle in self.obstacles: obstacle.draw(ax, elems)
  213. # ------------------------------------------------------------------------------
  214. class Goal:
  215. def __init__( self, x, y ):
  216. self.pos = np.array([x, y]).T
  217. self.id = None
  218. def draw( self, ax, elems ):
  219. c = ax.scatter(self.pos[0], self.pos[1], s=100, marker="*", label="goal", color="red")
  220. elems.append(c)
  221. elems.append(ax.text(self.pos[0], self.pos[1], "id:" + str(self.id), fontsize=10))
  222. # ------------------------------------------------------------------------------
  223. class Obstacle:
  224. def __init__( self, x, y, r ):
  225. self.pos = np.array([x, y]).T
  226. self.id = None
  227. self.radius = r
  228. def draw( self, ax, elems ):
  229. c = ax.scatter(self.pos[0], self.pos[1], s=100, marker="o", label="obstacle", color="black")
  230. elems.append(c)
  231. elems.append(ax.text(self.pos[0], self.pos[1], "id:" + str(self.id), fontsize=10))
  232. c = patches.Circle(xy=(self.pos[0], self.pos[1]), radius=self.radius, fill=False, color="blue")
  233. elems.append(ax.add_patch(c))
  234. # ------------------------------------------------------------------------------
  235. if __name__ == '__main__':
  236. ## 0.01 : very slow
  237. # world = World( 15, 0.1 )
  238. # world = World( 120, 0.1 )
  239. world = World( 200, 0.2 )
  240. # world = World( 120, 0.04 )
  241. # world = World( 120, 0.01 )
  242. m = Map()
  243. goal = Goal( 4, 8 )
  244. m.append_goal( goal )
  245. obstacle = Obstacle( 5, 6, 1 )
  246. m.append_obstacle( obstacle )
  247. obstacle = Obstacle( 3, 5, 1 )
  248. m.append_obstacle( obstacle )
  249. world.append( m )
  250. robot = Robot( np.array([0, -3, 0]).T, nu=0.2, omega=0.0, goal=goal, obstacles=m.obstacles )
  251. # robot = Robot( np.array([0, 0, 1]).T, nu=0.2, omega=0.0, goal=goal )
  252. # robot = Robot( np.array([0, 0, 0]).T, nu=1.0, omega=1.0, goal=goal )
  253. world.append( robot )
  254. world.draw()

まとめ

このくらいの複雑さからcontinuous integrationを組み込まないと検証できなくなる気がします。

入力と期待値の幅を用意することになる気がします。

それなりに厳しいobstacleの配置にしたけど、実機に搭載は怖くて出来ない。

広告

IT開発関連書とビジネス書が豊富な翔泳社の通販『SEshop』
さくらのレンタルサーバ
ムームードメイン
Oisix(おいしっくす)
らでぃっしゅぼーや
珈琲きゃろっと
エプソムソルト




«       »