智能车镜头组入门(三)巡线

本文章已经生成可运行项目,

镜头组的特点是通过摄像头来获取赛道的信息,从而达到前瞻的效果,完成转向和速度决策。

摄像头基本上就某飞和某邱的,我们选择的是某飞的总钻风。

我们的方案,带上元素识别,大概在TC264上5ms一帧,所以我们开了100hz的图象。

之前我看别的博客上有人说,他们组的50帧到100帧提升不大,但是我试了下100帧还是有明显的提升的,大家可以都试试

摄像头部分的主要参数是图象大小,帧率,曝光度,摄像头高度角度,和摄像头是否居中

一些小的建议

建议大家购买某飞的wifi图传模块,这样在上位机上可以实时看到小车获取到的图像

 没有打广告的意思,真的很好用。这个需要5v供电,使用的时候需要打开主板上的电源使用,不能依靠下载线供电!!!!

关于巡线

巡线方面我们采用的是某飞的方案,即先通过近端白线的平均值来获取认为的白块,然后通过这个白块来寻找最长上升白列,然后通过这个白列向左向右的寻找边线,之后我会详细介绍

是否二值化?

图象识别有一个比较大的差别就是是否二值化,就是把灰度图转换成只有黑白的图,一般常用的额算法是大津法,我们最开始也是用的这个方法,后来一段时间之后,觉得如果二值化的话时间实在太长了(印象中是>20ms的),就放弃了二值化的方案,采用了某飞的方案

如果画面撕裂,我们认为可能是摄像头采用dma传输数据的原因,把图象复制出来一份后,没有了撕裂的问题。而且memcpy这个函数和数组直接赋值的方式是不同的 memcpy非常快,时间基本可以忽略不计

从这句话也可以看出,逐飞的库摄像头的信息放在 mt9v03x_image[MT9V03X_W ][MT9V03X_H ]这个数组中的,当运行摄像头初始化mt9v03x_init()后就可以直接调用这个数组来使用图像了。

如果你购买了逐飞的wifi图传,需要打开板载的5v电源,修改zf_device_wifi_spi.h内的宏定义(wifi账密、上位机的ip地址)打开逐飞助手,才能使用

另外需要说明一点的是,屏幕刷新的过程会阻塞单片机cpu的运行,需要将这些不影响小车运行的东西(比如屏幕,串口调参)这类程序放在主函数中,其他的放在中断中运行

我来简单的介绍下我们采用的方案,也就是逐飞的方案,具体可以去看他们b站上的视频。

我们将搜线和元素识别分开来写,封装成了两个大的函数

这是我们用来搜线的函数,搜线的只要快和稳就行了,顶端有部分丢线其实也是可以接受的(比如20行之前的)

我们用了两个变量和一个定时器来获取搜线所用的时间,便于改进,在某些大直道的情况下,因为使用了边线继承的方法,车辆稳定不懂的情况下能做到800ns一帧

我们的搜线

主要用到的函数就是这几个

get_average_value();

首先是求图象近端的平均值,也就是这一块下面红色区域的平均值,然后用吧这个平均值用于

白线和出界的判断,比如平均值是小于50 那么说明车跑出界了(这个50的值会根据光线的变化而变的,换场地需要更改曝光值),需要设置一个信号量给调参的同学,让小车停下来。(这个边线不贴合赛道可能是无线图传的问题)

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 3; j++) {
            sum += copy_img[IMG_H - 1 - j][IMG_W / 2 - 5 + i];
        }
    }
    average_value = sum / 30;
    absolute_white = average_value * DESIRE_WHITE;
    absolute_black = average_value * DESIRE_BLACK;

此外还有两个变量,absolute_white 和absolute_black,这两个变量是认为的绝对白色和绝对黑色,在搜线中可以加快速度。

search_longest_white();

现在我们有了这个的白列值,我们可以用这个白列值来寻找最长上升白列,就是如图所示的黄线

    (这是逐飞的图象,所示的黄线就是最长上升白列)

for (int i = 0; i < IMG_W - 1; i += 3) {
          if(i > last_find - 5  && i < last_find + 5) continue;
          length = 0;
          for (int j = IMG_H - 1; j >= 0; j--) {
              if (copy_img[j][i] > average_value || abs(copy_img[j][i] - average_value) < FIND_LINE_LENGTH) {
                  length++;
              }
              else {
                  break;
              }
          }
          if (length > max_length) {
              if (max_length > IMG_H - HEAD_LENGTH) {
                  max_length = length;
                  max_line = i;
                  break;
              }
              max_length = length;
              max_line = i;
          }

      }

最长上升白列这个算法之后,会得到max_line 和max_length这两个参数,max_line说明的是最长上升白列出现在第几行,max_length说明最长上升白列的长度。

boundary_scan();

先介绍下逐飞的差比和算法,也叫对比度搜线

