上一篇里面讲过用八邻域来获得赛道边界:智能车八邻域图像算法
当已经能稳定获得赛道边界时,就可以进行元素识别,获得偏差了。
这里就介绍一下我的一些思路。
早在前两年,CSDN上就有一篇关于八邻域如何处理元素的文章第十五届全国大学生智能汽车竞赛-双车组三轮图像处理总结,写得很好,我当时也参考了这篇文章,但是我觉得不是很详细,所以我这里想写得详细一些。
免责声明:只提供思路,具体效果如何,不保证 , ^^。
我的想法是在扫线的过程中就要同时获得我想要的一些关键信息,比如各种关键点,边线的形状等。
这样的话就不需要再此处理边线了,扫完之后就可以进行元素判断了。
以十字的拐点举例:
这个拐点是重要的一个点,无论是判断十字还是十字补线都需要用到它。
有的同学可能会觉得这就是左边界的最外侧点嘛,很容易获得。
那下面这种情况肯定就不是最外侧点了。
或许会说,在第二次到达图像最左侧前的最外侧点,就是这个拐点了。
那其实在斜入十字的时候(没有图),像这样:
那也就不是最外侧点了。
当边线全部扫完后,可以通过斜率来判断这个点。就是满足三个条件:
该点的上面一段是直线;该点的下面一段是直线;这两个线段的斜率之间符合一个角度。
这种判断还是挺稳的,可以把这种带转折的点找出,同时不会在弯道误判。缺点就是计算量有点大。
顺着上面这个思路的话,就意味着只要能判断出上面三个条件,就可以判断拐点了,那么生长方向这个信息就可以利用起来了。
我想这个生长方向是啥应该不用再介绍了
上面这个图是扫描左边界时,0表示的方向为右侧,逆时针扫描。当扫描右边界时,0表示的方向就为左,顺时针扫描。下面是表示搜索方向的数组:
int8 Pointdirections_L[8][2] = { { 0, 1 }, { -1, 1}, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 },{ 1, 1 } }; //八邻域搜索方向数组
int8 Pointdirections_R[8][2] = { { 0, -1}, { -1, -1}, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 },{ 1, -1 } }; //八邻域搜索方向数组
由于我的图像的原点在左上角,所以这个数组看起来有点绕,对照着图看应该还是可以。
下面说一下怎么用它。我的做法是每个点的生长方向信息都用其周围四个点的生长方向来表示。看下面这个图就了解了:
上面是一条边线,那么红色的点的生长方向就用非黑色的点之间的生长方向来表示,分别为:上、右上、右上,也就是2、1、1。用一个数字来表示就是211。我当时脑子一抽用的八进制来表示,就是137。
还是以上面十字拐点来举例:
首先就是直线的判断,由于摄像头的畸变,所以直线实际上是这样的:
直线基本由以下8类点组成
int16 straight_line_Ary[8] = {145,138,81,82,137,74,73,146}; //左直线数组,生长方向为1,2之间. 146为全向上 73为全向右上
所以如果这些点所占的比重够多,就可以判断是一条直线了
/******左直线**********************************************/
case 145: L_straight_point_num++; L_Horizontal_up_line_num++; break;
case 138: L_straight_point_num++; L_Horizontal_up_line_num++; break;
case 81: L_straight_point_num++; L_Horizontal_up_line_num++; break;
case 82: L_straight_point_num++; L_Horizontal_up_line_num++;
if(Maybe_L_inflexion_point.flag!=0) break;
Maybe_L_inflexion_up_point.flag=1; Maybe_L_inflexion_up_point.row=Current_Row; Maybe_L_inflexion_up_point.col=Current_Col; break;
case 137: L_straight_point_num++; L_Horizontal_up_line_num++; break;
case 74: L_straight_point_num++; L_Horizontal_up_line_num++; break;
case 73: L_straight_point_num++; L_Horizontal_up_line_num++; break;
/*******基础线段的判断********************************************/
//左直线 //在十字拐点处被清除
if(L_straight_point_num>=5){
L_straight_line_flag=1;
if(L_straight_point_num>10) L_straight_line_flag=2;
if(L_straight_point_num>60) {L_straight_line_flag=3;L_straight_flag=1;}
if(L_straight_point_num>70) {L_straight_line_flag=4;L_straight_flag=2;}
}else{
L_straight_line_flag=0;
}
那么拐点也是同理,这个点的生长方向也有以下几种:
int16 L_inflexion_point_1_Ary[6] = {148,140,84,139,147,83}; //第一类左拐点 正常情况下出现在环岛下(后面一段方向略向下),十字下(后面一段方向略向上),车库下(后面一段方向略向上),三岔下(后面一段方向较多向上)
那么这三种条件都可以判断的话就可以判断拐点了:
//左下拐点
if(Maybe_L_inflexion_point.flag>=1)
{
if(L_inflexion_point.flag==1||L_inflexion_up_point.flag==1) Maybe_L_inflexion_point.flag=0;
//第一阶段,如果存在拐点且拐点下有一段直线
if(Maybe_L_inflexion_point.flag==1&&L_Horizontal_up_line_flag>=1){
L_Horizontal_line_flag=0; //清除之前的标志
Maybe_L_inflexion_point.flag=15; //如果后面20个点中依然不能使L_Horizontal_LittelUp_line_flag置1,则不是拐点
}else{
if(Maybe_L_inflexion_point.flag>1){
//用后面的线是否为左横向略向上来判断是不是左下拐点
if(L_Horizontal_line_flag==1){
Maybe_L_inflexion_point.flag=0; //清除疑似拐点的标志
L_inflexion_point.flag=1;
L_inflexion_point.row=Maybe_L_inflexion_point.row;
L_inflexion_point.col=Maybe_L_inflexion_point.col;
L_The_Outer_Point_1.flag=1;
L_The_Outer_Point_1_border_Row=Current_Row;
}else{
Maybe_L_inflexion_point.flag--;
if(Maybe_L_inflexion_point.flag<=2){Maybe_L_inflexion_point.flag=0;}
}
}else{
//不满足拐点下有段直线
Maybe_L_inflexion_point.flag=0;
}
}
}
用这种方式来找拐点,呃,不是很稳。那么为什么不稳还用呢,下面来讲一下我理解的稳与不稳。
这里的不稳定是指,不一定每一帧都可以准确地找到这个点,但是在小车运动的过程中,总会有一帧能找到这个点。
稳定的点就是正常情况下,每一帧都能准确地找到这个点。
所以就可以利用不稳定的点来作为判断的依据,用稳定的点作为补线的依据。
不稳定的点通常有十字的上下拐点。
稳定的点有:
我的建议是类似环岛,十字,三叉等元素都用状态机的方式去处理,这样的话思路比较清晰,后续有问题了也能很快定位问题。
同时找到两个拐点就可以作判断,另外针对图像的情况、运动的策略做出一些限制。
if(((L_inflexion_point.flag==1)&&(L_inflexion_up_point.flag==1)) //同时左边找到两个拐点
||((R_inflexion_point.flag==1)&&(R_inflexion_up_point.flag==1)) //同时右边找到两个拐点
||((L_inflexion_up_point.flag==1)&&(R_inflexion_up_point.flag==1))) //找到上面两个拐点
{
if((L_inflexion_point.row>45||R_inflexion_point.row>45)){
Crossroad_Flag=1;
}
if(((L_inflexion_point.flag==1)&&(L_inflexion_up_point.flag==1))&&L_inflexion_point.row<L_inflexion_up_point.row){
Crossroad_Flag=0;
}
if(((R_inflexion_point.flag==1)&&(R_inflexion_up_point.flag==1))&&R_inflexion_point.row<R_inflexion_up_point.row){
Crossroad_Flag=0;
}
}
能找到拐点就用拐点补,不能找到的话就用相近的稳定的点来补,
相近的点应该是,再次回到边界前的最外侧点,或者说XX行以下的最外侧点。
然后两点连线就完事。
正入十字这玩意只要点找的不离谱,基本都没啥问题。主要头疼的地方是斜入十字,这里大伙应该要好好研究研究了。
1阶段
找到下拐点以及另一侧直线即为环岛
这里容易误判,所以要注意检查这个状态,当一侧不为直线时就清除这个状态
2阶段
当找到拐点,且其在打角行下时;或
当没有找到拐点时,如果找到左下最外点并且该点在打角行下时
(打角行:我取偏差的方式就是某一行的赛道线与中线的差值,这一行的大小类似于前瞻。行数小(靠图像上方)、前瞻就远;行数大(靠图像下方)、前瞻就近)
3阶段
下拐点消失时
4阶段
最外侧点的行数大于60.。
我的图像一共120行,这就意味着最外侧点在图像的中线以下。
5阶段
另一侧最外点的列小于40
6阶段
找到另外一侧的下拐点,同时这个拐点的位置在右下方
7阶段
找到同侧上拐点
8阶段
非边界上的最低点的行数大于100(图像下方)
回到0(正常)阶段
非边界上的最低点的行数大于116(图像下方)
对于环岛,自己推着车慢慢看图像是最好理解的
贴上代码:
/*** 环岛 *****************************************************************************************************************************************/
//状态机
//正常位置环岛判断
//找到下拐点以及另一侧直线即为环岛
if(((L_inflexion_point.flag==1)&&(R_straight_point_num>=50))||((R_inflexion_point.flag==1)&&(L_straight_point_num>=50)))
{
if(L_inflexion_point.flag==1&&L_Island_Flag==0&&L_Garage==0){
L_Island_Flag=1;
}
if(R_inflexion_point.flag==1&&R_Island_Flag==0&&R_Garage==0){
R_Island_Flag=1;
}
}
//是否清除Island_Flag=1标志
//依据一侧是否有直线
if(L_Island_Flag==1||R_Island_Flag==1){
if(L_Island_Flag==1&&R_straight_point_num<50){
L_Island_Flag=0;
}
if(R_Island_Flag==1&&L_straight_point_num<50){
R_Island_Flag=0;
}
}
//判断是否更新到状态2
if(L_Island_Flag==1||R_Island_Flag==1){
if(L_Island_Flag==1){
//当找到拐点,且其在打角行下时
if(L_inflexion_point.flag==1&&L_inflexion_point.row>angle_line-10){
L_Island_Flag=2;
}
else{
//当没有找到拐点时,如果找到左下最外点并且该点在打角行下时
if(L_The_Outer_Point_1.flag==1&&L_The_Outer_Point_1.row>angle_line-10){
L_Island_Flag=2;
}
}
}
if(R_Island_Flag==1){
//当找到拐点,且其在打角行下时
if(R_inflexion_point.flag==1&&R_inflexion_point.row>angle_line-10){
R_Island_Flag=2;
}
else{
//当没有找到拐点时,如果找到左下最外点并且该点在打角行下时
if(R_The_Outer_Point_1.flag==1&&R_The_Outer_Point_1.row>angle_line-10){
R_Island_Flag=2;
}
}
}
}
//判断是否更新到状态4,
//没有按顺序,但状态3的判断顺序不能换
if(L_Island_Flag==3||R_Island_Flag==3){
if(L_Island_Flag==3){
if(L_The_Outer_Point_1.row>60&&L_inflexion_up_point.flag==1){
L_Island_Flag=4;
}
}
if(R_Island_Flag==3){
if(R_The_Outer_Point_1.row>60&&R_inflexion_up_point.flag==1){
R_Island_Flag=4;
}
}
}
//判断是否更新到状态3
//下拐点消失时
if(L_Island_Flag==2||R_Island_Flag==2){
if(L_Island_Flag==2){
if(L_The_Outer_Point_1.row<angle_line-10&&R_straight_point_num>=50){
L_Island_Flag=3;
}
}
if(R_Island_Flag==2){
if(R_The_Outer_Point_1.row<angle_line-10&&L_straight_point_num>=50){
R_Island_Flag=3;
}
}
}
//判断是否更新到状态5
//环岛内
if(L_Island_Flag==4||R_Island_Flag==4){
if(L_Island_Flag==4){
if(R_The_Outer_Point_2.col<40){
L_Island_Flag=5;
}
}
if(R_Island_Flag==4){
if(L_The_Outer_Point_2.col>COL-40){
R_Island_Flag=5;
}
}
}
//判断是否更新到状态6
//将出环岛
if(L_Island_Flag==5||R_Island_Flag==5){
if(L_Island_Flag==5){
if(R_inflexion_point.flag==1&&(R_inflexion_point.col>120||R_inflexion_point.row>60)){
L_Island_Flag=6;
}
}
if(R_Island_Flag==5){
if(L_inflexion_point.flag==1&&(L_inflexion_point.col<68||L_inflexion_point.row>60)){
R_Island_Flag=6;
}
}
}
//判断是否更新到状态7
if(L_Island_Flag==6||R_Island_Flag==6){
if(L_Island_Flag==6){
if(L_inflexion_up_point.flag==1){
L_Island_Flag=7;
}
}
if(R_Island_Flag==6){
if(R_inflexion_up_point.flag==1){
R_Island_Flag=7;
}
}
}
//判断是否更新到状态8
if(L_Island_Flag==7||R_Island_Flag==7){
if(L_Island_Flag==7){
if(L_The_Lowest_Point.row<100&&L_The_Lowest_Point.col>30){
L_Island_Flag=8;
}
}
if(R_Island_Flag==7){
if(R_The_Lowest_Point.row<100&&R_The_Lowest_Point.col<COL-30){
R_Island_Flag=8;
}
}
}
//判断是否回到到状态0
if(L_Island_Flag==8||R_Island_Flag==8){
if(L_Island_Flag==8){
if(L_The_Lowest_Point.row>=116){
L_Island_Flag=0;
}
};
if(R_Island_Flag==8){
if(R_The_Lowest_Point.row>=116){
R_Island_Flag=0;
}
}
}
if(L_Island_Flag>=1||R_Island_Flag>=1){
if(L_straight_point_num>50&&R_straight_point_num>50){
L_Island_Flag=0;
R_Island_Flag=0;
}
}
这个说起来有点麻烦,主要是每辆车的摄像头高度,俯仰角啥的都不一样,所以不太好写每个状态的各个点位,我觉得就对照着各个状态找点去补就行。包括上面的判断,也只是个思路,具体用什么来判断,还是要自己去看图像慢慢选。
/*** 环岛补线 **********************************************************************************************/
if(L_Island_Flag==2||R_Island_Flag==2)
{
if(L_Island_Flag==2){
float l_k = (L_The_Outer_Point_2.col-L_The_Outer_Point_1.col)*1.0f/(L_The_Outer_Point_2.row-L_The_Outer_Point_1.row);
float l_b = L_The_Outer_Point_2.col - l_k * L_The_Outer_Point_2.row;
for(int i = L_The_Outer_Point_1.row;i>=L_The_Outer_Point_2.row;i--)
L_Line[i]=i*l_k+l_b;
}
if(R_Island_Flag==2){
float l_k = (R_The_Outer_Point_2.col-R_The_Outer_Point_1.col)*1.0f/(R_The_Outer_Point_2.row-R_The_Outer_Point_1.row);
float l_b = R_The_Outer_Point_2.col - l_k * R_The_Outer_Point_2.row;
for(int i = R_The_Outer_Point_1.row;i>=R_The_Outer_Point_2.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
if(L_Island_Flag==3||R_Island_Flag==3){
if(L_Island_Flag==3){
float l_k = (L_The_Outer_Point_1.col-0)*1.0f/(L_The_Outer_Point_1.row-ROW-1);
float l_b = L_The_Outer_Point_1.col - l_k * L_The_Outer_Point_1.row;
for(int i = ROW-1;i>=L_The_Outer_Point_1.row;i--)
L_Line[i]=i*l_k+l_b;
}
if(R_Island_Flag==3){
float l_k = (R_The_Outer_Point_1.col-COL-1)*1.0f/(R_The_Outer_Point_1.row-ROW-1);
float l_b = R_The_Outer_Point_1.col - l_k * R_The_Outer_Point_1.row;
for(int i = ROW-1;i>=R_The_Outer_Point_1.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
if(L_Island_Flag==4||R_Island_Flag==4){
if(L_Island_Flag==4){
//如果找到左上拐点就用左上拐点
if(0){
float l_k = (L_inflexion_up_point.col-R_Line[118])*1.0f/(L_inflexion_up_point.row-118);
float l_b = L_inflexion_up_point.col - l_k * L_inflexion_up_point.row;
for(int i = 118;i>=L_inflexion_up_point.row;i--)
R_Line[i]=i*l_k+l_b;
}else{
float l_k = (L_Island_Highest_Point.col+40-R_Line[118])*1.0f/(L_Island_Highest_Point.row-118);
float l_b = L_Island_Highest_Point.col+40 - l_k * L_Island_Highest_Point.row;
for(int i = 118;i>=L_Island_Highest_Point.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
if(R_Island_Flag==4){
//如果找到左上拐点就用左上拐点
if(0){
float l_k = (L_inflexion_up_point.col-R_Line[118])*1.0f/(L_inflexion_up_point.row-118);
float l_b = L_inflexion_up_point.col - l_k * L_inflexion_up_point.row;
for(int i = 118;i>=L_inflexion_up_point.row;i--)
R_Line[i]=i*l_k+l_b;
}else{
float l_k = (R_Island_Highest_Point.col-40-L_Line[118])*1.0f/(R_Island_Highest_Point.row-118);
float l_b = R_Island_Highest_Point.col-40 - l_k * R_Island_Highest_Point.row;
for(int i = 118;i>=R_Island_Highest_Point.row;i--)
L_Line[i]=i*l_k+l_b;
}
}
}
if(L_Island_Flag==6||R_Island_Flag==6){
if(L_Island_Flag==6){
if(R_The_Outer_Point_1.col>100){
float l_k = (L_Island_Highest_Point.col+22-R_The_Outer_Point_1.col)*1.0f/(L_Island_Highest_Point.row-R_The_Outer_Point_1.row);
float l_b = L_Island_Highest_Point.col+22 - l_k * L_Island_Highest_Point.row;
for(int i = R_The_Outer_Point_1.row;i>=L_Island_Highest_Point.row;i--)
R_Line[i]=i*l_k+l_b;
}else{
float l_k = (L_Island_Highest_Point.col+22-R_Line[115])*1.0f/(L_Island_Highest_Point.row-115);
float l_b = L_Island_Highest_Point.col+22 - l_k * L_Island_Highest_Point.row;
for(int i = 115;i>=L_Island_Highest_Point.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
if(R_Island_Flag==6){
if(L_The_Outer_Point_1.col<COL-100){
float l_k = (R_Island_Highest_Point.col-22-L_The_Outer_Point_1.col)*1.0f/(R_Island_Highest_Point.row-L_The_Outer_Point_1.row);
float l_b = R_Island_Highest_Point.col-22 - l_k * R_Island_Highest_Point.row;
for(int i = L_The_Outer_Point_1.row;i>=R_Island_Highest_Point.row;i--)
L_Line[i]=i*l_k+l_b;
}else{
float l_k = (R_Island_Highest_Point.col-22-L_Line[115])*1.0f/(R_Island_Highest_Point.row-115);
float l_b = R_Island_Highest_Point.col-22 - l_k * R_Island_Highest_Point.row;
for(int i = 115;i>=R_Island_Highest_Point.row;i--)
L_Line[i]=i*l_k+l_b;
}
}
}
if(L_Island_Flag==7||R_Island_Flag==7){
if(L_Island_Flag==7){
float l_k = (L_inflexion_up_point.col-L_Line[115])*1.0f/(L_inflexion_up_point.row-115);
float l_b = L_inflexion_up_point.col - l_k * L_inflexion_up_point.row;
for(int i = 115;i>=L_inflexion_up_point.row;i--)
L_Line[i]=i*l_k+l_b;
}
if(R_Island_Flag==7){
float l_k = (R_inflexion_up_point.col-R_Line[115])*1.0f/(R_inflexion_up_point.row-115);
float l_b = R_inflexion_up_point.col - l_k * R_inflexion_up_point.row;
for(int i = 115;i>=R_inflexion_up_point.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
if(L_Island_Flag==8||R_Island_Flag==8){
if(L_Island_Flag==8){
float l_k = (L_The_Lowest_Point.col-L_Line[115])*1.0f/(L_The_Lowest_Point.row-115);
float l_b = L_The_Lowest_Point.col - l_k * L_The_Lowest_Point.row;
for(int i = 115;i>=L_The_Lowest_Point.row;i--)
L_Line[i]=i*l_k+l_b;
}
if(R_Island_Flag==8){
float l_k = (R_The_Lowest_Point.col-R_Line[115])*1.0f/(R_The_Lowest_Point.row-115);
float l_b = R_The_Lowest_Point.col - l_k * R_The_Lowest_Point.row;
for(int i = 115;i>=R_The_Lowest_Point.row;i--)
R_Line[i]=i*l_k+l_b;
}
}
通过再次回到边界前的最外侧点的位置以及这个点附近线段的斜率来判断。
/*** 三岔路 *****************************************************************************************************************************************/
if((L_The_Outer_Point_1.row>ROW-40)&&(R_The_Outer_Point_1.row>ROW-40)&&(junction_L==false&&junction_R==false)&&(L_The_Outer_Point_2.col<100&&R_The_Outer_Point_2.col>80)&&if_search_Junction==0&&if_search_Junction_1==0){
if(abs(L_The_Outer_Point_1.row-R_The_Outer_Point_1.row)<14&&(R_inflexion_up_point.flag==0&&L_inflexion_up_point.flag==0)&&(L_Island_Flag==0&&R_Island_Flag==0)&&junction_Flag==false){
L_Junction_Slope = (1-L_The_Outer_Point_1.col)*1.0f/(L_The_Outer_Point_1_border_Row-L_The_Outer_Point_1.row);
R_Junction_Slope = (187-R_The_Outer_Point_1.col)*1.0f/(R_The_Outer_Point_1_border_Row-R_The_Outer_Point_1.row)
if(((L_Junction_Slope<-0.6&&L_Junction_Slope>-2.1)||(R_Junction_Slope>0.6&&R_Junction_Slope<2.1))||
((L_Junction_Slope>0.6&&L_Junction_Slope<2.1)||(R_Junction_Slope<-0.6&&R_Junction_Slope>-2.1))){
//判断为三岔
//判断是进三岔还是出
junction_num++;
junction_Flag=true;
从中间向上找到第一个边界点,也就是上图的2号点。将这个点和1号点连线
距离比赛过了挺久了,很多东西也都不记得了。以上都是本实验室的一些思路,难免会有局限性,也大家有什么好的思路都可以评论或私信分享一下,其中有好的思路的话我也会在这里更新的。