智能车学习笔记乱写

乱写的归纳总结,翻着PPT想到什么写什么。

代码都是用的逐飞的库。

中断

定时循环触发一些事件。

中断处理优先级高于main且各个中断之间也有优先级差异。

一些需要高频重复进行的动作或者一些特殊情况判断可以扔到中断里。

举一个典型应用是在main里执行摄像头图像处理程序,然后定时中断读取error来执行PID控制舵机打角。和这个例子有关的代码如下:

//在main里初始化一个中断进程。前一个参数是定时器通道号,后一个参数是触发间隔时间
pit_ms_init(CCU60_CH0,10);
while(1){
  bin_image(KP,KD,image);//重复执行图像处理
}
//下面是中断里放的部分
IFX_INTERRUPT(cc60_pit_ch0_isr, 0, CCU6_0_CH0_ISR_PRIORITY) //选用和初始化的通道号相同的isr
{
    interrupt_global_enable(0);                     // 开启中断嵌套
    pit_clear_flag(CCU60_CH0);
    if(page==1){
        uint32 PID=(uint32)(KP*now_error-KD*(now_error-lst_error)+MID_SERVO); //PID控制
        if(PID>MID_SERVO+MAX_ANGLE) PID=MID_SERVO+MAX_ANGLE;
        if(PID<MID_SERVO-MAX_ANGLE) PID=MID_SERVO-MAX_ANGLE;
        pwm_set_duty(SERVO,PID);
        lst_error=now_error;
    }
}

PID

简而言之就是我现在有一个可以观测的量(比如车的实际速度)和一个我可以控制的量(比如电机的PWM输出),现在我希望通过控制控制量来使观测量达到一个目标值(例如预期速度),但是控制量与观测量之间并没有具体的数学关系,那么我就可以通过PID控制来确定控制量,从而影响观测量使其逼近目标值。

具体而言:我们不妨设控制量为\(p\),观测量为\(a\),目标值为\(t\)

\(e=a-t\),即e为误差。

那么将控制量实时设为\(p=PID=K_Pe+K_D \Delta e+K_I \Sigma e\)可以使\(a\)逐渐逼近\(t\)

其中\(K_P,K_D,K_I\)为参数,\(\Delta e,\Sigma e\)分别是\(e\)的微分量和积分量。

一般而言在控制舵机时只使用\(PD\)控制,控制舵机时只使用\(PI\)控制。

下面是一段\(PI\)控制速度时的代码:

pwm_set_duty(MOTOR_L,(uint32)(KP*error+KI*sum_speed_error));

最终跑起来的速度-时间曲线大概长这样:

用编码器获取速度

编码器实际获得的数据是目前已经走过的格数(不妨设该量为\(n\))(走一圈是走过多少格固定,比如我现在用的编码器是走一圈记1024格(不妨设该量为N))。

所以可以通过\(v=\frac{k\Delta n}{\Delta t}\)来确定速度。其中k是一个比例系数,用于把走过的格数映射为走过的距离,具体而言\(k=\frac{2\pi r }{aN}\)(其中a表示轮子用齿轮传递到编码器时的齿数比,r为轮子半径)。当然如果懒得搞计算的话完全可以推着车在地上走1m,然后看看编码器走了多少格,由此确定k。

要注意的是编码器有计数极限,爆了之后会变成正负性相反的极限值,所以车连着跑的时候检测到的速度会有突变,要注意筛掉。

图像处理

二值化

摄像头读进来的初始图像是灰度图,每个像素是[0,255],为了方便后面图像识别首先需要进行二值化处理,处理成黑白,01。显然我们需要确定一个阈值,大于阈值的判定为1,否则为0。

这个阈值可以自己给定,但是需要根据场地光照及时调整;也可以用下面所述的OTSU大津法确定。

OTSU大津法是指假设阈值把颜色分成两类,我们应该使类间方差最大,即\([\mu_1(t)-\mu_2(t)]^2\)最大,其中的平均值指的是一个类中所有像素颜色大小的平均值。

代码实现如下:

uint8 get_line(uint8 image[][MT9V03X_W]){
	int cnt[256];
	int i,j;
	uint8 ans;
	float now_max=0;
	float sum1=0,sum2=0; //sum1,sum2分别是两类的颜色加和
	float tmp;
	int cnt1=0,cnt2=MT9V03X_H*MT9V03X_W;
	for(i=0;i<256;i++) cnt[i]=0;
	for(i=0;i<MT9V03X_H;i++){
		for(j=0;j<MT9V03X_W;j++){
			cnt[(int)image[i][j]]++;
			sum2+=image[i][j];
		}
	}
	for(i=0;i<256;i++){ //依次尝试每种阈值
		cnt1+=cnt[i],cnt2-=cnt[i];
		sum1+=i*cnt[i],sum2-=i*cnt[i];
		tmp=(sum1*cnt1*cnt2*cnt2-sum2*cnt2*cnt1*cnt1)*(sum1*cnt1*cnt2*cnt2-sum2*cnt2*cnt1*cnt1);
		if(tmp>now_max){
			now_max=tmp;
			ans=(uint8) i;
		}
	}
	return ans;
}

当然,如果场地打光过于离谱(超级亮或者超级暗)OTSU也会爆,这种时候就考虑改固定阈值或者调摄像头的增益/曝光时间吧。

搜线

  1. 从画面中心开始向两侧搜索,碰到黑线则认为是边线。逻辑很简单,但是需要保持车身尽量在中线上,劣势挺明显的,实际上我也没采用过。

  2. 最长白列搜线法:先找到赛道的最长白列,从最长白列开始向两侧搜索。最长白列可以顾名思义,具体而言就是下面图里那条红线,它比其他列都长:

误差的确定

搜到边界线后显然可以根据每行的左右边界位置,由\(\frac{l_i+r_i}{2}\)确定赛道中线,然后将其与画面中线比较得到误差。其中每行的误差对总误差的贡献加权可以由该行的宽度确定,这样近处的赛道将获得更多的加权。

for(i=MT9V03X_H-1;i>=MT9V03X_H-max_length+FRONT_LINE;i--) error+=((r[i]+l[i])/2-MT9V03X_W/2)*(float)(r[i]-l[i])/(float)sum_s;
posted @ 2023-12-02 00:27  DarthVictor  阅读(31)  评论(0编辑  收藏  举报
莫挨老子!