智能车阳光算法(含大津法)

** ## 智能车阳光算法 **

谈不上真正的阳光算法,但是对于一些光干扰的场景和一些有噪点的图像还是可以进行处理。
在之前主流的摄像头主要是ov7725这个摄像头的优点就是硬件二值化,它的处理速度比较快,但是它输出只能是0和1,即就是黑和白,这对于阳光处理,反而成了缺点,我们只能人为的修改阈值,来适应部分场景,但是在光线较恶劣的场景下,我们不可能满足所有的光线要求,所以基于此,灰度摄像头由此诞生。灰度摄像头的灵活之处在于它返回的值不在仅仅是0和1,而是0-255之间,这样我们就可以根据图像,来人为进行一些光线处理,图像处理的操作。目前智能车主流的灰度摄像头有总钻风和神眼,一个是龙丘的一个是逐飞的。下面我们以逐飞的总钻风为例,给大家讲解一下摄像头的一些阳光处理。

1.图像二值化

上一篇文章里面我们说到了,如何获取图像和解压图像,但是我们主要针对的是ov7725,这里我们在给大家讲一下总钻风的处理。
首先就是正常的图像获取,但是今天我们用的是软件二值化的摄像头,就是灰度摄像头,所以我们首先就要对采集的数组进行处理。

void photo_get()
{
    int num=0;
    for(num=0;num<120;num++)
    {
       memcpy(dis_image[num],mt9v03x_csi_image[num],160);
    }
}

这里我们首先进行了一个操作就是想摄像头的图像数组进行复制,在这里复制的目的是:比如摄像头在进行图像采集的过程中,我们就要对图像进行操作,这样就会对原数据有一定的影响,因此我们首先是将图像进行复制,然后在对复制过来的图像进行操作。这样我们还可以在后面用原数据进行对比。这是第一步核心操作,接下来就是图像二值化了。一般图像二值化有几种常见的:大津法、和均值二值化。均值二值化是龙丘的库里面就会有,当然他的库里面也会有大津法,不过这个都需要去优化,因为一般大津法耗时比较长,所以软件二值化对芯片的要求就比较高,比如我们目前用的总钻风,就用k60带不起来,就要用主频更高的rt1064这个主频500M的芯片来配套使用。
下面给大家分享已经优化的大津法:

    #define GrayScale 256
    uint16 Image_Width  = col;
    uint16 Image_Height = row;
    int X; uint16 Y; 
    uint8* data = image;
    int HistGram[GrayScale];
  
    for (Y = 0; Y < GrayScale; Y++) 
    { 
        HistGram[Y] = 0; //初始化灰度直方图 
    } 
    for (Y = 0; Y <Image_Height; Y++) //Y
    { 
        //Y=Image_Height;
        for (X = 0; X < Image_Width; X++) 
        { 
        HistGram[(int)data[Y*Image_Width + X]]++; //统计每个灰度值的个数信息 
        }
    }

    uint32 Amount = 0; 
    uint32 PixelBack = 0; 
    uint32 PixelIntegralBack = 0; 
    uint32 PixelIntegral = 0; 
    int32 PixelIntegralFore = 0; 
    int32 PixelFore = 0; 
    double OmegaBack=0, OmegaFore=0, MicroBack=0, MicroFore=0, SigmaB=0, Sigma=0; // 类间方差; 
    int16 MinValue=0, MaxValue=0; 
    uint8 Threshold = 0;


    for (MinValue = 0; MinValue < 256 && HistGram[MinValue] == 0; MinValue++) ;        //获取最小灰度的值
    for (MaxValue = 255; MaxValue > MinValue && HistGram[MinValue] == 0; MaxValue--) ; //获取最大灰度的值

    if (MaxValue == MinValue) 
    {
        return MaxValue;          // 图像中只有一个颜色    
    }
    if (MinValue + 1 == MaxValue) 
    {
        return MinValue;      // 图像中只有二个颜色
    }

    for (Y = MinValue; Y <= MaxValue; Y++)
    {
        Amount += HistGram[Y];        //  像素总数
    }

    PixelIntegral = 0;
    for (Y = MinValue; Y <= MaxValue; Y++)
    {
        PixelIntegral += HistGram[Y] * Y;//灰度值总数
    }
    SigmaB = -1;
    for (Y = MinValue; Y < MaxValue; Y++)
    {
          PixelBack = PixelBack + HistGram[Y];    //前景像素点数
          PixelFore = Amount - PixelBack;         //背景像素点数
          OmegaBack = (double)PixelBack / Amount;//前景像素百分比
          OmegaFore = (double)PixelFore / Amount;//背景像素百分比
          PixelIntegralBack += HistGram[Y] * Y;  //前景灰度值
          PixelIntegralFore = PixelIntegral - PixelIntegralBack;//背景灰度值
          MicroBack = (double)PixelIntegralBack / PixelBack;//前景灰度百分比
          MicroFore = (double)PixelIntegralFore / PixelFore;//背景灰度百分比
          Sigma = OmegaBack * OmegaFore * (MicroBack - MicroFore) * (MicroBack - MicroFore);//g
          if (Sigma > SigmaB)//遍历最大的类间方差g
          {
              SigmaB = Sigma;
              Threshold = Y;
          }
    } 
   return Threshold;

