【第十七届智能车】智能车图像处理(2)-赛道边界的简单提取和无元素循迹

本博客使用的图像是188*120的大津法二值化图像。摄像头安装高度为25cm(离地),前瞻长度约1m。

智能车图像处理的过程就是读取输入的图像,经过处理后向控制部分输出一个偏差值,控制部分根据再这个偏差值输出相应的控制量,对偏差进行修正,这就是最基本的循迹。

​ 在得到二值化图像后,我们如何进行最基本的使用呢?

赛道边界的简单提取

​ 二值化图像中,在没有反光、不均匀光照和场外杂物的影响时,赛道和蓝布应当具有清晰的边界。我使用的是二值化后的原始图像,没有进行图像滤波等平滑毛刺处理,因为我在进行误差计算的时候使用的是均值,这么做的好处是容许因边界存在毛刺或者锯齿造成的误差而不会很大地影响最终计算的偏差值。误差的具体计算方式后面再细讲,这里先说边界的寻找。一般情况下,循迹主要靠图像远端偏差进行误差计算,这样可以有提前量。赛道边界不需要跟踪到迂回弯道之后,跟踪到迂回弯道之后不仅代码编写难度高很多,而且计算的偏差值意义不大。摄像头的优点是前瞻长,但是成也萧何败萧何,前瞻过长也不是好事。赛道边界需要分别保存在左右两个数组中,这两个数组最好是全局变量,以便后续循迹偏差计算和元素识别使用。

​ 赛道边界提取最简单有效的办法就是种子生长法。根据赛道近端一定是白色的特性(不是这样的话车基本已经出赛道了),首先找到图像图像最下方一行白色的中点,作为全图搜索的seed;接着从近到远(也就是图像中的从下到上)开始分别向左和向右搜索黑白交界点,找到黑白交界点就分别保存在左右边线数组中,并且根据(左边+右边)/2的方法计算该行中线横坐标,保存至中线数组中,并且作为下一行的seed,继续如上过程,直至全图搜索完毕。如果在该行某一侧没有搜索到边界,则给该侧边界数组的该行对应元素存入0或者187,标示为丢边。这样,整个过程就像是中线的种子从图像下方生长至图像上方,所以我给它起名种子生长法。这么做的好处就是,可以实现直道以及非迂回弯道的简单中线跟踪,且由于种子是根据最下方一行的白色赛道中点确定的,在小弯中的表现更加稳定,因为如果直接默认从最下方的一行中点作为seed向上搜线的话,在小弯中车的视野中赛道很有可能只在图像的一侧,一旦最下方的白色仅仅处于图像中心的一侧,就会出现边界扫描错误的情况,导致偏差计算出错,车会直接往弯的反方向打舵机冲出赛道。而种子生长法就不会出现这样的问题。

循迹偏差值的计算

​ 关于循迹,其实有很多可以利用的参数,也衍生出了很多方案,我也没有接触过那些高级的制导方法,只能简单讲一讲自己接触过的几种方法了。

  1. 动态中线长度+每行平均偏差法

    ​ 这里的“动态中线长度”指的是从下至上,中线的不截断长度。这里的“截断”,指的是手动设置一个截断阈值,在扫描中线的时候,如果下一行与上一行的中线点像素横坐标差值大于这个阈值,就认为中线发生了截断,发生截断后就停止向上搜索中线,并且记录下已经搜索到的中线长度,认为是“有效中线”。然后,就在这段“有效中线”内,每行与图像中心坐标相减,再求取平均值,就得到了一个偏差值。之所以想到了这样一个“动态中线长”的方法,是因为我考虑到近端图像相较于远端图像更为稳定,且在弯道处远端没有搜索到边线的一段不应该纳入计算。但是这种方法对于弯道和存在元素的地方适应性并不是很好,过弯轨迹并不是很理想,因为这种方法压制了前瞻,在远端出现变化需要做出响应时往往不能很及时地调整误差。

  2. 动态中线长度+各行权值偏差法

    ​ 这种方法是在第一种方法的基础上,给每行都引入了一个偏差权值,这个偏差权值提前计算好存储在一个数组内(权值表)。在PID参数不变的情况下,调整这个权值表中各行的权值,可以微调过弯轨迹(算是变相起到了PID的作用hh)。当然,这个权值的计算也可以在写程序的时候用一个函数计算,但我为了加快运算速度就把每行的权值打了一张表。这个表的获取也可以用简单的MATLAB程序实现。测试当这个权值符合σ(0.5,1)时,效果较好(其实只要基本上符合中间大远近小的规律就行了,这个测试的结果与我最后采用的一种方法得出的结果是一致的)。

  3. 动态中线长+最小二乘斜率法

    ​ 这里引入了最小二乘法进行中线偏差的计算,拟合出一条直线,使之尽可能多地经过中线上的点,接近曲线的形状。这里的中线偏差通过计算出直线的斜率k来体现。在这种机制下,理想的偏差值应当是∞。下面给出最小二乘法拟合直线的公式:

    在这里插入图片描述

  4. 固定区域中线+每行平均偏差法

    在追求了很久的“动态”和“自适应”之后,发现那么多花里胡哨的稳定性其实可能还不如固定的“呆”方法……

    ​ 这个方法就是在图像上选定两条横线,以这两条横线之间的中线为基准进行偏差计算,相当于第二种方法的某些行权值置为1,其他行都置为0。通过调整偏差计算的行数,可以调整循迹前瞻和循迹轨迹,对于抖动的消除也有一定的辅助作用。采用这种方法可以较好地固定过弯轨迹。这种方法最极端的简化版本就是使用单行循迹,只使用一行的边界信息(龙邱的库就是这样的),也就是类似于CCD的原理,实测也是可行的,就是容易在弯道处因为前瞻超出了弯道外,存在一定程度的抖动,所以就不采用了(虽然但是,摄像头不就是一个有很多行的线性CCD么hhh)。

