当前位置:   article > 正文

关于TC264单片机与智能车摄像头循迹的一些学习心得

tc264

前言:最近一段时间在准备智能车考核,其中要求使用TC264单片机实现PID控制的小车摄像头循迹。其中关于PID的部分在之前我已经上传过了,这篇文章主要讲怎么实现循迹与舵机的位置式PID调参和电机的增量式调参的一些心得。

一、摄像头循迹的实现

首先我们要明白英飞凌公司旗下的小钻风摄像头为灰度摄像头,它收集到的图像信号为黑白灰像素点构成的图像,那我们想要小车能识别到赛道边缘从而实现循迹,就需要对采集到的图像进行二值化处理。

那么什么叫二值化处理呢,下面我结合代码进行说明。

  1. int16 i,j;
  2. uint8 mt9v03x_image_2[120][188];
  3. uint8 th=100;//阈值大小
  4. mt9v03x_init();//摄像头初始化
  5. tft180_set_dir(TFT180_CROSSWISE_180); // 需要横屏不然显示不下
  6. tft180_init();//屏幕初始化
  7. if(mt9v03x_finish_flag)//这个if函数内部进行对采集到的每一帧画面进行处理
  8. {
  9. erzhihua();//二值化函数
  10. tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);//将处理的图像显示出来
  11. mt9v03x_finish_flag = 0;
  12. }
  13. void erzhihua(void)
  14. {
  15. //说明:摄像头采集的图像会以数组的形式存起来,其中数组的里面的数据为0~255的uint8型的数,其中0为黑色,255为白色
  16. int i,j;
  17. for(i=0;i<MT9V03X_H;i++)
  18. {
  19. for(j=0;j<MT9V03X_W;j++)
  20. {
  21. mt9v03x_image_2[i][j] = mt9v03x_image[i][j];
  22. }
  23. }//为防止二值化对原始数据的破坏导致不能正常输出,我们复制了一个一样的数组,对其进行操作
  24. for(i=0;i<MT9V03X_H;i++)//行遍历
  25. {
  26. for(j=0;j<MT9V03X_W;j++)//列遍历
  27. {
  28. if(mt9v03x_image[i][j]<th)
  29. {
  30. mt9v03x_image[i][j]=0;
  31. mt9v03x_image_2[i][j]=0; //小于阈值变黑
  32. }
  33. else
  34. {
  35. mt9v03x_image[i][j]=255;
  36. mt9v03x_image_2[i][j]=255; //大于阈值变白
  37. }
  38. }
  39. }
  40. }

简单来说二值化就是将黑白灰图像转化为只有黑白的图像,通过阈值的合理设定达到对赛道边缘的黑化(便于检测)的效果。

但是二值化仅仅使得我们知道了赛道边缘在哪里,而我们想要小车保持在赛道中间,就需要控制转向的舵机的配合。具体怎么配合呢?

首先,我们要将得到二值化图像进行处理,将赛道的中线表示出来。此时这个所谓的赛道中线其实本质上就是一串数组对应的宽和高。通过将得到的赛道中线的水平位置与屏幕显示的中间位置(就是数组的宽的一半)进行比较,算出小车与赛道中心的误差值,将这个误差值输入到舵机的PID中进行计算,最后输出PWM给舵机进行转向。

