四旋翼无人机动力学与控制(3)-路径规划


        本篇是这个小项目的最后一篇,仓库地址:四旋翼无人机仿真建模: 本仓库实现了四旋翼无人机的动力学仿真建模,并在此基础上完成了控制器的设计,包括串级PID、LQR、MPC等,实现了位置跟踪和路径规划。

        主要是做了一个路径规划用于验证之前的控制器设计是否能够跟踪上规划的路径进而评判控制器的优劣,采用APF-RRT路径规划算法,话不多说上代码:

  1. # Author:gyx
  2. # Usage: plan
  3. # Last change: 2024-7-20
  4. import numpy as np
  5. import matplotlib.pyplot as plt
  6. import random
  7. from scipy.interpolate import CubicSpline
  8. from scipy.interpolate import BSpline
  9. from scipy.ndimage import gaussian_filter1d
  10. class APF_RRTStar:
  11. def __init__(self, start, goal, obstacle_list, x_limits, y_limits, robot_radius=0.5):
  12. self.start = start
  13. self.goal = goal
  14. self.obstacle_list = obstacle_list
  15. self.x_limits = x_limits
  16. self.y_limits = y_limits
  17. self.robot_radius = robot_radius # Added robot radius
  18. self.tree_start = [start]
  19. self.tree_goal = [goal]
  20. self.parent_start = {start: None}
  21. self.parent_goal = {goal: None}
  22. self.goal_reached = False
  23. self.step_size = 0.8
  24. self.max_iterations = 10000 # Reasonable iteration limit
  25. self.connection_point = None # To store the connection point of both trees
  26. def distance(self, point1, point2):
  27. return np.linalg.norm(np.array(point1) - np.array(point2))
  28. def is_collision_free(self, point):
  29. for (ox, oy, radius) in self.obstacle_list:
  30. if self.distance((ox, oy), point) <= (radius + self.robot_radius):
  31. return False
  32. return True
  33. def get_random_point(self, goal_bias):
  34. if random.random() < goal_bias:
  35. return self.goal
  36. while True:
  37. point = (random.uniform(self.x_limits[0], self.x_limits[1]),
  38. random.uniform(self.y_limits[0], self.y_limits[1]))
  39. if self.is_collision_free(point):
  40. return point
  41. def get_nearest_node(self, point, tree):
  42. distances = [self.distance(node, point) for node in tree]
  43. nearest_index = np.argmin(distances)
  44. return tree[nearest_index]
  45. def generate_new_node(self, from_node, to_point):
  46. direction = np.array(to_point) - np.array(from_node)
  47. length = np.linalg.norm(direction)
  48. if length == 0:
  49. return None
  50. direction = direction / length
  51. new_node = tuple(np.array(from_node) + self.step_size * direction)
  52. if self.is_collision_free(new_node):
  53. return new_node
  54. return None
  55. def connect_trees(self, node_from_start, node_from_goal):
  56. return self.distance(node_from_start, node_from_goal) < self.step_size
  57. def potential_field(self, point):
  58. attractive_force = np.array(self.goal) - np.array(point)
  59. repulsive_force = np.zeros(2)
  60. for (ox, oy, radius) in self.obstacle_list:
  61. obstacle_vector = np.array(point) - np.array((ox, oy))
  62. distance = np.linalg.norm(obstacle_vector)
  63. if distance <= (radius + self.robot_radius):
  64. repulsive_force += obstacle_vector / (distance ** 2)
  65. return attractive_force + repulsive_force
  66. def search_path(self, goal_bias=0.1):
  67. iterations = 0
  68. while not self.goal_reached and iterations < self.max_iterations:
  69. iterations += 1
  70. # Extend the start tree
  71. random_point_start = self.get_random_point(goal_bias)
  72. nearest_node_start = self.get_nearest_node(random_point_start, self.tree_start)
  73. new_node_start = self.generate_new_node(nearest_node_start, random_point_start)
  74. if new_node_start and self.is_collision_free(new_node_start):
  75. self.tree_start.append(new_node_start)
  76. self.parent_start[new_node_start] = nearest_node_start
  77. nearest_node_goal = self.get_nearest_node(new_node_start, self.tree_goal)
  78. if self.connect_trees(new_node_start, nearest_node_goal):
  79. self.goal_reached = True
  80. self.connection_point = (new_node_start, nearest_node_goal)
  81. # Extend the goal tree
  82. random_point_goal = self.get_random_point(goal_bias)
  83. nearest_node_goal = self.get_nearest_node(random_point_goal, self.tree_goal)
  84. new_node_goal = self.generate_new_node(nearest_node_goal, random_point_goal)
  85. if new_node_goal and self.is_collision_free(new_node_goal):
  86. self.tree_goal.append(new_node_goal)
  87. self.parent_goal[new_node_goal] = nearest_node_goal
  88. nearest_node_start = self.get_nearest_node(new_node_goal, self.tree_start)
  89. if self.connect_trees(new_node_goal, nearest_node_start):
  90. self.goal_reached = True
  91. self.connection_point = (nearest_node_start, new_node_goal)
  92. if not self.goal_reached:
  93. print("Failed to find a path within the iteration limit.")
  94. return []
  95. # Construct the path
  96. path_start = []
  97. path_goal = []
  98. node = self.connection_point[0]
  99. while node is not None:
  100. path_start.append(node)
  101. node = self.parent_start[node]
  102. node = self.connection_point[1]
  103. while node is not None:
  104. path_goal.append(node)
  105. node = self.parent_goal[node]
  106. path_start.reverse()
  107. path = [self.start]+path_start + path_goal+[self.goal]
  108. return path
  109. def smooth_path(self, path):
  110. if len(path) < 3: # No need to smooth if path has less than 3 points
  111. return path
  112. x = [point[0] for point in path]
  113. y = [point[1] for point in path]
  114. # 调整采样点数量
  115. num_points = len(path) * 5 # 增加采样点数量
  116. t = np.linspace(0, len(path) - 1, len(path))
  117. t_new = np.linspace(0, len(path) - 1, num=num_points)
  118. # 使用三次样条进行插值
  119. cs_x = CubicSpline(t, x)
  120. cs_y = CubicSpline(t, y)
  121. smooth_x = cs_x(t_new)
  122. smooth_y = cs_y(t_new)
  123. # 使用高斯滤波器平滑路径
  124. smooth_x = gaussian_filter1d(smooth_x, sigma=10)
  125. smooth_y = gaussian_filter1d(smooth_y, sigma=10)
  126. smooth_path = list(zip(smooth_x, smooth_y))
  127. return smooth_path
  128. def draw_graph(self, path=None, smooth_path=None):
  129. plt.figure()
  130. for (ox, oy, radius) in self.obstacle_list:
  131. circle = plt.Circle((ox, oy), radius, color='r', alpha=0.5)
  132. plt.gca().add_patch(circle)
  133. for node in self.tree_start:
  134. if self.parent_start[node] is not None:
  135. plt.plot([node[0], self.parent_start[node][0]], [node[1], self.parent_start[node][1]], "g-")
  136. for node in self.tree_goal:
  137. if self.parent_goal[node] is not None:
  138. plt.plot([node[0], self.parent_goal[node][0]], [node[1], self.parent_goal[node][1]], "b-")
  139. if path:
  140. plt.plot([x for (x, y) in path], [y for (x, y) in path], '-o', color='orange', label='Original Path')
  141. if smooth_path:
  142. plt.plot([x for (x, y) in smooth_path], [y for (x, y) in smooth_path], '-', color='purple',
  143. label='Smoothed Path')
  144. plt.plot(self.start[0], self.start[1], "bo")
  145. plt.plot(self.goal[0], self.goal[1], "ro")
  146. plt.xlim(self.x_limits)
  147. plt.ylim(self.y_limits)
  148. plt.grid(True)
  149. plt.legend()
  150. # plt.show()
  151. def main():
  152. start = (0, 0)
  153. goal = (10, 10)
  154. obstacle_list = [
  155. (5, 5, 0.2),
  156. (7, 6, 1),
  157. (2, 3, 1.2),
  158. (4, 6, 0.4),
  159. (8, 2, 0.5),
  160. (2, 8, 0.4)
  161. ]
  162. x_limits = (0, 10)
  163. y_limits = (0, 10)
  164. robot_radius = 0.25 # Set the robot radius
  165. apf_rrt_star = APF_RRTStar(start, goal, obstacle_list, x_limits, y_limits, robot_radius)
  166. path = apf_rrt_star.search_path()
  167. if path:
  168. smooth_path = apf_rrt_star.smooth_path(path)
  169. apf_rrt_star.draw_graph(path, smooth_path)
  170. print(smooth_path)
  171. else:
  172. print("No path found.")
  173. class RTT_planer:
  174. def __init__(self, start, goal, obstacle_list, num_trials=10):
  175. self.start = start
  176. self.goal = goal
  177. self.obstacle_list = obstacle_list
  178. self.num_trials = num_trials # 进行多次规划的次数
  179. def way_plan(self):
  180. x_limits = (0, 10)
  181. y_limits = (0, 10)
  182. robot_radius = 0.5 # Set the robot radius
  183. best_path = None
  184. best_smooth_path = None
  185. best_rrt = None
  186. min_length = float('inf') # 初始化为正无穷
  187. for _ in range(self.num_trials):
  188. apf_rrt_star = APF_RRTStar(self.start, self.goal, self.obstacle_list, x_limits, y_limits, robot_radius)
  189. path = apf_rrt_star.search_path()
  190. if path:
  191. smooth_path = apf_rrt_star.smooth_path(path)
  192. # 计算路径的总长度
  193. length = sum(np.linalg.norm(np.array(smooth_path[i]) - np.array(smooth_path[i+1]))
  194. for i in range(len(smooth_path) - 1))
  195. # 比较并更新最优路径
  196. if length < min_length:
  197. min_length = length
  198. best_path = path
  199. best_smooth_path = smooth_path
  200. best_rrt = apf_rrt_star
  201. if best_path:
  202. # apf_rrt_star = APF_RRTStar(self.start, self.goal, self.obstacle_list, x_limits, y_limits, robot_radius)
  203. best_rrt.draw_graph(best_path, best_smooth_path)
  204. return best_path
  205. else:
  206. print("No path found.")
  207. return None
  208. if __name__ == '__main__':
  209. planner = RTT_planer(start=(0, 0), goal=(10, 10), obstacle_list=[
  210. (5, 3, 0.6),
  211. (7, 6, 1),
  212. (2, 3, 1.2),
  213. (4, 6, 0.4),
  214. (8, 2, 0.5),
  215. (2, 8, 0.4),
  216. (8,9,0.5)
  217. ], num_trials=10) # 进行10次规划
  218. best_path = planner.way_plan()
  219. if best_path:
  220. print("Best path found:", best_path)
  221. print(len(best_path))
  222. plt.show()
  223. # if __name__ == '__main__':
  224. # main()
  225. # plt.show()

最后是尝试了一下结合模糊控制进行跟踪,具体代码在仓库中,这里就不贴出来了,效果如下:

            </div><div data-report-view="{&quot;mod&quot;:&quot;1585297308_001&quot;,&quot;spm&quot;:&quot;1001.2101.3001.6548&quot;,&quot;dest&quot;:&quot;https://blog.csdn.net/perfectdisaster/article/details/144170119&quot;,&quot;extend1&quot;:&quot;pc&quot;,&quot;ab&quot;:&quot;new&quot;}"><div></div></div>
    </div>

文章作者: 郭昱鑫
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 郭昱鑫 !
  目录