摄像头硬件

摄像头选型肯定要用恩智浦的总钻风摄像头,这种摄像头对于单片机来说就相当于一个二维ccd。

课程中以(80*60)的数据阵为例,采集图像的过程,就是把这个数据阵存储到一个80*60的一维数组中。

uchar image[80*60];//图像数组

之后通过tc264读取摄像头向上位机发送的数据,以下为总钻风摄像头的引脚图:左边八个引脚为灰度输出脚即每次输出一个像素点的灰度值八位引脚输出范围是0~255,正好满足灰度输出范围。右边的(CLK、HR、VS)三个脚来确定左边八个口输出的灰度值为哪个像素点的。

VS:场信号每一幅图的信号要来之前会拉高一次场信号。

HR:行信号 场信号拉高一次后紧接着拉高一次行信号,如上述的60(行)*80(列)两个场信号之间会有60个行信号。

CLK:点信号和上述行信号一样行信号拉高一次后会拉高电信号,即两个行信号间有80个点信号。

通过以上三个引脚便可以定位当前发送的灰度值为具体哪个像素点的灰度值,正常不用行信号只用场信号和点信号便可定位当前像素点。以上便是单片机接收摄像头图像相关引脚的原理和作用。

总钻风摄像头引脚图

摄像头软件实现

大津法二值化

1.阈值获取

一幅图的灰度直方图一般会有两个峰值,两个峰值之间的谷底(最小值)便是要找的二值化的阈值。

首先便要用代码在一幅所有的数据中找到这个阈值。

                           /*大津法阈值获取*/

uint8 huidu[256];//灰度直方图数组
uint8  YUNZHI  = 0;

for(i=0;i<4800;i++)
{
    huidu[image[i]++];//统计灰度直方图,即统计图中的每个像素点的灰度值
}

uint16 H1 = 0;//灰度直方图第一次高峰海拔值
uint16 D1 = 0;//第一次高峰X轴位置

#define KUANDU 30

for(i=0;i<255;i++)//遍历灰度直方图
{
    if(huidu[i]>H1)//寻找最大值
    {
        H1 = huidu[i];//记录海拔位置
        D1 = i;//记录X轴位置
    }
}

uint16 H2 = 0;//灰度直方图第二次高峰海拔值
uint16 D2 = 0;//第二次高峰X轴位置
bit OK = 0;

for(i=H1-5;i>0;i-=5)//每次向下切五个单位长度
{
    for(j=0;j<256;j++)//再一次遍历灰度直方图
    {
       if(huidu[i]>i && abs(j-D1) > KUANDU)//找到高度高于H1-5、且距离第一个高峰宽度足的点
        {
            H2 = i;//记录第二高峰海拔
            D2 = j;//记录第二高峰X轴的距离
            OK = 1;//找到第二高峰标志位
            break;
        }
    }
if(OK) break;//如果找到,直接跳出
}


uint16 H3 = 0;//灰度直方图阈值海拔值
uint16 D3 = 0;//阈值X轴位置

if(OK)//已经找到第二高峰
{
    if(D1<D2)
    {
       for(i=D1;i<D2;i++)//遍历D1、D2中间所有点
        {
            if(huidu[i]<H3)//寻找最小值
            {
                H3 = huidu[i];//阈值的海拔值
                D3 = i;//阈值的X轴位置
            }
        }
    }
    YUNZHI = D3;//获取阈值
}



代码如上图所示:直接遍历整个灰度直方图找到海拔最高带的点便为第一个高峰,记录最高点的海报与X轴位置得到第一个高峰。

第二个高峰使用依次向下切的方式。上图代码为一次下切5个单位长度,找到高于这条切线海拔并且同时满足距离第一个高峰有足够的宽度距离的点,记录海拔和X轴数据便为第二个高峰。

最后遍历两个高峰之间的所有点找到其中的最小海拔值这个值便为此张图的阈值,得到阈值之后便可以进行下一步的二值化处理了。

2.二值化处理

/*********************图像二值化处理************************/