具体代码如下

  1. int16 i,j;
  2. uint8 mt9v03x_image_2[120][188];
  3. uint8 th=100;//阈值大小
  4. uint8 mid[120];
  5. int side_left[120],side_right[120];
  6. void erzhihua(void)
  7. {
  8. int i,j;
  9. for(i=0;i<MT9V03X_H;i++)
  10. {
  11. for(j=0;j<MT9V03X_W;j++)
  12. {
  13. mt9v03x_image_2[i][j] = mt9v03x_image[i][j];
  14. }
  15. }
  16. for(i=0;i<MT9V03X_H;i++)
  17. {
  18. for(j=0;j<MT9V03X_W;j++)
  19. {
  20. if(mt9v03x_image[i][j]<th)
  21. {
  22. mt9v03x_image[i][j]=0;
  23. mt9v03x_image_2[i][j]=0; //小于阈值变黑
  24. }
  25. else
  26. {
  27. mt9v03x_image[i][j]=255;
  28. mt9v03x_image_2[i][j]=255; //大于阈值变白
  29. }
  30. }
  31. }
  32. }
  33. void xunji_Mid(void)
  34. {
  35. erzhihua();
  36. int i,j;
  37. for(i=0;i<MT9V03X_H;i++)
  38. {
  39. for(j=80;j<MT9V03X_W;j++)
  40. {
  41. if( mt9v03x_image_2[i][j]==0)
  42. { side_right[i]=j;break;}//从屏幕中间开始向两边寻找黑点,找到就记下黑点的横坐标
  43. }
  44. for(j=80;j>=0;j--)
  45. {
  46. if( mt9v03x_image_2[i][j]==0)
  47. { side_left[i]=j;break;}
  48. }
  49. mid[i]=(side_right[i]+side_left[i])/2;//赛道中线就是左右端点之和除以二
  50. }
  51. for(int i=120;i>70;i--)
  52. {
  53. feedbackpositionleijia+=mid[i];
  54. }
  55. feedbackposition= feedbackpositionleijia/50;//为提高数据可信度,减小误差,将50个中点求和算平均值
  56. feedbackpositionleijia=0;
  57. }
  58. void show_line(void)//将中线与边界线在显示的图像中描黑
  59. {
  60. int i;
  61. for(i = MT9V03X_H-1; i >= 0; i--)
  62. {
  63. //画赛道中线
  64. if(mid[i] < MT9V03X_W && mid[i] >= 0)
  65. {
  66. mt9v03x_image_2[i][side_left[i]] = 0;
  67. mt9v03x_image_2[i][side_right[i]] = 0;
  68. mt9v03x_image_2[i][mid[i]] = 0;
  69. }
  70. }
  71. }
  72. if(mt9v03x_finish_flag)
  73. {
  74. erzhihua();
  75. xunji_Mid();
  76. show_line();
  77. tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);
  78. mt9v03x_finish_flag = 0;
  79. }//最后写在主函数循环里面的显示函数
  1. PID_Calc_wz(&mypid_Angle,targetposition,feedbackposition);//位置式pid控制舵机
  2. int angleout=dutyzhong+mypid_Angle.output;
  3. pwm_set_duty(SERVO_MOTOR_PWM,angleout);
  4. int dutyzhong=720;//每个舵机的中点对应的PWM占空比不同,一般在750上下
  5. float feedbackposition;
  6. float feedbackpositionleijia;
  7. float targetposition=80;//这里获取小车目标位置
  8. void PID_Init(PID *pid,float p,float i,float d,int16 maxI,int16 maxOut)
  9. {
  10. pid->kp=p;//调参只要改动这三个值就行p,i,d
  11. pid->ki=i;
  12. pid->kd=d;
  13. pid->maxIntegral=maxI;//最大积分
  14. pid->maxOutput=maxOut;//最大输出值
  15. }
  16. 进行一次位置式pid计算
  17. 参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
  18. void PID_Calc_wz(PID *pid,float reference,float feedback)
  19. {
  20. //更新数据
  21. pid->lastError=pid->error;//将旧error存起来
  22. pid->error=reference-feedback;//计算新error
  23. //计算微分
  24. int dout=(pid->error-pid->lastError)*pid->kd;//误差的差值乘以Kd
  25. //计算比例
  26. int pout=pid->error*pid->kp;//误差乘以Kp
  27. //计算积分
  28. pid->integral+=pid->error*pid->ki;//误差的累加乘以Ki
  29. //积分限幅
  30. if(pid->integral > pid->maxIntegral) pid->integral=pid->maxIntegral;//积分过大则限幅赋值为max
  31. else if(pid->integral < -pid->maxIntegral) pid->integral=-pid->maxIntegral;//积分过小则赋值为min
  32. //计算输出
  33. pid->output=pout+dout+pid->integral;//输出值为P+I+D
  34. //输出限幅
  35. if(pid->output > pid->maxOutput) pid->output=pid->maxOutput;
  36. else if(pid->output < -pid->maxOutput) pid->output=-pid->maxOutput;
  37. if(pid->output < pid->minOutput) pid->output=pid->minOutput;
  38. }

二、PID调参技巧

位置式PID:先调Kp,使得波形在目标值上下震荡,且幅度会逐渐减小。接下来就加入Kd来对震荡进行抑制,当Kd过大时就没有抑制作用,反而会使得波形不停震荡。最后加入Ki来使得最后趋于稳定的值更趋近于目标值。当你初步调节完成PID参数时,先记录下此时的参数,然后回过去继续微调Kp、Kd、Ki。直到最后波形达到一定的响应与稳定时才算调好了参数。而且每个电机的PID参数不尽相同,需要单独调节。

增量式PID:因为由于公式为增量,Ki对应的量才是error,所以先调Ki。接着再调Kp,最后调Kp。具体调到什么情况合适参考位置式PID。

可以用vofa这个软件,用电脑这个上位机来实时显示波形,便于我们的调参。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/268707?site
推荐阅读
相关标签
  

闽ICP备14008679号