时隔一年,对全国大学生智能车竞赛做段总结(四)

上一篇文章最后阐述到哪儿了?嗯,阐述到了把灰度图像处理成黑白图像,以便提取赛道特征。

好,接着上一篇继续往下说。

爬梯

黑白图像有了,那下一步是什么呢?嗯,对,提取赛道的边界线。提取赛道的边界线是为了干嘛呢?得到赛道的中线,然后计算其偏差,看所得到的偏差与直线相较,它这会儿是要前行还是要转向。

那么,要开始了。既然要提取边线,那边线代表的数据存哪儿就是一个问题了。所以,定义数组,存储数据。

 //左边界
  int8         ui8_LPoint[ROW];
  //右边界
  int8         ui8_RPoint[ROW];

左右各定义一串一维数组来存储数据。

除此之外,定义一个结构体。结构体有啥用啊?咱也不清楚,感觉用起来跟一般的变量区别不大。但是当定义的变量越来越多之后,自己写代码写着写着,或多或少也容易写懵。在我的理解里,把需要使用到的变量一块儿一块儿分类,然后封装到结构体中,需要用的时候就取出来。

typedef struct {
  //图像X坐标
  uint8 ui8_ImageX;
  //图像Y坐标
  uint8 ui8_ImageY;
  //图像单边最远点
  uint8 ui8_MaxY;
  //图像最远点
  uint8 ui8_AllMaxY;
} LadderMovePoint;

typedef struct {
    //状态判断计数
  uint16        ui16_counter[5];
  //状态标志
  int8          i8_StatusFlag[9];
  //状态处理变量
  int8          i8_StatusHandle[9];
  //图像处理范围
  uint8         ui8_DisposeScopeUp;
  uint8         ui8_DisposeScopeDown;
  uint8         ui8_DisposeScopeLeft;
  uint8         ui8_DisposeScopeRight;
    //直道行位置
  float        f_BaseY[10];
    //标准数组
  uint8*        ui8_LineWidth;
    //标准权重
  float        f_BaseLineWeight[10];
      //行权重
  float        f_LineWeight[10];
  //控制量数组
  int16*        i16p_dataImage;
  //图像数组
  uint8         ui8_ImageArray[ROW][COL];
  //左边界
  int8         ui8_LPoint[ROW];
  //右边界
  int8         ui8_RPoint[ROW];
  //扫描行距离
  uint8         ui8_ScanLineY[10];
  //扫描行左边界(补线)
  uint8         ui8_ScanLineL[10];
  //扫描行右边界(补线)
  uint8         ui8_ScanLineR[10];
  //扫描行左边界(最边界)
  uint8         ui8_ScanLineToL[10];
  //扫描行右边界(最边界)
  uint8         ui8_ScanLineToR[10];
  //扫描赛道宽度
  uint8         ui8_ScanLineWidth[10];
  //中值求取起点
  uint8         ui8_ScanDirection;
  //初始中值
  int16         i16_Mid[10];
  //最终中值
  int16         i16_FinallyMid[10];
  //最小可视距离
  uint8         ui8_MinH;
  int8          i8_MinHX;
  //反向可视距离
  uint8         VisitableScope;
  //爬梯最远点
  uint8        MaxPoint;
} Dispose_Image;

爬梯它自然是一层一层来的,首先肯定是从最底层开始,底层一般来说是黑白黑的一条线。当然,如果你使用的传感器过于高级,90度的那种当我没说,摆在直道上底下这条线都是全白的,也不是不可能。90度的摄像头我也用过,不得不说确实高级,写了好几行防止左右边线丢失的代码,然后使它一丢线就左摇右晃,让它尽快地摸到那条边线。但建议嘛,不是没啥变法不要用那种直戳戳90度的摄像头,咱是觉得不好使。130和150都挺不错的。

遍历黑白图像二维数组最底下的那条线,无论从右到左还是从左到右,找出黑白分割的那个像素点。然后确定左右边界线的第一个点,随后再以这个点向上遍历一层一层爬梯。不过不用爬太高,因为远了边线估摸着就不正常了。可能会黑白黑白黑这样来。

代码可参考:

