自动驾驶-决策规划算法九:人工势场算法(C++) (1)

自动驾驶-决策规划算法九:人工势场算法(C++)

摘要:

本文介绍无人驾驶决策规划算法中人工势场算法。

语言用的是C++,图形基于easyX库输出。

如有错误或者遗漏之处,欢迎指出。

//限于时间关系,公式就不用公式编辑器了,使用截图表示。


image

附赠自动驾驶学习资料和量产经验:链接

基本概念:

人工势场法(Artificial Potential Field)简称 APF,属于局部路径规划;

人工势场法的基本思想是在障碍物周围构建障碍物斥力势场,在目标点周围构建引力势场,类似于物理学中的电磁场;

被控对象在这两种势场组成的复合场中受到斥力作用和引力作用,斥力和引力的合力指引着被控对象的运动,生成无碰撞的路径;

一种比喻是,势场法可以将障碍物比作是平原上具有高势能值的山峰,而目标点则是具有低势能值的低谷;

目标点对物体产生引力,引导汽车朝向其运动,距离越大,汽车所受的引力就越大,这一点有点类似于A*算法中的启发函数h;

常用的引力场函数:

image

其中η为比例增益系数,ρ(q,qg)是一个向量,表示汽车位置q与目标点位置qg之间的欧式距离,也就是直线距离,方向是从汽车位置指向目标点位置;

从公式可以看出,离目标点越远,引力场越大,且是与距离的平方成正比。所以引力场模型是一个抛物面,在下图中左下角是目标点,则引力场的形状为:

image

相应的引力就是引力场的负梯度:

image

证明过程:

设车辆位置为(x, y),目标点位置为(xg, yg),则有:

image

所以梯度为:

image

也就是分别对x和y求偏导;

障碍物对车辆产生斥力,决定斥力的主要因素是汽车与障碍物的距离,还有一个作用范围,当汽车在作用范围之外时,不产生斥力,进入作用范围时,离障碍物距离越近,斥力越大;

常用的斥力场函数:

image

其中,k为比例增益系数,ρ(q,qo)是一个向量,表示汽车位置q与目标点位置qo之间的欧式距离,方向是从障碍物位置指向汽车位置;ρo为障碍物起作用的距离;

从公式可以看出,在作用范围内,离目标点越近,引力场越大,斥力场模型的形状如下图所示:

image

相应的斥力就是斥力场的负梯度:

image

证明过程:

设车辆位置为(x, y),障碍物位置为(xo, yo),则有:

image

所以梯度为:

image

对x求偏导:

image

对y求偏导类似,略去;

最后代入梯度公式可得:

image

缺陷与改进:

以上介绍的是标准的人工势场法,但存在几个缺陷:

1,目标不可达问题:当障碍物与目标点距离太近时,引力很小,而障碍物的斥力合力可能较大,因此可能会无法在目标点停下来;

2,局部最优问题:车辆在某个位置时,如果障碍物的斥力合力与目标点的引力大小相等方向相反,则车辆受到的合力为0,此时陷入局部最优,车辆不再向前搜索路径;

3,当物体离目标点比较远时,引力将变的特别大,相对较小的斥力在甚至可以忽略的情况下,物体路径上可能会碰到障碍物;

一种改进方法是:

在原斥力函数最后一项gradρ(q,qo)替换成ρg^n,也就是离目标点的距离的n次方,这样当车辆离目标点很近时,斥力也会很小,离目标点很远时,斥力不会很小,从而避免目标不可达问题,以及可能会和障碍物碰撞的问题;其中n是正整数,根据需要设置;

image

另外,增加一个指向目标点的斥力,令这个斥力由障碍物产生,但是方向是指向目标点,这样所有的合力不会出现完全抵消的情况,从而避免局部最优问题:

image

如下图所示,车辆在任意位置时,受到的总合力为F,既不会为0,也不会在目标点附近时还很大,且方向一定是把车辆往目标点指引:

image

道路边界斥力定义:

对于道路边界,也设置斥力,防止车辆驶出道路边界,保持在车道中。一种定义方法是:

image

x表示车辆中心点的横向坐标,离道路边界越近,斥力越大;

v表示车辆的速度,速度越大,斥力越大,因为偏离车道的危险更大;

η为增益系数;

测试一下这个函数,设200为分界点,更靠近道路的时候,斥力会明显增大,符合预期:

image

算法代码:

ArtificialPotential.h

#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;//道路边界斥力开关
};

ArtificialPotential.cpp

#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;
}

测试结果:

先测试没有障碍物时的场景:

image

没有障碍物时,将只有目标点的引力和道路边界的斥力有影响,如果目标点靠近车道中心,那道路边界斥力影响将非常小,在上图的场景中,是否有道路边界的斥力,轨迹都是近似的一条直线;

只有一个障碍物时轨迹:

image

可以看到障碍物的斥力起到作用了,此时道路边界斥力影响仍然较小;

然后测试五个障碍物的场景:

关闭道路边界斥力:

image

开启道路边界斥力:

image

关闭与开启道路边界时的轨迹对比:

image

可以看出在道路边界斥力的作用下,轨迹更倾向于靠近道路中心;

把障碍物斥力增益系数k调大,此时障碍物斥力作用明显增强,轨迹已经超出车道边界了,而此时道路边界斥力的作用就体现出来了,如果开启,轨迹就不会超出车道边界:

image

总结:

通过上述分析可知:

1,人工势场算法公式中的几个参数,对规划出的轨迹有直接影响,实际应用时,应反复调参直到轨迹合适;

2,引力系数和斥力系数的大小,会直接决定引力和斥力的大小,如果引力过大、斥力过小,则轨迹可能会和障碍物碰撞;反之,则可能会出现无法到达终点的情况;

3,而道路边界斥力对车辆保持在车道中,不驶出车道有约束作用;

从目前的公式来看,虽然解决了终点不可达、局部最优、路径可能会碰到障碍物的问题,但是生成的轨迹仍然不一定可以直接用于车辆驾驶,因为目前的轨迹仍然有很多不平滑的地方,并不适合车辆运动学要求。后续如果有更好的改进算法,再尝试加上。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值