中线与偏差的修正

弯道误差修正

​ 在以上的搜线逻辑中,在遇到边界丢线的情况时,默认该行边线在图像的最左侧或者最右侧,这样在过弯的时候就存在一个问题:无论弯的大小,偏差值的大小都是近似的,即使在急弯误差也非常小,使得过弯时舵机打角不足,从而导致过弯失败。这时候就需要进行左右判别和偏差修正了。因为直道的宽度基本是固定的,所以可以先记录下直道上图像上各行的赛道宽,在过弯时进行左右弯的判别,判定完毕后,在已有的一侧边线上向左或者向右补上这一段赛道宽度,就可以补出另一侧边线,再利用补完后的边线数组进行中线的计算,可以大大降低误差。不过,这种方法对于摄像头的安装高度和安装角度、前瞻距离以及摄像头是否居中都有较高的要求。

左右弯道的判别

​ 在以上修正中,需要先行判定左右弯道,如果判定不好的话,很容易在其他地方出现误判,使得边线修正异常触发,中线发生严重抖动。通过观察弯道处的图像可以发现,弯道的特征是:指向的一侧丢线较多,另一侧丢线较少, 且图像上方应当是黑色的,而且丢线较少的一侧边线横坐标是呈现逐渐递增或者递减的,这个逐渐递增或者递减可以利用从下而上边线斜率绝对值的逐渐递减来进行判定,当然停止判定的边界条件一定要找好,不然极其容易发生误判,这个边界条件是需要具体情况具体分析的,还是得自己慢慢摸索确定。根据这几个特征编写程序,可以有效判别弯道的左右,从而进行相应的中线修正。其实只要PID调整得够好,这种修正也不是必要的,因为防止误判的边界条件最后加了很多依旧会在少数情况下发生误判,所以最后我也弃用了这种方法。

偏差滤波

​ 曾经为了抑制车模运行过程中晃动使图像抖动从而使得偏差抖动过大的情况,我也尝试过对偏差进行滤波,使用的方法有均值滤波、加权平均递推滤波等,但是最终效果并不是很理想,使用滤波后,车子就像喝了假酒一样,过弯响应极其滞后(也有可能是因为图像处理速度跟不上,当时并没有调整过编译器优化),所以最终我还是放弃了偏差滤波,转而采用使得偏差本身更加稳定的方法计算误差。因为使用滤波就注定要使用前几帧的图像信息,图像处理至高50Hz,即使只采用前一帧的信息,延后量也达到了40ms,在2m/s的速度下这足够使车冲出去8cm,这对于元素识别或者循迹已经是很致命的影响了。当然也可能是我采用的滤波方法是我自己草草写的,很不完备,如果大家对于误差的平滑化有什么高见的话,欢迎在评论区一起交流!

总结

  1. 利用二值化图像的方法,首先就是将赛道边界提取出来存入边线数组内,使之成为可以直接参与运算的数据。
  2. 边界的搜索可以使用种子生长法,对于弯道效果较好。
  3. 图像的处理最终需要输出一个循迹偏差,这个偏差可以有多种形式,也可以不止是一个参数,甚至可以是一个数组甚至矩阵,但是最终反映到控制量上一定要稳定且可靠。
  4. 循迹偏差计算我采用的是最简单的固定区域平均偏差法,这种方法虽然看起来很笨,但是胜在稳定高效。
  5. 理论上,偏差在弯道处是需要修正的,偏差也需要进行滤波处理,但是实测效果并不是很理想,所以暂时放弃了这个方案。
  • 25
    点赞
  • 311
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是基于Arduino的智能小车红外循迹和避障代码: ```C++ #include <AFMotor.h> // 导入电机驱动库 AF_DCMotor motor1(1); // 定义电机1 AF_DCMotor motor2(2); // 定义电机2 int left_line_sensor = A0; // 左侧红外线传感器连接的引脚 int right_line_sensor = A1; // 右侧红外线传感器连接的引脚 int obstacle_sensor = A2; // 避障传感器连接的引脚 void setup() { Serial.begin(9600); // 初始化串口通信 motor1.setSpeed(150); // 设置电机1速度 motor2.setSpeed(150); // 设置电机2速度 } void loop() { int left_value = analogRead(left_line_sensor); // 读取左侧红外线传感器的 int right_value = analogRead(right_line_sensor); // 读取右侧红外线传感器的 int obstacle_value = analogRead(obstacle_sensor); // 读取避障传感器的 if (left_value < 500 && right_value < 500) { // 当左右两侧的传感器都检测到黑线时,直行 motor1.run(FORWARD); motor2.run(FORWARD); } else if (left_value < 500 && right_value >= 500) { // 当左侧的传感器检测到黑线时,向左转 motor1.run(BACKWARD); motor2.run(FORWARD); } else if (left_value >= 500 && right_value < 500) { // 当右侧的传感器检测到黑线时,向右转 motor1.run(FORWARD); motor2.run(BACKWARD); } else { // 当两侧的传感器都没有检测到黑线时,停车 motor1.run(RELEASE); motor2.run(RELEASE); } if (obstacle_value < 500) { // 当检测到障碍物时,倒车 motor1.run(BACKWARD); motor2.run(BACKWARD); delay(1000); motor1.run(BACKWARD); motor2.run(FORWARD); delay(1000); } } ``` 代码中使用了AFMotor库控制电机的转动,使用analogRead函数读取红外线传感器和避障传感器的。根据传感器的控制电机的转动,实现小车的红外循迹和避障功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值