FA_规划和控制(PC)-D*规划

FA:formulas and algorithm, PC:planning and control

D*(D-Star)是一种经典的增量式启发式路径规划算法,专门针对动态环境设计 ------ 也就是环境中障碍物可能随时变化的场景,这也是它和 A最核心的区别(A更适合静态环境)。下面我会从基础概念到代码实现,一步步帮你彻底理解 D*。

一、D * 算法核心解析

1. 核心定位与优势

  • 本质:反向搜索的增量式路径规划算法(从终点向起点搜索)
  • 核心思想:先在初始环境中规划一条从起点到终点的路径;当环境变化(发现新障碍物)时,不重新全局搜索,而是仅更新受影响的路径段,大幅提升效率
  • 对比 A*:
特性 A* D*
搜索方向 起点→终点 终点→起点(反向)
环境适应性 静态环境 动态环境(增量更新)
核心优势 静态环境下效率高 动态环境下重规划快
适用场景 一次性路径规划 实时避障、移动机器人导航

2. 核心概念

  • 状态(State) :节点的三种状态(区分待处理节点):
    NEW:新节点,未处理
    OPEN:待评估节点(优先级队列)
    CLOSED:已处理节点
  • 优先级(Priority):每个节点的优先级由两个值决定 (k1, k2),用于优先级队列排序
  • 代价更新(Cost Update):当环境变化时,仅更新受影响节点的代价值,而非重新计算全局代价

3. 典型使用场景

  • 移动机器人自主导航(如扫地机器人、无人机、AGV)
  • 自动驾驶车辆的实时避障(道路突发障碍物)
  • 游戏 AI 的动态路径规划(游戏中敌人 / 道具位置变化)
  • 物流机器人在动态仓库中的路径规划

表格

提出者 核心思想 适用场景 工程推荐 经典应用 学习建议
Anthony Stentz (1994) 反向搜索 + 增量更新 动态/未知环境中的路径规划 优先使用 D Lite NASA 火星车、军用无人平台 先掌握 A,再学 D Lite

二、D * 算法具体操作步骤

D * 的核心流程分为初始规划增量重规划两个阶段,步骤如下:

