第十七届CH32V307多车组头尾双车摄像头传统扫线循迹
Posted DawnBook_Yu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十七届CH32V307多车组头尾双车摄像头传统扫线循迹相关的知识,希望对你有一定的参考价值。
一.传统的扫线循迹,网上的资料繁杂,开源资料或者博客破碎化
1.我于2021年10份正式接触摄像头,在到最终比赛期间,遇到了不少困难和迷惑的地方,接下来我会阐述摄像头小车整个从图像处理到实现循迹的大概过程。
2.本开源博客的代码处理皆是基于逐飞科技提供的底层开源函数库
二.摄像头如何采集到图像
1.采集原始值
/*
*****图像处理函数*****
* 内部调用图像二值化函数,采集开始时先进行二值化,
* 然后进行扫线处理,得到赛道中点、边界和宽度
*/
void image_get(void)
if(mt9v03x_finish_flag_dvp)
Image_preprocessing();//图像二值化函数
Bin_Image_Filter ();//过滤噪点
image_scan();//双边扫线
mt9v03x_finish_flag_dvp = 0;//在图像使用完毕后 务必清除标志位,否则不会开始采集下一幅图像
//注意:一定要在图像使用完毕后在清除此标志位
除去Image_preprocessing();Bin_Image_Filter (); image_scan();三个函数,单片机通过dvp中段
采集摄像头传来的原始图像,原始图像是一张灰色的0—255图像:
在此之后,如果需要判断出赛道的形状,那么需要分辨出赛道和蓝布的边界,也就是说要找出赛道的边界,可以明显的看出,赛道部分的图像明显不同于蓝布区,赛道部分颜色浅,蓝布区域颜色深。图像二值化可以解决这个问题,顾名思义,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的只有黑和白的视觉效果。这时,如何划分这个值是一个问题,大津法可以很好的解决这个问题
/*
*************大津法*************
* *image---需要处理的图像
* col---列w
* row---行h
* 在二值化函数中被调用,每次二值化之前计算阈值
*/
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)//w,h
#define GrayScale 255
uint16 width = col;
uint16 height = row;
int pixelCount[GrayScale];
float pixelPro[GrayScale];
int i, j, pixelSum = width * height;
uint8 threshold = 0;
uint8* data = image; //指向像素数据的指针
for (i = 0; i < GrayScale; i++)
pixelCount[i] = 0;
pixelPro[i] = 0;
//统计灰度级中每个像素在整幅图像中的个数
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标
//计算每个像素在整幅图像中的比例
float maxPro = 0.0;
for (i = 0; i < GrayScale; i++)
pixelPro[i] = (float)pixelCount[i] / pixelSum;
if (pixelPro[i] > maxPro)
maxPro = pixelPro[i];
//遍历灰度级[0,255]
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
for (i = 0; i < GrayScale; i++) // i作为阈值
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for (j = 0; j < GrayScale; j++)
if (j <= i) //背景部分
w0 += pixelPro[j];
u0tmp += j * pixelPro[j];
else //前景部分
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
u0 = u0tmp / w0;
u1 = u1tmp / w1;
u = u0tmp + u1tmp;
deltaTmp = w0 * w1 * (u0 - u1) * (u0 - u1);
if (deltaTmp > deltaMax)
deltaMax = deltaTmp;
threshold = (uint8)i;
//XK.image_threshold = threshold;
return threshold;
/*
*****图像二值化函数*****
*
*/
void Image_preprocessing(void)
uint8 *image;//指向图像数组的指针
copy_image();//复制图像
image = mt9v03x_image_copy[0];//指针得到复制图像数组的地址
//otsuThreshold(mt9v03x_image_copy[0], MT9V03X_W, MT9V03X_H);//大津法计算阈值
XK.image_threshold=GetOSTU(mt9v03x_image_copy);
for(int i=0; i < MT9V03X_DVP_W * MT9V03X_DVP_H; i++)//图像二值化,大于阈值显示白色,小于阈值显示黑色
if(image[i] > XK.image_threshold)
image[i] = 0xff;//白色
else
image[i] = 0x00;//黑色
所以拿到灰色图像后,第一步就是用大津法算出二值化的阈值,在进行二值化处理,若大于这个值为255,小于这个值为0,这时,图像就会变成黑白的图像。
三.中线的提取
在得到一张二值化的赛道图后,我们需要做的是找到赛道的中心线,好让舵机跟着中线的左右打脚,这样小车就能根据中线来循迹了。
但要如何获取中线呢?上文提到,二值化将赛道和蓝布分开,赛道边界明显地显示出来,那么中线的坐标就是左边界加上右边界的二分之一。
下面的代码就是经典的左右扫线函数
/*
* *****图像向上左右扫线*****
*
*
*
* */
void image_scan(void)
// int Max=0;
// int Min=120;
// int left_Max,right_Max;
// int number = 0;XK.center[100]=94;//起始中点,图像最底端
XK.lift_time=0;
XK.right_time=0;
for(line=100;line>=1;line--)//大循环,向上扫线,line为行
for(list=XK.center[line];(list+1)<188;list++)//右边界判断
if( ((mt9v03x_image_copy[line][list]==0x00)&&(mt9v03x_image_copy[line][list+1]==0x00))||(((list+1)==187)) )//||((list+1)==187)
rightline[line]=list;//右边界数组,该行右边界列数
XK.right_line[line]=list;
if((list+1)==187)
lost_rim_r=1;//右边界丢失
else
lost_rim_r=0;
XK.right_time++;
break;
for(list=XK.center[line];(list-1)>0;list--)//左边界判断
if(( (mt9v03x_image_copy[line][list-1]==0x00)&&(mt9v03x_image_copy[line][list]==0x00)) || ((list-1)==1))//||((list-1)==1
leftline[line]=list;//左边界数组,该行左边界列数
XK.left_line[line]=list;
if((list-1)==1)
lost_rim_l=1;//左边界丢失
else
lost_rim_l=0;
XK.lift_time++;
break;
// if(line<=80&&line>=1)//前瞻边界丢失情况处理
//
//
// if(lost_rim_r==1&&lost_rim_l==0)//右边界丢失但左边界未丢失
//
// rightline[line]=XK.center[line+1]+(XK.road_width[line+1]/2);//补右边界
// //rightline[line]=(XK.center[line+1]-leftline[line])+XK.center[line+1];//补右边界
// // XK.right_line[line]=(XK.center[line+1]-leftline[line+1])+94;
// //rightline[line]=(XK.center[line+1]-leftline[line+1])+94;
//
// if(lost_rim_r==1&&lost_rim_l==1)//右边界丢失且左边界丢失
//
// leftline[line]=1;
// rightline[line]=187;
//
//
// if(lost_rim_r==0&&lost_rim_l==1)//右边界未丢失但左边界丢失
//
// leftline[line]=XK.center[line+1]-(XK.road_width[line+1]/2);//补左边界
// //leftline[line]= XK.center[line+1]-(rightline[line]-XK.center[line+1]);//补左边界
// //XK.left_line[line]= 94-(rightline[line+1]-XK.center[line+1]);
// //leftline[line]= 94-(rightline[line+1]-XK.center[line+1]);
//
//
//
XK.center[line-1]=(rightline[line]+leftline[line])/2;//中线,下行赛道(向上)中点以此行中点作为左右扫线起点)XK.road_width[line]=myabs(rightline[line]-leftline[line]);//此行赛道宽度,右边界列-左边界列
mt9v03x_image_copy[line][XK.center[line]]=0x00;//显示中线为黑色,行,列
本文旨在提供摄像头图像处理的基础流程,若有错误欢迎指正。
以上是关于第十七届CH32V307多车组头尾双车摄像头传统扫线循迹的主要内容,如果未能解决你的问题,请参考以下文章