0


第18届全国大学生智能汽车竞赛四轮车开源讲解【2】--图像

开源汇总写在下面

第18届全国大学生智能汽车竞赛四轮车开源讲解_Joshua.X的博客-CSDN博客

一、图像的基本参数

  1. volatile uint8 mt9v03x_finish_flag = 0; // 一场图像采集完成标志位
  2. uint8 mt9v03x_image[MT9V03X_H][MT9V03X_W];//采集到的图像数据

基本参数有两个,一个是采集标志位,一个是图像数组。

1.标志位

标志位很好理解,当摄像头采集完一帧图像,标志位会被置一,可以在主循环中不断读取标志位、当标志位是1时,你就可以读取该帧图像,处理完图像再把标志位清零,让他开始下一帧数据的采集。

根据习惯不同,也可以先清零标志位,再处理图像;或者先处理图像,再清零标志位。

理论上是有区别,个人实际使用感觉没什么差异。

  1. if(mt9v03x_finish_flag)//先处理图像,再清除标志位
  2. {
  3. Threshold=My_Adapt_Threshold(mt9v03x_image[0],MT9V03X_W, MT9V03X_H);//大津算阈值
  4. Image_Binarization(Threshold);//图像二值化
  5. mt9v03x_finish_flag=0;//标志位清除
  6. }
  1. if(mt9v03x_finish_flag)//先清除志位,再处理图像
  2. {
  3. mt9v03x_finish_flag=0;//标志位清除
  4. Threshold=My_Adapt_Threshold(mt9v03x_image[0],MT9V03X_W, MT9V03X_H);//大津计算阈值
  5. Image_Binarization(Threshold);//图像二值化
  6. }

本人习惯使用第一种处理方法,先将图像处理后,再清零标志位,让他写入下一帧数据,防止处理过程中,原始图像数组被写入下一帧数据。实际上我队友用的第二种,对实际使用也没有影响。

2.灰度数组

  1. uint8 mt9v03x_image[MT9V03X_H][MT9V03X_W];

这就是一个二维数组,长和宽就是在第一章我们定义的图像大小,MT9V03X_H代表图像的行数(在循环中一般是i),MT9V03X_W代表列数(在循环中一般是j),每个二维数组的值代表他的颜色。

我们看到的所有图像都是由一个个像素组成,常见的彩图的每个像素是RGB三个通道的数组值决定的,我们智能车常用的摄像头用的是灰度图像,只有一个通道。像素点数据用0~255的数字来表示颜色。


数字与颜色对应关系见图

其中,0是黑,255是白。其中越接近0越靠近黑,越靠近255越接近白。为了在后面便于使用,我们使用了两个宏定义。这两个宏定义在后面就会使用到。

(灰度值也对应了摄像头数组的数据类型uint8,就是8位数的数据范围0~255)

  1. #define IMG_BLACK 0X00 //0x00是黑
  2. #define IMG_WHITE 0Xff //0xff为白

需要注意的是,坐标系问题。

本文使用的坐标系是和数组一样的坐标系,也就是数组的(0,0)点位图像的的左上角,与数组的访问下标规则一样。并非数学上经常使用的直角空间坐标系,数学上常用的坐标系左下角是原点。

本文及以后所有图像都基于此坐标系

另一个需要注意的问题是数据的范围问题,就例如上图,我开的是18*9的一个数组,但是访问是时候,最边界的一个数据的坐标是17和8。因为数组访问的是偏移量,最开始的一位是0,所以大家在遍历图像时,请注意数据范围,不要越界操作。

下面两种方法都可以,不要弄混就好。不然数据越界轻则卡死,重则数据异常查不出bug。

(卡死,你能立刻反应到有bug;数据异常,很难想到是数组越界,更多的以为算法算错了)

  1. //访问范围<MT9V03X_H
  2. for(i=0;i<MT9V03X_H;i++)
  3. {
  4. for(j=0;j<MT9V03X_W;j++)
  5. {
  6. //处理摄像头数据
  7. }
  8. }
  9. //访问范围<=MT9V03X_H-1
  10. for(i=0;i<=MT9V03X_H-1;i++)
  11. {
  12. for(j=0;j<=MT9V03X_W-1;j++)
  13. {
  14. //处理摄像头数据
  15. }
  16. }

二、图像的基本处理

1.二值化

一幅灰度图的每个像素值从0~255,总共256个数值。二值化就是将这256个数进行“两极分化”,要么是0要么是255(0xFF)。

