摄像头循迹小车

本文为学校智能车队选拔赛的总结,此次比赛为双车追逐赛,要求利用环岛或车库实现两次超车,文章主要分为硬件电路设计以及软件算法实现两部分。由于本文是在前文电磁循迹小车的基础上进行撰写,故部分基础知识不再提及。

一、整体架构

1. 硬件设计方案

在这里插入图片描述

2. 软件设计方案

在这里插入图片描述

二、硬件设计

1. 电源管理模块

对于硬件设计部分,首先要考虑的是电源管理模块,其中主控RT1064需要5V供电,各种外设模块需要3.3V的供电,HIP4082驱动芯片需要12V供电。

5V稳压电路:
在这里插入图片描述
3.3V稳压电路:
在这里插入图片描述
以上两款芯片均为低压差线性稳压器(LDO),原理其实很简单,芯片内部通过电阻进行分压从而进行调节输出电压,一般来说LDO芯片只能用于降压,即要求输入电压要大于输出电压(具体原因需要读者自行了解LDO原理),芯片两端所接的电容主要用于电源滤波(本文由于针对初学者,滤波原理不进行赘述)

舵机稳压电路:
在这里插入图片描述
12V稳压电路:

在这里插入图片描述
这两款芯片为DC-DC芯片(特指开关电源电路),DC-DC的原理较为复杂,其与LDO芯片的区别本文也不进行赘述,读者自行查询。需要注意的是,DC-DC的输出电流一般比LDO大,但纹波也更大,实际设计电路时,需要针对各个外设的功耗进行调整。

PS:电源管理模块十分重要,对于第一次画板的同学,大部分的问题都集中在此。

2. 电机驱动电路

HIP电机驱动电路:
在这里插入图片描述
HIP4082驱动电路是一个经典的全桥驱动电路,其原理并不复杂,简单来说就是通过控制对角MOS管导通来控制电机正反转(注意:同侧MOS管同时导通会导致短路),此驱动电路的原理网上已有详细介绍,本文不过多介绍。

值得注意的是,整体驱动板的设计不仅于此,还需要考虑设计隔离电路,防止驱动板意外烧毁时同时导致主板烧毁。

3. 外设接口电路

由于使用的很多外设模块都是现成的,包括IPS屏幕,摄像头,无线串口,编码器等。对于这些模块,我们只需设计对应的接口电路即可,主要考虑供电以及与主控芯片的通信接口,常用通信协议有UART,SPI,IIC等,针对不同模块考虑不同的通信接口,合理利用芯片资源。

三、软件设计

1. 图像处理

1.1 图像二值化

图像二值化指的是通过设置阈值,将获取的原始图像的每一个像素点的像素值与阈值比较,大于阈值就将其像素值赋值为255(即白色),小于阈值就将其像素值赋值为0(即黑色),最终使得图像变为只存在黑白两种颜色,便于后续进行处理。

而对于灰度图像,为了提取出赛道边界以及降低运算量,首先要做的就是图像二值化。

对图像进行二值化最重要的就是设置阈值,只有阈值设置合适才能较好的对图像进行分割。作者在校赛时候主要使用过两种方式:

  • 均值法:将图像的每一个像素点相加再求均值来获得阈值,我们采用跳跃采集像素点来计算均值,以降低运算量,求得的均值即为图像阈值,此方法适用于光线均匀的场景,缺点在于场景光线分布不均时阈值不准确。
//均值法计算图像阈值
uint8 average(uint8 *image, uint16 col, uint16 row)                        
{
	int i, j;
	double sum = 0;
	uint8 count = 0;
	uint8 threshold = 0;
	uint16 width = col;
	uint16 height = row;
	uint8* data = image;  //指向像素数据的指针
	for (i = 0; i < height; i += 10)
	{
		for (j = 0; j < width; j += 10)
		{
			sum += data[i * width + j];
			count++;
		}
	}
	threshold = sum/count;
	return threshold;
}
  • 大津法:将图像分为目标和背景,通过最大类间方差法遍历256个像素值,获得能使目标和背景方差最大的像素值,即为图像阈值,适用于光线不均匀的场景,缺点在于运算量大,程序更为复杂。
