作为一名在机器人路径规划领域工作多年的工程师,我经常需要处理各种动态环境下的导航问题。D算法因其出色的动态重规划能力,成为我工具箱中不可或缺的利器。今天我就来分享一个经过实战检验的D算法Matlab实现,这个版本特别针对教学和快速原型设计进行了优化。
提示:本文代码已在Matlab R2021b及以上版本测试通过,建议配合Robotics System Toolbox使用以获得最佳体验。
D*(Dynamic A*)算法是Anthony Stentz在1994年提出的增量式启发式搜索算法,它解决了传统A*算法在环境变化时需要完全重新计算的痛点。其核心创新在于:
反向搜索机制:从目标点开始向起点搜索,这与A*的正向搜索形成鲜明对比。这种设计使得当机器人位置变化时,可以复用大部分已计算信息。
动态重规划能力:当探测到环境变化(如新增障碍物)时,D只会重新计算受影响节点的代价,而不是整个地图,这使得它的计算效率在动态环境中显著优于A。
双代价函数系统:维护h(x)和k(x)两个代价函数,其中k(x)记录节点最后一次在OPEN列表时的最小h(x)值,这是实现增量更新的关键。
在Matlab中,我们采用矩阵表示二维网格地图。值为1表示障碍物,0表示可通行区域。初始化时需要特别注意:
matlab复制function map = initializeMap(gridSize, obstacleDensity)
% 生成随机障碍物地图
map = zeros(gridSize);
obstacleNum = round(gridSize(1)*gridSize(2)*obstacleDensity);
obstacleIdx = randperm(gridSize(1)*gridSize(2), obstacleNum);
map(obstacleIdx) = 1;
% 确保起点和终点可通行
map(startPoint(1), startPoint(2)) = 0;
map(endPoint(1), endPoint(2)) = 0;
end
注意:实际应用中建议采用更精细的代价地图(costmap),其中每个网格可以存储0-1之间的代价值,表示通过难度。
良好的节点设计是算法高效运行的基础。我们采用结构体封装节点属性:
matlab复制function node = createNode(position, g, h, k, state)
node = struct(...
'position', position, ... % [x,y]坐标
'g', g, ... % 从起点到当前节点的实际代价
'h', h, ... % 当前节点到目标的启发式估计
'k', k, ... % 节点在OPEN列表时的最小h值
'state', state ... % 'OPEN', 'CLOSED' 或 'NEW'
);
end
D*的主循环包含以下几个关键步骤:
matlab复制while ~isempty(OPEN)
current = getMinKNode(OPEN);
if current.position == startPoint
break; % 已到达起点
end
% 处理状态转移
switch current.state
case 'RAISE'
processRaiseState(current);
case 'LOWER'
processLowerState(current);
end
% 更新邻居节点
neighbors = getNeighbors(current, map);
for n = 1:length(neighbors)
updateVertex(neighbors(n));
end
end
以下是经过完整实现的D*算法主函数:
matlab复制function [path, cost] = DStarPlanner(map, start, goal)
% 初始化参数
[rows, cols] = size(map);
OPEN = PriorityQueue(); % 自定义优先队列
% 初始化所有节点
for r = 1:rows
for c = 1:cols
nodes(r,c) = createNode([r,c], inf, heuristic([r,c],goal), inf, 'NEW');
end
end
% 设置目标节点
nodes(goal(1),goal(2)).h = 0;
nodes(goal(1),goal(2)).k = 0;
OPEN.insert(goal, 0);
% 主循环
while ~OPEN.isEmpty()
[currentPos, k_old] = OPEN.pop();
current = nodes(currentPos(1), currentPos(2));
if k_old < current.k
continue;
end
% 获取邻居节点
neighbors = getNeighbors(currentPos, map);
% 处理每个邻居
for i = 1:size(neighbors,1)
neighborPos = neighbors(i,:);
neighbor = nodes(neighborPos(1), neighborPos(2));
% 计算新代价
cost_move = map(currentPos(1), currentPos(2)) + 1; % 基础移动代价
new_h = current.h + cost_move;
if new_h < neighbor.h
neighbor.h = new_h;
neighbor.parent = currentPos;
end
% 更新节点状态
updateVertex(neighborPos);
end
end
% 提取路径
path = extractPath(nodes, start, goal);
cost = nodes(start(1), start(2)).h;
end
matlab复制function h = heuristic(pos1, pos2)
% 欧几里得距离
dx = pos2(1) - pos1(1);
dy = pos2(2) - pos1(2);
h = sqrt(dx^2 + dy^2);
% 实际应用中可根据需要改用曼哈顿距离或对角线距离
% h = abs(dx) + abs(dy); % 曼哈顿距离
% h = max(abs(dx), abs(dy)); % 切比雪夫距离
end
matlab复制function neighbors = getNeighbors(pos, map)
[rows, cols] = size(map);
neighbors = [];
% 8连通邻居
for dr = -1:1
for dc = -1:1
if dr == 0 && dc == 0
continue; % 跳过自身
end
r = pos(1) + dr;
c = pos(2) + dc;
% 检查边界和障碍物
if r >= 1 && r <= rows && c >= 1 && c <= cols && map(r,c) ~= 1
neighbors = [neighbors; [r,c]];
end
end
end
end
D*算法的真正威力体现在动态环境中。以下是处理地图变化的函数:
matlab复制function handleMapChange(changedCells)
for i = 1:size(changedCells,1)
pos = changedCells(i,:);
node = nodes(pos(1), pos(2));
% 更新障碍物信息
if map(pos(1), pos(2)) == 1 % 新障碍物
node.h = inf;
end
% 重新计算k值
if node.state == 'CLOSED'
node.k = node.h;
end
updateVertex(pos);
end
end
matlab复制function updateVertex(pos)
node = nodes(pos(1), pos(2));
if ~isequal(pos, goal) % 非目标节点
% 获取最优邻居
min_h = inf;
neighbors = getNeighbors(pos, map);
for i = 1:size(neighbors,1)
n_pos = neighbors(i,:);
n_node = nodes(n_pos(1), n_pos(2));
cost = n_node.h + map(pos(1), pos(2)) + 1;
if cost < min_h
min_h = cost;
node.parent = n_pos;
end
end
node.h = min_h;
end
% 更新OPEN列表
if node.state == 'OPEN'
OPEN.remove(pos);
end
if node.h ~= node.k
OPEN.insert(pos, node.h);
node.state = 'OPEN';
node.k = node.h;
else
node.state = 'CLOSED';
end
end
Matlab自带的队列性能有限,我们可以实现专门的优先队列:
matlab复制classdef PriorityQueue < handle
properties
elements = [];
priorities = [];
end
methods
function insert(obj, element, priority)
% 二分查找插入位置
idx = find(obj.priorities > priority, 1);
if isempty(idx)
obj.elements = [obj.elements; element];
obj.priorities = [obj.priorities; priority];
else
obj.elements = [obj.elements(1:idx-1,:); element; obj.elements(idx:end,:)];
obj.priorities = [obj.priorities(1:idx-1); priority; obj.priorities(idx:end)];
end
end
function [element, priority] = pop(obj)
if ~isempty(obj.elements)
element = obj.elements(1,:);
priority = obj.priorities(1);
obj.elements(1,:) = [];
obj.priorities(1) = [];
else
element = [];
priority = [];
end
end
function empty = isEmpty(obj)
empty = isempty(obj.elements);
end
end
end
良好的可视化能极大提升开发效率:
matlab复制function visualizePath(map, path, nodes)
figure;
imagesc(map); colormap([1 1 1; 0 0 0]); % 白底黑障碍物
hold on;
% 绘制搜索过程
for r = 1:size(nodes,1)
for c = 1:size(nodes,2)
if nodes(r,c).state == 'OPEN'
plot(c, r, 'yo', 'MarkerSize', 3);
elseif nodes(r,c).state == 'CLOSED'
plot(c, r, 'go', 'MarkerSize', 3);
end
end
end
% 绘制路径
if ~isempty(path)
plot(path(:,2), path(:,1), 'r-', 'LineWidth', 2);
end
axis equal; axis tight;
title('D* Algorithm Path Planning');
end
在动态环境中,有时会出现路径频繁变化的情况。解决方法包括:
对于大型地图,可以采取以下优化措施:
将算法部署到真实机器人时需要注意:
我在实际项目中发现,将D与局部避障算法(如动态窗口法)结合使用效果最佳。D负责全局路径规划,局部算法处理实时避障,这种分层架构既保证了效率又确保了安全性。