前言

寒假在家无聊刷着知乎,看到了上交的智能车开源方案,几个月前这个方案有帮助到我拿到了十六届智能车的国一。前段时间网上还在讨论开源对程序员来说是好还是坏,作为开源的受益者,我的回答肯定是好,在我看来,智能车竞赛,本就是一个探索学习的过程。所以我写下在智能车图像处理上的一些个人想法与大家共同学习,也是对两年智能车生涯的一个记录。
(由于电脑硬盘损坏,很多智能车相关的图片都没了,以下的一些图片部分是网上找的,侵删)
第二篇:智能车八邻域图像算法_二

一.当初选择用八邻域的原因

1.令人困扰的光线

做过智能车竞赛的车友们应该都或多或少被反光的问题所困扰,即使是现在各赛区的举办方都注意到光线问题,在场馆布置时都希望尽量减小光线的干扰,但是把这个问题抛给举办方总是让人感觉不太安心。
在这里插入图片描述

其实反光无非就是光线让赛道与赛道边界之间变得模糊。这类光线问题有两种情况,第一种就是光线实在太强烈,哪怕是把人眼放在摄像头的角度也无法辨认边界位置,这种似乎就很难处理了;另一种情况是,光线把赛道和赛道边界同时照亮,此时二者之间还是有分界线的,但是赛道边界已经不再是单纯的黑色了,用传统的二值化处理时,很可能这个边界会大于计算出来的阈值,然后赛道边界和赛道就都会被处理成白色,无法分辨。

2.差比和图像处理

在上文提到的光学问题的两种情况里,其实第一种情况比较少,即使是在某些环境里有,也可以通过调整摄像头图像设置来尝试解决,修改曝光时间,图像帧率,图像增益都会对解决这个问题有影响。

//需要配置到摄像头的数据
int16_t MT9V032_CFG[CONFIG_FINISH][2]=
{
    {AUTO_EXP,           0},   //自动曝光设置      范围1-63 0为关闭 如果自动曝光开启  EXP_TIME命令设置的数据将会变为最大曝光时间,也就是自动曝光时间的上限
                              //一般情况是不需要开启这个功能,因为比赛场地光线一般都比较均匀,如果遇到光线非常不均匀的情况可以尝试设置该值,增加图像稳定性
    {EXP_TIME,          300}, //曝光时间          摄像头收到后会自动计算出最大曝光时间,如果设置过大则设置为计算出来的最大曝光值
    {FPS,               100},  //图像帧率          摄像头收到后会自动计算出最大FPS,如果过大则设置为计算出来的最大FPS
    {SET_COL,           COL}, //图像列数量        范围1-752     K60采集不允许超过188
    {SET_ROW,           ROW}, //图像行数量        范围1-480
    {LR_OFFSET,         0},   //图像左右偏移量    正值 右偏移   负值 左偏移  列为188 376 752时无法设置偏移    摄像头收偏移数据后会自动计算最大偏移,如果超出则设置计算出来的最大偏移
    {UD_OFFSET,         0},   //图像上下偏移量    正值 上偏移   负值 下偏移  行为120 240 480时无法设置偏移    摄像头收偏移数据后会自动计算最大偏移,如果超出则设置计算出来的最大偏移
    {GAIN,              63},  //图像增益          范围16-64     增益可以在曝光时间固定的情况下改变图像亮暗程度

    
    {INIT,              0}    //摄像头开始初始化
};

那么光线的问题主要在第二点,就是当光线不均匀导致赛道以及赛道边界同时变亮以及同时变暗时如何解决。这里可能很多人会用大津法,或是局部大津法。但我觉得这样的方法可能并非最好,主要是两点,一个是实际效果,一个是运行时间。其实大津法在面对这种局部光线不均匀的问题上的效果并不好,然后对全局运行大津法也比较费时间。局部大津法倒是可以解决这个问题,但是应该要分的很小才会有比较好的效果,我并没有用这个方法所以就不多说了,上交用的是这个,感兴趣的可以去看看上交的边线提取。
另外除了在算法上解决外,还有一种方法,就是加偏振片,把偏振片旋到一个合适的角度时,可以抵消掉反光的影响,但这时进入摄像的光线就少了,获得的图片就会很暗,这时候要么选择牺牲帧率增加进光量,要么牺牲车重用个灯把车前面照亮,我当时尝试这个方案时用了两个1W的小聚光灯分别把左右照亮,效果好像还行但是车太重了。
我曾经看到过逐飞科技的一篇推文,里面有他们对于解决这个问题的看法:电磁及摄像头(总钻风)寻迹算法浅析–逐飞科技。用过电磁循迹的车友应该对差比和不陌生,那么用在电磁处理的方法也可以用来解决光线不均匀导致的图像问题。差比和处理图像的大意可以看那篇推文,在此不再赘述。
在这里插入图片描述

