文章目录
- 前言
- 一、摄像头图像处理
- 1、摄像头图像采集
- 2、图像二值化与大津算法
- 二、左右边界,中线扫描
前言
参加了第十六,十七和第十八届全国大学生智能车竞赛,对摄像头的学习有部分心得,分享给大家,三届车赛,车赛生涯也算是到了尽头。打算从基础的算法开始,给各位一些个人看法,也是对车赛的一次总结。
一、摄像头图像处理
闲话:其实摄像头的算法有很多种,弄了两年摄像头,也只是学会了其中很小的一部分,但最终,作用都是大同小异的,也不必太过于追求算法上的完美。只需要达到能稳定提取特征,识别元素其实就够用了。(个人用的是普通大津+二值化+八领域做边界提取)
1、摄像头图像采集
打开摄像头相关例程,可以发现其实最终摄像头所采集的数据都存入了一个二维数组中,工作方式也很简单:图像采集,将图像采集标志置一。手动清零,就可以达到重复采集的目的。(没有相关例程的可以去找客服要,记得B站上也有逐飞的摄像头摄像头图像采集视频讲解)
2、图像二值化与大津算法
智能车使用的摄像头所采集的图像一般都是灰度图像,将图像分割、数字化成一个个的数(一个个的像素点),0~255,像素点颜色越白数字越大。我们可以看出图像信息很丰富,图像处理方法自然也就多种多样了,首先我们可以将图像二值化。
二值化就是将图像上的灰度点分别设置为0,255(0xFF)(有点像二级分化)。图像直观上就会变为黑白图像。我们该按照何种规则分黑和白呢?这时候就需要我们找出黑、白的分界值(也就是阈值)(大于阈值即为白,反之为黑),然后可以遍历图像数组中每一个像素点,大于阈值设为0XFF(白),反之设为0(黑)。(二值化有一个进阶的思想:图像每一个点都需要二值化嘛,我们能不能只将要使用的点二值化呢,这样速度是不是就快了,这个其实开始没有必要弄,有一张完整的二值化图也方便我们理解)
注:1,数组均从零开始。
2,二值化不能作用于摄像头采集原图上,应该重新定义一个同大小的数组,专门存放二值化后的图像信息。(防止在进行图像处理时,摄像头重新采集数据,覆盖之前数据)
/*
mt9v03x_image_dvp : 原图像 存放摄像头灰度原图
mt9v03x_image_baz :二值化图像 用于存放图像右边界
WHTIE 宏定义 替换作用 与0xff相同
*/
#define WHTIE 0xff //255
#define BLACK 0x00//使用宏 通过单词代替抽象的数字 for(uint8_t i=0;i<MT9V03X_DVP_H;i++) //MT9V03X_DVP_H:图像高{for(uint8_t j=0;j<MT9V03X_DVP_W;j++) //MT9V03X_DVP_W:图像宽{if(mt9v03x_image_dvp[i][j]>=threshold ) //threshold:图像阈值{mt9v03x_image_baz[i][j]=WHITE;}else{mt9v03x_image_baz[i][j]=BLACK;}}}
后面就是确定图像的阈值了,因为真实环境亮度是变化的,单纯的给定阈值二值化就显得不够稳定,在图像的基础上动态的计算阈值适应性更强。我选择的是大津算法,也是常用且基础的一种了,缺点就是运算时间有点稍长。
大津算法的相关我就不说了,网上有很多相关介绍。(其实我理解的也不太透彻, 手动狗头保命)
二、左右边界,中线扫描
在二值化之后,我们得到了一个由黑和白组成的二维数组,而我们的目的是让小车时刻处于赛道中间位置,也就是图像中间位置。我们想要小车不出赛道,如果能让赛道的中线始终贴合图像数组的中间(此处图像数组的中间我们可以想象为图像的宽/2),那我们的小车是不是就不会出赛道了。图像数组的中间就像PID中的理论值,实际赛道的中线就像实际值。这样我们是不是就可以将图像与PID联系起来。
说回边界扫描,想直接找赛道中线还是比较难的,主要是没有明显特征,我们不妨先寻找左右边界(左右边界黑白相夹),左右相加除二求得中线。这样就得到比较真实的中线坐标,再对比理想中线坐标(图像中间,也就是图像宽/2),从而求出中线偏差,用于PID控制。思路大抵都是这样,但找的方法就很多种多样了。
基础的方法就是从中间往两边找,缺点是遍历了整幅图像,消耗时间稍长,而进阶的就有对普通两边找线方法的优化,(如:双最长白列法)。再进一步稍微复杂一点的有八邻域,迷宫法等等。
我们先来看看普通的两边找线法:
基本思路就是从最下行往上 (利于对之后对中线的处理),每一行从中间往两边,分别寻找左右边界(特征:黑白跳变),存入左右边界数组。
同时存下中线坐标,用于下一行中线的扫描。(这样减小运算量的同时还可以加大中线扫描时的连续性)左右边界相加除2求出该行中线坐标,存入中线数组。
注:
/* last_mid: 边界扫描起始坐标 每行边界从此开始 起始行为图像宽/2 后为前一行图像中线坐标MT9V03X_DVP_W: 图像宽MT9V03X_DVP_H: 图像高mt9v03x_image_baz[][]: 二值化图像数组left_flag[] : 左边界存在数组 左边界存在 标志置1left_flag[] : 右边界存在数组 右边界存在 标志置1left_border[]: 左边界数组 存放左边界坐标right_border[]: 右边界数组 存放右边界坐标Mid_border[]: 中线左边数组 存放中线坐标
*/last_mid = MT9V03X_DVP_W / 2;
for (int i = MT9V03X_DVP_H - 1; i >= 0; i--)//从下往上扫描
{left_flag[i] = 1;right_flag[i] = 1;for (int j = last_mid; j > 1; j--)//中间往左边扫描{if (mt9v03x_image_baz[i][j] == 0xff && mt9v03x_image_baz[i][j-1] == 0x00 && mt9v03x_image_baz[i][j-2]==0x00)//黑黑白认为找到左边界{left_border[i] = j; //将左边界存入左边界数组left_flag[i] = 1; //左边界找到,标志置0break; //跳出循环}}if (left_flag[i]==0) left_border[i]=0; //补线标志未置一,此行左丢线,取图像左边界for (int j = last_mid; j < MT9V03X_DVP_W-2; j++) //往右扫描{if (mt9v03x_image_baz[i][j]==0xff && mt9v03x_image_baz[i][j+1]==0x00 && mt9v03x_image_baz[i][j+2]==0x00)//白黑黑认为找到右边界{right_border[i] = j; //将右边界存入右边界数组right_flag[i] = 1; //右边界找到,标志置0break; //跳出循环}}if (right_flag[i]==0) right_border[i] = MT9V03X_DVP_W-1; //补线标志未置一,此行右丢线,取图像右边界Mid_border[i] = (left_border[i] + right_border[i]) / 2; //中线坐标mt9v03x_image_baz[i][left_border[i]-2] = BLACK; //左边界涂黑mt9v03x_image_baz[i][Mid_border[i]] = BLACK; //中线涂黑mt9v03x_image_baz[i][right_border[i]+2] = BLACK; //右边界涂黑 /* 注意 左右边界-2 +2 很容易存在数组越界 导致程序卡住 如 左边界点 left_border[i]=0; [right_border[i]=MT9V03X_DVP_W-1自己使用时可以加个限制如mt9v03x_image_baz[i][(left_border[i]<2?2:left_border[i])-2] = BLACK; //左边界涂黑*/last_mid = Mid_border[i]; //中线查找开始点,方便中线寻找
}
可以在图像上把边界显示出来,方便后面观察图像,像这样:
后续给各位说说我对八领域的理解,欢迎大家关注!!!