1 """ 2 version2.0,增加環境動態 3 version1.3 4 Mobile robot motion planning sample with Dynamic Window Approach 5 結合https://blog.csdn.net/heyijia0327/article/details/44983551來看,里面含中文注釋 6 符號參考《煤礦救援機器人地圖構建與路徑規划研究》礦大碩士論文 7 """ 8 9 import math 10 import numpy as np 11 import matplotlib.pyplot as plt 12 13 14 class Config(object): 15 """ 16 用來仿真的參數, 17 """ 18 19 def __init__(self): 20 # robot parameter 21 self.max_speed = 1.4 # [m/s] # 最大速度 22 # self.min_speed = -0.5 # [m/s] # 最小速度,設置為可以倒車 23 self.min_speed = 0 # [m/s] # 最小速度,設置為不倒車 24 self.max_yawrate = 40.0 * math.pi / 180.0 # [rad/s] # 最大角速度 25 self.max_accel = 0.2 # [m/ss] # 最大加速度 26 self.max_dyawrate = 40.0 * math.pi / 180.0 # [rad/ss] # 最大角加速度 27 self.v_reso = 0.01 # [m/s],速度分辨率 28 self.yawrate_reso = 0.1 * math.pi / 180.0 # [rad/s],角速度分辨率 29 self.dt = 0.1 # [s] # 采樣周期 30 self.predict_time = 3.0 # [s] # 向前預估三秒 31 self.to_goal_cost_gain = 1.0 # 目標代價增益 32 self.speed_cost_gain = 1.0 # 速度代價增益 33 self.robot_radius = 1.0 # [m] # 機器人半徑 34 35 36 def motion(x, u, dt): 37 """ 38 :param x: 位置參數,在此叫做位置空間 39 :param u: 速度和加速度,在此叫做速度空間 40 :param dt: 采樣時間 41 :return: 42 """ 43 # 速度更新公式比較簡單,在極短時間內,車輛位移也變化較大 44 # 采用圓弧求解如何? 45 x[0] += u[0] * math.cos(x[2]) * dt # x方向位移 46 x[1] += u[0] * math.sin(x[2]) * dt # y 47 x[2] += u[1] * dt # 航向角 48 x[3] = u[0] # 速度v 49 x[4] = u[1] # 角速度w 50 # print(x) 51 52 return x 53 54 55 def calc_dynamic_window(x, config): 56 """ 57 位置空間集合 58 :param x:當前位置空間,符號參考碩士論文 59 :param config: 60 :return:目前是兩個速度的交集,還差一個 61 """ 62 63 # 車輛能夠達到的最大最小速度 64 vs = [config.min_speed, config.max_speed, 65 -config.max_yawrate, config.max_yawrate] 66 67 # 一個采樣周期能夠變化的最大最小速度 68 vd = [x[3] - config.max_accel * config.dt, 69 x[3] + config.max_accel * config.dt, 70 x[4] - config.max_dyawrate * config.dt, 71 x[4] + config.max_dyawrate * config.dt] 72 # print(Vs, Vd) 73 74 # 求出兩個速度集合的交集 75 vr = [max(vs[0], vd[0]), min(vs[1], vd[1]), 76 max(vs[2], vd[2]), min(vs[3], vd[3])] 77 78 return vr 79 80 81 def calc_trajectory(x_init, v, w, config): 82 """ 83 預測3秒內的軌跡 84 :param x_init:位置空間 85 :param v:速度 86 :param w:角速度 87 :param config: 88 :return: 每一次采樣更新的軌跡,位置空間垂直堆疊 89 """ 90 x = np.array(x_init) 91 trajectory = np.array(x) 92 time = 0 93 while time <= config.predict_time: 94 x = motion(x, [v, w], config.dt) 95 trajectory = np.vstack((trajectory, x)) # 垂直堆疊,vertical 96 time += config.dt 97 98 # print(trajectory) 99 return trajectory 100 101 102 def calc_to_goal_cost(trajectory, goal, config): 103 """ 104 計算軌跡到目標點的代價 105 :param trajectory:軌跡搜索空間 106 :param goal: 107 :param config: 108 :return: 軌跡到目標點歐式距離 109 """ 110 # calc to goal cost. It is 2D norm. 111 112 dx = goal[0] - trajectory[-1, 0] 113 dy = goal[1] - trajectory[-1, 1] 114 goal_dis = math.sqrt(dx ** 2 + dy ** 2) 115 cost = config.to_goal_cost_gain * goal_dis 116 117 return cost 118 119 120 def calc_obstacle_cost(traj, ob, config): 121 """ 122 計算預測軌跡和障礙物的最小距離,dist(v,w) 123 :param traj: 124 :param ob: 125 :param config: 126 :return: 127 """ 128 # calc obstacle cost inf: collision, 0:free 129 130 min_r = float("inf") # 距離初始化為無窮大 131 132 for ii in range(0, len(traj[:, 1])): 133 for i in range(len(ob[:, 0])): 134 ox = ob[i, 0] 135 oy = ob[i, 1] 136 dx = traj[ii, 0] - ox 137 dy = traj[ii, 1] - oy 138 139 r = math.sqrt(dx ** 2 + dy ** 2) 140 if r <= config.robot_radius: 141 return float("Inf") # collision 142 143 if min_r >= r: 144 min_r = r 145 146 return 1.0 / min_r # 越小越好 147 148 149 def calc_final_input(x, u, vr, config, goal, ob): 150 """ 151 計算采樣空間的評價函數,選擇最合適的那一個作為最終輸入 152 :param x:位置空間 153 :param u:速度空間 154 :param vr:速度空間交集 155 :param config: 156 :param goal:目標位置 157 :param ob:障礙物 158 :return: 159 """ 160 x_init = x[:] 161 min_cost = 10000.0 162 min_u = u 163 164 best_trajectory = np.array([x]) 165 166 trajectory_space = np.array([x]) # 記錄搜索所有采樣的軌跡,用來畫圖 167 168 # evaluate all trajectory with sampled input in dynamic window 169 # v,生成一系列速度,w,生成一系列角速度 170 for v in np.arange(vr[0], vr[1], config.v_reso): 171 for w in np.arange(vr[2], vr[3], config.yawrate_reso): 172 173 trajectory = calc_trajectory(x_init, v, w, config) 174 175 trajectory_space = np.vstack((trajectory_space, trajectory)) 176 177 # calc cost 178 to_goal_cost = calc_to_goal_cost(trajectory, goal, config) 179 speed_cost = config.speed_cost_gain * (config.max_speed - trajectory[-1, 3]) 180 ob_cost = calc_obstacle_cost(trajectory, ob, config) 181 # print(ob_cost) 182 183 # 評價函數多種多樣,看自己選擇 184 # 本文構造的是越小越好 185 final_cost = to_goal_cost + speed_cost + ob_cost 186 187 # search minimum trajectory 188 if min_cost >= final_cost: 189 min_cost = final_cost 190 min_u = [v, w] 191 best_trajectory = trajectory 192 193 # print(min_u) 194 # input() 195 196 return min_u, best_trajectory, trajectory_space 197 198 199 def dwa_control(x, u, config, goal, ob): 200 """ 201 調用前面的幾個函數,生成最合適的速度空間和軌跡搜索空間 202 :param x: 203 :param u: 204 :param config: 205 :param goal: 206 :param ob: 207 :return: 208 """ 209 # Dynamic Window control 210 211 vr = calc_dynamic_window(x, config) 212 213 u, trajectory, trajectory_space = calc_final_input(x, u, vr, config, goal, ob) 214 215 return u, trajectory, trajectory_space 216 217 218 def plot_arrow(x, y, yaw, length=0.5, width=0.1): 219 """ 220 arrow函數繪制箭頭,表示搜索過程中選擇的航向角 221 :param x: 222 :param y: 223 :param yaw:航向角 224 :param length: 225 :param width:參數值為浮點數,代表箭頭尾部的寬度,默認值為0.001 226 :return: 227 length_includes_head:代表箭頭整體長度是否包含箭頭頭部的長度,默認值為False 228 head_width:代表箭頭頭部的寬度,默認值為3*width,即尾部寬度的3倍 229 head_length:代表箭頭頭部的長度度,默認值為1.5*head_width,即頭部寬度的1.5倍 230 shape:參數值為'full'、'left'、'right',表示箭頭的形狀,默認值為'full' 231 overhang:代表箭頭頭部三角形底邊與箭頭尾部直接的夾角關系,通過該參數可改變箭頭的形狀。 232 默認值為0,即頭部為三角形,當該值小於0時,頭部為菱形,當值大於0時,頭部為魚尾狀 233 """ 234 plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw), 235 head_length=1.5 * width, head_width=width) 236 plt.plot(x, y) 237 238 239 def dynamic_obstacle(): 240 """ 241 生成多個障礙物,但是不能生成在起點和終點 242 :return: 243 """ 244 obstacle = np.random.randint(1, 10, size=(10, 2)) 245 return obstacle 246 247 248 def main(): 249 """ 250 主函數 251 :return: 252 """ 253 # print(__file__ + " start!!") 254 # 初始化位置空間 255 x = np.array([0.0, 0.0, math.pi / 2.0, 0.0, 0.0]) 256 goal = np.array([10, 10]) 257 258 u = np.array([0.0, 0.0]) 259 config = Config() 260 trajectory = np.array(x) 261 # obstacle_time = 0 262 263 while True: 264 265 ob = dynamic_obstacle() # 障礙物初始化 266 267 u, best_trajectory, trajectory_space = dwa_control(x, u, config, goal, ob) 268 269 # 據前面計算的結果使曲線前進 270 x = motion(x, u, config.dt) 271 # print(x) 272 273 trajectory = np.vstack((trajectory, x)) # store state history 274 275 # 畫出每次前進的結果 276 draw_dynamic_search(best_trajectory, x, goal, ob, trajectory_space) 277 278 # check goal,小於機器人半徑,則搜索結束 279 if math.sqrt((x[0] - goal[0]) ** 2 + (x[1] - goal[1]) ** 2) <= config.robot_radius: 280 print("Goal!!") 281 282 break 283 284 # obstacle_time += config.dt 285 286 print("Done") 287 288 289 def draw_dynamic_search(best_trajectory, x, goal, ob, trajectory_space): 290 """ 291 畫出動態搜索過程圖 292 :return: 293 """ 294 plt.cla() # 清除上次繪制圖像 295 plt.plot(best_trajectory[:, 0], best_trajectory[:, 1], "-g") 296 297 plt.plot(trajectory_space[:, 0], trajectory_space[:, 1], '-g') 298 299 plt.plot(x[0], x[1], "xr") 300 plt.plot(0, 0, "og") 301 plt.plot(goal[0], goal[1], "ro") 302 plt.plot(ob[:, 0], ob[:, 1], "bo") 303 plot_arrow(x[0], x[1], x[2]) 304 plt.axis("equal") 305 plt.grid(True) 306 plt.pause(0.0001) 307 308 309 def draw_path(trajectory, goal, ob, x): 310 """ 311 畫圖函數 312 :return: 313 """ 314 plt.cla() # 清除上次繪制圖像 315 316 plt.plot(x[0], x[1], "xr") 317 plt.plot(0, 0, "og") 318 plt.plot(goal[0], goal[1], "ro") 319 plt.plot(ob[:, 0], ob[:, 1], "bs") 320 plot_arrow(x[0], x[1], x[2]) 321 plt.axis("equal") 322 plt.grid(True) 323 plt.plot(trajectory[:, 0], trajectory[:, 1], 'r') 324 plt.show() 325 326 327 if __name__ == '__main__': 328 main()