动态窗口法

动态窗口法

运动模型建立

假设机器人的平移速度和旋转角速度可以独立地进行控制。如下图使用角速度ω(t)、速度v(t)表示在t时刻的运动状态。

car

除此之外,还需要引入坐标x(t)y(t)θ(t)分别表示t时刻的坐标(x,y)以及机器人前进方向与水平方向的夹角。可以使用一组状态变量表示

X(t)=[x(t)y(t)v(t)θ(t)ω(t)]T

另外,还需要设置系统输入,由于动态窗口法中主要使用运动模型预测轨迹,所以设置输入为

U(n)=[vp(n)ωp(n)]T

假设机器人的速度、加速度、角速度、角加速度均有最大值,使用下面一组变量约束

[vmaxωmaxamaxαmaxratiovratioω]T

其中ratiov,ratioω分别表示速度分辨率和角速度分辨率。

假设初始时刻X(tn)已知,令dt=tn+1tn即采样时间,输入为U(n)

x(n)=x(tn)+0×y(tn)+0×v(tn)+0×θ(tn)+0×ω(tn)+dt×cos(θ(tn))×vp(n)+0×ωp(n)y(n)=0×x(tn)+y(tn)+0×v(tn)+0×θ(tn)+0×ω(tn)+dt×sin(θ(tn))×vp(n)+0×ωp(n)θ(n)=0×x(tn)+0×y(tn)+0×v(n)+1×θ(t)+0×ω(t)+0×vp(n)+dt×ωp(t)v(n)=0×x(tn)+0×y(tn)+0×v(n)+0×θ(t)+0×ω(t)+1×vp(n)+0×ωp(t)ω(n)=0×x(tn)+0×y(tn)+0×v(n)+0×θ(t)+0×ω(t)+0×vp(n)+1×ωp(t)

简化为

X(n)=FX(t)+BU(n)

其中

F=[1000001000000000001000000],B=[dt×cos(θ(tn))0dt×sin(θ(tn))0100dt01]

动态窗口设置

​ 对于全向移动的机器人,其动态窗口应为下一采样时间dt内所有可能的v(t),ω(t)构成的运动轨迹。采用vmax,ωmax约束机器人状态,即v(t)[0,vmax],ω(t)[ωmax,ωmax]

​ 假设tn时刻X(tn)已知,则tn+1时刻

v(tn+1)[v(tn)dtamax,v(tn)+dtamax]ω(tn+1)[ω(tn)dtαmax,ω(tn)+dtαmax]

再由v(t)[0,vmax],ω(t)[ωmax,ωmax]进行约束,即可得到tn+1时刻的动态窗口。由于任意两个速度之间有无数种取值,所以引入ratiov,ratioω进行有限次数的采样,不同ratiov,ratioω下采样效果如下图

image-20240224134725897

​ 圆圈为机器人位置,绿色线条为预测轨迹;ratiov影响前进方向上预测轨迹的密集程度,ratioω影响以机器人为中心扇形区域的预测轨迹的密集程度。

​ 如果只对未来dt内进行轨迹预测,由于预测范围过小可能无法完成避障要求。

image-20240224134833565

(a)中由于预测轨迹范围过小无法成功避障,对比(b)(c)两图,较大的预测范围可以在避障时有更安全的距离。

动态窗口评价函数

采样的速度组中,有若干组采样轨迹是可行的,因此需要采用评价函数的方式对每条轨迹的预测终点进行评估。在传统动态窗口法中,评价函数只考虑三个指标,分别是方位角评价函数、空隙评价函数以及速度评价函数。

G(ν,ω)=αnormal(heading(ν,ω))+βnormal(dist(ν,ω))+γnormal(vel(ν,ω))

方位角评价

方位是衡量机器人当前方向与目标方向对准的程度,heading函数输出的最终结果为180θ

image-20240224135121287

空隙评价

​ 空隙评价可以理解为机器人距离最近障碍物的距离。需要注意的是,如果距离障碍物距离非常远或周围没有障碍物时,应设置一个较大常数约束空隙评价。

速度评价

​ 对机器人移动速度绝对值的评价。

最终评价

​ 分别对上述三个评价进行归一化,使用α,β,γ进行加权计算得到最终评价。从所有预测轨迹中找到最终评价最大值对应的vp,ωp最为下一时刻输入。为了便于处理信息,设置一组向量存储评价函数加权信息及预测时间

Eval=[αβγtp]

不足与改进

终点靠近障碍物

