机器人与人工智能爱好者论坛

 找回密码
 立即注册
查看: 8349|回复: 1
打印 上一主题 下一主题

机器人循线算法原理与实践

[复制链接]

43

主题

44

帖子

269

积分

中级会员

Rank: 3Rank: 3

积分
269
跳转到指定楼层
楼主
发表于 2015-12-15 14:29:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
机器人循线算法原理与实践

[硬件基本构架]
对于机器人的循线,为了获得场地上白线(黑线)的信息,硬件结构一般有如下几种种类。
1、红外对管阵列。采取这种方式的机器人比较多,尤其在各种机器人竞赛中,几乎是标准配置。但是这种技术有一个致命的弱点,就是对于场地光线的干扰特别敏感,而且也很难把红色和白线区别开来,所以使用受到一定的限制。一般解决这类问题的方法是在红外光上加载一个调制波,通过检测这个调制波来消除场地光线的干扰,至于如何解决红色和白色的区别问题,那就几乎是五花八门了。
2、光纤传感器阵列。采用这种传感器阵列的原因是,光纤非常细,在单位面积内可以安装更多的传感器,从而获得更精确地场地信息。当然,钱也也花得更多。
3、线性CCD。这种硬件方法几乎是一种对场地信息分辨率的BT追求,如果说红外对管阵列还是离散信息的话,那么线性CCD就是线性的连续数据。当然驱动它也不是一件容易的事情,对于单片机也有更高的速度要求。
4、视觉。废话少说——否则明天我都别想吃饭。

[基本原理]
    所谓循线,就是通过一定的传感器探测地面色调迥异的两种色彩从而获得引导线位置,修正机器人运动路径的一种技术。——说的太拗口了。不说太多理论的东西,我们就从基于红外对管阵列的循线技术来说起。
    假设,我们使用的是黑底白线的场地。红外对管阵列由3个红外对管1字摆开组成。白线的宽度略小于或等于红外对管阵列的宽度。
1、数据的采集。
    对于机器人来说,通过传感器感知周围事物的信息,利用这些信息并不作太多智能上的计算而直接通过一定的转换,指导机器人的运动——这种形式在人工智能学上叫做机器人的“反应范式”。所以,我们要想让我们的机器人能够寻着我们给定的轨迹线运动,第一步就必须让他感知到轨迹线的存在。一般的做法就是通过AD采样,获得红外对管(传感器)反馈回来的电压信息。然而,这样获得的电压值信息是无法直接指导运动的,必须把他们转化为二值的(也就是二进制信息,1表示线存在,0表示线不存在)信息,然后通过处理每一个管子反馈回来的二值信息获得白线的位置信息。
>>技术点A  AD信号的阀值化。(你可以参考其它的算法,获得比较详尽的技术,我这里只是举例一二)
    所谓阀值化,就是通过一定的范围把握,从而把线性的数据转化为离散数据的一种变换。简单的说,就是通过分段函数的方法,将数据分类。在我们这个应用中,就是想方设法使AD采集回来的电压值变化为一个恰恰能够准确表示白线位置信息的二进制信息,1代表白线存在,0代表白线不存在。由于白色和黑色在电压差异上非常之巨大,所以可以简单的通过一个标志线来区分它们,当电压值高于这个标志线了,就把他标志为1,否则就标志为0,算法描述为:
  1.     if (AdValue[i] > MarkLing)
  2.     {
  3.         LineInfor[i] = 1;

  4.     }
  5.     else
  6.     {
  7.         LineInfor[i] = 0;
  8.     }
复制代码

这样做非常简单,适合于比较标准的场地,然而对于那些模糊了的场地或者是非标准场地,虽然人的肉眼能够看出来,但是对于机器人来说,可能看到的就是花白的一片或者是黑色的夜幕。当标志线值过高时,机器人能看到的只是那些特别明显的白线,其他则是黑色的夜幕,很容易丢失轨迹线;当标志线值过低时,机器人眼中就是白茫茫的一片毛刺。总而言之,对场地的适应性非常差。解决方法是,通过设定两个标志线来标定轨迹线信息,当AD值高于某一值时,标志1;当AD值低于另外某一值时,则标定0。算法描述为:
  1.     if (AdValue[i] > High_MarkLine)
  2.     {

  3.         LineInfor[i] = 1;
  4.     }

  5.     else if (AdValue[i] < Low_MarkLine)
  6.     {

  7.         LineInfor[i] = 0;
  8.     }
  9.     else
  10.     {

  11.         LineInfor[i] = NoInfor;
  12.     }