例如我的图象是188*120的图象,则往竖直方向,找到一个最长的白列,或是大于某一个数值的长白列(就是如图所示的黄色的线),以这个线为依据开始向左扫左线,向右扫右线之后要做的就是边线的扫描,其他学校有用八领域法的,算法难度比较高。我们用的也是逐飞的差比和算法,比如说 ,目前找到的点是(40,100)灰度值为a,我们要搜寻左边线,就是要搜索和这个点的左边隔了一段距离,如(35,100)这个点,灰度值为b,通过这个(a - b)/(a +b) 所得的值 大于某一个阈值的时候,则说明了这个是一个明显的黑白交接点,可以把它认为是赛道的边线。

unsigned char chabihe_L(int a, int b) {
    float result = 0;
    if(abs(a - b) <10) return 0;
    cbhnum_L ++;
    result = (a - b) * 1000 / (a + b);
    if (result > CHABIHE_THRESHOLD * 1000 ) {
        return 1;
    }
    else return 0;
}

我们统计了各点通过差比和算法来找到边线的方式,就是上面所看到的cbhnum_L ++,差比和算法和上面提到的continue break的方法相比,速度固然慢了很多。所以要尽可能的找到方法少用差比和。

有些优化的方案,比如每继承每一次的值,如果上一条边找到了左边线,那就往右回撤几个像素 再继续进行差比和运算,可以大大减少运算量(其实前面说到的寻找最长上升白列的算法也可以采用集成的方法,因为每一帧图象间隔的时间非常近,每一帧的是相近的)。

第一次搜线是继承了上一条线的往回BACK_LENGTH个像素,搜到了设一个标志位,如果没搜到或者是上一条线也没搜到,则重新开始。

需要对继承的搜线进行一些限制,不然会有一些意想不到的情况,我给出的我们的代码是做过一些限制的,大家如果没看懂可以试着自己先写写,遇到问题了再来看看我们的思路

if (find_flag_L == 1 && (boundary[i + 1][LEFT]  > BACK_LENGTH)){
            find_flag_L = 0;
            for(j = last_update_L + BACK_LENGTH; j >= last_update_L  - BACK_LENGTH; j--){//这里是回退了5格,搜左边线,也就是向右走了5格,然后才开始搜索
                if(copy_img[i][j] > absolute_white) continue;
                if(copy_img[i][j] < absolute_black && chabihe_L(copy_img[i][j + CHABIHE_LENGTH], copy_img[i][j])) {
                    boundary[i][LEFT] = j;
                    find_flag_L = 1;
                    last_update_L = j;
                    break;
                }
                if (chabihe_L(copy_img[i][j + CHABIHE_LENGTH], copy_img[i][j])) {
                    boundary[i][LEFT] = j;
                    find_flag_L = 1;
                    last_update_L = j;
                    break;
                }
            }
        }
        else {//这里是没搜到的情况,就从最长上升白列开始搜索
            for(j = max_line; j > 0; j--){
                if(copy_img[i][j] > absolute_white) continue;
                if(copy_img[i][j] < absolute_black && chabihe_L(copy_img[i][j + CHABIHE_LENGTH], copy_img[i][j])) {
                    boundary[i][LEFT] = j;
                    find_flag_L = 1;
                    last_update_L = j;
                    break;
                }
                if (chabihe_L(copy_img[i][j + CHABIHE_LENGTH], copy_img[i][j])) {
                    boundary[i][LEFT] = j;
                    find_flag_L = 1;
                    last_update_L = j;
                    break;
                }
            }
        }

这样我们就基本完成了搜线,接下去一个更重要的部分是元素识别和补线。搜线中还可以获取一些额外的信息用于元素识别。

从边线中我们还可以获取到两个信息。一个是左右边线的丢线,和连续丢线的长度。另一个是如果小车在一条大直道的情况下,假设最顶端宽度是25,最低端宽度是150,这样我们就可以估计一个正常赛道的宽度。然后我们计算扫线扫到的赛道宽度,如果这个宽度和理论赛道宽度差距比较大的话,那就有是元素的嫌疑了

摄像头比较重要的一点是 确定好图象的大小尺寸,摄像头的高度和角度,之后就不要在变化了,确定好前瞻距离和最近距离。近端距离是来控制转向的,前瞻距离是做速度决策的

如果突然卡死了,或是时间出乎意料地长, 大概率是数组越界了,检查下数组的索引

最后分享一下调试方法。因为给英飞凌下载比较慢,而且往往刚开始需要大规模的改写你的代码,需要频繁的下载,比较耗时间网上看到有些大佬通过c#写了上位机,这样就可以直接在电脑上写代码了。我们用的是最原始的办法

我们用串口传出了一张图片的灰度值,然后把他保存到一个文件里,用这个数组给copy_img赋值

 通过这个函数,把边线数组打印出来,这样调试就方便多了,美中不足的是不能测花费的时间。

void print() {
    for (int i = 0; i < 120; i++) {
        printf("%-3d ", i);
        for (int j = 0; j < 188; j++) {
            if (boundary[i][LEFT] == j) {
                printf("*");
            }
            else if (boundary[i][MID] == j) {
                printf("_");
            }
            else if (boundary[i][RIGHT] == j) {
                printf("#");
            }
            else {
                printf(" ");
            }

        }
        printf("\n");
    }

}

本文已生成可运行项目
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值