​ 在临近终点时,由于终点距离障碍过近导致空隙评价得分较低,进而导致选取的预测轨迹已经越过终点,使得预测轨迹无法准确的聚焦在终点上,最后由于评价函数限制使得机器人在障碍物和终点附近运动,无法收敛到终点。

image-20240224135449683

​ 考虑到现有DWA评价函数指标只包括方位角,空隙,速度三个指标,导致轨迹无法保证全局最优性。在原有评价函数引入A算法的启发式函数,采用欧式距离计算。

G(ν,ω)=αnormal(heading)+βnormal(dist)+γnormal(vel)+ζnormal(astar)astar=g(n)+h(n)

Eval修改为

Eval=[αβγζtp]

具体效果如下图

image-20240224135630814

应用代码思路解析

该文档只对MATLAB代码进行解释,不对DWA原理进行详细阐述,具体原理请详细查看另一文件。

代码框架

│  Dwa_main.m
│  README.en.md
├─DwaFunction
└─InitializeData
  1. main.m运行DWA主要过程。
  2. README.en.md为中文讲解文档。
  3. DwaFunction存储DWA用到的函数。
  4. InitializeData存储栅格地图、运动障碍等信息。

初始化过程

%% 初始化参数设置
% 设置仿真步长
global dt;
dt = 0.1;
% 设置最长仿真时间200s
% 超出仿真时间路径依旧不收敛记为规划失败
End_Time = 200;
% 循环次数
cycles = End_Time/dt;
% 起点下标[y,x]
startpossub = [20,36];
% 起点索引
startposind = sub2ind([42,42],startpossub(1),startpossub(2));
% 目标点下标[y,x]
goal = [15,5];
% 目标点索引
goalposind = sub2ind([42,42],goal(1),goal(2));
% 初始化参数
[field,obstacle,RM_parameters,Lidar] = DwaInitialization(field,startpossub);

仿真步长dt设置为全局变量,仿真时间(EndTime)设置为200s超出时间未找到终点视为规划失败,cycles为仿真的循环次数。

起始点可自行设置,startposind,goalposind为一维索引,这里对应42×42主要是因为后续对40×40地图进行边缘扩充。

DwaInitialization

function [field,obstacle,RM_parameters,Lidar,IMM] = DwaInitialization(field,startpossub)
global dt;
% 填充边缘
field = padarray(field, [1 1], 1);
% 获取障碍二维坐标
obstacle = GetAllobstacle(field);
%% 机器人模型参数
% 状态变量[x,y,v,theta,w]
RM_parameters.x = [startpossub(1);startpossub(2);0;0;toRadian(0)];
% 机器人运动学限制:最高速度[m/s],最高旋转速度[rad/s],加速度[m/ss],旋转加速度[rad/ss],速度分辨率[m/s],转速分辨率[rad/s]
% RM_parameters.KinematicLimit = [2.3,toRadian(100.0),1.0,toRadian(80.0),0.1,toRadian(5)];
RM_parameters.KinematicLimit = [1.5,toRadian(100.0),1.0,toRadian(80.0),0.1,toRadian(5)];
% 动态窗口
RM_parameters.DynamicWin = [];
% 评价函数参数 [heading,dist,velocity,predictDT]
RM_parameters.evalParam=[0.08,0.3,0.12,0.1,20*dt];
% 记录一次评价参数
RM_parameters.evalDB = [];
% 记录一次动态窗口轨迹
RM_parameters.trajDB = [];
% 机器人移动状态汇总
RM_parameters.savex = [];
%% Lidar参数
% 雷达半径
Lidar.r = 10;
% 雷达全局坐标
Lidar.x = RM_parameters.x(1);
Lidar.y = RM_parameters.x(2);
% 雷达数据(极坐标格式)
Lidar.Data = [];
% 局部障碍位置
Lidar.PartObstacle = [];
% 最小距离索引
Lidar.indmin = 0;
% 检测动态点标志
Lidar.dynamic = 0;
end

边缘填充目的是对原地图增加边框限制,防止跑出栅格地图外。

padding

获取栅格地图内所有障碍的二维索引保存至obstacle,用于局部窗口的筛选和雷达的仿真。

预处理

%% 预处理
% 速度色阶
colorv = cmap(1+ceil(255*RM_parameters.x(3)/RM_parameters.KinematicLimit(1)),:);
Fig=figure;
filename = 'dynamic2.gif';      % 输出路径+保存的文件名.gif
% 移动障碍索引
j = 1;
  1. RMparameters.x(3)为当前线速度。
  2. RMparameters.KinematicLimit(1)为动力学最大线速度。
  3. ceil(255RMparameters.x(3)/RMparameters.KinematicLimit(1))+1相当于将当前速度映射到0255的色阶上。

