第十八届全国大学生智能车竞赛华东赛区落下帷幕,很幸运,我们最后获得了省一,但同时也很遗憾,我们在决赛时出现失误,没有机会冲击国赛。但也无所谓了,能够有机会去历练这些就已经很难得了······
谨以此篇博客分享一下我对智能车竞赛的感悟。
在开篇附上我的帅车,嘻嘻,特意给它喷了红蓝颜料,势必要成为华东最靓的小车。
今年的摄像头组限定由三轮控制,摄像头限高15cm,恢复了第十四届的横断路障和断路元素。从赛题要求就可以发现今年的摄像头完全可以用电磁跑,用电磁跑的好处就是可以不用考虑比赛举办方场地的光线原因。但是由于摄像头看得远,前瞻长,上限高,跑完全程速度也更快。我们组最后还是采用摄像头为主导,仅在断路那块使用了电磁。
首先谈谈今年摄像头组的元素:车库,S弯,圆环,横断路障,断路,坡道。
车库主要就是入库和出库。出库好办,实在不行,你就先让车子行驶一段距离,再固定占空比左转或者右转一定角度,就完成一次简单的出库了,但是这样有点麻烦,而且出库不是很丝滑哈哈。我使用的方法是利用图像单边出库,这里涉及到了一个名词:单边。那什么叫单边呢,在我的理解里就是令左右边线其中的任意一条边线全部丢边,然后让另一条边线加上或者减去一定值。简单来说就是固定给车子一个图像方向环偏差,让车子固定打角。为了让出库更加丝滑,可以分成几步出库。伪代码如下:
//出库第一步,初次固定打脚
for (j = 59; j > 3; j--)
{
int temp = (int)(79 - 0.495 * (59 - j));
if (temp > 79)
temp = 79;
if (temp < 0)
temp = 0;
图像右边界 = temp;
图像左边界 = 0;
图像中线 = 图像左边界加上右边界除以2;
}
//出库第二步,此时车子已经形式到斑马线处,再次固定打脚,增大打角力度
if((图像[42].左边线 == '0' &&图像[42].右边线 == '1')
||(图像[39].左边线 == '0' &&图像[39].右边线 == '1')
||(图像[45].左边线 == '0' &&图像[45].右边线 == '1'))
{
for (j = 59; j > 3; j--)
{
int what = (int)(79 - 1.5 * (59 - j);
if (what > 79)
what = 79;
if (what < 0)
what = 0;
图像右边界 = what;//顺时针,右出库
图像左边界 = 0;
图像中线 = 图像左边界加上右边界除以2;
}
chuku_flag1_l = 1;
}
// 出库第三步陀螺仪角度达到一定值结束出库
if((图像[42].左边线 == '1' &&图像[42].右边线 == '1')
||(图像[39].左边线 == '1' &&图像[39].右边线 == '1')
||(图像[45].左边线 == '1' &&图像[45].右边线 == '1'))&& Angle_z>500&& chuku_flag1_l == 1)
{
ChuKu_Flag_L = 0;
chuku_flag1_l = 0;
}
入库也比较简单,主要是识别斑马线,然后图像单边入库,陀螺仪角度积分,编码器路程积分就可以轻松入库。斑马线的区别于其他元素的特点主要是它是那一块黑白跳变点比较多,所以可以选择图像的固定行,计数黑白跳变点的个数来确定是否是斑马线。伪代码如下:
unsigned char RoadIsZebra(unsigned char *flag)
{
unsigned char i = 0, j = 0;
unsigned char times = 0;
unsigned char errL = 0, errR = 0;
unsigned char num = 0;
unsigned char pointL = 0, pointR = 0;
for(i = 20;i < 30;i++)
{
unsigned char black_blocks = 0;
unsigned char cursor = 0;//指向栈顶的游标
for(j=92; j<1; j--)//从最右边开始往左扫
{
if(二值化图像==0)
{
if(cursor >= 15)
{
cursor = 0;//当黑色元素过长就排除掉
}
else
{
cursor++;
}
}
else//遇到白色元素
{
if(cursor >= 4 && cursor <= 8)//4~8个像素点为黑表示黑胶带长度
{
black_blocks++;//记录黑色胶带数量
cursor = 0;//清零黑色像素点
}
else
{
cursor = 0;
}
}
}
if(black_blocks >= 5 && black_blocks <= 9)
times++;
}
if(times >= 4)
{
*flag = 1;
return 1;
}
else
return 0;
}
但是用这个方法容易在坡道顶端或者进出断路处误判,在断路处是由于光线原因可能会在进出断路处识别成为斑马线,在坡道处主要是由于坡道有一定高度,此时摄像头看到的图像信息就更加多了,图像中会有很多黑白噪点,此时也比较容易识别成斑马线,最后导致没有到入库的地方就已经入库了。所以,我的建议是可以在正式入库前加入一些其他元素的标志位进去,只有当全部元素都经过了才能入库,没有满足这个条件的话不入库。这样就可以防止错误入库了。接着就是入库,和出库思路差不多,利用图像单边,让车子拐一定角度后,编码器积分,当车子行驶一定距离后速度给0就成功入库了。
if(RuKu_Flag_R == 1)
{
XZ_Angle();
// ruku_jiaodu_flag = Angle_z;
// if(ruku_jiaodu_flag < -550)//-350
if(Angle_z < -550)
{
// Angle_z = 0;
rukustep1_r = 1;
}
}
if(rukustep1_r == 1)
{
if(编码器积分距离>=6800)
{
左右轮速度清零;
}
}
接着谈谈断路,断路比较好处理,进断路无非就是图象远端一片黑,图像近端一片白,出断路则相反,识别到进断路就关闭摄像头方向环,打开电磁循迹,识别到出断路则相反。
int Dianci_Flag = 0;//摄像头切换成电磁标志位
uint8 sum[6];//统计图像靠前处每行的白点数
uint8 sum1[6];//统计图像靠后处每行的白点数
void Duanlu_Test(void)
{
摄像头 和 电磁切换 ,如果是纯摄像头,注释这段程序
// 判定中间一些行是否全黑,如果全黑切换到电磁
int n = 0 , m = 0;
for(n=0;n<5;n++)
sum[n]=0;
for(n=20;n<25;n++) //图像靠前处判定
{
for(m=5;m<75;m++)
{
if(Pixle[n][m] == 1)
sum[n-20] = sum[n-20] + 1; //每行白点个数
}
}
for(n=0;n<5;n++)
sum1[n]=0;
for(n=45;n<50;n++) //图像靠后处判定
{
for(m=5;m<75;m++)
{
if(Pixle[n][m] == 1)
sum1[n-45] = sum1[n-45] + 1; //每行白点个数
}
}
if(Dianci_Flag == 0)
{
if( (sum[0]<4) && (sum[1]<4) && (sum[2]<4) && (sum[3]<4) && (sum[4]<4) ) //5行全黑(每行最多4个白点)
{
Dianci_Flag = 1; //切换到电磁
beep_on();
}
}
else
{
if( (sum1[0]>60) && (sum1[1]>60) && (sum1[2]>60) && (sum1[3]>60) && (sum1[4]>60) ) //5行正常
Dianci_Flag = 0; //摄像头正常
}
判定结束 //
}
接下来的元素,看时间更新吧。。。