这篇推文刚出来不久我就尝试了,在逐飞的推文里介绍的是对左右的点进行差比和,而且为了避免出现边界差距不大分辨不出而不用相邻点做差比和处理(这一点挺重要也很符合直觉,但是我在初次尝试的时候就用的相邻点,效果挺好,也就没有在乎这一点,后面也没有改动过,这似乎会有影响到后面图像处理的稳定性,后文再细说)。当时的效果好也不是用的左右点,我用的是一个点和其周围的8个点都进行差比和处理,然后相加再与阈值做比较,这个阈值的大小自己看效果调试即可。代码贴上:

void image_CBH(uint8 Data[ROW][COL], int16 Threshold_cbh)
{
	int16 row,column;
	int8 image_cbh;
	int8 cbh_ary[3][COL*2];

//	for(row=ROW-1;row>=1;row--){
//			for(column=1;column<COL;column++){
//					
//				
//					if(row<3||row>ROW-3||column<3||column<3||column>COL-3){
//							cbh_ary[2][column*2] = 100*(Data[row][column] - Data[row+1][column])/(Data[row][column] + Data[row+1][column]);
//							cbh_ary[2][column*2-1] = 100*(Data[row][column] - Data[row+1][column-1])/(Data[row][column] + Data[row+1][column-1]);
//							cbh_ary[2][column*2+1] = 100*(Data[row][column] - Data[row+1][column+1])/(Data[row][column] + Data[row+1][column+1]);
//							cbh_ary[1][column*2+1] = 100*(Data[row][column] - Data[row][column+1])/(Data[row][column] + Data[row][column+1]);

//							cbh_ary[1][column*2-1] = 100*(Data[row][column] - Data[row][column-1])/(Data[row][column] + Data[row][column-1]);
//							cbh_ary[0][column*2+1] = 100*(Data[row][column] - Data[row-1][column+1])/(Data[row][column] + Data[row-1][column+1]);
//							cbh_ary[0][column*2-1] = 100*(Data[row][column] - Data[row-1][column-1])/(Data[row][column] + Data[row-1][column-1]);
//							cbh_ary[0][column*2] = 100*(Data[row][column] - Data[row-1][column])/(Data[row][column] + Data[row-1][column]);

//							image_cbh=cbh_ary[0][column*2]+cbh_ary[1][column*2-1]+cbh_ary[1][column*2+1]
//												+cbh_ary[0][column*2-1]+cbh_ary[0][column*2+1]+cbh_ary[2][column*2-1]
//												+cbh_ary[2][column*2+1]+cbh_ary[2][column*2];
//					}else{

//							cbh_ary[0][column*2+1] = cbh_ary[2][column*2+1];
//							cbh_ary[0][column*2-1] = cbh_ary[2][column*2+1];
//							cbh_ary[0][column*2] = cbh_ary[2][column*2+1];

//							cbh_ary[2][column*2] = 100*(Data[row][column] - Data[row+1][column])/(Data[row][column] + Data[row+1][column]);
//							cbh_ary[2][column*2-1] = 100*(Data[row][column] - Data[row+1][column-1])/(Data[row][column] + Data[row+1][column-1]);
//							cbh_ary[2][column*2+1] = 100*(Data[row][column] - Data[row+1][column+1])/(Data[row][column] + Data[row+1][column+1]);

//							cbh_ary[1][column*2-1] = cbh_ary[1][column*2+1];

//							cbh_ary[1][column*2+1] = 100*(Data[row][column] - Data[row][column+1])/(Data[row][column] + Data[row][column+1]);

//							image_cbh=cbh_ary[0][column*2]+cbh_ary[1][column*2-1]+cbh_ary[1][column*2+1]
//												+cbh_ary[0][column*2-1]+cbh_ary[0][column*2+1]+cbh_ary[2][column*2-1]
//												+cbh_ary[2][column*2+1]+cbh_ary[2][column*2];
//					}

//					if(myabs(image_cbh)>Threshold_CBH)
//					{
//							image_buffer[row][column]=White;
//					}else
//					{
//							image_buffer[row][column]=Black;
//					}
//			}
//	}

//八个方向直接差比和
 for(row=ROW-2;row>=1;row--)
	{
      	for(column=1;column<=COL-2;column++)
			{
				cbh_ary[2][column*2]   = 100*(Data[row][column] - Data[row+1][column])/(Data[row][column] + Data[row+1][column]);
				cbh_ary[2][column*2-1] = 100*(Data[row][column] - Data[row+1][column-1])/(Data[row][column] + Data[row+1][column-1]);
				cbh_ary[2][column*2+1] = 100*(Data[row][column] - Data[row+1][column+1])/(Data[row][column] + Data[row+1][column+1]);
				cbh_ary[1][column*2+1] = 100*(Data[row][column] - Data[row][column+1])/(Data[row][column] + Data[row][column+1]);

				cbh_ary[1][column*2-1] = 100*(Data[row][column] - Data[row][column-1])/(Data[row][column] + Data[row][column-1]);
				cbh_ary[0][column*2+1] = 100*(Data[row][column] - Data[row-1][column+1])/(Data[row][column] + Data[row-1][column+1]);
				cbh_ary[0][column*2-1] = 100*(Data[row][column] - Data[row-1][column-1])/(Data[row][column] + Data[row-1][column-1]);
				cbh_ary[0][column*2] = 100*(Data[row][column] - Data[row-1][column])/(Data[row][column] + Data[row-1][column]);

				image_cbh=cbh_ary[0][column*2]+cbh_ary[1][column*2-1]+cbh_ary[1][column*2+1]
									+cbh_ary[0][column*2-1]+cbh_ary[0][column*2+1]+cbh_ary[2][column*2-1]
									+cbh_ary[2][column*2+1]+cbh_ary[2][column*2];
				
				if(myabs(image_cbh)>Threshold_cbh)
				{
					image_buffer[row][column]=Black;
				}else
				{
					image_buffer[row][column]=White;
				}
			}
			
		}
		
		for(int i=0;i<ROW;i++){
			image_buffer[i][0]=Black;
			image_buffer[i][COL-1]=Black;
		}
		
		for(int j=0;j<COL;j++)
		{
			image_buffer[0][j]=White;
			image_buffer[ROW-1][j]=White;
		}
}

