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

     }

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>