colorv用于存储每次仿真速度对应的RGB信息。

j为移动障碍位置信信息的索引。

Lidar模拟

DWA过程

image-20240210085326366

动态窗口计算

%{
动态窗口计算
输入:当前机器人状态,机器人动力学限制
输出:动态窗口[vmin,vmax,wmin,wmax](下一个步长的所有可能速度和角速度范围)
%}
function DynamicWin=CalcDynamicWindow(x,KinematicLimit)  
% 仿真步长
global dt;  
% 参数重命名
v = x(3);
w = x(5);
vmax = KinematicLimit(1);
wmax = KinematicLimit(2);
a = KinematicLimit(3);
wa = KinematicLimit(4);
%% 计算动态窗口
% 机器人速度和角速度限制
Vs=[0 vmax -wmax wmax];  
% 根据当前速度以及加速度限制计算的动态窗口  
Vd=[v - a*dt v+a*dt w-wa*dt w+wa*dt];  
% 最终的Dynamic Window  
Vtmp=[Vs;Vd];  
DynamicWin = [max(Vtmp(:,1)) min(Vtmp(:,2)) max(Vtmp(:,3)) min(Vtmp(:,4))];  
end

Lidar局部区域检索

%{
仿真Lidar获取周围无遮挡障碍信息
输入:所有障碍坐标,雷达参数,动态障碍信息
输出:Lidar参数
%}
function Lidar = LidarSearch(obstacle,Lidar,dynamicOb)
% 清除标志
Lidar.dynamic = 0;
%% 重命名坐标
x = Lidar.x;
y = Lidar.y;
r = Lidar.r;
% 记录局部无遮挡障碍(局部极坐标形式)
partObstacle = [];
% 获取局部障碍
for i = floor(x-r):1:ceil(x+r)
    for j = floor(y-r):1:ceil(y+r)
        ind = find(obstacle(:,1) == i&obstacle(:,2) == j);
        if ~isempty(ind)
            partObstacle = [partObstacle;obstacle(ind,:)+0.5];
        end
    end