这个代码当时只是测试用,现在也不记得有什么问题了,仅用来给车友提供个思路。

3.难以接受的计算量

我们当时处理的是120*188的图像,除了边界外的每个点都要与其周围的8个点作差比和处理,当时跑在TC264单片机上,好像是用了30ms,光一个预处理用了30ms显然是无法接受的。这里还是会有一些优化的办法,比如太亮或者太黑的点就不做这样的处理;不处理8个点而是处理上下左右四个点等,但是处理的时间还是让人无法接受,当时觉得不如花时间研究研究电机更好地控速以及上位机传输图像。所以这个方法也就被我们丢弃了很久,然后有一天知道了八邻域算法,就觉得这个寻边界的方法正好可以解决这个计算量大的问题,当时已经是4月多了,我还是准备尝试一下。

二.八邻域算法

关于八邻域的算法我当时借鉴的这一篇博客:图像处理(五):八邻域边缘跟踪与区域生长算法。不了解八邻域的先搜索了解一下,这里主要讲如何在运用在智能车上,由于电脑硬盘损坏,以前的小demo都没有了,只有最终的比赛代码,所以就用伪代码来讲解一下。
首先我的图像坐标系的原点是在左上角,向下行越来越大,向右列越来越大;
然后左右是分开扫线的,先扫左还是先扫右暂时都可以,这里以左边界为例;
直接处理原图像数组,只需要开一个image_buffer[120*188]用来存边界点。
我们从图像的左下角开始向上寻找左边界,比如图像的左下角是这样的
在这里插入图片描述
我们所希望的寻边界的路径大概是这样的
在这里插入图片描述

先定义一个for循环,就从最下方的中间点开始,先列向左,到边界后行向上,注意我的原点是在图像左上方

for(Current_Col=StartPoint_Left;Current_Col>=0;Current_Col--)
{
	if(1.是不是边界的判断)
	{
	while(3.周围八个点中有一个是边界)
	{
		2.八邻域扫线
	}
	}
	if(Current_Col==0||Current_Col==Again_R_Start_Col)
	{
		Current_Row-=1;
	}
}
  1. 是不是边界的判断就用当前点与周围八个点的差比和来做:
    注意图像边界的点(col=0、col=187、row=0)做特殊处理
