赞
踩
目录
“愿大家都少走弯路,在迷茫时看到希望!”
方法①:识别四顶点位置->连接对角线得到中心点->PID调节使激光点与中心点重合
方法②:识别四顶点位置->对角顶点坐标求平均值得中心点位置->PID调节使重合
方法③:固定所有器件位置,保证各点PWM值不变,得到中心点PWM固定值,开环设定
步骤I:激光点由中心点到达边线左上角
步骤II:顺时针绕一圈
方法①:两点定线,先确定两点坐标,连线确定等分点,使用PID算法在等分点间移动
方法②:不使用PID,利用与目标点坐标差计算移动方向,每次移动距离为舵机最小精度值
方法③:求PWM和坐标(x,y)的函数关系(近似线性),直接设定PWM值到达指定点
(与任务二区别:矩形放置角度可以倾斜;要区分两矩形宽度以识别A4纸)
方法①:区分红绿色激光并得到坐标->PID直接跟踪
将上述任务分解成多个要完成的技术,以便分工:
1.硬件平台搭建
2.stm32控制算法:
①PID控制激光点移动到目标点算法(核心)
②舵机以最小分度值移动算法(细微调节)
③给定两点以及等分数计算所有等分点算法(线上移动减少偏差)
④在PID寻点时获取基本点(矩形顶点及中心)PWM值算法
⑤stm32和jetson nano的通信规则设计与数据互传
3.OpenCV识别算法
①识别铅笔线边框:灰度图转换->阈值分割成二值图->霍夫直线变换得到直线上两点(非端点)->从得到的多条直线中筛选去重->编写“已知两直线上两点求直线交点”算法->求得四端点
②识别A4纸边框:阈值分割后利用Harris角点检测出A4框的8个顶点->编写“从8个顶点中识别两两相邻顶点”算法->求得框中心线4顶点
③区分红绿激光点算法:转换到Hsv色彩空间->分别设置阈值,在Hsv空间中二值化图像提取红绿色区域以得到激光点坐标
4.主函数(程序流程)设计
5.电赛报告书写
(1)引脚使用说明
- //*************************引脚使用说明*************************
- /*
- oled.h GPIOA PIN0/1
- bluetooth.h GPIOA PIN2/3
- joystick.h GPIOA PIN4/5 ADC1_CH4/5 GPIOB PIN11/12/13 EXTI12/13
- Pwm.h GPIOA PIN8/11 TIM1_CH1/4 50hz
- usart.h GPIOA PIN9/10 TX/RX Black/White
- beep.h GPIOB PIN14
- led.h GPIOB PIN15
- Timer.h TIM2/3
- */
(2)头文件声明
- //************************头文件声明************************
- #include "public.h" //公用引用函数封装
- //#include "bluetooth.h" //蓝牙模块
- #include "oled.h" //OLED显示屏模块
- #include "Pwm.h" //PWM波生成模块
- #include "servo_motor.h" //云台控制函数模块
- #include "joystick.h" //摇杆控制模块
- #include "string.h"
- #include "Delay.h"
- #include "Timer.h" //定时器模块
- #include "usart.h" //uart通信模块
- #include "beep.h" //蜂鸣器模块
- #include "led.h" //led灯模块
- #include "dma.h" //dma数据转存模块
(3)全局变量和宏定义声明
- //************************全局变量和宏定义声明************************
- //#define OpenLoop_OL //开环实现功能执行
- #define CloseLoop_CL //闭环实现功能执行
-
- extern float Voltage[2]; //ad测量电压值[0.3.3] //ad.c
- extern char USART_RX_INFO[USART_REC_LEN]; //uart接收数据 //usart.c
- extern int x,y; //激光当前坐标 //servo_motor.c
- extern int Vertex[4][2]; //四顶点位置 //servo_motor.c
- extern int Vertex_Peak_Pos[4][2];
- extern int Vertex_A4[4][2];
- extern Pwm Center_Pwm;
- extern Pwm Peak_Pwm[4];
- extern Pwm A4_Pwm[4];
-
- int Programme_Progress=0; //比赛程序进度
- int order=0; //蓝牙接收到的命令
- int Main_Wait_Stop_Sign =1; //主程序等待标志位
- extern int JoyStick_Control_Stop_Sign; //摇杆控制程序结束标志位
- int Get_Depend_Point_Pos_Stop_Sign=1;
- int Get_A4_Point_Pos_Stop_Sign=1;
- extern int Follow_Track_Stop_Sign; //矩形寻迹结束标志位
- extern int Follow_Point_Stop_Sign; //绿激光跟随红激光结束标志位
- #include "Timer.h"
-
- //TIM2/3
-
- void Timer_Init(void)
- {
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
-
- TIM_InternalClockConfig(TIM2);
- TIM_InternalClockConfig(TIM3);
-
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
- TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_TimeBaseInitStructure.TIM_Period = 60000 - 1; //分辨率1us,最大60ms
- TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
-
- }
-
- void Timer_delay_us(int xus)
- {
- TIM_Cmd(TIM2,ENABLE); //启动定时器
- while(TIM2->CNT < xus);
- TIM2->CNT = 0;
- TIM_Cmd(TIM2,DISABLE); //关闭定时器
- }
-
- void Timer_delay_ms(int xms)
- {
- int i=0;
- for(i=0;i<xms;i++)Timer_delay_us(1000);
- }
-
- //外部中断专用延时函数
- void EXIT_LINE_Timer_delay_us(int xus)
- {
- TIM_Cmd(TIM3,ENABLE); //启动定时器
- while(TIM3->CNT < xus);
- TIM3->CNT = 0;
- TIM_Cmd(TIM3,DISABLE); //关闭定时器
- }
-
- void EXIT_LINE_Timer_delay_ms(int xms)
- {
- int i=0;
- for(i=0;i<xms;i++)EXIT_LINE_Timer_delay_us(1000);
- }
说明:
在Timer_Init()中开启了两个定时器TIM1/2,由Timer_delay_us()和EXIT_LINE_Timer_delay_us()分别使用,分别在中断函数内外使用,避免重复调用冲突
- int Oc_Lp[4]={750,750,750,750};
- int Oc_Vp[4]={763,763,763,763};
- /*********************************************************
- 函数功能:云台水平方向旋转
- *********************************************************/
- void Spinnig_Level(int diff)
- {
- if(diff<0)
- {
- Oc_Lp[0]=Oc_L=(Oc_L+diff)<660?660:(Oc_L+diff);
- }
- else if(diff>0)
- {
- Oc_Lp[0]=Oc_L=(Oc_L+diff)>840?840:(Oc_L+diff);
- }
- TIM_SetCompare1(TIM1,Oc_L);
- int i;
- for(i=3;i>0;i--)Oc_Lp[i]=Oc_Lp[i-1];
- }
说明:
这里的Oc_Lp存储的是控制舵机的pwm波参数中的OC寄存器中的值,作为舵机运动最基本的函数,舵机的控制通过改变pwm波参数中的OC寄存器中的值实现。这里定义数组实现记忆功能,可存储前三次的OC值。并通过三元运算符设定上下限,将最终的OC值通过TIM_SetCompare1()设定。
- /*********************************************************
- 函数功能:云台控制激光点到达某一点
- 函数参数:目标点的坐标
- *********************************************************/
- int x=360,y=360; //跟随点当前坐标
- int Reach_Pos_CL_Stop_Sign=1;
- //云台水平方向旋转PID值
- float Level_Kp=0.06;
- float Level_Ki=0.02;
- float Level_Kd=0.01;
- //云台竖直方向旋转PID值
- float Vert_Kp=0.06;
- float Vert_Ki=0.02;
- float Vert_Kd=0.01;
- void Reach_Pos_CL(int Target_X,int Target_Y,int Reach_Pos_CL_MODE)
- {
- int Sign(int num);
- void Get_Point_Pos(void);
- int near(int Target_X,int Target_Y);
-
- int diff_x,diff_y;
- while(Reach_Pos_CL_Stop_Sign)
- {
- Timer_delay_ms(30);
- Get_Point_Pos();
- if(near(Target_X,Target_Y)<=6)
- {
- Beep_Times(10,1,NORMAL_MODE);
- break;
- }
- if(Reach_Pos_CL_MODE==PID_MODE && near(Target_X,Target_Y)>60) //用pid计算舵机单位数
- {
- diff_x=Pid_Control(Level_Kp,Level_Ki,Level_Kd,Target_X,x,PID_REALIZE);
- diff_y=Pid_Control(Vert_Kp,Vert_Ki,Vert_Kd,Target_Y,y,PID_REALIZE);
- }
- else if(Reach_Pos_CL_MODE==MINMIZE_MODE) //以舵机最小分辨率为单位
- {
- diff_x=-Sign(x-Target_X);
- diff_y=-Sign(y-Target_Y);
- }
- else if(Reach_Pos_CL_MODE==PID_MODE && near(Target_X,Target_Y)<=60) //用pid计算舵机单位数
- {
- diff_x=-Sign(x-Target_X);
- diff_y=-Sign(y-Target_Y);
- Timer_delay_ms(30);
- }
- Spinnig_Level(X_DIR*diff_x);
- Spinnig_Vert(Y_DIR*diff_y);
- Timer_delay_ms(20);
- }
- }
-
- int Sign(int num)
- {
- if(num>5)return 1;
- else if(num<-5)return -1;
- else return 0;
- }
-
- int my_abs(int a,int b)
- {
- return a-b>0?a-b:b-a;
- }
-
- int near(int Target_X,int Target_Y)
- {
- return my_abs(Target_X,x)+my_abs(Target_Y,y);
- }
说明:
输入参数:目标点像素坐标;追踪模式(PID【PID与最小精度混合】模式和最小精度值模式)
追踪过程:
——得到当前激光点坐标:Get_Point_Pos()
——如果接近目标点则蜂鸣器鸣叫并退出【near(Target_X,Target_Y)<=6,说明当前坐标与目标横纵坐标差之和{“距离”}小于6个像素】
——如果使用PID模式:
——“距离”大于60时采用PID算法快速靠近,计算出OC变化值diff_x、diff_y
——“距离”小于60时使用最小精度模式缓慢靠近,利用“符号函数sign()”计算diff
——调用Spinnig_Level()、Spinnig_Level()进行水平和垂直舵机的旋转
a.激光点坐标的实时接收
- /*********************************************************
- 函数功能:stm32获取当前激光坐标
- *********************************************************/
- void Get_Point_Pos(void)
- {
- if(USART_RX_INFO[0]=='x') //检查数据定位是否正确(上位机发送信息为:x123y456)
- {
- x=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
- }
- if(USART_RX_INFO[4]=='y') //检查数据定位是否正确(上位机发送信息为:x123y456)
- {
- y=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
- }
- }
说明:
规定上位机每次发送数据格式为:以#开头,以$结尾;stm32usart模块对接收数据进行解析
上位机坐标数据格式为:x123y456;123、456代表三位坐标值,字符'x'、'y'起定位作用
stm32对接收到的字符坐标进行解析如上
b.特殊坐标接收
- //********************************************************高级控制函数(CloseLoop--CL)********************************************************
- int Vertex_Peak_Pos[4][2];
- int Center_Pos[2];
- Pwm Center_Pwm;
- Pwm Peak_Pwm[4];
- Pwm A4_Pwm[4];
- //获取重要点坐标
- void Get_Point_5(void)
- {
- int i,j;
- while(1)
- {
- for(i=0;i<8;i++)
- {
- if(USART_RX_INFO[4*i]=='a'+i)continue;
- else break;
- }
- if(i==8)
- {
- for(i=0;i<4;i++)
- {
- for(j=0;j<2;j++)Vertex_Peak_Pos[i][j]=(USART_RX_INFO[4*(2*i+j)+1]-'0')*100+(USART_RX_INFO[4*(2*i+j)+2]-'0')*10+(USART_RX_INFO[4*(2*i+j)+3]-'0');
- }
- break;
- }
- }
-
- while(!(USART_RX_INFO[0]=='i'&&USART_RX_INFO[4]=='j'));
-
- Center_Pos[0]=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
- Center_Pos[1]=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
- Beep_Times(50,5,NORMAL_MODE);
-
- }
说明:
这里接收的是铅笔线框四个顶点的坐标和中心点坐标,但是一次发送的数据长度不能太长,这里拆分成两部分接收(数据格式为:axxxbxxxcxxx...hxxx共8组值四个坐标),关键在于两部分的衔接
while(!(USART_RX_INFO[0]=='i'&&USART_RX_INFO[4]=='j'));确保收到四个顶点坐标后持续等待中心点坐标的发送
- int sum_num(int *num,int n)
- {
- int i,sum;
- for(i=sum=0;i<n;i++)sum+=num[i];
- return sum;
- }
- //获取目标点pwm值
- void Get_Pwm(int px,int py,Pwm *target_pwm,int n)
- {
- Reach_Pos_CL(px,py,PID_MODE);
- target_pwm->level=sum_num(Oc_Lp,n)/n;
- target_pwm->vert=sum_num(Oc_Vp,n)/n;
- }
说明:
通过②控制函数控制激光点到达指定点后记录目标点pwm值并返回;Pwm结构体定义如下
- typedef struct Pwm{
- int level;
- int vert;
- }Pwm;
可以通过改变参数n的值选择是否滤波,4>n>1时进行滤波,取前几次OC值的平均值,不建议滤波
- //巡线
- void Follow_Track(int Vertex[4][2],int divide_num)
- {
- int i,j;
- float sub_l,sub_v;
- Pwm Vertex_Pwm[4];
- for(i=0;i<4;i++)Get_Pwm(Vertex[i][0],Vertex[i][1],&Vertex_Pwm[i],1);
-
- for(i=0;i<4;i++)
- {
- sub_l=(Vertex_Pwm[(i+1)%4].level-Vertex_Pwm[i].level); //下一个顶点与当前顶点pwm之差
- sub_v=(Vertex_Pwm[(i+1)%4].vert-Vertex_Pwm[i].vert); //下一个顶点与当前顶点纵坐标之差
- for(j=0;j<divide_num;j++)
- {
- Reach_Pos_OL(Vertex_Pwm[i].level+j*sub_l/divide_num,Vertex_Pwm[i].vert+j*sub_v/divide_num);
- Timer_delay_ms(200);
- }
- Reach_Pos_OL(Vertex_Pwm[(i+1)%4].level,Vertex_Pwm[(i+1)%4].vert);
- Timer_delay_ms(300);
- }
-
- Beep_Times(50,5,NORMAL_MODE);
- }
说明:
输入参数:四边形顺时针顺序顶点坐标、每段等分数divide_num
巡线过程:
——得到四个顶点坐标对应的水平、数值舵机OC值
——在for循环内依次经过四个顶点,视作四个大任务
——内部使用for循环分解小任务,根据等分段数divide_num计算等分点横纵pwm值并移动至
——任务结束鸣叫示意
- //*************************主函数部分*************************
- //重新重启初值还原设置
- void Programme_Reset(void)
- {
- Beep_Times(1000,1,NORMAL_MODE);
- Led_Times(1000,1,NORMAL_MODE);
- Programme_Progress=0;
-
- Main_Wait_Stop_Sign=1;
- JoyStick_Control_Stop_Sign=1;
- Follow_Track_Stop_Sign=1;
-
- Get_A4_Point_Pos_Stop_Sign=1;
- Get_Depend_Point_Pos_Stop_Sign=1;
- }
-
- int main(void)
- {
- //********************初始化程序********************
- Timer_Init(); //定时器初始化
- // BlueToothInit(9600,USART_Parity_No,USART_StopBits_1,USART_WordLength_8b); //蓝牙初始化
- OLED_Init(); //oled初始化
- Beep_Init(); //蜂鸣器初始化
- Led_Init(); //led灯初始化
- TIM1_PWM_Init(9999,143); //一周期20ms,分辨率20ms/10000)
- TIM_SetCompare1(TIM1,750); //对齐角度为90度(1.5ms)
- TIM_SetCompare4(TIM1,763); //对齐角度为90度(1.5ms)
- uart_init(115200); //uart1初始化
- JoyStick_Init(); //JoyStick摇杆初始化
-
- //*************************比赛程序部分*************************
- while(1)
- {
- int i;
- //重新重启初值还原设置
- Programme_Reset();
- // Reach_Pos_CL(50,50,PID_MODE);
-
- Axes_Init();
-
- // Follow_Track(Vertex_Peak_Pos,1);
-
- while(Main_Wait_Stop_Sign);
- //摇杆控制
- JoyStick_Control();
-
-
- //#ifdef OpenLoop_OL
- // Follow_Track_OL();
- //#endif
- //#ifdef CloseLoop_CL
- // //等待上位机发送初始坐标
- // Get_Depend_Point_Pos();
- // //环绕正方形顺时针旋转一周
- // while(Get_Depend_Point_Pos_Stop_Sign);
-
- //Follow_Track_CL(Vertex_Peak_Pos,2,PID_MODE);
-
- //#endif
-
- Pwm_Track(Peak_Pwm,1);
- while(Follow_Track_Stop_Sign);
-
- Get_A4_Point_Pos();
- Timer_delay_ms(2000);
- // Follow_Track_CL(Vertex_A4,4,MINMIZE_MODE);
- // Follow_Track(Vertex_A4,4);
- for(i=0;i<4;i++)Get_Pwm(Vertex_A4[i][0],Vertex_A4[i][1],&A4_Pwm[i],1);
- Pwm_Track(A4_Pwm,6);
- while(Get_A4_Point_Pos_Stop_Sign);
-
- }
- }
- //*********************************************中断函数部分*********************************************
- //按键中断函数
- void EXTI15_10_IRQHandler()
- {
- if (EXTI_GetITStatus(EXTI_Line11) == SET)
- {
- EXIT_LINE_Timer_delay_ms(10);
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0) //软件防抖
- {
- Beep_Times(50,2,EXIT_LINE_MODE);
- Reach_Pos_OL(Oc_L,Oc_V); //保持激光当前指向位置
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0); //等待按键松开
-
- //再次按下才退出
- EXIT_LINE_Timer_delay_ms(10);
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==1);
- EXIT_LINE_Timer_delay_ms(10);
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0); //软件防抖
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11==1)); //等待按键松开
- Beep_Times(50,2,EXIT_LINE_MODE);
-
- EXTI_ClearITPendingBit(EXTI_Line11);
- }
- }
-
- else if (EXTI_GetITStatus(EXTI_Line12) == SET)
- {
- EXIT_LINE_Timer_delay_ms(10);
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0) //软件防抖
- {
- Programme_Progress++;
- Beep_Times(500,1,EXIT_LINE_MODE);
- if(Programme_Progress==1)
- {
- Main_Wait_Stop_Sign=0;
- }
- else if(Programme_Progress==2)
- {
- JoyStick_Control_Stop_Sign=0;
- }
- else if(Programme_Progress==3)
- {
- // Get_Depend_Point_Pos_Stop_Sign=0;
- Follow_Track_Stop_Sign=0;
- }
- else if(Programme_Progress==4)
- {
- Get_A4_Point_Pos_Stop_Sign=0;
- // Follow_Track_Stop_Sign=0;
- }
- else if(Programme_Progress==5)
- {
- // Get_A4_Point_Pos_Stop_Sign=0;
- }
- else if(Programme_Progress==6)
- {
- ;
- }
- else if(Programme_Progress==7)
- {
- ;
- }
- else
- {
- Programme_Reset();
- }
-
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0); //等待按键松开
- EXTI_ClearITPendingBit(EXTI_Line12);
- }
-
- }
-
- else if (EXTI_GetITStatus(EXTI_Line13) == SET)
- {
- EXIT_LINE_Timer_delay_ms(10);
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0) //软件防抖
- {
- Beep_Times(50,3,EXIT_LINE_MODE);
-
- Reach_Pos_OL(Center_Pwm.level,Center_Pwm.vert);
-
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0); //等待按键松开
-
- //再次按下才退出
- EXIT_LINE_Timer_delay_ms(10);
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==1);
- EXIT_LINE_Timer_delay_ms(10);
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0); //软件防抖
- while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13==1)); //等待按键松开
- Beep_Times(50,3,EXIT_LINE_MODE);
-
- EXTI_ClearITPendingBit(EXTI_Line13);
- }
- }
- }
说明:
主函数与中断函数相辅相成,程序整体使用外部中断推进以及实现一些特殊功能(立即复位);
由于主函数内小功能函数都借助while()循环实现,设置循环标志位Stop_Sign和程序阶段标志位Programme_Progress来推进主函数;
按下GPIOB,GPIO_Pin_12的按键触发中断,Programme_Progress++以及相应的Stop_Sign=0,以控制目前运行小功能停止并进入下一阶段;
在程序开始和结束处执行Programme_Reset()函数,重置各标志位实现程序重新运行;
注意:
在中断函数内部涉及到的延时函数统统使用EXIT_LINE_Timer_delay_ms()函数,区别外部使用的Timer_delay_ms()函数,防止调用冲突程序卡死
- //绿车
- int r_x=0,r_y=0;
- void Get_RaG_Point_Pos(void)
- {
- if(USART_RX_INFO[0]=='g'&& USART_RX_INFO[8]=='r'&& USART_RX_INFO[4]=='y'&& USART_RX_INFO[12]=='y') //检查数据定位是否正确(上位机发送信息为:x123y456)
- {
- x=(USART_RX_INFO[1]-'0')*100+(USART_RX_INFO[2]-'0')*10+USART_RX_INFO[3]-'0';
- y=(USART_RX_INFO[5]-'0')*100+(USART_RX_INFO[6]-'0')*10+USART_RX_INFO[7]-'0';
- r_x=(USART_RX_INFO[9]-'0')*100+(USART_RX_INFO[10]-'0')*10+USART_RX_INFO[11]-'0';
- r_y=(USART_RX_INFO[13]-'0')*100+(USART_RX_INFO[14]-'0')*10+USART_RX_INFO[15]-'0';
- }
- }
-
- void G_Follow_R(int Reach_Pos_CL_MODE)
- {
- int Sign(int num);
- void Get_RaG_Point_Pos(void);
- int near(int r_x,int Target_Y);
-
- int diff_x,diff_y,dis;
- while(Reach_Pos_CL_Stop_Sign)
- {
- if(x==0&&y==0)Reach_Pos_OL(750,750);
- Get_RaG_Point_Pos();
- dis=near(r_x,r_y);
- if(dis<=20)
- {
- Beep_Times(300,1,NORMAL_MODE);
- Led_Times(300,1,NORMAL_MODE);
- continue;
- }
- if(Reach_Pos_CL_MODE==PID_MODE && dis>60) //用pid计算舵机单位数
- {
- Get_RaG_Point_Pos();
- diff_x=Pid_Control(Level_Kp,Level_Ki,Level_Kd,r_x,x,PID_REALIZE);
- diff_y=Pid_Control(Vert_Kp,Vert_Ki,Vert_Kd,r_y,y,PID_REALIZE);
- }
- else if(Reach_Pos_CL_MODE==MINMIZE_MODE) //以舵机最小分辨率为单位
- {
- Get_RaG_Point_Pos();
- diff_x=-0.5*Sign(x-r_x);
- diff_y=-0.5*Sign(y-r_y);
- }
- else if(Reach_Pos_CL_MODE==PID_MODE && dis<=60) //用pid计算舵机单位数
- {
- Get_RaG_Point_Pos();
- diff_x=-0.4*Sign(x-r_x);
- diff_y=-0.4*Sign(y-r_y);
-
- }
- Spinnig_Level(X_DIR*diff_x);
- Spinnig_Vert(Y_DIR*diff_y);
- Timer_delay_ms(20);
- }
- }
说明:
上位机数据格式为:g123y123r123y123,实时传输红绿激光点两个坐标;
执行点到点的跟踪即可,在主函数中不断重复即可,即while(1)G_Follow_R(PID_MODE);
文件说明:
mian_10、main_11、mian_12是测试函数,分别测试铅笔线识别效果、A4纸识别效果、红绿激光分别识别效果。设置了滑动条供调参使用,确定好参数
q_1、q_2、q_3即为三个问题对应的程序,分别实现发送铅笔线顶点和中心坐标后实时传输红色激光点坐标、发送A4纸顶点坐标后实时传输红色激光点坐标、实时传输红色和绿色激光点坐标
- string gstreamer_pipeline(int capture_width, int capture_height, int display_width, int display_height, int framerate, int flip_method)
- {
- return "nvarguscamerasrc exposurecompensation=1 ! video/x-raw(memory:NVMM), width=(int)" + to_string(capture_width) + ", height=(int)" +
- to_string(capture_height) + ", format=(string)NV12, framerate=(fraction)" + to_string(framerate) +
- "/1 ! nvvidconv flip-method=" + to_string(flip_method) + " ! video/x-raw, width=(int)" + to_string(display_width) + ", height=(int)" +
- to_string(display_height) + ", format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink";
- }
这里设置好管道参数,主要调整曝光和饱和度,方便之后线条的检测以及红绿激光的区分
可以参考:NVIDIA Jetson Nano 2GB 系列文章(9):调节 CSI 图像质量
变量解析:
- int Find = 0, l_x = 0, l_y = 0, r_x = 0, r_y = 0;
- int l[2][2],r[2][2],u[2][2],d[2][2];
- int ul[2],ur[2],dl[2],dr[2],ce[2];
Find有效个数标志位,表示找到了几组有效的边上两点;
l_x、l_y、r_x、r_y寻找标志位,为1则分别表示上下左右边未找到有效值的两点值
l[2][2]、r[2][2]、u[2][2]、d[2][2]分别存储上下左右边上两点坐标
ul[2]、ur[2]、dl[2]、dr[2]、ce[2]分别存储最终的顶点和中心点坐标
过程:
——转换成灰度图->阈值划分成二制图->霍夫直线检测得到直线并输出直线上两点坐标
——设计算法过滤筛选重复直线并存储两点坐标
- for (size_t i = 0; i < linesPPHT.size(); i++) {
- x1 = linesPPHT[i][0], y1 = linesPPHT[i][1], x2 = linesPPHT[i][2], y2 = linesPPHT[i][3];
- line(image, Point(x1, y1), Point(x2, y2), Scalar(0), 1, 8);
-
- if (x1 < 150 && x2 < 150 && myabs(x2 - x1) < 3 && !l_x){Find++;l_x = (x2 + x1) / 2;l[0][0]=x1;l[0][1]=y1;l[1][0]=x2;l[1][1]=y2;}
- else if (y1 < 150 && y2 < 150 && myabs(y1 - y2) < 3 && !l_y){Find++;l_y = (y1 + y2) / 2;u[0][0]=x1;u[0][1]=y1;u[1][0]=x2;u[1][1]=y2;}
- else if (x1 > 570 && x2 > 570 && myabs(x2 - x1) < 3 && !r_x){Find++;r_x = (x2 + x1) / 2;r[0][0]=x1;r[0][1]=y1;r[1][0]=x2;r[1][1]=y2;}
- else if (y1 > 570 && y2 > 570 && myabs(y1 - y2) < 3 && !r_y){Find++;r_y = (y1 + y2) / 2;d[0][0]=x1;d[0][1]=y1;d[1][0]=x2;d[1][1]=y2;}
- }
linesPPHT是霍夫直线检测函数的输出,linesPPHT.size()表示检测到直线的条数;这里根据直线上两点坐标值大小判断属于四条边的那一条;属于其中一条且之前未存储(标志位为1)(见if语句中的判断)则存储并将找点标志位Find+1;Find==4时即寻找结束
——由于霍夫直线检测算法得到的并非顶点而是直线上两点,设计求两直线交点函数
- void crossline(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,int cross[2])
- {
- cross[0]=(y3*x4*x2-y4*x3*x2-y3*x4*x1+y4*x3*x1-y1*x2*x4+y2*x1*x4+y1*x2*x3-y2*x1*x3)/(x4*y2-x4*y1-x3*y2+x3*y1-x2*y4+x2*y3+x1*y4-x1*y3);
- cross[1]=(-y3*x4*y2+y4*x3*y2+y3*x4*y1-y4*x3*y1+y1*x2*y4-y1*x2*y3-y2*x1*y4+y2*x1*y3)/(y4*x2-y4*x1-y3*x2+x1*y3-y2*x4+y2*x3+y1*x4-y1*x3);
- }
输入的(x1,y1)~(x4,y4)是两条直线上四点坐标,输出交点坐标并赋值给cross;
- crossline(l[0][0],l[0][1],l[1][0],l[1][1],u[0][0],u[0][1],u[1][0],u[1][1],ul);
- crossline(r[0][0],r[0][1],r[1][0],r[1][1],u[0][0],u[0][1],u[1][0],u[1][1],ur);
- crossline(l[0][0],l[0][1],l[1][0],l[1][1],d[0][0],d[0][1],d[1][0],d[1][1],dl);
- crossline(r[0][0],r[0][1],r[1][0],r[1][1],d[0][0],d[0][1],d[1][0],d[1][1],dr);
-
- crossline(ul[0],ul[1],dr[0],dr[1],ur[0],ur[1],dl[0],dl[1],ce);
输入之前得到的坐标计算四个顶点值和中心坐标
——向下位机stm32输出坐标
- sprintf(m,"#a%03db%03dc%03dd%03de%03df%03dg%03dh%03d$\n",ul[0],ul[1],ur[0],ur[1],dr[0],dr[1],dl[0],dl[1]);
- uart.sendUart(m);
- usleep(50000);
- sprintf(m,"#i%03dj%03d$\n",ce[0],ce[1]);
- uart.sendUart(m);
过程:
——灰度图->二值化->角点检测得到角点坐标CornerImg
——设计算法过滤筛选得到八个顶点P[8][2](绝缘胶布内外边形成两个矩形)
- #define MAX_DIS 20
- int Is_Exit(int i, int j)
- {
- int k = 0;
- for (k = 0; k < Find; k++)
- {
- if (myabs(P[k][0]-i)+ myabs(P[k][1]-j)<MAX_DIS)return 1;
- }
- return 0;
- }
- int P[8][2] = { 0 };
- int Find = 0;
-
- for (int j = 0; j < CornerImg.rows; j++) {
- for (int i = 0; i < CornerImg.cols; i++) {
- if (CornerImg.at<float>(j, i) > 150.0f) {
- if (!Is_Exit(i, j))
- {
- P[Find][0] = i;
- P[Find][1] = j;
- Find++;
- }
- }
- }
- }
Is_Exit()函数遍历已经视作有效的点,如果与当前坐标(i,j)接近则不存储;找到八个有效点退出
——设计根据八个顶点P[8][2]求得巡线四边形的顶点Vertex[4][2](同一个角的内外顶点的中点)
- int Vertex[4][2] = { 0 };
- int sign[8] = { 0 };
- int i,j,k,dis,min = 1000;
- int temp1, temp2;
- for (k=0,i = 0; i < 8; i++)
- {
- if (sign[i])continue;
- min = 2000;
- for (j = 0; j < 8; j++)
- {
- if (i == j||sign[j])continue;
- dis = myabs(P[i][0] - P[j][0]) + myabs(P[i][1] - P[j][1]);
- if (dis< min)
- {
- min = dis;
- temp1 = i;
- temp2 = j;
- }
- }
- sign[temp1] = 1;
- sign[temp2] = 1;
- Vertex[k][0] = (P[temp1][0] + P[temp2][0])/2;
- Vertex[k][1] = (P[temp1][1] + P[temp2][1])/2;
- k++;
- }
这里使用for循环遍历P[8][2]中顶点,将距离最近的两点视为A4纸一个角内外两边的两个顶点,求其中点存储在Vertex[4][2]中
——设计算法使巡线的四个端点按照顺时针传输给下位机,否则巡线顺序错误
- int temp;
- //先整体按y值大小排序
- for(i=0;i<4;i++)
- {
- for(min=Vertex[i][1],j=k=i;j<4;j++)
- {
- if(Vertex[j][1]<=min)k=j;
- }
- temp=Vertex[k][0];
- Vertex[k][0]=Vertex[i][0];
- Vertex[i][0]=temp;
- temp=Vertex[k][1];
- Vertex[k][1]=Vertex[i][1];
- Vertex[i][1]=temp;
- }
- //y值中等的两点按x值排序
- if(Vertex[1][0]<Vertex[2][0])
- {
- temp=Vertex[1][0];
- Vertex[1][0]=Vertex[2][0];
- Vertex[2][0]=temp;
- temp=Vertex[2][1];
- Vertex[2][1]=Vertex[1][1];
- Vertex[1][1]=temp;
- }
- if(Vertex[0][0]&& Vertex[0][1]&&Vertex[1][0]&&Vertex[1][1]&&Vertex[3][0]&& Vertex[3][1]&&Vertex[2][0]&& Vertex[2][1])
- {
- sprintf(m,"#k%03dl%03dm%03dn%03do%03dp%03dq%03dr%03d$\n", Vertex[0][0], Vertex[0][1],Vertex[1][0], Vertex[1][1],Vertex[3][0], Vertex[3][1],Vertex[2][0], Vertex[2][1]);
- u.sendUart(m);
- }
观察任意矩形顶点坐标规律,要顺时针发送,可将y值最小的作为第一个发送,y值最大的第三个发送,介于中间的两点按x值大小判断,x小的最后发送,大的第二个发送
即先整体按y值大小排序,y值中等的两点按x值排序->排序后按0~1~3~2的顺序发送坐标
过程如下:
- Point color_recognite(Mat image, Scalar Low, Scalar High)
- {
-
- vector<vector<Point>> g_vContours;
- vector<Vec4i> g_vHierarchy;
- vector<Mat> hsvSplit;
- double maxarea = 0;
- int maxAreaIdx = 0;
- Mat g_grayImage, hsv, g_cannyMat_output;
-
- cvtColor(image, hsv, COLOR_BGR2HSV);
- split(hsv, hsvSplit);
- equalizeHist(hsvSplit[2], hsvSplit[2]);
- merge(hsvSplit, hsv);
- inRange(hsv, Low, High, g_grayImage);//二值化识别颜色
-
- //开操作 (去除一些噪点)
- Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));
- morphologyEx(g_grayImage, g_grayImage, MORPH_OPEN, element);
-
- //闭操作 (连接一些连通域)
- morphologyEx(g_grayImage, g_grayImage, MORPH_CLOSE, element);
- // Canny(g_grayImage, g_cannyMat_output, 80, 80 * 2, 3);
-
- // 寻找轮廓
- findContours(g_grayImage, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
-
- //假设contours是用findContours函数所得出的边缘点集
- RotatedRect box;
- Point centre;
-
- if (g_vContours.size() != 0)
- {
- for (int index = 0; index < g_vContours.size(); index++)
- {
- double tmparea = fabs(contourArea(g_vContours[index]));
- if (tmparea > maxarea)
- {
- maxarea = tmparea;
- maxAreaIdx = index;//记录最大轮廓的索引号
- }
- }
-
- box = minAreaRect(g_vContours[maxAreaIdx]);
- rectangle(image, box.boundingRect(), Scalar(0, 0, 255), 2);
- centre = box.center;
- }
- return centre;
- }
关键是调用inRange()函数HSV色彩空间二值化的阈值上下限设置
可以参考:OpenCV学习笔记-inRange()阈值操作函数怎么用_cv.inrange函数
并设置滑动条调整参数获得经验值
最终评判标准:
激光在绝缘胶布上是能否识别(黑色胶布吸光;通过提高曝光,调参,增大激光功率等可以解决)
红绿激光靠近时能否区分(红绿在HSC空间互斥,更亮的会掩盖另一个;调inRange()参数解决)
OpenCV学习笔记——《基于OpenCV的数字图像处理》_switch_swq的博客-CSDN博客
图像识别小车(电源部分)——电赛学习笔记(1)_switch_swq的博客-CSDN博客
图像识别小车(电机部分)——电赛学习笔记(2)_switch_swq的博客-CSDN博客
图像识别小车(jetson nano部分)——电赛学习笔记(3)_switch_swq的博客-CSDN博客
图像识别小车(PCB设计)——电赛学习笔记(4)_switch_swq的博客-CSDN博客
PID控制算法理解_switch_swq的博客-CSDN博客
1.唐老师讲电赛的个人空间-唐老师讲电赛个人主页-哔哩哔哩视频
2.电赛资料:电赛资料_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:1234
3.我的“电赛”、“VS Studio”、“cmake”、“opencv”、“makefile”、“linux、操作系统”、“stm32”收藏夹
3.以及上面笔记中所包含信息
7.2.2023
7.3.2023
7.4.2023
7.5.2023
7.6.2023
7.7.2023
7.8.2023
7.9.2023
7.10.2023
7.11.2023
7.12.2023
7.13.2023
7.14.2023
7.15.2023
7.16.2023
7.17.2023
7.18.2023
7.19.2023
7.20.2023
7.21.2023
7.22.2023
7.23.2023
7.24.2023
7.25.2023
7.26.2023
7.27.2023
7.28.2023
7.29.2023
7.30.2023
7.31.2023
8.1.2023
1.stm32和jetson nano通讯不稳定(可能原因:杜邦线传输能力差、波特率可能设置高了【但低了影响系统处理速度】)(实际原因:while写成了if,导致时机很难对上,通信规则设计失误!)
2.stm32主函数设计不行,没花时间改进,想要重复运行某个程序只能重启,人机交互也不友好
3.比赛报告没有在头脑风暴之后就开始写,导致后期书写太急,不够规范
4.linux操作不熟,开机自启动程序出现问题,且jetson nano上的程序设计缺乏系统性结构性(每问都写了一个程序,而不是整合成一个大的测试程序)
5.所有任务完成太晚,没有留下时间仔细调试调参找问题。而且全流程过一遍后立马就要封箱了,急急忙忙乱改代码导致出现了意想不到的错误!再给一天就刚好了啊!!!
1.器件准备很重要:比赛发布器件清单后要备齐,最好每个器件都多买几个。以满足比赛器件需求并防止比赛时器件损坏!(本次比赛oled屏、舵机都反复坏过)
2.器件精度很重要:比赛前统计自己所有器件清单,并实测是否可以使用?精度如何?硬件精度不足会直接导致结果无法满足!(本次比赛刚开始使用的舵机为20kg大扭矩低精度,调了一晚PID参数舵机仍然运动不准,最后才发现是精度问题)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。