for(i=0;i<4800;i++)
{
    if(image[i]>YUZHI)//与阈值比较判断灰白度
    {
        image1[i/80][i%80] = 1;//白点//判断完灰白后将之前的一维数组的数据转存到有行列的二维 
                                       数组中,便于后续操作
    }
    else
    {
        image1[i/80][i%80] = 0;//黑点
    }
}

确定阈值后图像的二值化处理就简单了,只需要遍历每一个点然后同阈值比较就好,大于阈值为黑点小于阈值为白点。

二值化处理完成后便会得到下面的图即只有黑白两色组成的图。

3.边缘提取

二值化图像处理

1.找赛道中线

得到二值化的图像后便可以在白色的赛道寻找赛道的中线,作为小车前进和车身姿态调整的参考线。

通过代码寻找到中线:

/*************************找中线**************************/
uchar ZHONGJIAN[60] = [39];//中线位置
uchar ZUO[60] = {0};//左线位置
uchar YOU[60] = {79};//右线位置

//先找最底下一行中心线

for(i=ZHONGJIAN[59];i>=1;i--)//从中间向左找上升沿
{
    if(image1[59][i-1]-image[59][i] == 1)//找到上升沿,说明到达赛道边缘
    {
        ZUO[59] = i;//左线
    }
}

for(i=ZHONGJIAN[59];i<79;i++)//从中间向右找上升沿
{
    if(image1[59][i-1]-image[59][i] == 1)//找到上升沿,说明到达赛道边缘
    {
        YOU[59] = i;//右线
    }
}

ZHONGJIAN[59] = (ZUO[59] + YOU[59])/2;//找到最底下一行的中心点

for(i=58;i>0;i--)//向上迭代求出剩下的中心点
{
    for(j=ZHONGJIAN[i][i+1];j>=1;j--)//从中间向左找上升沿
    {
        if(image1[i][j-1]-image1[i][j] == 1)//找到上升沿
        {
            ZUO[i] = j;//左线
        }
    }
    for(j=ZHONGJIAN[i][i+1];j<79;j++)//从中间向右找上升沿
    {
        if(image1[i][j+1]-image1[i][j] == 1)//找到上升沿
        {
            YOU[i] = j;//右线
        }
    }
ZHONGJIAN[i] = (ZUO[i] + YOU[i])/2;//计算当前位置中线点
}

代码先找到图像最下面一行的中心点,如何通过一步步向上迭代算出剩下的中线点,通过中心就可以标定中心线了。

2.标线补线

此部分代码为摄像头核心部分,本部分代码也是决定你车身速度上线的代码。本文章作为基础入门的第一步,只是提供一个基础的算法作为参考学习。

当图像进行二值化处理后小车在赛道上要通过图像数据辨别各种元素,因此要实时对图像数据进行补线,元素辨别等处理。

如图通过十字路口时要先通过预设的几个关键点(如上面第一幅图)判断出其为十字路口,然后对其进行补线如第二幅图红色圈内的黑线。

代码如下:

/************************图像处理*******************************/
uchar AX,AY;//A点
uchar BX,BY;//B点
uchar CX,CY;//C点
uchar DX,DY;//D点


//获取A,B两点

for(i=39;i>=1;i--)//从中间向左找上升沿
{
    if(image1[59][i-1]-image[59][i] == 1)//找到上升沿,说明到达赛道边缘
    {
       AX = i;//左线
    }
}

for(i=39;i<79;i++)//从中间向右找上升沿
{
    if(image1[59][i-1]-image[59][i] == 1)//找到上升沿,说明到达赛道边缘
    {
        BX = i;//右线
    }
}
AY = 59;
BY = 59;


//获取C,D两点

CY = AY-1;//迭代C点
CX = AX-1;
for(i=CY;i>0;i--)//由近及远,先是每一行找
{
    fro(j=CX;j<80;j++)//由左向右直到找到白色(赛道)
    {
        if(image1[i][j] == 0)//找到白点
        {
            CX = j-1;//向左退到黑色,之后继续向上一行迭代
            break;
        }
    }

    if(image1[i-1][j] == 1)//判断上方是否还有黑点,无黑点说明到达十字路口即找到C点
    {
        CY = i;//得到C点Y位置
        break;
    }
}