bool _Point_CBH(uint8 row,uint8 column)
{
	int8 point_cbh_sum;
	if(row==ROW-1||row==0){
		return false;
	}
	
	if(column==0||column==COL-1||(saoxian_said==0&&column==Again_L_Start_Col)||(saoxian_said==1&&column==Again_R_Start_Col)){
		return true;
	}
	
	
	
#if (用不用八邻域)
	point_cbh_sum=100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column]);
//我这里用的×100,算力差的单片机可以选择左移7位,也就是×128
	if(myabs(point_cbh_sum)>Threshold_CBH)
	{
		return true;
	}else{
		return false;
	}
			 
#else
	if(mt9v03x_csi_image[row][column]<Threshold){
		return true;
	}else{
		return false;
	}
	
#endif
}
  1. 八邻域扫线
				tra_flag = true;
				//将边界点设为灰色
				image_buffer[Current_Row][Current_Col]=Left_Border_Point;
				//开始搜索
				while(tra_flag&&traverse_times1<300)
				{
					// 循环八次
					for (counts = 0; counts < 8; counts++)
					{
						// 防止索引出界
						if (curr_d >= 8)
						{
							curr_d -= 8;
						}
						if (curr_d < 0)
						{
							curr_d += 8;
						}
						
						Current_Point_Row = Current_Row+Pointdirections_L[curr_d][0];
						Current_Point_Col = Current_Col+Pointdirections_L[curr_d][1];
						
						//图像边界检测
						if((Current_Point_Row>0&&Current_Point_Row<ROW-1)&&
							(Current_Point_Col>=0&&Current_Point_Col<COL-1))
							{
								//找到下一个边界点
//								if(image_buffer[Current_Point_Row][Current_Point_Col]==Black)
//								{
								if(_Point_CBH(Current_Point_Row,Current_Point_Col)&&image_buffer[Current_Point_Row][Current_Point_Col]!=Left_Border_Point)
								{
									Current_Row=Current_Point_Row; //更新行
									Current_Col=Current_Point_Col; //更新列
									
									L_Line[Current_Row]=Current_Row<L_Highest_Row?Current_Col:L_Line[Current_Row];
											
									//扫线点数量限制 除非横向的点否则都会加一
									if(curr_d!=0&&curr_d!=4){
										L_Astrict_Num++;
									}
									if(L_Astrict_Num>=90){
										return Current_Row;
									}
									
									这一行是图像处理相关的,通过记录每一步的生长方向来分辨各个元素_L_Deal_Growth_Direction(curr_d);  
									
									last_curr_d=curr_d+4<7?curr_d+4:curr_d-4;  //更新上次方向
									curr_d -=2; //更新方向
									
									image_buffer[Current_Row][Current_Col]=Left_Border_Point;  //边线点赋值
									
									if(Current_Col!=0)
										sideline_points++;
									break;
								}
								//判断是否超出检测边界
								if(Current_Row<=minrow||Current_Col>=maxcol)
								{
									return Current_Row;
								}
								//如果找到的边界点足够多,就认为找到了边界
								if(sideline_points>15)
								{
									is_search_sideline=true;
								}
								//如果找到的边界点超过范围就退出
								if(sideline_points>200)
								{
									
									return Current_Row;
								}
								
								//防止大弯道再次到达底部扫线
								if(L_Highest_Row<=ROW-25&&Current_Row>=ROW-8)
									{
									return Current_Row;
								}
								
							}
							
						curr_d++;
					}
					
					if(counts==8)
					{
						curr_d = 0;
						tra_flag = false;
						Current_Col=Current_Col-1;
						is_counts_flag=1;
						
						if(is_search_sideline)
						{
							//返回断点行
							return Current_Row;
						}
						else
							{
							break;
						}
						
					}
					
					//如果在搜左边界时,当前行大于COL-10 ,就退出
					if((Current_Col>COL-10)||(Current_Col==0)){
						if(is_search_sideline)
						{ 
							L_The_Outer_Point_1.flag=1;
							L_The_Outer_Point_1_border_Row=Current_Row;
							if(Crossroad_Flag!=1&&L_Island_Flag!=2&&L_Island_Flag!=3&&L_Island_Flag!=4&&L_Island_Flag!=6&&L_Island_Flag!=7&&!junction_L){
								L_End_Point.row=Current_Row; 
								return Current_Row;
							}
						}
					}

					traverse_times1++;
				}
			}
  1. 上面定义的tra_flag就是是否继续巡线的标志

