智能汽车的八邻域图像算法
前言
上一篇讲过使用八邻域获取轨道边界:智能汽车八邻域图像算法
当轨道边界已经稳定获得时,可以进行元素识别以获得偏差。
以下是我的一些想法。
早在前两年,CSDN上就有一篇关于八邻域如何处理元素的文章第十五届全国大学生智能汽车竞赛-双车组三轮图像处理总结,写得很好,我当时也参考了这篇文章,但是我觉得不是很详细,所以我这里想写得详细一些。
免责声明:仅提供思路,不保证具体效果,^^。
1.一些想法
我的想法是在扫线的过程中同时得到一些我想要的关键信息,比如各个关键点,边缘的形状等等。
这种情况下,不需要再对边缘进行处理,扫描后进行元素判断即可。
以十字架的拐点为例:
这个拐点是一个重要的点,无论是判断十字架还是十字架的补足,都需要用到它。
有的同学可能会认为这是左边框的最外点,很容易得到。
那么下面的情况肯定不是最外的一点。
可以说,第二次到达图像最左侧之前的最外点就是这个拐点。
事实上,当它倾斜成十字架时(没有图片),它看起来像这样:
这还不是最外面的一点。
当所有的边缘都被扫过时,可以通过斜率来判断该点。也就是说,满足三个条件:
该点上方的线段是一条直线;该点下方的线段是一条直线;两条线段的斜率对应一个角度。
这种判断还是比较稳的,在弯道不误判的情况下,能够找出这样的转折点。缺点是计算量有点大。
按照上面的思路,就是只要能判断出以上三个条件,就可以判断出拐点,然后就可以利用增长方向的信息了。
二、使用增长方向
1.点的生长方向的表示
我觉得这个增长方向应该不用再介绍了。
上面这个图是扫描左边界时,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。
2. 用点的生长方向来做初步判断
或者以上面的交叉拐点为例:
首先是直线的判断。由于相机的畸变,直线实际上是这样的:
直线基本由以下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;
}
}
}
以这种方式寻找拐点并不是很稳定。那为什么不稳定的时候还要用呢?说说我对稳定性和不稳定性的理解。
3.不稳定的点与稳定的点
这里的不稳定性意味着可能不是每一帧都能准确找到这个点,但是在小车移动的过程中,总会有一帧找到这个点。
稳定点是在正常情况下每一帧都能准确找到的点。
因此,不稳定点可以作为判断的依据,稳定点可以作为补线的依据。
不稳定点通常具有十字的上拐点和下拐点。
稳定点是:
- 第一个非边界点
- 再次回到边界前的最外点
- 完整边界的最外点
- 再次回到边界后的第一个非边界点
- xx列以内的最高点/低点
…
三、元素判断
我的建议是用状态机来处理像环形交叉路口、十字路口、三叉戟等元素,这样思路比较清晰,后续如果有问题可以快速定位问题。
1.十字
1. 判断
同时可以找到两个拐点进行判断,并根据图像的情况和运动的策略做出一些限制。
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;
}
}
2. 补线
如果能找到拐点,就用拐点来弥补。如果找不到,就用类似的稳定点补上。
相近的点应该是,再次回到边界前的最外侧点,或者说XX行以下的最外侧点。
然后两点线就完成了。
只要点击十字,基本没有问题。最头疼的就是斜交,在座的各位要仔细研究一下。
2.环岛
1. 判断
- 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;
}
}
2. 补线
这个说起来有点麻烦,主要是因为每辆车的摄像头高度和俯仰角都不一样,所以每个状态的各个点都不好写。我觉得每个州找点补上就够了。包括上面的判断,也只是一个想法,用什么来判断,还是自己看图慢慢选择。
/*** 环岛补线 **********************************************************************************************/
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;
}
}
3.三叉
1. 判断
通过再次回到边界前最外点的位置和该点附近线段的斜率来判断。
/*** 三岔路 *****************************************************************************************************************************************/
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. 补线
从中间向上找到第一个边界点,也就是上图的2号点。将这个点和1号点连线
概括
比赛已经很久了,很多事情我都不记得了。以上是本实验室的一些想法,难免有局限。如果您有什么好的想法,可以评论或私信分享。如果有什么好的想法,我会在这里更新。
文章出处登录后可见!