由于比赛规则规定的很清楚,赛道是蓝底白皮。这两者之间颜色差异很大,我们只需要分得清蓝色的是赛道背景,白色的是我们的赛道即可,没必要分析出其他的信息。

所以我们可以简单粗暴的将摄像头数据进行二值化处理,将0~255的像素数值,找到一个合适的阈值,直接变成0或者255这样只有两个值,也就是“非黑即白”。黑色的就是蓝布0,白色的就是赛道255。这就是二值化。


比赛场地铺设规范中有提到蓝色背景布

第十八届全国大学生智能车竞赛赛场赛道铺设规范(实际稿件)_卓晴的博客-CSDN博客

当然,二值化必定会损失一些信息,但是只要前期图像获取的比较好,配上合理的二值化,损失的信息如果都是无关信息,那么对我们就没有影响,就可以忽略不计。


彩色图像 灰度图像 二值化图像

二值化效果如下,只要做到白的是赛道,黑色的是背景蓝布,这就可以了,图像中间的光斑,后文会提到解决办法的。

二值化前后

二值化代码也很简单,找到一个阈值,大于该阈值的,给255,小于该阈值的,给0。

需要注意的是,我对图像二值化处理后,我没有把他放在原数组,而是新开了一个数组,后续所有图像识别操作都是对这个新数组进行识别。这样可以尽可能的避免刚二值化处理的一幅图像,摄像头采集的新的数据写入,覆盖掉我们二值化的数据。

  1. uint8 image_two_value[MT9V03X_H][MT9V03X_W];//二值化后的原数组

二值化代码如下:

  1. /*-------------------------------------------------------------------------------------------------------------------
  2. @brief 图像二值化处理函数
  3. @param 二值化阈值
  4. @return NULL
  5. Sample Image_Binarization(Threshold);//图像二值化
  6. @note 二值化后直接访问image_two_value[i][j]这个数组即可
  7. -------------------------------------------------------------------------------------------------------------------*/
  8. void Image_Binarization(int threshold)//图像二值化
  9. {
  10. uint16 i,j;
  11. for(i=0;i<MT9V03X_H;i++)
  12. {
  13. for(j=0;j<MT9V03X_W;j++)//灰度图的数据只做判断,不进行更改,二值化图像放在了新数组中
  14. {
  15. if(mt9v03x_image[i][j]>=threshold)
  16. image_two_value[i][j]=IMG_WHITE;//白
  17. else
  18. image_two_value[i][j]=IMG_BLACK;//黑
  19. }
  20. }
  21. }

1.1 大津法

大津法应该是二值化中比较出名的算法,个人理解如下。

由于图像的灰度范围已知,为0255。那么我就去计算每个像素值的点的然个数,后就可以得到一张灰度直方图,横坐标是0255,纵坐标是每个像素值点的个数。由于赛道的特殊性,会在在深色(蓝色)区域附近,在白色的区域附近会有两个尖峰,那我们就在这两个尖峰中间,找到一个最低值,作为阈值。对图像进行分割,大于该阈值的,直接给255小于该阈值的给0。

注:以上仅作为个人理解,真实性没有任何保证。


灰度直方图

但是大津法有个弊端,由于需要遍历全图进行像素点的数值计算,那么我遍历一张18070的图,就起码需要18070=12600次访问,再加上一些计算处理,其实是比较费时间的,一般的单片机需要几ms来对图像进行大津法+二值化处理,略费时间。所以不建议将图像开的太大。