三.八邻域的优点与不足

这里的不足仅仅是我没能处理好,相信后面车友的会有更好的解决方法,我只是说说我遇到的一些不好解决的地方。

优点


处理速度快,我用的是NXP RT1064的主控,处理完一副120*188的图像得到偏差只需要0.3ms左右。
可以有生长方向这个信息,用这个信息可以在处理元素时可以给予很大帮助。
能解决一些光线问题,但解决这个问题的前提是你得先解决这个方法的不足之处。

不足


1.对图像的质量要求高。
这句话听起来像废话,我都要处理图像了,图像质量差怎么行对吧。
传统的大津法二值化处理完后就是非黑即白的世界了,在调试完之后,如果环境不怎么变的话,这个图像基本上是很稳定的,而且巡线的方式也并非是寻边界,即便是寻边界附近,也和直接爬边界有很大不同。所以传统大津法是允许图像的质量有一些不足,比如边界模糊,抖动,赛道上有脏点。对于八邻域算法来说,赛道边缘有渐变是很危险的,很容易低于阈值然后就爬到赛道外面去了,即便作很多的处理比如防止死循环,对某些点的生长方向给个限制等,都是指标不治本的方法。有的车友可能会问,赛道边界怎么会有渐变呢?其实路肩的影子就很容易造成这种渐变。这时或许可以用非相邻点作差比和来减小渐变的影响。但我最终没有用这个,具体效果如何也就不知道了。

2.如果左右都只扫一次线的话,处理三岔以及一些元素时中间一部分的边界无法获取
在这里插入图片描述
3.这里在合适的地方放个种子也能解决,但是不太方便,我是直接补线的。

不稳定。具体怎么个不稳定法我也不好形容,反正稳定的车千篇一律,不稳定的车各有不同。导致这种不稳定的原因应该是我选取的范围太小了,只有相邻的8个点,可能是由于我的高帧率弥补了不稳定的因素,最终的结果还是令我满意的。我是觉得如果用这种扫线的方式,前期需要话很大的功夫去把边界调稳定,把图像显示到屏幕上,图像的边界要很稳定才能做下一步的元素判断,而且最好是有一个图像上位机,这玩意可以帮你快速定位问题。这里顺便再贴上我显示图像的函数,可以同时显示原图像和边界线。

/****展示原始图像以及边界点***************************
此函数展示mt9v03x_csi_image数组,同时展示image_buffer里的特殊点
图像的长宽均与mt9v03x_csi_image相同。
*/

void Show_ImageAndLine__Lcd_Isp114()
{
	uint32 i,j;
	uint8 *p = mt9v03x_csi_image[0];
	uint8 *p1 = image_buffer[0];
	uint16 color = 0;
	uint16 temp = 0;
	uint16 temp1 = 0;
	
	uint16 coord_x = 0;
	uint16 coord_y = 0;
	
	coord_x = COL>IPS114_X_MAX?IPS114_X_MAX:COL;
	coord_y = ROW>IPS114_Y_MAX?IPS114_Y_MAX:ROW;
	ips114_set_region(0,0,coord_x-1,coord_y-1);
	
	for(j=0;j<coord_y;j++)
	{
		for(i=0;i<coord_x;i++)
		{
			temp1 = *(p1+j*COL+i*COL/coord_x);       //读取像素点
			if(temp1==Left_Border_Point||temp1==Right_Border_Point)
			{
				if(temp1==Left_Border_Point){
					color= BLUE;
				}
				else
				{
					color=RED;
				}
				ips114_writedata_16bit(color);
				*(p1+j*COL+i*COL/coord_x)=0;
			}
			else
			{
				temp = *(p+j*COL+i*COL/coord_x);//读取像素点
				color=(0x001f&((temp)>>3))<<11;
				color=color|(((0x003f)&((temp)>>2))<<5);
				color=color|(0x001f&((temp)>>3));
				ips114_writedata_16bit(color); 
			}
			

			}
	}
}

小结

八领域只是生长算法的一种,类似的生长算法现在已经挺多了,很多学校的技术报告都或少提到了生长算法。我比赛时的图像算法也不过2500行左右,与很多学校动辄上万行的图像算法相比算“轻量级”的了,代码量少通常也意味着裕度小,而智能车最不能容忍的就是裕度小了。这里只是介绍用八邻域来获得赛道边界,之后如何用生长方向来处理元素,我有空再写。
第二篇:智能车八邻域图像算法_二

参考