end
% 添加动态点
if sqrt((dynamicOb(1) - x)^2+(dynamicOb(2) - y)^2) < r
    Lidar.dynamic = 1;
    partObstacle = [partObstacle;dynamicOb'];
end
[p,Lidar.PartObstacle] = obstacleDetection(x,y,partObstacle);
Lidar.Data = p ;
end

floor为向下取整,ceil为向上取整

for i = floor(x-r):1:ceil(x+r)
    for j = floor(y-r):1:ceil(y+r)
        ind = find(obstacle(:,1) == i&obstacle(:,2) == j);
        if ~isempty(ind)
            partObstacle = [partObstacle;obstacle(ind,:)+0.5];
        end
    end
end

上面部分代码实际选取的是[xrx+r],[yr,y+r]的矩形区域障碍。

% 添加动态点
if sqrt((dynamicOb(1) - x)^2+(dynamicOb(2) - y)^2) < r
    Lidar.dynamic = 1;
    partObstacle = [partObstacle;dynamicOb'];
end

动态点运行到以雷达坐标为中心、检测半径的圆形范围内时,将动态点加入局部障碍。

obstacleDetection
%{
仿真Lidar检索局部范围内无遮挡障碍
输入:Lidar x坐标,Lidar y坐标,局部障碍集合
输出:无遮挡的障碍信息(极坐标格式),无遮挡的障碍信息(全局二维格式)%}
function [polar,partob] = obstacleDetection(x,y,ob,r)
polarsum = [];
partob = [];
replace = 0;
% 遍历局部区域障碍筛选满足条件障碍
for i = 1:size(ob,1)
    % 计算新障碍的极坐标模长(长度)
    L = sqrt((x - ob(i,1))^2+(y - ob(i,2))^2);
    % 在雷达检测范围内才会进行测距
    if L<=r
        % 计算新障碍的极坐标角度(弧度制)
        angle = atan2(ob(i,1) - x,ob(i,2) - y);
        % 使用新障碍信息与所有已录入障碍信息进行比较
        j = 1;
        while j <= size(polarsum,1)
            % 由于该循环内有对polarsum的删减,所以需要注意数组访问越界问题
            if j > size(polarsum,1)
                break;
            end
            % 如果已录入障碍与新障碍的角度相近
            if abs(polarsum(j,2) - angle) <= atan(0.75/polarsum(j,1))
                % 新增障碍的模长比已录入障碍的模长短则进行替换
                if L < polarsum(j,1)
                    % 如果新障碍信息已替换,删除当前障碍信息
                    if replace == 1
                        partob(j,:) = [];
                        polarsum(j,:)=[];
                        % 抵消当前索引增加
                        j = j - 1;
                    else % 新障碍信息还未录入
                        % 将已录入障碍信息替换为新障碍信息
                        replace = 1;
                        polarsum(j,:) = [L,angle];
                        partob(j,:) = [ob(i,:)];
                    end
                else % 新增障碍的模长比已录入障碍的模长更长,则不在录入,仅进行删减
                    replace = 1;
                end
            end
            j = j + 1;
        end
        % 如果没有发生障碍的删减,将新障碍信息录入局部障碍
        if ~replace
            polarsum = [polarsum;[L,angle]];
            partob = [partob;ob(i,:)];
        end
        replace = 0;
    end
end
    polar = polarsum;
end

该函数主要解决的问题是:如何选取圆形区域内无遮挡的障碍信息?

传入该函数的ob为边长为r的矩形区域内的所有障碍位置信息,后续通过

    % 计算新障碍的极坐标模长(长度)
    L = sqrt((x - ob(i,1))^2+(y - ob(i,2))^2);
    % 在雷达检测范围内才会进行测距
    if L<=r
    ...
    END

筛选圆形区域内障碍,再进行遮挡区域去除。

Lidar

例如上图中绿色直虚线指向的障碍实际应该是测不到的(由于有更近的障碍物遮挡),所以筛选的主要思路是排除指定角度范围内,距离更远的障碍,因为距离更近的障碍会遮挡远距离的障碍。

image-20240211160854143

角度相邻的范围θ,可以由上述示意图计算。其中红色虚线部分在θ范围内,且被该角度范围内更近的障碍物遮挡,所以会排除红色虚线指向的障碍。地图中的栅格尺寸为1×1,所以θ=arctan(2/2/L),其中L为到该障碍的距离。

            % 如果已录入障碍与新障碍的角度相近
            if abs(polarsum(j,2) - angle) <= atan(0.75/polarsum(j,1))
                % 新增障碍的模长比已录入障碍的模长短则进行替换
                if L < polarsum(j,1)
                    % 如果新障碍信息已替换,删除当前障碍信息
                    if replace == 1
                        partob(j,:) = [];
                        polarsum(j,:)=[];
                        % 抵消当前索引增加
                        j = j - 1;
                    else % 新障碍信息还未录入
                        % 将已录入障碍信息替换为新障碍信息
                        replace = 1;
                        polarsum(j,:) = [L,angle];
                        partob(j,:) = [ob(i,:)];
                    end
                else % 新增障碍的模长比已录入障碍的模长更长,则不在录入,仅进行删减
                    replace = 1;
                end
            end

DWA 过程

另一文档中有原理说明不在赘述。

绘图部分

    % 计算速度色阶
    colorv = [colorv;cmap(1+ceil(254*RM_parameters.x(3)/RM_parameters.KinematicLimit(1)),:)];

将每次循环计算的速度映射到0255的色阶上,并保存到colorv中。

% 绘制速度映射的色阶轨迹
if USE_Traj
    for c =1:size(RM_parameters.savex,2)
        scatter(RM_parameters.savex(2,c),RM_parameters.savex(1,c),6,...
            'MarkerEdgeColor',colorv(c,:),...
            'MarkerFaceColor',colorv(c,:)...
            );hold on;
    end
end

RMparameters结构体中,找出指定的位置信息,并配合该速度的色阶信息,绘制圆点。

    % 动态窗口轨迹
    if ~isempty(RM_parameters.trajDB)
        for it=1:length(RM_parameters.trajDB(:,1))/5
            ind=1+(it-1)*5;
            plot(RM_parameters.trajDB(ind+1,:),RM_parameters.trajDB(ind,:),'-g');hold on;
        end
    end

trajDB中每五行为动态窗口的一条预测轨迹,所以每绘制完一条轨迹线,ind索引需要增加5。

Gitee

参考:

局部路径规划 DWA 算法完全解析(理论推导+代码实现,包你看懂!)
DWA算法总结
基于环境信息的局部路径规划算法改进与应用
融合动态窗口法与A*算法的港口AGV路径规划方法研究

作者:whwhhf

出处:https://www.cnblogs.com/whwhhf/p/18031024

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   DOinging  阅读(367)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题