大津法参考代码参考如下

  1. /*-------------------------------------------------------------------------------------------------------------------
  2. @brief 普通大津求阈值
  3. @param image 图像数组
  4. width 列 ,宽度
  5. height 行,长度
  6. @return threshold 返回int类型的的阈值
  7. Sample threshold=my_adapt_threshold(mt9v03x_image[0],MT9V03X_W, MT9V03X_H);//普通大津
  8. @note 据说没有山威大津快,我感觉两个区别不大
  9. -------------------------------------------------------------------------------------------------------------------*/
  10. int My_Adapt_Threshold(uint8*image,uint16 width, uint16 height) //大津算法,注意计算阈值的一定要是原图像
  11. {
  12. #define GrayScale 256
  13. int pixelCount[GrayScale];
  14. float pixelPro[GrayScale];
  15. int i, j;
  16. int pixelSum = width * height/4;
  17. int threshold = 0;
  18. uint8* data = image; //指向像素数据的指针
  19. for (i = 0; i < GrayScale; i++)
  20. {
  21. pixelCount[i] = 0;
  22. pixelPro[i] = 0;
  23. }
  24. uint32 gray_sum=0;
  25. for (i = 0; i < height; i+=2)//统计灰度级中每个像素在整幅图像中的个数
  26. {
  27. for (j = 0; j <width; j+=2)
  28. {
  29. pixelCount[(int)data[i * width + j]]++; //将当前的点的像素值作为计数数组的下标
  30. gray_sum+=(int)data[i * width + j]; //灰度值总和
  31. }
  32. }
  33. for (i = 0; i < GrayScale; i++) //计算每个像素值的点在整幅图像中的比例
  34. {
  35. pixelPro[i] = (float)pixelCount[i] / pixelSum;
  36. }
  37. float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
  38. w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
  39. for (j = 0; j < GrayScale; j++)//遍历灰度级[0,255]
  40. {
  41. w0 += pixelPro[j]; //背景部分每个灰度值的像素点所占比例之和 即背景部分的比例
  42. u0tmp += j * pixelPro[j]; //背景部分 每个灰度值的点的比例 *灰度值
  43. w1=1-w0;
  44. u1tmp=gray_sum/pixelSum-u0tmp;
  45. u0 = u0tmp / w0; //背景平均灰度
  46. u1 = u1tmp / w1; //前景平均灰度
  47. u = u0tmp + u1tmp; //全局平均灰度
  48. deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);//平方
  49. if (deltaTmp > deltaMax)
  50. {
  51. deltaMax = deltaTmp;//最大类间方差法
  52. threshold = j;
  53. }
  54. if (deltaTmp < deltaMax)
  55. {
  56. break;
  57. }
  58. }
  59. if(threshold>255)
  60. threshold=255;
  61. if(threshold<0)
  62. threshold=0;
  63. return threshold;
  64. }

这里还有我找到的山威的快速大津,使用效果没什么差别,据说计算速度会快一些。

  1. /*-------------------------------------------------------------------------------------------------------------------
  2. @brief 快速大津求阈值,来自山威
  3. @param image 图像数组
  4. col 列 ,宽度
  5. row 行,长度
  6. @return null
  7. Sample threshold=my_adapt_threshold(mt9v03x_image[0],MT9V03X_W, MT9V03X_H);//山威快速大津
  8. @note 据说比传统大津法快一点,实测使用效果差不多
  9. -------------------------------------------------------------------------------------------------------------------*/
  10. int my_adapt_threshold(uint8 *image, uint16 col, uint16 row) //注意计算阈值的一定要是原图像
  11. {
  12. #define GrayScale 256
  13. uint16 width = col;
  14. uint16 height = row;
  15. int pixelCount[GrayScale];
  16. float pixelPro[GrayScale];
  17. int i, j;
  18. int pixelSum = width * height/4;
  19. int threshold = 0;
  20. uint8* data = image; //指向像素数据的指针
  21. for (i = 0; i < GrayScale; i++)
  22. {
  23. pixelCount[i] = 0;
  24. pixelPro[i] = 0;
  25. }
  26. uint32 gray_sum=0;
  27. //统计灰度级中每个像素在整幅图像中的个数
  28. for (i = 0; i < height; i+=2)
  29. {
  30. for (j = 0; j < width; j+=2)
  31. {
  32. pixelCount[(int)data[i * width + j]]++; //将当前的点的像素值作为计数数组的下标
  33. gray_sum+=(int)data[i * width + j]; //灰度值总和
  34. }
  35. }
  36. //计算每个像素值的点在整幅图像中的比例
  37. for (i = 0; i < GrayScale; i++)
  38. {
  39. pixelPro[i] = (float)pixelCount[i] / pixelSum;
  40. }
  41. //遍历灰度级[0,255]
  42. float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
  43. w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
  44. for (j = 0; j < GrayScale; j++)
  45. {
  46. w0 += pixelPro[j]; //背景部分每个灰度值的像素点所占比例之和 即背景部分的比例
  47. u0tmp += j * pixelPro[j]; //背景部分 每个灰度值的点的比例 *灰度值
  48. w1=1-w0;
  49. u1tmp=gray_sum/pixelSum-u0tmp;
  50. u0 = u0tmp / w0; //背景平均灰度
  51. u1 = u1tmp / w1; //前景平均灰度
  52. u = u0tmp + u1tmp; //全局平均灰度
  53. deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
  54. if (deltaTmp > deltaMax)
  55. {
  56. deltaMax = deltaTmp;
  57. threshold = j;
  58. }
  59. if (deltaTmp < deltaMax)
  60. {
  61. break;
  62. }
  63. }
  64. return threshold;
  65. }

