大津法优化之在飞卡智能车中的应用

此优化算法是本人前两年参加智能车比赛,在总钻风摄像头例程基础上优化的(分辨率为188*70,工程文件:含优化算法的小车程序)。移植方便,亲测只需2ms(逐飞给的例程是很常规的,网上都能找到,需要40ms不止吧,根本无法直接使用,不知道现在优化了没,手动滑稽)。

与鹰眼摄像头的硬件二值化不同,总钻风需要软件二值化。而大津法便是最为常见的一种自适应算法,效果也不错。网上资料蛮多,简单来说就是遍历0~255的灰度值,用该灰度值将一副图像分割为前景和背景(黑和白),用类间方差所定义的算式得到类间方差(deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);详见下面程序),当其最大时,即为最佳阈值(即得到的黑白图像区分最佳)

下面先来看看常规大津法为何如此缓慢

uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{
    #define GrayScale 256
    uint16 width = col;
    uint16 height = row;
    int pixelCount[GrayScale];
    float pixelPro[GrayScale];
    int i, j, pixelSum = width * height;
    uint8 threshold = 0;
    uint8* data = image;  //指向像素数据的指针
    for (i = 0; i < GrayScale; i++)
    {
        pixelCount[i] = 0;
        pixelPro[i] = 0;
    }

    //统计灰度级中每个像素在整幅图像中的个数  
    for (i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {
            pixelCount[(int)data[i * width + j]]++;  //将像素值作为计数数组的下标
        }
    }

    //计算每个像素值的点在整幅图像中的比例  
    float maxPro = 0.0;
    for (i = 0; i < GrayScale; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / pixelSum;
        if (pixelPro[i] > maxPro)
        {
            maxPro = pixelPro[i];
        }
    }

    //遍历灰度级[0,255]  
    float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
    for (i = 0; i < GrayScale; i++)     // i作为阈值
    {
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
        for (j = 0; j < GrayScale; j++)
        {
            if (j <= i)   //背景部分  
            {
                w0 += pixelPro[j];  //背景部分每个灰度值的像素点所占比例之和   即背景部分的比例
                u0tmp += j * pixelPro[j];  //背景部分 每个灰度值*比例  
            }
            else   //前景部分  
            {
                w1 += pixelPro[j];
                u1tmp += j * pixelPro[j];
            }
        }
        u0 = u0tmp / w0;              //背景平均灰度
        u1 = u1tmp / w1;              //前景平均灰度
        u = u0tmp + u1tmp;            //全局平均灰度
        deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
        if (deltaTmp > deltaMax)
        {
            deltaMax = deltaTmp;
            threshold = i;
        }
    }

    return threshold;
}

其中的两层for循环便为最为耗时的一部分:外循环–假设256个灰度值中的一个是最佳阈值;内循环–在对每个假设的最佳阈值进行0-255的灰度遍历算前景背景像素点所占比例,其繁琐可想而知

那么,应该从何处对大津法进行优化呢

1.去内外层的256次循环—可通过先算出每幅图像的总灰度,则接下来只用算前景,不用在算背景的多次重复遍历(背景用总的减去前景即可)
此步骤为最主要的,执行一次程序所需时间将大幅度减少!!
2.大家知道相邻两个像素点的灰度值其实是相近甚至相等的,尤其是分辨率高的时候,所以我们可以相间取点,但不可相间太远以免影响效果
3.一副图像的最佳阈值处的类间方差为最大值,那么如果以0-255的灰度值为横坐标,相应的类间方差为纵坐标,那么最佳阈值附近的灰度值所对应的类间方差又是如何变化的呢?凭着直觉,我猜测是二次的,后来也通过实验证明了这点。所以,当我们找到了最大类间方差,即类间方差有减小的趋势,便不用在继续遍历下去(对应程序末尾)

优化大津法如下,如果有不当之处望斧正,有需要完整小车程序的可在评论区留邮箱

uint8 my_adapt_threshold(uint8 *image, uint16 col, uint16 row)   //注意计算阈值的一定要是原图像
{
   #define GrayScale 256
    uint16 width = col;
    uint16 height = row;
    int pixelCount[GrayScale];
    float pixelPro[GrayScale];
    int i, j, pixelSum = width * height/4;
    uint8 threshold = 0;
    uint8* data = image;  //指向像素数据的指针
    for (i = 0; i < GrayScale; i++)
    {
        pixelCount[i] = 0;
        pixelPro[i] = 0;
    }
    
    uint32 gray_sum=0;
    //统计灰度级中每个像素在整幅图像中的个数  
    for (i = 0; i < height; i+=2)
    {
        for (j = 0; j < width; j+=2)
        {
            pixelCount[(int)data[i * width + j]]++;  //将当前的点的像素值作为计数数组的下标
            gray_sum+=(int)data[i * width + j];       //灰度值总和
        }
    }
                      
    //计算每个像素值的点在整幅图像中的比例  
  
    for (i = 0; i < GrayScale; i++)
    {
        pixelPro[i] = (float)pixelCount[i] / pixelSum;
        
    }

    //遍历灰度级[0,255]  
    float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
    
     
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
        for (j = 0; j < GrayScale; j++)         
        {
            
                w0 += pixelPro[j];  //背景部分每个灰度值的像素点所占比例之和   即背景部分的比例
                u0tmp += j * pixelPro[j];  //背景部分 每个灰度值的点的比例 *灰度值 
           
               w1=1-w0;
               u1tmp=gray_sum/pixelSum-u0tmp;
        
                u0 = u0tmp / w0;              //背景平均灰度
                u1 = u1tmp / w1;              //前景平均灰度
                u = u0tmp + u1tmp;            //全局平均灰度
                deltaTmp = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2);
                if (deltaTmp > deltaMax)
                {
                    deltaMax = deltaTmp;
                    threshold = j;
                }
                if (deltaTmp < deltaMax)
                {
                break;
                }
          
         }

    return threshold;

}

你可能感兴趣的:(笔记)