电磁及摄像头(总钻风)寻迹算法浅析–逐飞科技

图像处理(五):八邻域边缘跟踪与区域生长算法

部分代码

伪代码,不能直接运行,只提供参考

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 } }; //八邻域搜索方向数组

int Current_Row=0;//当前行
int Current_Col=0;//当前列

/**十字相关***以及与环岛共用的标志**************/
int8 Crossroad_Flag=0;
Image_Point L_inflexion_point;      //左拐点结构体
Image_Point R_inflexion_point;
Image_Point Maybe_L_inflexion_point;   //可能是左拐点的点  (区别在于左侧是否会有较多的左方向生长的点)
Image_Point Maybe_R_inflexion_point;

Image_Point L_inflexion_up_point;      //左上拐点结构体
Image_Point R_inflexion_up_point;
Image_Point Maybe_L_inflexion_up_point;   //可能是左上拐点的点  (区别在于上侧是否会有较多的上方向生长的点)
Image_Point Maybe_R_inflexion_up_point;

/**环岛相关******************/
int8 L_Island_Flag=0;
int8 R_Island_Flag=0;
Image_Point L_Island_Highest_Point;   //左侧30列里的最高点
Image_Point R_Island_Highest_Point;   //右侧30列里的最高点

/**车库相关******************/
int8 L_Garage=0;
int8 R_Garage=0;
int8 Garage_num=0;
bool Out_Garage_Flag=true;
bool In_Garage_Flag=false;


/***三岔路相关*********************/
float L_Junction_Slope=0;
float R_Junction_Slope=0;
int junction_num=0;
bool junction_L=false;
bool junction_R=false;
bool junction_Flag=false;
int junction_row=0;
int junction_col=60;
int8 last_junction=0; //0为左 1为右
int8 all_Junction_num=1;
int8 Junction_ary[8];
uint32 Junction_time=0;
uint32 Junction_time_1=0;
int8 if_search_Junction=0;
int8 if_search_Junction_1=0;

/***AprilTag相关*********************/
int8 AprilTag_flag=0;
Image_Point AprilTag_first;
uint32 AprilTag__Time_1=0; //识别到AprilTag后滑行的距离
uint32 AprilTag__Time_2=0; //识别到AprilTag后滑行的距离
uint32 AprilTag__Time_all=0; //识别到AprilTag后滑行的距离
int8 AprilTag_num=10;
int8 Same_AprilTag_num=0;

/**扫线相关*****************/
bool tra_flag=true;  //搜索标志位
bool is_search_sideline=false;  //是否搜索到边界线过
int Again_L_Start_Col=0;
int Again_R_Start_Col=187;
int8 Sweep_line_number=0;       //每一帧扫线次数
Image_Point L_Start_Point;      //左起始点
Image_Point L_End_Point;        //左结束点
Image_Point R_Start_Point;   
Image_Point R_End_Point;
Image_Point L_The_Outer_Point_1;   //扫线后第一次达到边界前的最靠右的点
Image_Point L_The_Outer_Point_2;   //整个边界中最靠右的点
Image_Point R_The_Outer_Point_1;  //扫线后第一次达到边界前的最靠左的点
Image_Point R_The_Outer_Point_2;  //整个边界中最靠左的点
int8 R_DOWN_Point_Num=0;
int8 L_DOWN_Point_Num=0;
int8 L_Astrict_Num=0;     //扫线点数量限制 除非横向的点否则都会加一
int8 R_Astrict_Num=0;
int8 L_straight_flag=0;
int8 R_straight_flag=0;
int8 L_Last_Frame_Row=0;      //左边最后一个是边框的边界点
int8 R_Last_Frame_Row=0;      //左边最后一个是边框的边界点
int8 L_First_NoFoame_Row=ROW-1;   //左边第一个非边框的边界点
int8 R_First_NoFoame_Row=ROW-1;   //左边第一个非边框的边界点
int8 L_The_Outer_Point_1_border_Row=ROW-1;  //扫线后第一次达到边界的点
int8 R_The_Outer_Point_1_border_Row=ROW-1;  //扫线后第一次达到边界的点
Image_Point L_The_Lowest_Point;  //左边非边界最低点
Image_Point R_The_Lowest_Point;


int8 is_counts_flag=0;
int8 saoxian_said=0;    //0 左边 1右边

int8 CBH_Point_Num=0;      //只为斑马线以及AprilTag服务

