第十七届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多车组头尾双车摄像头传统扫线循迹的主要内容,如果未能解决你的问题,请参考以下文章

第十七届智能车竞赛比赛系统软件修改-多车组时间延迟

第十七届智能汽车竞赛-多车编队组入门讲解

第十七届智能车竞赛英飞凌专题培训 ——四轮摄像头组入门讲解

沁恒CH32V307使用记录:GPIO与EXTI

沁恒RISC-V MCU 为全国大学生智能汽车竞赛加速

多车组软件使用说明