阶段 1:初始反向搜索(终点→起点)
  • 初始化:
    • 设定终点为起始节点,初始化其代价值 g(goal)=0g(goal) = 0g(goal)=0
    • 将终点加入优先级队列(OPEN)
    • 所有其他节点的代价值初始化为无穷大
  • 反向搜索:
    • 从优先级队列中取出优先级最高的节点 n
    • 遍历节点 n 的所有邻居节点 n'
    • 计算邻居节点的新代价值 gnew=g(n)+cost(n,n′)g_new = g(n) + cost(n, n')gnew=g(n)+cost(n,n′)(cost为两节点间的移动代价)
    • 如果 gnew<g(n′)g_new < g(n')gnew<g(n′),更新 g(n′)g(n')g(n′),并将 n' 加入 / 更新到优先级队列
    • 将节点 n 标记为 CLOSED
    • 重复直到起点被标记为 CLOSED(初始路径规划完成)
  • 生成初始路径:从起点出发,每一步选择相邻节点中代价值最小的节点,直到终点
阶段 2:增量重规划(环境变化时)
  • 检测环境变化:机器人移动过程中发现新障碍物(某条边 / 节点不可通行)
  • 更新代价:将障碍物节点的代价值设为无穷大,并加入优先级队列
  • 重新评估
    • 从优先级队列中取出节点,更新其邻居节点的代价值
    • 仅更新受障碍物影响的节点(增量更新)
  • 重新生成路径:从当前机器人位置出发,基于更新后的代价值,重新选择最优路径

三、D * 算法代码实例

下面实现一个简化版的 D * 算法,包含核心的初始规划和增量重规划功能,并附带动态可视化:

bash 复制代码
import math
import heapq
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

# 定义优先级队列元素(用于D*的优先级排序)
class PriorityQueueItem:
    def __init__(self, priority, node):
        self.priority = priority  # (k1, k2) 优先级
        self.node = node          # 节点坐标 (x, y)
    
    def __lt__(self, other):
        # 优先级排序规则:先比k1,再比k2
        if self.priority[0] != other.priority[0]:
            return self.priority[0] < other.priority[0]
        return self.priority[1] < other.priority[1]

# D*算法核心类
class DStarPlanner:
    def __init__(self, grid_size=0.5, obstacle_radius=1.0):
        self.grid_size = grid_size          # 网格步长
        self.obstacle_radius = obstacle_radius  # 障碍物半径
        self.g = {}  # 节点到终点的实际代价
        self.rhs = {} # 节点的局部一致代价(D*核心)
        self.open_list = []  # 优先级队列
        self.obstacles = set()  # 障碍物集合
        self.goal = None        # 终点
        self.start = None       # 起点
        self.current = None     # 当前位置
        
        # 可视化相关
        self.fig, self.ax = None, None
        self.path_line = None
        self.obstacle_patches = []
        self.node_scatter = None

    # 初始化节点代价
    def init_node(self, node):
        if node not in self.g:
            self.g[node] = float('inf')
            self.rhs[node] = float('inf')

    # 计算两节点间的欧几里得距离
    def distance(self, node1, node2):
        return math.hypot(node1[0]-node2[0], node1[1]-node2[1])

    # 计算节点的优先级
    def calculate_key(self, node):
        min_val = min(self.g[node], self.rhs[node])
        k1 = min_val + self.heuristic(node, self.current)
        k2 = min_val
        return (k1, k2)

    # 启发函数:到当前位置的欧几里得距离
    def heuristic(self, node1, node2):
        if node2 is None:
            return 0
        return self.distance(node1, node2)

    # 向优先级队列添加节点
    def insert(self, node):
        key = self.calculate_key(node)
        heapq.heappush(self.open_list, PriorityQueueItem(key, node))

    # 更新节点的rhs值
    def update_vertex(self, node):
        if node != self.goal:
            min_rhs = float('inf')
            # 遍历所有邻居节点
            for neighbor in self.get_neighbors(node):
                if not self.is_collision(neighbor):
                    min_rhs = min(min_rhs, self.g[neighbor] + self.distance(node, neighbor))
            self.rhs[node] = min_rhs
        
        # 从开放列表中移除旧的节点项
        self.open_list = [item for item in self.open_list if item.node != node]
        heapq.heapify(self.open_list)
        
        # 如果g和rhs不一致,加入开放列表
        if self.g[node] != self.rhs[node]:
            self.insert(node)

    # 获取节点的邻居(8方向)
    def get_neighbors(self, node):
        x, y = node
        directions = [
            (self.grid_size, 0), (-self.grid_size, 0),
            (0, self.grid_size), (0, -self.grid_size),
            (self.grid_size, self.grid_size), (-self.grid_size, -self.grid_size),
            (self.grid_size, -self.grid_size), (-self.grid_size, self.grid_size)
        ]
        neighbors = []
        for dx, dy in directions:
            nx, ny = x + dx, y + dy
            # 限制坐标范围
            if 0 <= nx <= 10 and 0 <= ny <= 10:
                neighbors.append((nx, ny))
        return neighbors

    # 检查节点是否碰撞
    def is_collision(self, node):
        for (ox, oy) in self.obstacles:
            if self.distance(node, (ox, oy)) < self.obstacle_radius:
                return True
        return False

    # 初始化D*算法
    def initialize(self, start, goal, initial_obstacles):
        self.start = start
        self.goal = goal
        self.current = start
        self.obstacles = set(initial_obstacles)
        
        # 初始化代价
        for x in np.arange(0, 10.1, self.grid_size):
            for y in np.arange(0, 10.1, self.grid_size):
                node = (round(x, 1), round(y, 1))
                self.init_node(node)
        
        # 终点的rhs值为0
        self.rhs[self.goal] = 0
        self.insert(self.goal)
        
        # 初始化可视化
        self.init_visualization()

    # 主规划循环(计算最短路径)
    def compute_shortest_path(self):
        while True:
            if not self.open_list:
                break  # 开放列表为空
            
            # 获取优先级最高的节点
            top_item = heapq.heappop(self.open_list)
            current_node = top_item.node
            current_key = top_item.priority
            new_key = self.calculate_key(current_node)
            
            # 如果当前节点的key已过期,跳过
            if current_key > new_key:
                self.insert(current_node)
                continue
            
            # 如果g > rhs(需要更新g值)
            if self.g[current_node] > self.rhs[current_node]:
                self.g[current_node] = self.rhs[current_node]
                # 更新所有邻居
                for neighbor in self.get_neighbors(current_node):
                    self.update_vertex(neighbor)
            # 如果g < rhs(需要提高代价)
            else:
                self.g[current_node] = float('inf')
                self.update_vertex(current_node)
                for neighbor in self.get_neighbors(current_node):
                    self.update_vertex(neighbor)
            
            # 检查起点是否已收敛
            if self.current == self.start and self.g[self.start] == self.rhs[self.start]:
                break

    # 检测新障碍物(模拟机器人感知)
    def detect_new_obstacle(self, obstacle):
        if obstacle not in self.obstacles:
            self.obstacles.add(obstacle)
            # 更新受影响的节点
            for x in np.arange(obstacle[0]-2, obstacle[0]+2.1, self.grid_size):
                for y in np.arange(obstacle[1]-2, obstacle[1]+2.1, self.grid_size):
                    node = (round(x, 1), round(y, 1))
                    if 0 <= x <= 10 and 0 <= y <= 10:
                        self.update_vertex(node)
            # 重新规划路径
            self.compute_shortest_path()
            # 更新可视化
            self.add_obstacle_visualization(obstacle)

    # 生成当前路径
    def get_path(self):
        path = [self.current]
        current_node = self.current
        
        while current_node != self.goal:
            min_cost = float('inf')
            next_node = None
            # 选择代价最小的邻居
            for neighbor in self.get_neighbors(current_node):
                if not self.is_collision(neighbor):
                    cost = self.g[neighbor] + self.distance(current_node, neighbor)
                    if cost < min_cost:
                        min_cost = cost
                        next_node = neighbor
            
            if next_node is None or next_node in path:
                break  # 无可行路径
            
            path.append(next_node)
            current_node = next_node
        
        return path

    # 初始化可视化
    def init_visualization(self):
        plt.ion()
        self.fig, self.ax = plt.subplots(figsize=(8, 8))
        
        # 绘制初始障碍物
        for (ox, oy) in self.obstacles:
            patch = patches.Circle((ox, oy), self.obstacle_radius, color='r', alpha=0.3)
            self.ax.add_patch(patch)
            self.obstacle_patches.append(patch)
        
        # 绘制起点和终点
        self.ax.scatter(self.start[0], self.start[1], color='green', s=100, label='Start')
        self.ax.scatter(self.goal[0], self.goal[1], color='red', s=100, label='Goal')
        
        # 初始化路径线条
        self.path_line = self.ax.plot([], [], 'b-', linewidth=2, label='D* Path')[0]
        self.node_scatter = self.ax.scatter([], [], color='cyan', s=20, alpha=0.5, label='Explored Nodes')
        
        # 图表设置
        self.ax.set_xlabel('X (m)')
        self.ax.set_ylabel('Y (m)')
        self.ax.set_title('D* Path Planning (Dynamic Environment)')
        self.ax.legend(loc='upper left')
        self.ax.grid(True)
        self.ax.axis('equal')
        self.ax.set_xlim(0, 10)
        self.ax.set_ylim(0, 10)
        
        plt.draw()
        plt.pause(0.1)

    # 添加新障碍物的可视化
    def add_obstacle_visualization(self, obstacle):
        patch = patches.Circle((obstacle[0], obstacle[1]), self.obstacle_radius, color='r', alpha=0.3)
        self.ax.add_patch(patch)
        self.obstacle_patches.append(patch)
        plt.draw()
        plt.pause(0.5)

    # 更新路径可视化
    def update_visualization(self, path):
        if path:
            path_x = [p[0] for p in path]
            path_y = [p[1] for p in path]
            self.path_line.set_data(path_x, path_y)
        
        # 绘制探索过的节点
        explored_x = [node[0] for node in self.g if self.g[node] < float('inf')]
        explored_y = [node[1] for node in self.g if self.g[node] < float('inf')]
        self.node_scatter.set_offsets(np.c_[explored_x, explored_y])
        
        plt.draw()
        plt.pause(0.1)

# -------------------------- 测试代码 --------------------------
if __name__ == "__main__":
    # 1. 初始化D*规划器
    dstar = DStarPlanner(grid_size=0.5, obstacle_radius=1.0)
    
    # 2. 设置起点、终点和初始障碍物
    start = (0.0, 0.0)
    goal = (8.0, 8.0)
    initial_obstacles = [(3.0, 3.0), (5.0, 5.0)]
    
    # 3. 初始化并执行初始规划
    dstar.initialize(start, goal, initial_obstacles)
    dstar.compute_shortest_path()
    
    # 4. 获取初始路径并可视化
    path = dstar.get_path()
    dstar.update_visualization(path)
    print("初始路径长度:", len(path))
    print("初始路径前5个节点:", path[:5])
    
    # 5. 模拟动态环境:发现新障碍物
    print("\n检测到新障碍物 (7.0, 3.0),开始重规划...")
    dstar.detect_new_obstacle((7.0, 3.0))
    
    # 6. 获取重规划后的路径并可视化
    new_path = dstar.get_path()
    dstar.update_visualization(new_path)
    print("重规划后路径长度:", len(new_path))
    print("重规划后路径前5个节点:", new_path[:5])
    
    # 保持可视化窗口
    plt.ioff()
    plt.show()

代码核心说明

  1. 核心数据结构
  • ggg:节点到终点的实际代价(全局代价)
  • rhsrhsrhs:节点的局部一致代价(D * 的核心,用于增量更新)
  • 优先级队列:基于(k1, k2)排序,保证每次处理最优节点
  1. 关键方法
  • update_vertex():更新节点的局部代价,是增量更新的核心
  • compute_shortest_path():主规划循环,计算最短路径
  • detect_new_obstacle():模拟动态环境,检测新障碍物并触发重规划
  • get_path():从当前位置生成最优路径
  1. 可视化效果
  • 初始路径:蓝色线条(从起点到终点)
  • 障碍物:红色半透明圆形
  • 探索节点:青色小点
  • 动态效果:添加新障碍物后,路径会自动绕开

四、运行代码的前置条件

  1. 安装依赖库:
bash 复制代码
pip install numpy matplotlib
  1. 运行环境:Python 3.6+
  2. 无需额外配置,直接运行即可看到动态效果

五、结束语

  1. D * 核心特性
  • 反向搜索(终点→起点),增量式更新,适合动态环境
  • 通过g和rhs两个代价函数实现局部更新,避免全局重算
  • 优先级队列保证每次处理最关键的节点,效率远高于重新规划
  1. 使用要点
  • 适合机器人导航、自动驾驶等需要实时避障的场景
  • 核心优势是环境变化后的重规划效率极高
  • 实现关键是正确维护g/rhs代价和优先级队列
  1. 与 A * 的选择原则
  • 静态环境选 A*(实现简单、效率高)
  • 动态环境选 D*(增量更新,重规划快)
相关推荐
冰西瓜6002 小时前
深度学习的数学原理(五)—— 非线性与激活函数
人工智能·深度学习
love530love2 小时前
【OpenClaw 本地实战 Ep.2】零代码对接:使用交互式向导快速连接本地 LM Studio 用 CUDA GPU 推理
人工智能·windows·gpu·cuda·ollama·lm studio·openclaw
2401_828890642 小时前
实现变分自编码器 VAE- MNIST 数据集
人工智能·python·深度学习·cnn·transformer
DevilSeagull2 小时前
大语言模型完全指南
人工智能·语言模型·自然语言处理
予枫的编程笔记2 小时前
【YF技术周报 Vol.01】OpenAI 国会指控 DeepSeek,字节发布 Seedance 2.0,Java 26 预览版来了
java·人工智能·openai·后端开发·ai技术·spring ai·deepseek
Faker66363aaa2 小时前
基于Faster-RCNN_C4的绝缘子缺陷检测与分类实现
人工智能·分类·数据挖掘
草莓熊Lotso2 小时前
Linux 磁盘基础:从物理结构到 CHS/LBA 寻址,吃透数据存储底层逻辑
linux·运维·服务器·c++·人工智能
We་ct2 小时前
LeetCode 61. 旋转链表:题解+思路拆解
前端·算法·leetcode·链表·typescript
Felven2 小时前
D. Find the Different Ones!
算法