开源汇总写在下面
第18届全国大学生智能汽车竞赛四轮车开源讲解_Joshua.X的博客-CSDN博客
开源链接写在下面
https://gitee.com/joshua_xu/the-18th-smartcar
https://gitee.com/joshua_xu/the-18th-smartcar
注:文章中所有参数,角点范围之类的东西仅作为参考,实际参数,请根据需要实际调整!!!!!!!!!
实际上,智能车所有参数都需要根据你的实际情况进行调整,万万不可照搬不误!!!!!
一、巡线/找边界
当摄像头成功获取一帧图像,并且预处理(二值化)之后,我们最重要的事情就是获取赛道信息。其中最基本的就是赛道编边界信息,左边,右边,中线等基础数据。
事先声明,没有那种算法是完美的,只要算法能够得到足够多想要的信息,那么他就是好算法。
不同算法之间没有优劣之分,多少国赛选手仍跑着最简单的算法,这并不影响什么。
1.(双)最长白列
首先介绍我使用的(双)最长白列法。
首先获取到一张二值化图像,那么我先从最下面一列,对于每一列向上寻找白点,同时计数,遇到停止黑点就停止。
示意图如下:

最长白列寻找代码如下
volatile int White_Column[MT9V03X_W];//每列白列长度 //从左到右,从下往上,遍历全图记录范围内的每一列白点数量 for (j =start_column; j= 0; i--) { if(image_two_value[i][j] == IMG_BLACK) break; else White_Column[j]++; } }
这样我们就得到了每一列连续白点的个数分布直方数据。
随后,从左到右寻找白点数量最多的一列;同样的,从右到左也寻找白点数最多的一列。
有些同学可能会疑惑,既然是最多,那么只有一个,为什么要从左往右找,再从右往左找呢?
这其实不矛盾,同样是最多,有可能都是整幅图像的高,但是位置有可能一不样。参考上方的图。
参考代码如下:
//从左到右找左边最长白列 Longest_White_Column_Left[0] =0; for(i=start_column;i=start_column;i--)//从右往左,注意条件,找到左边最长白列位置就可以停了 { if (Longest_White_Column_Right[0]到这里我们已经找到了左最长白列,右最长白列,这两个值理论上是一样的,都是规定范围内的最长白列的长度值。
这个值还有一个名字,叫搜索截止行,做元素判断时非常有用,因为他代表了我们的视野,最长白列越长,说明我们看到越远,我们面前越有可能是直道,反之面前大概率是弯道。
Search_Stop_Line = Longest_White_Column_Left[0];//搜索截止行选取左或者右区别不大,他们两个理论上是一样的直道与弯道最长白列位置与长度的区别可以用来判断直道弯道
接下来就可以找边界了,从图像最下一行开始,到截止行结束。
从左最长白列向左找,找到"黑黑白",即白点为左边界;
从右最长白列向右找,找到“白黑黑”,即白点为右边界。.
注:黑黑白,白黑黑,均以数组生长方向为参考,详情见下图。
边线像素情况
当找到图像的边界仍然没有找到赛道边界,认为丢线,将屏幕边界放到赛道边界。如上图右下角即为丢线。
参考代码如下:
//相关数据使用前清零 Longest_White_Column_Left[0] = 0;//最长白列,[0]是最长白列的长度,[1】是第某列 Longest_White_Column_Left[1] = 0;//最长白列,[0]是最长白列的长度,[1】是第某列 Longest_White_Column_Right[0] = 0;//最长白列,[0]是最长白列的长度,[1】是第某列 Longest_White_Column_Right[1] = 0;//最长白列,[0]是最长白列的长度,[1】是第某列 for (i = MT9V03X_H - 1; i >=MT9V03X_H-Search_Stop_Line; i--)//常规巡线 {//从最下面一行,访问到我的有效是视野行 for (j = Longest_White_Column_Right[1]; j =MT9V03X_W-1-2)//没找到右边界,把屏幕最右赋值给右边界 { right_border = j; Right_Lost_Flag[i] = 1; //右丢线数组,丢线置1,不丢线置0 break; } } for (j = Longest_White_Column_Left[1]; j >= 0 + 2; j--)//往左边扫描 {//从最下面一行,访问到我的有效是视野行 if (image_two_value[i][j] ==IMG_WHITE && image_two_value[i][j - 1] == IMG_BLACK && image_two_value[i][j - 2] == IMG_BLACK)//黑黑白认为到达左边界 { left_border = j; Left_Lost_Flag[i] = 0; //左丢线数组,丢线置1,不丢线置0 break; } else if(j= 0; i--)//右边直接写在边上 { Right_Line[i]=MT9V03X_W-1; } } else if(Left_Island_Flag==1)//左环 { for (i = MT9V03X_H - 1; i >= 0; i--)//左边直接写在边上 { Left_Line[i]=0; //右边线线数组 } } } //debug使用,屏幕显示相关参数 // ips200_showint16(0,0, Longest_White_Column_Right[0]);//【0】是白列长度 // ips200_showint16(0,1, Longest_White_Column_Right[1]);//【1】是下标,第j列) // ips200_showint16(0,2, Longest_White_Column_Left[0]);//【0】是白列长度 // ips200_showint16(0,3, Longest_White_Column_Left[1]);//【1】是下标,第j列) }2.常规巡线
常规巡线是我17届智能车比赛时使用的,核心想法与最长白列差不多,起始点的处理有所不同,这里简单讲下。
首先在图像最下一行进行判断搜索起始点,默认是图像中间,如果是图像中间是黑的话,那就判断图像左1/4处是不是黑,右1/4处是不是黑,这样找到一个搜索起始点,从起始点开始,向左向右进行判断。同样的“黑黑白”白点是左边界,“白黑黑”白点是右边界,左加右除以2,计算出当前的中线位置,下一次的起始点是这一次的中点,就这样断搜下去,也叫“中线继承法”,同样可以找到边线。
先进行起始点判断,再进行中线继承巡边
这也是一种可行的办法,参考代码如下:
/* * 从下往上巡线,从中巡间往两边线 */ void Find_Boundry(void)//从中间往两边搜索中线 { int i,j; static int left_border=0,right_border=0,mid=MT9V03X_W/2,last_mid=MT9V03X_W/2; right_line_lost=0; left_line_lost=0;. //起始点合理性判断 if(image_two_value[MT9V03X_H-1][MT9V03X_W/2]==0x00)//屏幕中线是黑的话 { if(image_two_value[MT9V03X_H-1][MT9V03X_W/4]==0xff)//看看左1/4是不是白 { last_mid=MT9V03X_W/4;//更改搜索起始点 } else if(image_two_value[MT9V03X_H-1][MT9V03X_W/4*3]==0xff)//看看右1/4是不是白 { last_mid=MT9V03X_W/4*3;//更改搜索起始点 } } //开始巡边 for(i=MT9V03X_H-1;i>=0;i--)//从最底下往上扫描 { for(j=last_mid;j1;j--)//往左边扫描 { if(image_two_value[i][j]==0xff&&image_two_value[i][j-1]==0x00&&image_two_value[i][j-2]==0x00)//黑黑白认为到达左边界 { left_border=j; Left_Lost_Flag[i]=0; //左丢线数组,丢线置1,不丢线置0 break;//跳出,找到本行边界就没必要循环下去了 } else { left_border=j;//找到头都没找到边,就把屏幕最左右当做边界 Left_Lost_Flag[i]=1; //左丢线数组,丢线置1,不丢线置0 } } left_line_lost+=Left_Lost_Flag[i]; mid=(left_border+right_border)/2;//中线坐标 last_mid=mid;//中线查找开始点,方便下一次找中线 Left_Line [i]= left_border ; //左边线线数组 Right_Line[i]= right_border; //右边线线数组 Road_Wide[i]=Right_Line[i]-Left_Line [i]; } }3.八邻域
八邻域呢是智能车处理的一大热门话题,他可以以超快的速度处理出图像的边缘,我称之为“爬线”,因为只要起始点正确,他可以沿着图像的边缘(赛道边缘)快速爬出整条边界。
其中有灰度八邻域,二值化八邻域,核心原理都差不多,以当前点为中心,开一个九宫格,去看附近的格子变化趋势如何,找到下一个点,再以下一个点为中心,开九宫格,判断在下一个点的情况,如此类推,沿着边线爬完全图。
八邻域原理
八邻域搜线图示来自b站大佬:AI云归
具体的操作,建议参考b站大佬:AI云归,他开源了很多资料,大家可以前去学习
但是八邻域在智能车方面需要有不少限制,不然可能会爬到某个怪圈里面,把自己死卡,出不来。
4.逆透视
逆透视是好多大佬使用的方法,核心原理就是坐标系的转化,让一张图片从智能车视角,转化为上帝视角,这样有好处,赛道还原度可以非常高,并且在一些角点,弯道,可以进行角度,甚至是曲率的计算,非常有利于元素的处理。
智能车视角的十字回环
逆透视处理后看到的十字回环
关于逆透视方面,需要借助MATLAB工具进行标定,测量等,前期需要做一些准备工作,但是一但标定完成,使用起来效果还是很高端的。
关于逆透视方面,大家可以参考上交AuTop开源,以及b站大佬:__苏格拉没有底___,他们的开源,讲的很多很好。
5.迷宫法
迷宫法巡线是上交开源出的一套巡线法,在开源之后广受好评,大家也可参考
第16届智能车智能视觉组-上海交通大学AuTop战队开源汇总 - 知乎
二、赛道基本信息
1.标准赛道宽度
const uint8 Standard_Road_Wide[MT9V03X_H]=//标准赛宽 { 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98,100,102,104,106,108, 110,112,114,116,118,120,122,124,126,128, 130,132,134,136,138,140,142,144,146,148};这个是数据是在摄像头高度,角度都确定了之后,在长直道实测出来的数据,赛道每一行的理论赛宽。在后期坡道,横断,环岛的一些阶段,都非常有用。
宽度是用右边界-左边界得到的,单位是像素,需要各位实测得到。
2.边界分析数据
for (i = MT9V03X_H - 1; i >= 0; i--)//赛道数据初步分析 { if (Left_Lost_Flag[i] == 1)//单边丢线数 Left_Lost_Time++; if (Right_Lost_Flag[i] == 1) Right_Lost_Time++; if (Left_Lost_Flag[i] == 1 && Right_Lost_Flag[i] == 1)//双边丢线数 Both_Lost_Time++; if (Boundry_Start_Left == 0 && Left_Lost_Flag[i] != 1)//记录第一个非丢线点,边界起始点 Boundry_Start_Left = i; if (Boundry_Start_Right == 0 && Right_Lost_Flag[i] != 1) Boundry_Start_Right = i; Road_Wide[i]=Right_Line[i]-Left_Line[i]; }赛道信息是我们识别元素的关键,主要包括
- 左/右边界数组
- 左/右丢线数组,左/右丢线数
- 双边丢线数
- 左/右边界起始点
- 赛道截止行
- 当前赛道宽度
这些信息都是初步分析得出的数据,在不同的排列组合判断后,还需要根据这些信息进一步去获取角点判断,连续性判断,单调性判断,电磁信号判断等,在进行补线,划线等措施,去判断和处理不同的素。
注:上面并没有计算中线,我因为得到了左右边线,随时都可以用(左+右)/2得到中线。我是在控制方向的时候才去计算中线。
到这里,我们就成果的获取到了一帧图像的大部分数据,下一章将讲简单的控制,车子就可以跑起来了。
希望能够帮助到一些人。
本人菜鸡一只,各位大佬发现问题欢迎留言指出。