一、自动泊车代码整体思路

自动泊车代码主要应用三种算法,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点拿出来,重新判断。

三、matlab代码详细注释如下
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