线性CCD图像处理与二值化

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的小车还是够用的。

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

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

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

  • 35
    点赞
  • 157
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
线性 CCD 模块是智能车的“眼睛”,本文档旨在向大家阐述线性 CCD 的基本原理及其软硬件使用说明。   说到 CCD,想必大家都不陌生。在我们常用的手机、数码相机等电子设备的摄像头中,CCD 得到了广泛的应用。但是细心的各位可能发现了,我们这篇文档从始至终都在强调“线性CCD,那么什么是线性 CCD,它又与我们常说的 CCD 有什么区别呢?   我们常说的 CCD 指的是面阵 CCD,在你使用手机拍照后,会得到一幅图像,如果你感兴趣的话,打开图像的属性可以看到它的尺寸,例如 1920x1080。从此可以发现,手机里面的 CCD 拍下的图像可以被认为是一个 1920x1080 的矩阵(这里只考虑灰度图像)。但是如果你使用线性 CCD 拍摄下同样一幅图像,你会发现它的尺寸是 1x128,也就是说线性 CCD 拍下的图像是一个仅有一个像素宽的长条,我们可以将其作为一个由 128 个灰度值组成的向量(数组),这也就是两种 CCD 最大的不同之处。两种 CCD 拍到的图片如下图所示。   TSL1401 芯片包含 128 个线性排列的光电二极管,同时片内为每个光电二极管集成了独自的积分电路,下面为了便于理解,我们将这些光电二极管及其积分电路统称为像素。对于每个像素来说,其采集到的灰度值均与其感知的光强与积分(曝光)时间成正比,而采集到的灰度值将在 AO 线上以模拟信号(电压)的形式输出,下面将每个像素采集到的灰度值称为其像素值。那么问题来了,我们共有 128 个像素值要传输给单片机,但是 AO 线只有一根,怎么办呢?这时候就轮到 CLK 和 SI 这两个信号上场了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值