复制代码


>>技术点 B 动态预值。(你可以参考其它的算法,获得比较详尽的技术,我这里只是举例一二)

当然,这种算法在简单的机器人循线中不是很常用。比较常见,适应性强的方法是,首先从AD值中找到一个中间值作为MarkLine,(或者可以从AD值中找那些比较接近最大值和最小值之差的0.618倍的数值),然后再使用第一种方法标记,这样的算法叫做动态预值。如果把这种算法应用于第二种当然也不多啦。

2、数据的简单加工——第一个循线程序。
    到目前为止,我们已经把AD的值的数组转变为了一个表示白线位置的二进制位的数组——我们不妨直接把他用一个字节表示哈。那么,这个字节的状态就表示了当前白线的位置信息。再假设,我们已经写好了几个函数用来分别控制小车的左右运动。那么我们就可以通过以下的简单方式来实现循线了。

  1. //用字节的高三位表示三个管子检测到的白线信息。
  2. switch (LineInforByte)
  3. {
  4.     case 0b11100000:         //全部在白线上
  5.         Motor_Left_GoFront(FullSpeed);
  6.         Motor_Right_GoFront(FullSpeed);
  7.         break;
  8.     case 0b01100000:         //明显车子向左偏了哈
  9.         Motor_Left_GoFront(FullSpeed);
  10.         Motor_Right_GoFront(NormalSpeed);
  11.         break;
  12.     case 0b00100000:
  13.         Motor_Left_GoFront(FullSpeed);
  14.         Motor_Right_GoFront(LowSpeed);
  15.         break;
  16.     ……
  17.          
  18.     //其他情况仿照上面自己写了哈。
  19.     default:
  20.         Motor_Left_GoFront(StopNow);
  21.         Motor_Right_GoFront(StopNow);
  22.        break
  23. }
复制代码