uint8 LeftPointLadder (uint8* ui8p_LF) {
  if (!DI.ui8_ImageArray[L_Move.ui8_ImageY][L_Move.ui8_ImageX])                                //黑点进入白区
  {
        while (L_Move.ui8_ImageX < DI.ui8_DisposeScopeRight
               && !DI.ui8_ImageArray[L_Move.ui8_ImageY][L_Move.ui8_ImageX + 1])
        {
          L_Move.ui8_ImageX ++;
        }
                if (L_Move.ui8_ImageX < DI.ui8_DisposeScopeRight)
                {
                  L_Move.ui8_ImageX ++;
                  return TRUE;
                } else 
                {
                  return FALSE;
                }
   } 
  
  else if (DI.ui8_ImageArray[L_Move.ui8_ImageY - 1][L_Move.ui8_ImageX])                     //白区内向上
  {
        if (L_Move.ui8_ImageY == DI.ui8_DisposeScopeDown 
            && DI.ui8_LPoint[L_Move.ui8_ImageY] - DI.ui8_DisposeScopeLeft > MID_POINT >> 2
            && DI.ui8_LPoint[L_Move.ui8_ImageY] - L_Move.ui8_ImageX > MID_POINT >> 2) 
        {
            L_Move.ui8_ImageX = (DI.ui8_LPoint[L_Move.ui8_ImageY] + L_Move.ui8_ImageX) / 2;//左下角出现噪点,根据上次左点比较跳变
        }
        else 
        {
          if (!ui8p_LF[L_Move.ui8_ImageY]) 
          {
            DI.ui8_LPoint[L_Move.ui8_ImageY] = L_Move.ui8_ImageX;//找到并记录!!!
            L_Move.ui8_AllMaxY = L_Move.ui8_ImageY;
            ui8p_LF[L_Move.ui8_ImageY] = 1;
          }
            L_Move.ui8_ImageY --;
        }
    return TRUE;
  }
  
  else if (DI.ui8_ImageArray[L_Move.ui8_ImageY][L_Move.ui8_ImageX + 1])                 //向上是黑则向右
 {
        L_Move.ui8_ImageX ++;
        return TRUE;
  }
  
  else if (L_Move.ui8_ImageY < DI.ui8_DisposeScopeDown                                  //向上找不到白点,且向右找不到白点,则返回找
             && DI.ui8_ImageArray[L_Move.ui8_ImageY + 1][L_Move.ui8_ImageX]) 
  {
        while (L_Move.ui8_ImageY < DI.ui8_DisposeScopeDown
               && DI.ui8_ImageArray[L_Move.ui8_ImageY + 1][L_Move.ui8_ImageX]
                 && !DI.ui8_ImageArray[L_Move.ui8_ImageY + 1][L_Move.ui8_ImageX + 1]) 
        {
          L_Move.ui8_ImageY ++;
        }
        if (L_Move.ui8_ImageY < DI.ui8_DisposeScopeDown)
        {
          L_Move.ui8_ImageY ++;
          L_Move.ui8_ImageX ++;
          return TRUE;
        } else 
        {
          return FALSE;
        }
  }
  
  else {
    return FALSE;
  }
}

中线拟合

得到了左右边界线就可以试着中线拟合了。也不能说是最蠢的办法,毕竟我这个水平就只喜欢用这个办法,那就是左右两边的坐标(嗯,对,刚刚边线提取出来的不是像素点,而是每一行边界点所在的坐标)相加除以2.但是这方法的缺点也很明显,等到弯道的时候,拟合出来的中线往往不如人意,特别是在像180度的这种大弯,有着明显丢线的情况下,这种中线拟合的办法就是依托答辩,多半弯道无法完全转过去就冲出了赛道。我的建议是,写个最小二乘法拟合曲线的函数,这里的最小二乘法肯定不是拟合线性kx+b这种曲线的,虽然拟合这种直线也可以,但是觉着效果不会那么好,x^2的应该就差不多。将左右边线拟合一下,再好好拟合中线,处理好了中线,就能确保正常情况下车不会偏航,剩下的就只需要处理赛道元素和组别需要完成的相关任务了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沈千曦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值