草庐IT

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

DawnBook 2023-04-13 原文

一.传统的扫线循迹,网上的资料繁杂,开源资料或者博客破碎化

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多车组头尾双车摄像头传统扫线循迹的更多相关文章

  1. 萤石开放平台——怎么通过API接口远程添加摄像头? - 2

    高科技摄像头特别是海康萤石摄像头,已经不再只局限于简单的视频功能,特别是智能AI的普及,摄像头也华丽变身成了一个个独立的智能个体,可以实现人脸抓拍,人形检测,客流统计等店铺值守场景,也可以实现安全帽识别,车辆识别,非法入侵识别等智慧工地场景。但用户也许会问,摄像头又不会说话,他得知的这些信息怎么告诉我们,还是说需要配一个主机去处理,这成本又有点太高了。这点正是萤石云要为大家解决的,下面来介绍下如何让设备更简便智能的说话。API(应用程序编程接口)提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力。形象一点API可以理解为一个管道,通过该管道,可以传入约定好的命令,来获得摄像头的反馈,

  2. javascript - 使用网络摄像头捕捉图像 - 2

    我正在使用Asp.netMVC4开发一个项目。我需要用网络摄像头拍照。此应用程序应该可以配置为任何平板电脑。目前在台式机上运行该应用程序,在平板电脑上没有。我正在使用jscam.swf和jscam_canvas_only.swf。在平板电脑上,我在这些设备上使用SamsungGalaxy安装adobeflashplayer无法正常工作...有人知道其他一些拍照技术,或者如果有人可以帮助我解决这个问题,我将不胜感激 最佳答案 Flash在Android平板电脑上的支持有限,在iOS设备上不支持。更好的解决方案可能是转向100%HTML

  3. javascript - WebRTC/getUserMedia API 教程 - 多个摄像头 - 2

    有谁知道可以让两个用户通过网络摄像头相互连接的WebRTC/getUserMediaAPI脚本的好教程?一个恰当的例子应该是Chatroulette,只是它不需要那么大。并且应该可以在本地主机上创建它。希望有人能帮帮我! 最佳答案 使用SimpleWebRTC与Signallingserver实现你的目标。在mainsite找到更多信息您将需要nodejs来运行信令服务器,或者您可以使用simplewebrtcsignallingserver用于测试目的。虽然屏幕共享仅适用于HTTPS。工作DEMOSimpleWebRTCDemoS

  4. javascript - 水平翻转 .​​getUserMedia 的网络摄像头图像流 - 2

    所以我一直在搞乱这个页面:https://tutorialzine.github.io/pwa-photobooth/基本上它的作用是激活您的网络摄像头并让您直接从流中拍摄快照,我为我的网络借用了它,但视频流被翻转了,我想镜像视频流以便感觉更好。注意:我是一个js新手,所以欢迎详细解释。这是代码,您可能必须使用Firefox而不是Chrome:$('.closecam').click(function(){$('.webcam__overlay').hide();}); $('.camera').click(function(){$('.webcam__overlay').show()

  5. javascript - 如何使对象在 three.js 场景中仅对一个摄像机可见 - 2

    我使用three.js创建了一个用于3D场景的嵌入式轨迹球相机Controller。目前,这使用一个小立方体、一个圆和一个放置在世界原点的正交相机。然而,这三个对象在场景本身中仍然可见,如通过主摄像机所见。(在我下面的演示代码中,我特意将立方体设为10x10x10,以便清晰可见,但它可以做得更小。)此外,作为主场景一部分的穿过原点的元素在插图中可见。例如:插图中可以看到属于主场景的AxisHelper。是否可以在three.js/webgl中使某些对象仅对某些相机可见?如果没有,那么一个解决方法是将轨迹球功能所需的对象放置在远离深空的地方,主摄像头看不到它们,但如果可能的话我更喜欢更纯

  6. javascript - 如何从 MediaStream 和 "stop"网络摄像头中删除轨道? - 2

    我正在尝试从MediaStream中删除轨道。MediaStream.removeTrack()从流中删除轨道,但摄像头灯保持亮起,表示摄像头仍处于事件状态。https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack?redirectlocale=en-US&redirectslug=DOM%2FMediaStreamTrack这引用了一个stop()方法,我想它会完全停止相机,但是在chrome中我得到"ObjectMediaStreamTrackhasnomethod'stop'"有没有办法解决这个问题,还是我

  7. win11摄像头黑了用不了的七个解决办法 - 2

    目录前言必读方法一、重置和隐式设置摄像头方法二、更新windwos驱动方法三、检查串行总线控制器方法四、下载驱动精灵来安装驱动方法五、驱动精灵里面修复 方法六、检查键盘上面有没有物理摄像头按键 方法七、使用万能摄像头前言必读读者手册(必读)_云边的快乐猫的博客-CSDN博客使用电脑时候,电脑摄像头会遇到黑了用不了,图像无法显示的问题。是设置的问题或者驱动的问题方法一、重置和隐式设置摄像头1.快捷键:win键+X,然后点击设置2.点击蓝牙和其他设备,然后再点击摄像头3.点开连接的摄像头 4.重置一下(也可以调一下亮度对比度这些试试看),正常来说摄像头那里是会看见的。ps:如果还不行就点击下面的2

  8. javascript - 如何通过 Internet Explorer 访问网络摄像头(11)? - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion上下文:我正在构建一个离线网站(html5、css、js),我想通过IE(11)调用它。为了拍摄/保存照片,我需要访问网络摄像头。方法:我读到IE不支持getUserMedia;唯一的方法是使用闪光灯。问题:是否只有使用flash才能通过IE访问网络摄像头?如果是,请推荐一个例子。非常感谢您的意见。

  9. javascript - 如何使用 iPad 摄像头读取网站上的二维码? - 2

    我正在使用以下代码访问相机,但目的是使用相机读取QR码。使用以下代码,我只能拍照并保存,然后使用我的后端从保存的文件中读取QR码。如何修改代码在摄像头读取时处理图片。或者将流发送到后端,一旦检测到QR码,它就会通知用户。我需要使用平板电脑工作。我也可以使用下面的来录制视频,但是如何将流发送到后端我的拍照代码ColorThiefDemo#yourimage{width:100%;}#swatches{width:100%;height:50px;}.swatch{width:18%;height:50px;border-style:solid;border-width:thin;floa

  10. javascript - 如何让网络摄像头与 AngularJS 一起工作? - 2

    之前我已经将可工作的网络摄像头代码放入我的应用程序中,但现在当我更新到AngularJSv1.5.0时它不工作了。我正在使用webcam-directive它与v1.3.0完美配合。这是我的代码:但现在AngularJSv1.5.0出现以下错误:UncaughtError:[$parse:isecdom]ReferencingDOMnodesinAngularexpressionsisdisallowed!Expression:onSuccess(video)http://errors.angularjs.org/1.5.0/$parse/isecdom?p0=onSuccess(vi

随机推荐