DY = BY-1;//迭代D点
DX = BX-1;
for(i=DY;i>0;i--)//由近及远,先是每一行找
{
    fro(j=DX;j>0;i--)//由右向左直到找到白色(赛道)
    {
        if(image1[i][j] == 0)//找到白点
        {
            DX = j+1;//向左退到黑色,之后继续向上一行迭代
            break;
        }
    }

    if(image1[i-1][DX] == 1)//判断上方是否还有黑点,无黑点说明到达十字路口即找到D点
    {
        DY = i;//得到C点Y位置
        break;
    }
}

/*****通过特殊点判断出赛道元素类型****/
if(abs(CY-DY)<10  && CY>30 && DY>30)//初级判断十字路口
{
    Y = min(CY,DY)//取得CD高度较小值
    uchar HEI = 0;//十字路口上方区域黑点数量
    for(i=Y;j>Y-10;i-=2)//抽五行
    {
        for(j=10;j<70;j+=5)//没五列抽一次
        {
            if(image1[i][j] == 1)
            {
                HEI++;
            }
        }
    }
    if(HEI < 10)//最终判断十字路口 并补线
    {
       float K; //补线斜率
        K = (CX-AX)/(CY-AY);//计算AC斜率
        for(i=CX;I>CY-20;i--)//补AC 长度为20像素  宽度为2像素
        {
            image1[i][CX+(CY-i)*K] = 1;
             image1[i][(CX+(CY-i)*K)-1] = 1;
        }

        K = (CX-AX)/(CY-AY);//计算BD斜率
        for(i=DX;I>DY-20;i--)//补BD 长度为20像素  宽度为2像素
        {
            image1[i][DX+(DY-i)*K] = 1;
             image1[i][(DX+(DY-i)*K)-1] = 1;
        }
    }
}

小车姿态调整

通过对图像数据的一系列处理之后得到有中心线的图像后便可以计算小车自身姿态误差来进行闭环调整。

摄像头拍出的一幅图片小车只通过下方少部分图片来进行姿态调整,因此在此引入前瞻,如下图,我们只通过每张图前两个前瞻的图像来调整小车的轨迹,在此我们只使用最基础的方法来调整小车姿态即确定,最低点、第一前瞻、第二前瞻三个点,判断此三点的位置和斜率差来调整小车姿态。

/**************************车身姿态调整***************************/
uchar QIANJIN = 15//摄像头图像前瞻
uchar YUAN,JIN,ZHING ;

char err = 0;//前瞻偏差
char yerr = 0;//车身横向偏差,即车身不在赛道中线

JIN = ZHONGJIAN[59];
ZHONG = ZHONGJIAN[59-QIANZHAN];
YUAN = ZHONGJIAN[59-QIANZHAN*2];

if(YUAN<ZHONG && ZHONG<JIN)//情况1
{
    err = ((ZHONG - YUAN)+(JIN-ZHONG))/2;//获取前瞻偏差
}
else if(YUAN<ZHONG && ZHONG>=JIN)//情况2
{
    err = JIN-ZHONG;//获取前瞻偏差
}
else if(YUAN>=ZHONG && ZHONG<JIN)//情况3
{
    err = JIN-ZHONG;//获取前瞻偏差
}
else if(YUAN<ZHONG && ZHONG<JIN)//情况4
{
    err = ((ZHONG - YUAN)+(JIN-ZHONG))/2;//获取前瞻偏差
}


yerr = JIN - 39;//获取车身横向偏差

如上述代码得到偏差后便可以带入PID控制器进行计算实现对车身姿态的闭环控制。

本文章参考:【智能车制作加餐:摄像头数字图像处理算法】https://www.bilibili.com/video/BV1eL411L7NR?vd_source=689aa264ab39f68895bf8849ce151464

Logo

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

更多推荐