呵呵,这样就完成了一个循线小车的程序了哈。简单吧。
顺便说明一下下,Motor_Left_GoFront()函数是一个控制电机PWM输出的函数。FullSpeed NormalSpeed LowSpeed StopNow StopFree 是一些控制PWM的宏定义,你可以修改这些宏定义的值来实现以上的功能。我想,你看了这个程序应该已经对循线的基本原理了然于胸了吧。哈哈哈哈哈哈哈哈。
3、数据的高级加工——复杂地面情况的模糊识别算法。

   以上的算法的确可以应付规范场地下的情况了,但是由于其类似查表式的数据处理方式,一旦出现真值表中没有的情况——哪怕是很明显的直线存在——机器人都没有办法处理了。典型的就是在地上有大块的白色斑点,导致机器人对白线视而不见。

   解决以上问题的方法还要从人眼识别白线的原理上说起。在破坏严重的场地上,人类的眼睛仍然可以识别出原先的白线,这是为什么呢?通过重心。人类的眼睛通过捕捉白线的重心确立白线的大体轨迹,从而辨认出白线的位置。从概率的角度上说,在破坏严重的场地上,出现在白线两边的浅色干扰的概率是一样的,即使不同,由于白线本身的存在,其重心至少是不会偏离白线很远的,所以,只要简单的获得地面浅色标志的重心,就可以大体确立白线的所在。我们可以利用物理学上质心的算法获得这一信息。忘了说一点,要想机器人增强对环境的适应力,就需要增加传感器的数目。我们不妨用8个红外管作为传感器。这样通过处理后获得的场地信息就整整1个字节了。假设1个光电管的1拥有1单位的重量,八个光电管的坐标分别为 -7 -5 -3 -1  1 3 5 7,其间距都是2个单位,通过置信公式很容易计算出质心的坐标,通过这个坐标和0的绝对值,就可以知道当前机器人偏离白线的多少,而这个偏离值则可以通过简单的比例直接指导运动函数。典型实例如下:

  1. /********************************************************
  2. *  函数说明:电机动作调速函数                           *
  3. *  说明:    该函数放在定时器或者主循环里面用于产生软PWM*
  4. ********************************************************/
  5. void SpeedPWM(char PWMLine)
  6. {
  7.     char PWMLine_L = PWMLine;
  8. char PWMLine_R = PWMLine;
  9. static char PWMCount_L = 0;
  10. static char PWMCount_R = 0;

  11. char Temp = 0;

  12. if (FollowLineEnable == True)
  13. {
  14.      Temp = (char)fabs((float)CG_X);
  15.      if (AdcValueFlag == 0)
  16.   {
  17.       Temp = 0;
  18.   }
  19.   else
  20.   {
  21.       if (CG_X <0)
  22.    {
  23.        if ((Temp<<4) <= PWMLine_R)
  24.     {
  25.         PWMLine_R -= ((Temp<<5)+Temp<<2);
  26.     }
  27.     else
  28.     {
  29.         PWMLine_R = 0;
  30.     }
  31.    }
  32.    else
  33.    {
  34.        if ((Temp<<4) <= PWMLine_L)
  35.     {
  36.            PWMLine_L -= (Temp<<5);
  37.     }
  38.     else
  39.     {
  40.         PWMLine_L = 0;
  41.     }
  42.    }
  43.   }
  44. }

  45. PWMCount_L ++;
  46. PWMCount_R ++;
  47. if (PWMCount_L > Fastest)
  48. {
  49.      PWMCount_L = Stop;
  50. }
  51. if (PWMCount_R > Fastest)
  52. {
  53.      PWMCount_R = Stop;
  54. }

  55. if (PWMCount_L < PWMLine_L)
  56. {
  57.         switch (GoDirection)
  58.      {
  59.          case Front:
  60.        Motor_Left_GoFront;
  61.           break;
  62.       case Back:
  63.        Motor_Left_GoBack;
  64.           break;
  65.       case Left:
  66.        Motor_Left_GoFront;
  67.           break;
  68.        case Right:
  69.        Motor_Left_GoBack;
  70.           break;
  71.       case Stop:
  72.        Motor_Left_Stop_Free;
  73.           break;
  74.      }
  75. }
  76. else
  77. {
  78.      Motor_Left_Stop_Free;
  79. }

  80. if (PWMCount_R < PWMLine_R)
  81. {
  82.         switch (GoDirection)
  83.      {
  84.          case Front:
  85.        Motor_Right_GoFront;
  86.           break;
  87.       case Back:
  88.        Motor_Right_GoBack;
  89.           break;
  90.       case Left:
  91.        Motor_Right_GoBack;
  92.           break;
  93.        case Right:
  94.        Motor_Right_GoFront;
  95.           break;
  96.       case Stop:
  97.        Motor_Right_Stop_Free;
  98.           break;
  99.      }
  100. }
  101. else
  102. {
  103.      Motor_Right_Stop_Free;
  104. }
  105. }


  106. /********************************************************
  107. *  函数说明:获取偏离轨迹线的数值                       *
  108. *  输入:    表明寻线状态的字节                         *
  109. *  [说明]                                               *
  110. *           通过类质心算法获取当前机器人偏离轨迹线的量  *
  111. *           - 表示偏左 + 表示偏右                       *
  112. ********************************************************/
  113. signed char GetCG_X(unsigned char AdcValues)
  114. {
  115. signed char a = 0;
  116. signed char Temp = 0;
  117. signed char Totals = 0;
  118. for (a = 0;a<8;a++)
  119. {
  120.    if ((AdcValues <<a)>>7)
  121.    {
  122.        Temp += ((-7)+ (a<<1));
  123.     Totals++;
  124.    }
  125. }

  126. if (Totals ==0)
  127. {
  128.      return  0;
  129. }

  130.     return  (Temp / Totals);
  131. }
复制代码

函数调用GetCG_X函数,用来获取CG_X,CG_X直接在PWM输出函数里面指导机器人的运动。

以上方法的好处是,提供了一个比例调节循线动作的可能。支持多传感器的情况,尤其适合线性CCD类的线性数据的处理。为机器人提供了一个相对完整的视觉,不可能出现无法识别的情况,而且,这种情况可以使机器人在不加修改程序的情况下直接在在白线循线和黑线循线状态下切换。

回复

使用道具 举报

0

主题

120

帖子

251

积分

中级会员

Rank: 3Rank: 3

积分
251
沙发
发表于 2016-2-22 13:20:32 | 只看该作者
看后感悟了点,收下了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|Archiver|手机版|小黑屋|陕ICP备15012670号-1    

GMT+8, 2024-4-28 20:57 , Processed in 0.058702 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表