Freescale实纪——线性CCD图像处理与二值化

        上一篇博文我简单论述了Freescale光电组传感器——线性CCD的曝光与采集,那在对赛道的路面信息处理中,大多有两种算法:凹槽法,可详见上海交大的技术报告与论文;更常见且本人采取的做法是软件二值化。

        大家知道,线性CCD一个曝光周期只能采集视野范围内一条线的信息,即128个像素信息。我们通过AD采样获取的数据其实是通过运算电路得到的像素点的电压值,是一个数值。通过采样函数ImageCapture(Pixel)将一个曝光周期内采集到的128个像素点的电压值寄存到位长128的数组Pixel[i]中,从而用于计算与数据处理。在128个像素点的信息中,光线越强的点电压值越高,越弱的点电压值越低。针对赛道来说,CCD采集到白色的赛道部分电压值较高,我们认为是区域白电平;赛道两边的黑线以及黑线外的深蓝色背景光线暗,电压值低,即暗电压。由此我们可以区分赛道与边界,通过寻找赛道两边的黑线位置,计算赛道中间位置,让小车近似循中线行进。

        引出本文重点内容——二值化与动态阈值。对于CCD来说,黑与白的电压差是很大的。下图显示的是反映在上位机中CCD采集的一个画面。中间的凹槽表明在视野的那个位置有一个黑色区域。

        实际赛道黑色与白色部分的电压差值比上图要大许多,如果我们找到白电压与黑电压数值上的一个合适的中间值作为阈值,高于此阈值的像素点电压一律拉高到250(250数值根据上位机显示设置),低于此阈值的像素点电压一律置0,那么画面中的数据只有两个值,会产生明显的黑白电压跳变沿,这样只要找到两边的跳变沿,就可以确定黑线位置,从而计算出赛道中值。这就是二值化的基本原理与用法。

        二值化的数据处理不难,难在找到一个合适的阈值。首先要保证黑白电压的差值要很明显,通过更改曝光时间,加偏振片可以一定程度改变电压的差距。我个人还用了一个方法。在上一篇博文里我介绍的采样函数中有这一条采样语句:

temp_int = AD_Measure12(0);

*ImageData++ =(byte)(temp_int>>4); 

我们把第二句改成这样:

*ImageData++ = k* (byte)(temp_int>>4); 加上系数k,黑白电压的数值都乘上一个合理的系数,在保证图像清晰的前提下可以黑白电压的差值也相应增大,这样也扩大了我们可寻找的阈值范围,避免了出现阈值离黑白电压的上下限太近,二值出现紊乱的情形。

        现在有以下三种方法确定阈值:

        1、固定阈值

        在黑白电压值得较稳定,浮动范围很小的情况下,如白电压始终在170上下极小的范围内浮动,黑电压在50左右稳定,此时阈值可选范围很大,我们随机取一个100或120都是没问题的。但很多CCD存在畸变,视野两侧的电压始终很低,而且有时考虑到CCD视角的变化(如更改前瞻,焦距),图像的波动可能也会较大,用以下两种方法可能更实用。

        2、取平均电压值作阈值

        我们计算出128个像素点电压的平均值,取此值作为二值化的阈值,可以增强环境的适应性,哪怕黑白电压的门限不断变化,有一定的波动,或者光线略有不均,此方法都还比较凑效。

        3、计算max与min的均值作阈值

        我们对128个像素点做排序,取出电压最大与最小的两个像素点的电压值,取二者的平均值作阈值,事实证明效果也还是可以的,本人在安徽赛区的赛场上用的就是这个方法。但是考虑到比赛赛场上的光线过强,加上CCD畸变的影响,即使加上偏振片,得到的图像可能仍不理想,如下:

        这是我在比赛前一天试车时观察到的真实图像,两边的下降沿不明显,有个渐变的过程。此时用上述均值的方法得到的阈值可能恰好卡在平缓的下降沿那里,容易严重影响二值化后黑线的间距。我的做法是在所得阈值的基础上加上一个合适的常数作为阈值,使阈值达到稍靠近白电压的位置,这样的处理二值化后是没有问题的。

        当然针对安徽赛区的坑爹光线,第一种固定阈值的方案也是不错的选择。我们学校一个光电组一等奖的队员在试车后听了我们队关于阈值确定的建议,改成固定阈值,效果也是不错的。

        下面是我写的固定阈值选取与二值化的函数(本队使用的是双CCD):

//======固定阈值======//

 void Get_Dyn_Th(void) 