uint8 L_Line[ROW];
uint8 R_Line[ROW];
uint8 Center_Line[ROW];
int8 Valid_Row;                 //有效行
int8 L_Highest_Row;             //目前扫过的最高行
int8 R_Highest_Row; 
int8 Image_Differ;
int8 Last_Image_differ;
uint8 angle_line=ROW-53;
/***对点进行差比和************
内部调用
*/
bool _Point_CBH(uint8 row,uint8 column)
{
	int8 point_cbh_sum;
	if(row==ROW-1||row==0){
		return false;
	}
	
	if(column==0||column==COL-1||(saoxian_said==0&&column==Again_L_Start_Col)||(saoxian_said==1&&column==Again_R_Start_Col)){
		return true;
	}
	
	
	
#if (0==if_Otsu)
	point_cbh_sum=100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row+1][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row+1][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column+1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column+1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column-1])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column-1])
								+100*(mt9v03x_csi_image[row][column] - mt9v03x_csi_image[row-1][column])/(mt9v03x_csi_image[row][column] + mt9v03x_csi_image[row-1][column]);
	if(myabs(point_cbh_sum)>Threshold_CBH)
	{
		return true;
	}else{
		return false;
	}
			 
#else
	if(mt9v03x_csi_image[row][column]<Threshold){
		return true;
	}else{
		return false;
	}
	
