第十七届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图像:

第十七届CH32V307多车组头尾双车摄像头传统扫线循迹

在此之后,如果需要判断出赛道的形状,那么需要分辨出赛道和蓝布的边界,也就是说要找出赛道的边界,可以明显的看出,赛道部分的图像明显不同于蓝布区,赛道部分颜色浅,蓝布区域颜色深。图像二值化可以解决这个问题,顾名思义,就是将图像上的像素点的灰度值设置为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

/
*图像二值化函数
*
/
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,这时,图像就会变成黑白的图像。

第十七届CH32V307多车组头尾双车摄像头传统扫线循迹

三.中线的提取

在得到一张二值化的赛道图后,我们需要做的是找到赛道的中心线,好让舵机跟着中线的左右打脚,这样小车就能根据中线来循迹了。

但要如何获取中线呢?上文提到,二值化将赛道和蓝布分开,赛道边界明显地显示出来,那么中线的坐标就是左边界加上右边界的二分之一。

下面的代码就是经典的左右扫线函数

/
*
*图像向上左右扫线
*

*
*/
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)
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

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;//显示中线为黑色,行,列
}

本文旨在提供摄像头图像处理的基础流程,若有错误欢迎指正。

Original: https://blog.csdn.net/DawnBook_Yu/article/details/125950669
Author: DawnBook_Yu
Title: 第十七届CH32V307多车组头尾双车摄像头传统扫线循迹

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/632691/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球