{

     extern unsigned char PixelAverageValue;     //CCD1阈值

     extern unsigned char Pixel2AverageValue;     //CCD2阈值

     unsigned char i;

     

     PixelAverageValue_old=PixelAverageValue;

     Pixel2AverageValue_old=Pixel2AverageValue;

     

     //计算CCD1像素点最大值

     value1_max=Pixel[0];

     for(i=1;i<128;i++) 

     {

        if(value1_max<=Pixel[i])

        value1_max=Pixel[i];

     }

     //计算CCD1像素点最小值

     value1_min=Pixel[0];

     for(i=1;i<128;i++) 

     {

        if(value1_min>=Pixel[i])

        value1_min=Pixel[i];

     }

     //计算CCD1阈值

     PixelAverageValue=(value1_max+value1_min)/2+40;

     

     if(abs(value1_max-value1_min)<=110)

     {

        PixelAverageValue=PixelAverageValue_old;   

     }

     

     //计算CCD2像素点最大值

     value2_max=Pixel2[0];

     for(i=1;i<128;i++) 

     {

        if(value2_max<=Pixel2[i])

        value2_max=Pixel2[i];

     }

     //计算CCD2像素点最小值

     value2_min=Pixel2[0];

     for(i=1;i<128;i++) 

     {

        if(value2_min>=Pixel2[i])

        value2_min=Pixel2[i];

     }

     //计算CCD2阈值

     Pixel2AverageValue=(value2_max+value2_min)/2+30;

     

     if(abs(value2_max-value2_min)<=100)

     {

        Pixel2AverageValue=Pixel2AverageValue_old;   

     }

}

 

//=========二值化处理==========//

void Bi_conversion(void) 

{

     unsigned char i;

     Get_Dyn_Th();

     for(i=0;i<128;i++)

     {

         if(Pixel[i]>PixelAverageValue)      //PixelAverageValue即为阈值

         {

              Pixel[i]=250;

         }

         else

         {

              Pixel[i]=0;

         }

         if(Pixel2[i]>Pixel2AverageValue)//下午用110,晚上用100

         {

              Pixel2[i]=250;

         }

         else

         {

              Pixel2[i]=0;

         }

     }

}

        还有二值化后的数据均值滤波函数(参考了第八届安工大的均值滤波算法):

//====================均值数据滤波==================//

void Filter_Pixel_Two(void)

{

     unsigned char i;

     for(i=1;i<127;i++)

     {

         if(Pixel[i]==0&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+1])

         {

              Pixel[i]=250;

         } 

         else if(Pixel[i]==250&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+1])

         {

              Pixel[i]=0;

         }

         

         if(Pixel2[i]==0&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+1])

         {

              Pixel2[i]=250;

         } 

         else if(Pixel2[i]==250&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+1])

         {

              Pixel2[i]=0;

         }

     }

            

     //当黑线上的点数超过三个时使用

     for(i=1;i<126;i++)

     {

          if(Pixel[i]==0&&Pixel[i]==Pixel[i+1]&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+2])

          {

              Pixel[i]=250;

              Pixel[i+1]=250;

          }

          if(Pixel[i]==250&&Pixel[i]==Pixel[i+1]&&Pixel[i]!=Pixel[i-1]&&Pixel[i]!=Pixel[i+2])            

          {

              Pixel[i]=0;

              Pixel[i+1]=0;

          }

          

          if(Pixel2[i]==0&&Pixel2[i]==Pixel2[i+1]&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+2])

          {

              Pixel2[i]=250;

              Pixel2[i+1]=250;

          }

          if(Pixel2[i]==200&&Pixel2[i]==Pixel2[i+1]&&Pixel2[i]!=Pixel2[i-1]&&Pixel2[i]!=Pixel2[i+2])            

          {

              Pixel2[i]=0;

              Pixel2[i+1]=0;

          }

      }

}

        这样,固定曝光的动态阈值与二值化数据处理就完成了,这些都是图像处理的基本步骤。

        曾看过第八届长春理工的图像处理算法,考虑到CCD畸变的影响,越往视野两边电压值因硬件缘故会自然的降低,所以他们将很多组CCD采集的128个像素点电压值导入Matlab进行数据分析,得出了一个畸变的曲线,然后用软件分析得出了一个128位加权数组,也就是让每个采集到的像素点原始电压值乘上加权数组里对应的权值,再加上一个补偿常数(也是经过计算得到)后得到的值作为待处理的像素点电压值。这样得到的图像即使不二值化,也跟二值化后的图像几乎类似了,再辅以一些滤波函数,最终二值化出来的图像极为稳定好看。   还有电子科大的离散余弦法处理图像,都是些高大上的处理方法。本人是非985,211的三流大学学生,学校、老师都提供不了这样高级的数学理论支持,本人用的处理方法和他们一比简直土的掉渣,不过对于不到2.8m/s的小车还是够用的。

        图像处理这块可挖掘的东西真的很多,是一门单独的学问。

        在后面的内容中我会提到鄙人的黑线提取算法与中线处理,包括特别针对十字弯与丢线情况的判断与解决方案。

        本人的水平十分有限,希望大家批评指正,不胜荣幸!

 

你可能感兴趣的:(Freescale)