#endif
}
int16 SaoXian_Left(int StartPoint_Left,int maxrow,int minrow,int maxcol,int mincol)
{
	int traverse_times,traverse_times1=0;//定义遍历次数,防止死循环
	int Current_Point_Row=ROW-1, Current_Point_Col=StartPoint_Left;       //当前点
	int sideline_points=0;    //边界点数量
	int8 down_curr_d_num=0;   //向下方向次数
	tra_flag=true;   //搜索标志位

	int8 curr_d=0;      //搜索方向
	int8 last_curr_d=8;   //上一次搜索方向
	int8 counts;

	Current_Row=ROW-1;//当前行
	Current_Col=StartPoint_Left;//当前列
	saoxian_said=0;
	while(traverse_times<300)
	{
		
		for(Current_Col=StartPoint_Left;Current_Col>=0;Current_Col--)
		{
			
			//如果是边界点
//			if(image_buffer[Current_Row][Current_Col]==Black&&is_border_flag) //全局差比和时使用
//			{
			if(_Point_CBH(Current_Row,Current_Col))
			{
				
				//限制左右起始点的列数来解决左右错乱问题
				if(Current_Col>=5&&Current_Row<=ROW-5)
				{
					continue;
				}
				//设置下一帧的起始行
				if(R_Start_Point.flag==0){
					R_Start_Point.flag=1;
					R_Start_Point.col=Current_Col+100<COL?Current_Col+100:COL-15;
					if(R_Start_Point.col>=COL-15) R_Start_Point.col=COL-15;
				}
//				if(L_Start_Point.flag==0){
//					L_Start_Point.flag=1;
//					L_Start_Point.col=Current_Col+30<COL?Current_Col+30:COL-5;
//				}
				traverse_times1=0;
				tra_flag = true;
				//将边界点设为灰色
				image_buffer[Current_Row][Current_Col]=Left_Border_Point;
				//开始搜索
				while(tra_flag&&traverse_times1<300)
				{
					// 循环八次
					for (counts = 0; counts < 8; counts++)
					{
						// 防止索引出界
						if (curr_d >= 8)
						{
							curr_d -= 8;
						}
						if (curr_d < 0)
						{
							curr_d += 8;
						}
						
						Current_Point_Row = Current_Row+Pointdirections_L[curr_d][0];
						Current_Point_Col = Current_Col+Pointdirections_L[curr_d][1];
						
						//图像边界检测
						if((Current_Point_Row>0&&Current_Point_Row<ROW-1)&&
							(Current_Point_Col>=0&&Current_Point_Col<COL-1))
							{
								//找到下一个边界点
//								if(image_buffer[Current_Point_Row][Current_Point_Col]==Black)
//								{
								if(_Point_CBH(Current_Point_Row,Current_Point_Col)&&image_buffer[Current_Point_Row][Current_Point_Col]!=Left_Border_Point)
								{
									Current_Row=Current_Point_Row; //更新行
									Current_Col=Current_Point_Col; //更新列
									
									L_Line[Current_Row]=Current_Row<L_Highest_Row?Current_Col:L_Line[Current_Row];
									
									L_Highest_Row=L_Highest_Row<Current_Row?L_Highest_Row:Current_Row;  //更新最高行
									
									if(L_The_Outer_Point_1.col<=Current_Col&&L_The_Outer_Point_1.flag==0)   //更新第一次最外侧点
									{
										if(Current_Row<ROW-80||Current_Col>150){
											L_The_Outer_Point_1.flag=1;
										}
										else{
											L_The_Outer_Point_1.col=Current_Col;
									  		L_The_Outer_Point_1.row=Current_Row;
										}
									}
									
									if(L_The_Outer_Point_2.col<=Current_Col)   //更新最外侧点
									{
										L_The_Outer_Point_2.col=Current_Col;
										L_The_Outer_Point_2.row=Current_Row;
									}
									
									if(L_Island_Highest_Point.row>=Current_Row&&L_Island_Highest_Point.flag==0&&Current_Col!=0){  //目前只为环岛服务
										if(Current_Col>60||(Current_Row<60&&L_The_Outer_Point_1.flag==0&&L_The_Outer_Point_1.col==0)){
											
											L_Island_Highest_Point.flag=1;
										}else{
											L_Island_Highest_Point.row=Current_Row;
											L_Island_Highest_Point.col=Current_Col;
										}
									}
									
									if(Current_Col==0||Current_Col==Again_L_Start_Col){                           //更新左边最后一个是边框的边界点
										L_Last_Frame_Row=Current_Row;
									}
									
									if(L_First_NoFoame_Row==ROW-1&&Current_Col>0){
										L_First_NoFoame_Row=Current_Row;
									}
									
									if(Current_Col!=0&&Current_Row>=L_The_Lowest_Point.row){
										L_The_Lowest_Point.row=Current_Row;
										L_The_Lowest_Point.col=Current_Col;
									}
									
									//扫线点数量限制 除非横向的点否则都会加一
									if(curr_d!=0&&curr_d!=4){
										L_Astrict_Num++;
									}
									if(L_Astrict_Num>=90){
										return Current_Row;
									}
									
									_L_Deal_Growth_Direction(curr_d);  
									
									last_curr_d=curr_d+4<7?curr_d+4:curr_d-4;  //更新上次方向
									curr_d -=2; //更新方向
									
									image_buffer[Current_Row][Current_Col]=Left_Border_Point;  //边线点赋值
									
									if(Current_Col!=0)
										sideline_points++;
									break;
								}
								//判断是否超出检测边界
								if(Current_Row<=minrow||Current_Col>=maxcol)
								{
									return Current_Row;
								}
								//如果找到的边界点足够多,就认为找到了边界
								if(sideline_points>15)
								{
									is_search_sideline=true;
								}
								//如果找到的边界点超过范围就退出
								if(sideline_points>200)
								{
									
									return Current_Row;
								}
								
								//防止大弯道再次到达底部扫线
								if(L_Highest_Row<=ROW-25&&Current_Row>=ROW-8)
									{
									return Current_Row;
								}
								
							}
							
						curr_d++;
					}
					
					if(counts==8)
					{
						curr_d = 0;
						tra_flag = false;
						Current_Col=Current_Col-1;
						is_counts_flag=1;
						
						if(is_search_sideline)
						{
							//返回断点行
							return Current_Row;
						}
						else
							{
							break;
						}
						
					}
					
					//如果在搜左边界时,当前行大于COL-10 ,就退出
					if((Current_Col>COL-10)||(Current_Col==0)){
						if(is_search_sideline)
						{ 
							L_The_Outer_Point_1.flag=1;
							L_The_Outer_Point_1_border_Row=Current_Row;
							if(Crossroad_Flag!=1&&L_Island_Flag!=2&&L_Island_Flag!=3&&L_Island_Flag!=4&&L_Island_Flag!=6&&L_Island_Flag!=7&&!junction_L){
								L_End_Point.row=Current_Row; 
								return Current_Row;
							}
						}
					}

					traverse_times1++;
				}
			}else
			{
//				image_buffer[Current_Row][Current_Col]=0;
			}
			
			if(Current_Col==0||Current_Col==Again_R_Start_Col)
			{
				if(is_search_sideline)
				{ 
					if(Crossroad_Flag!=1&&L_Island_Flag!=2&&L_Island_Flag!=3&&L_Island_Flag!=4&&L_Island_Flag!=6&&L_Island_Flag!=7&&!junction_L)
					{
						L_End_Point.row=Current_Row; 
						return Current_Row;
					}
					
				}
				Current_Row-=2;
			}
		}
		traverse_times++;
		
	}
	return 0;
}