赞
踩
本文介绍无人驾驶决策规划算法中人工势场算法。
语言用的是C++,图形基于easyX库输出。
如有错误或者遗漏之处,欢迎指出。
//限于时间关系,公式就不用公式编辑器了,使用截图表示。
人工势场法(Artificial Potential Field)简称 APF,属于局部路径规划;
人工势场法的基本思想是在障碍物周围构建障碍物斥力势场,在目标点周围构建引力势场,类似于物理学中的电磁场;
被控对象在这两种势场组成的复合场中受到斥力作用和引力作用,斥力和引力的合力指引着被控对象的运动,生成无碰撞的路径;
一种比喻是,势场法可以将障碍物比作是平原上具有高势能值的山峰,而目标点则是具有低势能值的低谷;
目标点对物体产生引力,引导汽车朝向其运动,距离越大,汽车所受的引力就越大,这一点有点类似于A*算法中的启发函数h;
其中η为比例增益系数,ρ(q,qg)是一个向量,表示汽车位置q与目标点位置qg之间的欧式距离,也就是直线距离,方向是从汽车位置指向目标点位置;
从公式可以看出,离目标点越远,引力场越大,且是与距离的平方成正比。所以引力场模型是一个抛物面,在下图中左下角是目标点,则引力场的形状为:
相应的引力就是引力场的负梯度:
证明过程:
设车辆位置为(x, y),目标点位置为(xg, yg),则有:
所以梯度为:
也就是分别对x和y求偏导;
障碍物对车辆产生斥力,决定斥力的主要因素是汽车与障碍物的距离,还有一个作用范围,当汽车在作用范围之外时,不产生斥力,进入作用范围时,离障碍物距离越近,斥力越大;
其中,k为比例增益系数,ρ(q,qo)是一个向量,表示汽车位置q与目标点位置qo之间的欧式距离,方向是从障碍物位置指向汽车位置;ρo为障碍物起作用的距离;
从公式可以看出,在作用范围内,离目标点越近,引力场越大,斥力场模型的形状如下图所示:
相应的斥力就是斥力场的负梯度:
证明过程:
设车辆位置为(x, y),障碍物位置为(xo, yo),则有:
所以梯度为:
对x求偏导:
对y求偏导类似,略去;
最后代入梯度公式可得:
以上介绍的是标准的人工势场法,但存在几个缺陷:
1,目标不可达问题:当障碍物与目标点距离太近时,引力很小,而障碍物的斥力合力可能较大,因此可能会无法在目标点停下来;
2,局部最优问题:车辆在某个位置时,如果障碍物的斥力合力与目标点的引力大小相等方向相反,则车辆受到的合力为0,此时陷入局部最优,车辆不再向前搜索路径;
3,当物体离目标点比较远时,引力将变的特别大,相对较小的斥力在甚至可以忽略的情况下,物体路径上可能会碰到障碍物;
一种改进方法是:
在原斥力函数最后一项gradρ(q,qo)替换成ρg^n,也就是离目标点的距离的n次方,这样当车辆离目标点很近时,斥力也会很小,离目标点很远时,斥力不会很小,从而避免目标不可达问题,以及可能会和障碍物碰撞的问题;其中n是正整数,根据需要设置;
另外,增加一个指向目标点的斥力,令这个斥力由障碍物产生,但是方向是指向目标点,这样所有的合力不会出现完全抵消的情况,从而避免局部最优问题:
如下图所示,车辆在任意位置时,受到的总合力为F,既不会为0,也不会在目标点附近时还很大,且方向一定是把车辆往目标点指引:
对于道路边界,也设置斥力,防止车辆驶出道路边界,保持在车道中。一种定义方法是:
x表示车辆中心点的横向坐标,离道路边界越近,斥力越大;
v表示车辆的速度,速度越大,斥力越大,因为偏离车道的危险更大;
η为增益系数;
测试一下这个函数,设200为分界点,更靠近道路的时候,斥力会明显增大,符合预期:
#pragma once #include<iostream> #include<easyX.h> #include<cmath> #include<ctime> #include<vector> #include<string> using namespace std; constexpr auto Swidth = 800; constexpr auto Sheight = 1200; constexpr auto e = 2.71828; class Point//点 { public: Point(int _x, int _y) :x(_x), y(_y)//初始化列表 { } public: void drawoint(COLORREF color);//画点 double x; double y; int r = 5;//画图半径 }; class newVector//向量 { public: newVector(Point p1, Point p2) :pBegin(p1), pEnd(p2)//, length(len)//初始化列表 { length = sqrt(pow((pEnd.x - pBegin.x), 2) + pow(pEnd.y - pBegin.y, 2));//计算长度 //drawVector(); } newVector addVector(newVector v1, newVector v2);//向量求和 void drawVector();//绘制向量 void printVector(string name);//打印向量 public: Point pBegin; Point pEnd; double length; }; class Obs//障碍物 { public: Obs(Point p) :po(p)//初始化列表 { cout << "ObsLoc: (" << po.x << ", " << po.y << ")" << endl; } void drawObs();//绘制障碍物 public: Point po; double r = 20.0;//半径 }; class Car//车辆 { public: Car(Point p) :pc(p) { cout << "CarLoc: (" << pc.x << ", " << pc.y << ")" << endl; } void drawCar(Point p);//绘制车辆 public: double length = 100.0;//长度 double width = 50.0;//宽度 Point pc;//中心点 }; class Road//道路 { public: Road(); ~Road(); void showRoad(Point p);//绘制道路 public: double RWidth = 200.0; vector<Obs> obTotal;//所有障碍物 Car *car0;//车辆 Point *pTarget;//终点 bool bObs = true;//障碍物开关 }; class ArtificialPotential//人工势场法 { public: double disCal(Point p1, Point p2);//两点距离计算 newVector Fatt();//计算引力 newVector Fobs();//计算障碍物斥力 newVector Fedge();//计算道路边界斥力 void process();//整个过程 void delay(int time); //延时函数,单位ms public: Road road; double delta_t = 0.02;//时间步长 double Rho_att;//与目标点的距离 double eta = 0.3;//引力增益系数//已考虑车辆质量,算出的力直接当作加速度用 double k = 0.3;//障碍物斥力增益系数//已考虑车辆质量,算出的力直接当作加速度用 int n = 3; double Rho0 = 500.0;//斥力起作用的范围 vector<newVector> vFreo;//存储每个障碍物产生的合斥力 double v0 = 0.0;//起始速度为0 double Eta = 0.02;//道路边界斥力增益系数 bool bEdgeF = true;//道路边界斥力开关 };
#include"ArtificialPotential.h" #if 0 void test_for_RoadF()//道路斥力测试 { setlinestyle(PS_SOLID, 3); setlinecolor(BLACK); setfillcolor(BLACK); setorigin(400, 100); double e = 2.71828; double Eta = 0.02; double v = 10.0; double x = 0.0; double Fedge = 0.0; while (-300.0 < x && x < 300.0) { if (-200.0 <= x && x <= 200.0) { Fedge = Eta * pow(x, 2) / 3; } else if (-300.0 < x && x < 300.0) { Fedge = Eta * v * pow(e, abs(x)); } solidcircle(x, Fedge, 3); solidcircle(-x, Fedge, 3); cout << "(" << x << ", " << Fedge << ")" << endl; x += 0.5; } } void testVector()//向量计算与绘制测试 { setlinestyle(PS_SOLID, 3); setlinecolor(BLACK); setfillcolor(BLACK); setorigin(0, Swidth / 2); Point p0(0, 0); Point p1(200, 300); Point p2(400, -100); p0.drawoint(); p1.drawoint(); p2.drawoint(); Point pt(p2.x + p1.x - p0.x, p2.y + p1.y - p0.y); pt.drawoint(); Point p3(750, -150); p3.drawoint(); Point pt1(p3.x + p2.x - pt.x, p3.y + p2.y - pt.y); pt1.drawoint(); system("pause"); cleardevice(); setorigin(Swidth / 2, Sheight); Point p4(0.0, 0.0); Point p5(200.5, -300.5); Point p6(-300.5, -400.5); newVector v1(p4, p5); newVector v2(p4, p6); newVector vt = v1.addVector(v1, v2); } #endif void Point::drawoint(COLORREF color)//画点 { setfillcolor(color); solidcircle(x, y, r); } newVector newVector::addVector(newVector v1, newVector v2)//向量求和 { Point pEndtmp(v2.pEnd.x + v1.pEnd.x - v1.pBegin.x, v2.pEnd.y + v1.pEnd.y - v1.pBegin.y); newVector vt(v1.pBegin, pEndtmp); return vt; } void newVector::drawVector()//绘制向量 { pBegin.drawoint(BLACK); pEnd.drawoint(BLACK); setlinestyle(PS_SOLID, 3); setlinecolor(LIGHTRED); line(pBegin.x, pBegin.y, pEnd.x, pEnd.y); } void newVector::printVector(string name)//打印向量 { cout << name << ": pBegin: (" << pBegin.x << ", " << pBegin.y << ")" << ", pEnd: (" << pEnd.x << ", " << pEnd.y << ")" << ", length: " << length << endl; } void Obs::drawObs()//绘制障碍物 { setfillcolor(RED); fillcircle(po.x, po.y, r); } void Car::drawCar(Point p)//绘制车辆 { setlinestyle(PS_SOLID, 3); setlinecolor(BLACK); double leftPos = p.x - width / 2; double rightPos = p.x + width / 2; double topPos = p.y - length / 2; double bottomPos = p.y + length / 2; rectangle(leftPos, topPos, rightPos, bottomPos); } Road::Road() { car0 = new Car(Point(0.0, -80.0));//定义车辆 pTarget = new Point(-20.0, -1100.0);//定义终点 cout << "pTarget: (" << pTarget->x << ", " << pTarget->y << ")" << endl; if (bObs) { obTotal.push_back(Obs(Point(-100.0, -250.0))); obTotal.push_back(Obs(Point(0.0, -460.0))); obTotal.push_back(Obs(Point(100.0, -660.0))); obTotal.push_back(Obs(Point(-60.0, -830.0))); obTotal.push_back(Obs(Point(50.0, -1000.0))); } showRoad(car0->pc);//绘制道路 pTarget->drawoint(GREEN);//绘制终点 system("pause"); } Road::~Road() { if (car0 != nullptr) { delete car0; car0 = nullptr; } if (pTarget != nullptr) { delete pTarget; pTarget = nullptr; } } void Road::showRoad(Point p)//绘制道路 { setlinecolor(BLACK); setorigin(Swidth / 2, Sheight); //绘制道路中心 setlinestyle(PS_DASH, 5); line(0, 0, 0, -Sheight); //绘制左右边界 setlinestyle(PS_SOLID, 5); line(-RWidth, 0, -RWidth, -Sheight); line(RWidth, 0, RWidth, -Sheight); for (auto it = obTotal.begin(); it != obTotal.end(); it++) { it->drawObs();//绘制障碍物 } car0->drawCar(p);//绘制车辆 } double ArtificialPotential::disCal(Point p1, Point p2)//两点距离计算 { return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); } newVector ArtificialPotential::Fatt()//计算引力 { Rho_att = disCal(*road.pTarget, road.car0->pc);//与目标点距离 double F_att = eta * Rho_att;//引力大小 Point pFatt(F_att / Rho_att * (road.pTarget->x - road.car0->pc.x) + road.car0->pc.x, F_att / Rho_att * (road.pTarget->y - road.car0->pc.y) + road.car0->pc.y); newVector vFatt(road.car0->pc, pFatt); vFatt.printVector("vFatt"); //vFatt.drawVector(); return vFatt; } newVector ArtificialPotential::Fobs()//计算斥力 { if (road.obTotal.empty())//没有障碍物 { return newVector (road.car0->pc, road.car0->pc); } for (auto it = road.obTotal.begin(); it != road.obTotal.end(); it++)//分别计算每个障碍物产生的合斥力 { double Rho_obs = disCal(it->po, road.car0->pc);//与这个障碍物中心点的距离 double Freo1 = 0.0; double Freo2 = 0.0;//这个障碍物产生的两个引力的大小 if (Rho_obs >= Rho0) { Freo1 = 0.0; Freo2 = 0.0; } else { Freo1 = k * (1 / Rho_obs - 1 / Rho0) * pow(Rho_att, n) / pow(Rho_obs, 2); Freo2 = n * k / 2 * pow(1 / Rho_obs - 1 / Rho0, 2) * pow(Rho_att, n - 1); } Point pFreo1((road.car0->pc.x - it->po.x) / Rho_obs * (Freo1 + Rho_obs) + it->po.x, (road.car0->pc.y - it->po.y) / Rho_obs * (Freo1 + Rho_obs) + it->po.y);//由这个障碍物指向车辆 Point pFreo2(Freo2 / Rho_att * (road.pTarget->x - road.car0->pc.x) + road.car0->pc.x, Freo2 / Rho_att * (road.pTarget->y - road.car0->pc.y) + road.car0->pc.y);//由车辆指向目标点 newVector vFreo1(road.car0->pc, pFreo1); newVector vFreo2(road.car0->pc, pFreo2); newVector vFobs = vFreo1.addVector(vFreo1, vFreo2);//计算这个障碍物的合斥力 vFreo.push_back(vFobs);//存入容器 vFreo1.printVector("vFreo1"); vFreo2.printVector("vFreo2"); vFobs.printVector("vFobs"); //vFreo1.drawVector(); //vFreo2.drawVector(); //vFobs.drawVector(); } //计算所有障碍物产生的合斥力 newVector vFobsTotal = vFreo[0]; for (int i = 1; i < vFreo.size(); i++) { vFobsTotal = vFobsTotal.addVector(vFobsTotal, vFreo[i]); } vFobsTotal.printVector("vFobsTotal"); //vFobsTotal.drawVector(); return vFobsTotal; } newVector ArtificialPotential::Fedge()//计算道路边界斥力 { if (!bEdgeF)//不考虑道路边界斥力 { return newVector(road.car0->pc, road.car0->pc); } double Fedge = 0.0; Point pFedge(0.0, 0.0); if (-(road.RWidth - road.car0->width / 2) <= road.car0->pc.x && road.car0->pc.x < 0.0) { Fedge = Eta * pow(road.car0->pc.x, 2) / 3; pFedge = Point(road.car0->pc.x + Fedge, road.car0->pc.y); } else if (road.car0->pc.x >= 0.0 && road.car0->pc.x <= (road.RWidth - road.car0->width / 2)) { Fedge = Eta * pow(road.car0->pc.x, 2) / 3; pFedge = Point(road.car0->pc.x - Fedge, road.car0->pc.y); } else if (-road.RWidth < road.car0->pc.x && road.car0->pc.x < -(road.RWidth - road.car0->width / 2)) { Fedge = Eta * v0 * pow(e, -road.car0->pc.x); pFedge = Point(road.car0->pc.x + Fedge, road.car0->pc.y); } else if ((road.RWidth - road.car0->width / 2) < road.car0->pc.x && road.car0->pc.x < road.RWidth) { Fedge = Eta * v0 * pow(e, road.car0->pc.x); pFedge = Point(road.car0->pc.x - Fedge, road.car0->pc.y); } newVector vFedge(road.car0->pc, pFedge); vFedge.printVector("vFedge"); //vFedge.drawVector(); return vFedge; } void ArtificialPotential::process()//整个过程 { while (true) { newVector vFatt = Fatt();//引力 newVector vFobsTotal = Fobs();//总障碍物斥力 newVector vFedge = Fedge();//道路边界斥力 newVector vFtotal = vFatt.addVector(vFatt, vFobsTotal);//引力与斥力的合力 vFtotal = vFtotal.addVector(vFtotal, vFedge);//再与道路斥力计算合力 vFtotal.printVector("vFtotal"); //vFtotal.drawVector(); double dis = v0 * delta_t + vFtotal.length * pow(delta_t, 2) / 2;//每个时间间隔内走过的距离 v0 += vFtotal.length * delta_t;//速度 road.car0->pc.x = dis / vFtotal.length * (vFtotal.pEnd.x - vFtotal.pBegin.x) + vFtotal.pBegin.x; road.car0->pc.y = dis / vFtotal.length * (vFtotal.pEnd.y - vFtotal.pBegin.y) + vFtotal.pBegin.y;//更新车辆坐标位置 road.car0->pc.drawoint(BLUE); cout << "CarLoc: (" << road.car0->pc.x << ", " << road.car0->pc.y << "), dis to goal: " << Rho_att << ", dis: " << dis << ", v: " << v0 << endl << endl; //road.car0->drawCar(road.car0->pc); vFreo.clear(); if (Rho_att <= road.car0->length / 2)//已到达目标点 { break; } //delay(100); //system("pause"); } } void ArtificialPotential::delay(int time) //延时函数,单位ms { clock_t now = clock(); while (clock() - now < time) { } } int main() { initgraph(Swidth, Sheight, EW_SHOWCONSOLE); setbkcolor(WHITE); cleardevice(); ArtificialPotential ap; ap.process(); system("pause"); closegraph(); return 0; }
没有障碍物时,将只有目标点的引力和道路边界的斥力有影响,如果目标点靠近车道中心,那道路边界斥力影响将非常小,在上图的场景中,是否有道路边界的斥力,轨迹都是近似的一条直线;
可以看到障碍物的斥力起到作用了,此时道路边界斥力影响仍然较小;
关闭道路边界斥力:
开启道路边界斥力:
关闭与开启道路边界时的轨迹对比:
可以看出在道路边界斥力的作用下,轨迹更倾向于靠近道路中心;
把障碍物斥力增益系数k调大,此时障碍物斥力作用明显增强,轨迹已经超出车道边界了,而此时道路边界斥力的作用就体现出来了,如果开启,轨迹就不会超出车道边界:
通过上述分析可知:
1,人工势场算法公式中的几个参数,对规划出的轨迹有直接影响,实际应用时,应反复调参直到轨迹合适;
2,引力系数和斥力系数的大小,会直接决定引力和斥力的大小,如果引力过大、斥力过小,则轨迹可能会和障碍物碰撞;反之,则可能会出现无法到达终点的情况;
3,而道路边界斥力对车辆保持在车道中,不驶出车道有约束作用;
从目前的公式来看,虽然解决了终点不可达、局部最优、路径可能会碰到障碍物的问题,但是生成的轨迹仍然不一定可以直接用于车辆驾驶,因为目前的轨迹仍然有很多不平滑的地方,并不适合车辆运动学要求。后续如果有更好的改进算法,再尝试加上。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。