当然还有另外一种大津法,如果感兴趣的同学可以给我留言,我和大家分享第二种方法,但是此时效果已经差不多了。

2.灰度转换成二值化

敲黑板!!!不论是硬件二值化还是软件二值化,最终我们的有效数据都是二值化的图像,所以我们最终都是以二值化的图像为准!

void img_conversion(uint8 (*src)[COL],uint8 (*dst)[COL],uint8 val)
{
    uint8 line=0,col=0;
    for(line=0;line<ROW;line++)
    {
       for(col=0;col<COL;col++)
       {
          if(src[line][col]>val)  dst[line][col]=250;  //!!!!敲黑板 划重点 250 不是255
          else dst[line][col]=0;
       }
    }
}

!!!在这里有一个地方很重要,我们将黑点都变成了250,而不是255。这里是为后面埋下伏笔。因为我们一般都是0和255,也就是黑和白,但是在这里我们先将黑点都标位疑似点,也就是在这里我不能确定这个点就是黑点,所以后面我们还是要继续判断,所以这也是一个重点。

3.黑点确认!

这就是我们的第三点,就是我们刚才上述的疑似点,这里我们就将刚才的疑似点进行处理。

void find_borders()
{
    int16 i,j,temp_centere; //临时中点
    /**********先初始化边界数组********************/
    for(i=0;i<ROW;i++) //64行
    {
       for(j=0;j<3;j++)
       {
          fb_image[i][j]=0;
       }
    }
    /**********找119行的中心检测点********************/
    temp_centere=COL/2;	     //   上一次中点位置 
    for(int i=0;i<=30;i++)  
    {
         if(tv_image[119][temp_centere+i]==250 || tv_image[119][temp_centere+i]==255)
         {
            temp_centere=temp_centere+i;
            break;
         }
         if(tv_image[119][temp_centere-i]==250 || tv_image[119][temp_centere-i]==255)
         { 
            temp_centere=temp_centere-i;
            break;
         }
    }
    /**********最后一行向左扫描********************/
    for(i=temp_centere;i>=0;i--)   
    {
        if(tv_image[119][i] !=0 )
        {
           tv_image[119][i] =255;
        }
        else break;
    }
    if(i==-1)  //for循环中1-2循环 到达0时 值已经为-1了
    {
       fb_image[119][0] = 0;
    }
    else
    {
       fb_image[119][0] = i+1; //存储数组  假设左边界是20
    }
    /**********最后一行向右扫描********************/
    for(i=temp_centere; i<COL; i++)        
    {
        if(tv_image[119][i] !=0 )
        {
          tv_image[119][i] =255;
        }
        else break;
    }
    if(i == 160)
    {
        fb_image[119][2] = 159;
    }
    else
    {
        fb_image[119][2] = i-1; //存储数组
    }
     
    /****************自下往上,扫描一次 筛选跳变点*****************/
    for(i=119;i>=tv_min_row;i--)    //根据119行的255遍历找本行的250或者255 及边界
    {
        for(j=0;j<160;j++)
        {
          if( tv_image[i+1][j] == 255 )     //根据上一行的赛道来确定本行的赛道
          {
            if( tv_image[i][j]== 250 || tv_image[i][j] == 255 )
            {
              tv_image[i][j] = 255;
            }
            if( j-1 >=0 &&( tv_image[i][j-1] == 250 || tv_image[i][j-1] == 255 ))
            {
              tv_image[i][j-1] =255;
            }
            if( j+1<=159 &&(tv_image[i][j+1] == 250 || tv_image[i][j+1] == 255))
            {
              tv_image[i][j+1]= 255;
            }
          }
        }
         /**********向左扫描,寻找第一个255点********************/
        for(j=0;j<=159;j++)        
        {
            if(tv_image[i][j] == 255)
            {
               break;
            }
        }
        if(j==160)  //本行无赛道,跳出
        {
          break;
        }
        for(;j>=0;j--)      //根据上面找到的255,向回找255 或者 250 核心亮点 for循环还能这样玩
        {
            if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
            {
              tv_image[i][j] =255;
            }
            else
            {
              fb_image[i][0] = j+1;
              break;
            }
        }
        if(j==-1)   //本行无赛道,跳出
        {
          fb_image[i][0] = 0;
        } 
        /**********向左扫描,寻找第一个255点********************/
        for(j=159;j>=0; j--)        //同上
        {
          if(tv_image[i][j] == 255)
          {
            break;
          }
        }
        if(j==-1)
        {
          break;
        }
        for(;j<=159;j++)     //同上
        {
          if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
          {
            tv_image[i][j] = 255;
          }
          else
          {
            fb_image[i][2] = j-1;
            break;
          }
        }
        if(j==160)
        {
          fb_image[i][2] = 159;
        }
    }
    
    
    /*******************************二次扫描是为了处理特殊情况****************************************************/ 
#if 1
    if(i >= tv_min_row )  //代表图像未扫描完
    {
        temp_centere=COL/2;	     //   上一次中点位置
        for(int j=0;j<=30;j++)
        {
          if(tv_image[i][temp_centere+j]==250 || tv_image[i][temp_centere+j]==255)
          {
            temp_centere=temp_centere+j;
            break;
          }
          if(tv_image[i][temp_centere-j]==250 || tv_image[i][temp_centere-j]==255)
          {
            temp_centere=temp_centere-j;
            break;
          }
        }
        if( temp_centere != COL/2)        //不相等说明找到了该行的赛道
        {
           //自下往上,扫描一次
            for(i=temp_centere;i>=tv_min_row;i--)    //根据159行的255遍历找本行的250或者255 及边界
            {
              for(j=0;j<159;j++)
              {
                if( tv_image[i+1][j] == 255 )     //根据上一行的赛道来确定本行的赛道
                {
                  if( tv_image[i][j]== 250 || tv_image[i][j] == 255)
                  {
                    tv_image[i][j] = 255;
                  }
                  if( j-1 >=0 &&( tv_image[i][j-1] == 250 || tv_image[i][j-1] == 255 ))
                  {
                    tv_image[i][j-1] =255;
                  }
                  if( j+1<=159 &&(tv_image[i][j+1] == 250 || tv_image[i][j+1] == 255))
                  {
                    tv_image[i][j+1]= 255;
                  }
               }
             }   
              
              
              
              for(j=0;j<=159;j++)         //向左扫描,寻找第一个255点
              {
                if(tv_image[i][j] == 255)
                {
                  break;
                }
              }
              if(j==160)  //本行无赛道,跳出
              {
                break;
              }
              for(;j>=0;j--)      //根据上面找到的255,向回找255 或者 250
              {
                if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
                {
                  tv_image[i][j] =255;
                }
                else
                {
                  fb_image[i][0] = j+1;
                  break;
                }
              }
              if(j==-1)   //本行无赛道,跳出
              {
                fb_image[i][0] = 0;
              }
              
              
              
              for(j=159;j>=0; j--)        //同上
              {
                if(tv_image[i][j] == 255)
                {
                  break;
                }
              }
              if(j==-1)
              {
                break;
              }
              for(;j<=159;j++)     //同上
              {
                if(tv_image[i][j] == 250 || tv_image[i][j] == 255)
                {
                  tv_image[i][j] = 255;
                }
                else
                {
                  fb_image[i][2] = j-1;
                  break;
                }
              }
              if(j==160)
              {
                fb_image[i][2] = 159;
              }
              
              
            }
        }
    }
#endif
}