//大津法计算图像阈值
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{
    #define GrayScale 256
    uint16 width = col;
    uint16 height = row;
    int pixelCount[GrayScale];
    float pixelPro[GrayScale];
    int i, j, pixelSum = width * height/4;
    uint8 threshold = 0;
    uint8* data = image;  //指向像素数据的指针
    for (i = 0; i < GrayScale; i++)
    {
        pixelCount[i] = 0;
        pixelPro[i] = 0;
    }

    //统计灰度级中每个像素在整幅图像中的个数  
    uint32 gray_sum=0;
    for (i = 0; i < height; i+=2)
    {
        for (j = 0; j < width; j+=2)
        {
            pixelCount[(int)data[i * width + j]]++;  //将像素值作为计数数组的下标
            gray_sum+=(int)data[i * width + j];             //灰度值总和
        }
    }

    //计算每个像素在整幅图像中的比例  
    for (i = 0; i < GrayScale; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / pixelSum;
    }

    //遍历灰度级[0,255]  
    float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;

	w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
	for (j = 0; j < GrayScale; j++)                                    //j作为阈值
	{
        w0 += pixelPro[j];                                             //背景灰度值所占比例
        u0tmp += j * pixelPro[j];                                      //背景灰度值总和

        w1 = 1-w0;
        u1tmp = gray_sum/pixelSum-u0tmp;

        u0 = u0tmp / w0;
        u1 = u1tmp / w1;
        u = u0tmp + u1tmp;
        deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
        if (deltaTmp > deltaMax)
        {
                deltaMax = deltaTmp;            //最大类间方差法
                threshold = j;
        }
        if(deltaTmp < deltaMax)
        {
                break;
        }
	}
	return threshold;
}

获取的阈值有时对赛道的区分并不明显,此时需要对计算的出来的阈值加上一个阈值补偿以便对赛道的区分度更好。

PS:二值化处理时不能直接修改原有程序中用来接收摄像头图像的数组,原因在于大部分例程的摄像头图像采集都在中断,且摄像头采集速度远大于图像处理速度,就会出现二值化时图像数组在中断被反复更新,从而失去二值化的作用。

1.2 赛道中线提取

赛道中线提取是决定巡线是否稳定的关键因素,中线提取有许多方法,本文主要介绍最简单的全行扫描法。

  • 全行扫描法:基本思路是从图像底部往上扫,从中间往两边扫,前一行扫描的中线作为后一行扫描的起点,寻找到白—>黑跳变点即为赛道边界,左右边界列数相加除以2即为中线列数,若扫描至图像边界还未寻找到赛道边界,即把图像边界作为赛道边界,且判定为丢线。此巡线方式的问题在于大角度弯道的中线会有明显偏移,不够准确。

2. 速度闭环

首先编码器的原理是旋转一定角度就会产生固定的脉冲数,对于1024线编码器,每转一圈,就会产生1024个脉冲。而为了得到轮胎的转速,就需要将编码器数据采集放在定时器中断,即得到中断时间内编码器的输出脉冲数(转换即可得到转速)。

通过编码器实时采集电机转速,通过增量式PID实现速度闭环。

//编码器数据采集
encoder1 = -qtimer_quad_get(QTIMER_1,QTIMER1_TIMER1_C1 ); //这里需要注意第二个参数务必填写A相引脚
encoder2 = qtimer_quad_get(QTIMER_1,QTIMER1_TIMER2_C2 );

//清除编码器值                    
qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER1_C1 );
qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER2_C2 );

//增量式PID计算PWM值
speed_error1 = speed1_want-encoder1;                                                //左轮电机    
motor1 += s_p * (speed_error1-speed_error1_1) + s_i * (speed_error1) + s_d * (speed_error1-2*speed_error1_1+speed_error1_2);
speed_error1_2 = speed_error1_1;
speed_error1_1 = speed_error1;

speed_error2 = speed2_want-encoder2;                                                //右轮电机    
motor2 += s_p * (speed_error2-speed_error2_1) + s_i * (speed_error2) + s_d * (speed_error2-2*speed_error2_1+speed_error2_2);
speed_error2_2 = speed_error2_1;
speed_error2_1 = speed_error2;

增量式PID的实现网上已有详细解释,本文不作过多介绍。

3. 差速控制

差速控制主要用于无舵机的车模,例如三轮和两轮车模,但四轮车模也可以使用后轮差速进行辅助转向,使得转向更加流畅。

由于这部分作者涉及并不多,不做过多介绍,可以参考其他博客。

写在最后

本文为作者第二次校赛的记录,由于作者本身在其承担的主要工作是硬件设计以及图像处理代码的撰写,故其他部分的代码设计本文并未提及。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