一、自动泊车代码整体思路
自动泊车代码主要应用三种算法,A*,HybridA*,RS曲线,整体思路较简单,其调用结构为
1、先应用A*算法,生成costmap地图,此地图表示每个栅格到终点栅格的cost值,用于HybridA*中,指引扩展点的搜索方向,往cost小的方向扩展
2、调用HybridA*算法,RS曲线作为HybridA*的启发函数,命中终点后,通过碰撞检测检查扩展路径和RS路径有无碰撞障碍物,如果没有碰撞,则搜索成功,若碰撞则继续搜索。
此次先具体介绍A*算法如何应用。
二、A*算法在泊车代码中的应用
A*算法内部分成了GridAStar函数(主函数),CalcObstMap函数、AStarSearch函数三部分,其中AStarSearch是A*算法核心,AStarSearch函数又调用了update和popopen函数,其嵌套结构如下:
(A)GridAStar函数主要内容:
1、先用CalcObstMap函数,将世界坐标系,结合障碍线世界坐标,转换为栅格坐标,并将障碍物栅格设置为1,得到的obmap如下图所示;
可见,车位边界均被填入1,此障碍物地图gres是设置为0.5,生成栅格地图的大小为40*40,原始代码中gres设置的是2,生成的是10*10的栅格地图,更改gres会得到不同的obmap,同时gres也会影响障碍物占据栅格的个数。设置过小会显著增加计算时间。
另外,栅格坐标系下与世界坐标系下的车位位置不一致,这是因为世界坐标系原点(0,0)在左下角,而栅格坐标系原点(0,0)在左上角,因此需要注意栅格坐标与世界坐标的对应。
2、基于obmap生成costmap,如果obmap栅格值为1,则costmap对应栅格值填入inf,意为此点不可到达;如果为0,则调用AstarSearch函数,计算此栅格到目标栅格的代价;
3、针对每个栅格循环步骤2,对每个栅格填值,得到costmap,如下图所示,红色轨迹就是指引HybridA*的最快到达终点的扩展方向,但显然其不是合理的路径,因此,costmap只是用来做一个引导。
注意:纯的A*算法是计算一个起点A到终点G的路径,得到的是此起点A到终点G的最短路径的总cost,而这里应用的A*是把obmap中每一个栅格当做起点,都计算一遍到终点G的cost。
例如,此obmap大小为40*40,有1600个栅格,那么A*就要运行1600遍,得到1600个cost,且每个cost都是该栅格到终点的最短路径的cost。
(B)CalcObstMap函数:计算obmap,终点在于如何确定哪个是障碍物占据的栅格
1、根据世界坐标系的大小,和gres(分辨率)确定栅格地图的行列个数;
2、利用矩阵表示栅格地图,以障碍物中心点的坐标为基准确定哪个栅格是有障碍物的
3、因为障碍物不可能是刚好占据一个栅格,必定会有超出栅格的部分,因此设定一个原则,来判断栅格是不是被障碍物占据,原则就是将栅格坐标转换为世界坐标,假如坐标与障碍物坐标的最小距离小于0.5m,则认为这个栅格被障碍物占据。
4、重复步骤三,判断每一个栅格,得到障碍物地图obmap;
(C)AStarSearch函数:A*的核心
1、预先生成4个grids矩阵,一个open集合,一个close集合,open集存储扩展的栅格(又称节点),close集存储已经处理过的栅格,“处理过“”的意思为,假如一个栅格A,以A为基准,向周围扩展8个栅格(只能是8个),这个扩展过程就是处理。这8个栅格就要放到open中,之后,这个A就要被放进close集合中。
2、grids含义:
① grids1存储每个栅格的父节点的行坐标,比如(i,j)栅格的父节点是(a,b)栅格,则grids1(i,j)=a;
② grids2存储每个栅格的父节点的列坐标,比如(i,j)栅格的父节点是(a,b)栅格,则grids2(i,j)=b;
grids1 与 grids2的作用是为了记录父节点是谁。
例如,基准点wknode扩展了A(i,j)点,那么wknode(a,b)就是A的父节点,grids1与grids2就是为了在A点的栅格,记录A点的父节点坐标。
③ grids3用于存储当前栅格到目标栅格goal的启发值H,一般用欧式距离;
④ grids4用于存储起始点到当前栅格所走过的距离,即代价G值;
grids3 与 grids4之和就是cost,即A*公式 F = H + G;
3、调用PopOpen函数,计算open集中cost最小的点wknode(wknode就是1中的栅格A)
4、更新open集,close集,调用update函数,然后把wknode节点加入到close集合
5、循环步骤3、4,当open集为空时,搜索完毕。通过grids矩阵就可以从终点找父节点的方式,一路反向找回起点,当然,这里的A*主要是为了计算cost,因此不需要反向找父节点生成路径点。
(D)popopen函数:为了找基准点wknode
1、遍历open集中所有数据,open集中存放的是栅格坐标,需要根据栅格坐标,找到grids中对应的cost = h + g,找到cost最小的栅格坐标,这个栅格就是要走的下一个栅格点,记为wknode;
2、由于取出了wknode栅格点,所以open集中删除此数据。
(E)update函数:为了更新open集,主要是把扩展点加入到open集中,同时检查close集中是否有已经处理的点又成了扩展点。
1、根据选中的wknode点,以起为中心,扩展周围8个点;
2、wknode点到每个扩展点的距离,与wknode自身的g值之和,即扩展点的g值,把此值填入grids4中;并将wknode作为扩展点的父节点,将wknode的坐标填入扩展点坐标 对应的grids1与grids2中;
3、如果扩展点不是终点,则把扩展点加入到open集;
4、判断扩展点在不在close集中,如果扩展点在close集中,就把他从close集中删除,意思为,之前这个扩展点被当做wknode点处理过,现在重新扩展到了这个点,需要对它重新处理,所以从close中删除它。
5、对每个扩展点重复2-4的操作。
注:“检查close集中是否有已经处理的点又成了扩展点”可按下面的例子理解。
假设当前搜索的路是A-C-D-E,B点因为cost比C大,选择走A-C道路。但走到E时,发现此路不通,或者E的cost太大,又需要重新搜B点,那么C点此时已经处理过了,在close当中,但扩展B时,又扩展了C点,所以需要把C点拿出来,重新判断。
function costmap = GridAStar(obstlist,goal,gres)
[minx,miny,obmap] = CalcObstMap(obstlist,gres);//minx miny其实是障碍物在普通地图的坐标,不是栅格地图的坐标,利用CalcObstMap函数生成障碍物栅格地图
col = goal(1);
row = goal(2);
col = ceil((col-minx)/gres);// 将goal(目标点)的普通地图坐标转换为栅格坐标,四舍五入为整数,x坐标对应列数,y对应行数
row = ceil((row-miny)/gres);
// goal = [gx,gy];
// goal.parent = [gx,gy];
// goal.precost = 0;
// goal.postcost = inf;
goal = [row,col];
costmap = 0*obmap; //生成cost地图的0矩阵
dim = size(obmap); //计算矩阵尺寸,方便给cost填值
// 对costmap填值,如果障碍物栅格地图的值是1,则cost地图上填入inf,如果不是1,且不是目标点的行列值,则运行Astarsearch
for i = 1:dim(1)
for j = 1:dim(2)
if obmap(i,j) == 1
costmap(i,j) = inf;
continue
elseif i == col && j == row
continue
end
start = [i,j];//将每个栅格都当做起点,
cost = AStarSearch(start,goal,obmap);//调用搜索主程序
costmap(i,j) = cost;//每一个点到目标点做一次Astar,然后得到每个点到终点的最短cost,生成整张地图的cost地图
end
end
end
function cost = AStarSearch(start,goal,obmap)
dim = size(obmap);// 计算栅格地图尺寸,返回m*n,m行,n列
// Grids(i,j,1) - x of parent pos; 2 - y of parent pos; 3 - precost; 4 -
// postcost
Grids = zeros(dim(1),dim(2),4);//生成四个dim尺寸的0矩阵
for i = 1:dim(1)
for j = 1:dim(2)
Grids(i,j,1) = i;// 栅格地图中点(i,j)的父节点的栅格行坐标,每格都是该格子行坐标值
Grids(i,j,2) = j;// 栅格地图中点(i,j)的父节点的栅格列坐标,每格都是该格子列坐标值
Grids(i,j,3) = norm(([i,j]-goal));// 构造栅格的启发值h,每个点到目标点的向量的模
Grids(i,j,4) = inf;// 从起始点到当前点i,j走过的距离,实际就是g值
end
end
Open = start; // Open集合,存放待拓展的栅格节点,不是实际坐标
Grids(start(1),start(2),4) = 0; // 从起始点到该点的距离
Close = [];// Close集合,存放拓展过的栅格节点,不是实际坐标
while ~isempty(Open)// 当open为空时,搜索完毕,为A*核心搜索代码的三个过程1.找出最小的点 2.拓展点 3.把点放入close集合中
[wknode,Open] = PopOpen(Open,Grids);//计算cost最小点wknode,同时open中剔除wknode点。
[Grids,Open,Close] = Update(wknode,goal,obmap,Grids,Open,Close);//调用update函数,将扩展点加入open中,更新open集
Close(end+1,:) = wknode;//wknode已经处理过,加入close中
end
cost = Grids(goal(1),goal(2),3)+Grids(goal(1),goal(2),4);//得到起点到终点的最小cost
end
function [Grids,Open,Close] = Update(wknode,goal,obmap,Grids,Open,Close)
dim = size(obmap);
for i = -1:1
for j = -1:1
adjnode = wknode+[i,j];//adjnode是扩展点,wknode栅格周围一共八个扩展点,
row = adjnode(1);
col = adjnode(2);
if i == 0 && j == 0//扩展点是wknode自身,不处理,继续循环
continue
elseif row < 1 || row > dim(1)//扩展点行超出栅格地图边界,不处理,继续循环
continue
elseif col < 1 || col > dim(2)//扩展点列超出栅格地图边界,不处理,继续循环
continue
elseif obmap(row,col) == 1//扩展点是障碍物占据栅格,不处理,继续循环
continue
end
tcost = Grids(wknode(1),wknode(2),4)+norm([i,j]);//计算扩展点adjnode的G值
if Grids(row,col,4) > tcost
Grids(row,col,1) = wknode(1);
Grids(row,col,2) = wknode(2);//记录该扩展点的父节点到grids中
Grids(row,col,4) = tcost;//记录扩展点的G值
// add adjnode to Open except wknode is goal
if ~ismember(adjnode,Open,'rows') && ~isequal(adjnode,goal)
Open(end+1,:) = adjnode;//如果扩展点不在open中,且扩展点不是终点,则把扩展点加入到open中
end
// if adjnode is in Close remove it
if isempty(Close)
// do nothing
elseif ismember(adjnode,Close,'rows')//判断扩展点在不在close中
[~,rIdx] = ismember(adjnode,Close,'rows');
Close(rIdx,:) = [];//扩展点在close中则从close中删除此点
end
end
end
end
end
function [wknode,Open] = PopOpen(Open,Grids)
mincost = inf;
minidx = 1;//初始化mincost,minidx
for i = 1:size(Open,1)//遍历open集,找到cost最小的栅格节点
node = Open(i,:);
tcost = Grids(node(1),node(2),3)+Grids(node(1),node(2),4);
if tcost < mincost
minidx = i;
mincost = tcost;
end
end
wknode = Open(minidx,:);//提出cost最小的节点记为wknode
Open(minidx,:) = [];//open集中删除wknode。
end
function [minx,miny,obmap] = CalcObstMap(obstlist,gres)// 使用障碍物中点的坐标构建地图
minx = min(obstlist(:,1));
maxx = max(obstlist(:,1));
miny = min(obstlist(:,2));
maxy = max(obstlist(:,2));
xwidth = maxx - minx;// 计算普通地图的x向距离
xwidth = ceil(xwidth/gres);//转换为栅格地图的个数
ywidth = maxy - miny;// 计算普通地图的y向距离
ywidth = ceil(ywidth/gres);//转换为栅格地图的个数
obmap = zeros(ywidth,xwidth);//结合栅格行列数,生成空矩阵
for i = 1:ywidth
for j = 1:xwidth
ix = minx+(j-1/2)*gres;// 把离散栅格转换为实际位置坐标,单位是米
iy = miny+(i-1/2)*gres;
[~,D] = knnsearch(obstlist,[ix,iy]); //计算点(ix, iy)距离obstlist中点最近的一个点的距离
if D < 0.5 // 距离小于0.5则认为是障碍物的一部分,障碍物的边长,形状为正方形
obmap(i,j) = 1;
end
end
end
end
// function [xidx,yidx] = CalcIdx(x,y,minx,miny,gres)
// xidx = ceil((x-minx)/gres);
// yidx = ceil((y-miny)/gres);
// end
评论(14)
您还未登录,请登录后发表或查看评论