下面就是我们的二次判定,主要是对刚才的疑似点再次进行判定,这样我们就可以将刚才的疑似点再次进行处理,这样就有一定的图像干扰处理。

void twoBJ()
{
    int i,j,k,temp_centere;
  
  //寻找63行的中点
    temp_centere=COL/2;	     //   上一次中点位置
    for(int i=0;i<=30;i++)
    {
      if(tv_image[119][temp_centere+i]==255)
      {
        temp_centere=temp_centere+i;
        break;
      }
      if( tv_image[119][temp_centere-i]==255)
      {
        temp_centere=temp_centere-i;
        break;
      }
    }
  
  
    for(i=ROW-1;i>=0;i--)        
    {
      for(j=temp_centere;j<COL;j++)	//前一行中点位置作为本行的向右侧扫描的起点
      {
          if(tv_image[i][j]==0)		
          {
            if(j==temp_centere)      //第一个点就等于0,说明此行丢赛道,跳过
            {
              return ;
            }
            fb_image[i][2]=j-1;		//右侧扫描到第一个黑点,判定为赛道的边界
            break;
          }
          else			//改写赛道,消除干扰
          {
            tv_image[i][j]=255;
          }
      
      }
      if(j==COL)
      {
        fb_image[i][2]=COL-1;
      }
    
    
      for(k=temp_centere;k>=0;k--)	//同上
      {
          if(tv_image[i][k]==0)
          {
            if(k==temp_centere)     第一个点就等于0,说明此行丢赛道,跳过
            {
              return ;
            }
            fb_image[i][0]=k+1;
            break;
          }
          else
          {
            tv_image[i][k]=255;
          }
          
      }
      
      if(k==-1)
      {
        fb_image[i][0]=0;
      }
      
      temp_centere=(j+k)/2;		//保存本行的中线,为下一行做参考
      
      fb_image[i][1]=(fb_image[i][0]+fb_image[i][2])/2;
      
      if((k==(COL-1)/2&&j==(COL-1)/2)||(k==0&& j==0))
      {
        break;
      }
      if( k == 119 && j == 119)
      {
        break;
      }
 
  }
  
}

上述就是部分的图像干扰处理,可以将部分噪点进行处理,同时也能让图像的数据更加有效,在后期我们进行赛道分析的时候,也会有很大的帮助。如果有写的不好的地方,请大佬见谅。仅自己个人观点。

你可能感兴趣的:(智能车,算法,自动驾驶)