大家在使用时也不必关心计算方法,只需要关心传入的图像,传出的阈值就好,如果认为处理过于复杂,有能力的朋友可以自行优化上述代码。

1.2 索贝尔算子Sobel operator

索贝尔算子是计算机视觉领域的一种重要处理方法。主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。索贝尔算子是把图像中每个像素的上下左右四领域的灰度值加权差,在边缘处达到极值从而检测边缘。

索贝尔算子主要用作边缘检测。在技术上,它是一离散性差分算子,用来运算图像亮度函数的梯度之近似值。在图像的任何一点使用此算子,将会产生对应的梯度矢量或是其法矢量。

索贝尔算子不但产生较好的检测效果,而且对噪声具有平滑抑制作用,但是得到的边缘较粗,且可能出现伪边缘。

这个算法是专业的图像边缘检测算法,核心原理是对数学公式的推导,大概就是什么计算相邻像素之间的梯度,也就是数值下降,上升的速度比较快吧,本人不懂。


Sobel边缘检测

参考代码如下(某邱的开源代码)

  1. /*-------------------------------------------------------------------------------------------------------------------
  2. @brief sobel二值化
  3. @param imagein 原图数组
  4. imangeout 二值化后的数组
  5. @return null
  6. Sample lq_sobelAutoThreshold (mt9v03x_image[MT9V03X_H][MT9V03X_W],image_two_value[MT9V03X_H][MT9V03X_W])
  7. @note 会比大津慢一些,效果比大津法好不少
  8. -------------------------------------------------------------------------------------------------------------------*/
  9. void lq_sobelAutoThreshold (unsigned char imageIn[LCDH][LCDW], unsigned char imageOut[LCDH][LCDW])
  10. {
  11. /**卷积核大小**/
  12. short KERNEL_SIZE = 3;
  13. short xStart = KERNEL_SIZE / 2;
  14. short xEnd = LCDW - KERNEL_SIZE / 2;
  15. short yStart = KERNEL_SIZE / 2;
  16. short yEnd = LCDH - KERNEL_SIZE / 2;
  17. short i, j, k;
  18. short temp[4];
  19. for (i = yStart; i < yEnd; i++)
  20. {
  21. for (j = xStart; j < xEnd; j++)
  22. {
  23. /* 计算不同方向梯度幅值 */
  24. temp[0] = -(short) imageIn[i - 1][j - 1] + (short) imageIn[i - 1][j + 1]//{{-1, 0, 1},
  25. - (short) imageIn[i][j - 1] + (short) imageIn[i][j + 1] // {-1, 0, 1},
  26. - (short) imageIn[i + 1][j - 1] + (short) imageIn[i + 1][j + 1]; // {-1, 0, 1}};
  27. temp[2] = -(short) imageIn[i - 1][j] + (short) imageIn[i][j - 1] //{{0, -1, -1}
  28. - (short) imageIn[i][j + 1] + (short) imageIn[i + 1][j] // {1, 0, -1}
  29. - (short) imageIn[i - 1][j + 1] + (short) imageIn[i + 1][j - 1]; // {1, 1, 0}};
  30. temp[3] = -(short) imageIn[i - 1][j] + (short) imageIn[i][j + 1] //{{-1, -1, 0}
  31. - (short) imageIn[i][j - 1] + (short) imageIn[i + 1][j] // {-1, 0, 1}
  32. - (short) imageIn[i - 1][j - 1] + (short) imageIn[i + 1][j + 1]; // { 0, 1, 1}};
  33. temp[0] = abs(temp[0]);
  34. temp[1] = abs(temp[1]);
  35. temp[2] = abs(temp[2]);
  36. temp[3] = abs(temp[3]);
  37. /* 找出梯度幅值最大值 */
  38. for (k = 1; k < 4; k++)
  39. {
  40. if (temp[0] < temp[k])
  41. {
  42. temp[0] = temp[k];
  43. }
  44. }
  45. /* 使用像素点邻域内像素点之和的一定比例 作为阈值 */
  46. temp[3] = (short) imageIn[i - 1][j - 1] + (short) imageIn[i - 1][j] + (short) imageIn[i - 1][j + 1]
  47. + (short) imageIn[ i ][j - 1] + (short) imageIn[ i ][j] + (short) imageIn[ i ][j + 1]
  48. + (short) imageIn[i + 1][j - 1] + (short) imageIn[i + 1][j] + (short) imageIn[i + 1][j + 1];
  49. if (temp[0] > temp[3] / 12.0f)
  50. {
  51. imageOut[i][j] = 0;
  52. }
  53. else
  54. {
  55. imageOut[i][j] = 0xff;
  56. }
  57. }
  58. }
  59. }

实际二值化速度要比大津法慢不少,但是效果要更棒,看大佬们有无时间对算法进行优化。

2.灰度

灰度巡线就是利用赛道的特点,在黑白交界处会发生非常明显的数值跳变,计算每一行的跳变,放大他,当大于或者小于某一阈值,认为找到边界。

由于本人没有使用灰度,所以不多做介绍,详细情况请参考下面的推文。

电磁及摄像头(总钻风)寻迹算法浅析--逐飞科技

直接处理灰度图好处如下:

  1. 减少运算量,二值化处理需要对全图进行遍历,浪费时间。
  2. 抗干扰能力强,对于大津法,在光线不均的情况下,阈值会爆炸,而灰度会保留更多信息。

其实最开始我也是想使用灰度图的,但是到后期控制逐渐成熟,对于车子也不敢有太大的改动,所以也就没用进行灰度处理,这里还是比较推荐各位使用灰度的。

3.图像压缩

图像压缩可以在视野广阔的前提下缩小数组,就是把一张很大的图片等比例进行缩小。这样必定会损失一部分细节,但是好处是可以显著降低图像体积,在后续处理的时候,对于全图的遍历扫线,找点的操作计算量会减小很多。

进行图像压缩的可以将原数组开的大一些,保证获得广阔的视野。然后每间隔几个点,取一个点作为有效点存起来,我印象中有些国赛的队伍甚至图像压缩到80*60。这样对后续图像处理速度会有质的提升。

由于我没有使用,代码就不放在下面了。原理就是间隔,按照比例选取点丢到另一个数组去即可。

这里提一个注意事项,在压缩时,尽量压缩整倍数。如果压缩的倍数不合理,会产生小数点丢弃的情况,这样会造成图像变歪。摄像头歪着图像才是正的。我们实验室的同学就发生过这样的情况。所以将原始图像调好后,选择合适的缩放比例,尽量去凑整倍数,这样才会有比较好的效果。

4.抗干扰

每年的线下赛举办方各不相同,现场情况更是无法估计,总会出现非常多的问题,光线就是摄像头最大的杀手之一。

这里提出几个抗干扰的方法。

1.遮光窗帘


未使用遮光窗帘

调车时要记得开灯,这里只是示意图,窗帘遮光性能很好。


使用遮光窗帘后

由于智能车对于光线要求高,这里的高值得是均匀度,只要是光线均匀,哪怕暗一些都无所谓。所以直接买一套遮光窗帘,屏蔽掉室外的太阳光,只使用室内的灯,会使光线条件变好。当然也要注意,整体变亮或者变暗,需要调整摄像头的曝光,让摄像头尽量得到比较适中的数据。

当然,比赛现场有没有,那得看命。

2.偏振片


摄像头前面那个大圆盘就是偏振片

偏振片可以清除掉特定角度的反射光线,所以我们在开头看到的灯光反射的亮斑,使用偏振片,根据实际情况调整他的角度,就可以直接滤掉。


旋转偏振片角度,滤掉某一特定角度的光

据我实际使用经验,偏振片+遮光窗帘就处理掉90%光线问题。

3.抹布

当光线效果是在不佳时,征得裁判员同意后,老老实实用抹布吧,有些情况是技术上无解的,得采取一些物理手段了。


十七届电子科技大学充电组国赛现场

到这里,我们就获取到了一帧可以直接处理的优秀图像了(二值化),具体如何提取赛道信息,提取那些信息,下期我们再讲。

希望能够帮助到一些人。

本人菜鸡一只,各位大佬发现问题欢迎留言指出。

标签: 汽车 c语言 stm32

本文转载自: https://blog.csdn.net/qq_58114029/article/details/132050763
版权归原作者 Joshua.X 所有, 如有侵权,请联系我们删除。

“第18届全国大学生智能汽车竞赛四轮车开源讲解【2】--图像”的评论:

还没有评论