Niblack算法的快速实现技巧

来源:互联网 发布:梦里花落知多少背影 编辑:程序博客网 时间:2024/05/22 04:54

From:http://blog.csdn.net/ieogxw/article/details/3871750

在许多文本图像的预处理过程中, 二值化过程是至为关键的一个环节。二值化算法的效果会对后续的处理如版面分析,字符定位以及识别等产生决定性的影响。

 

       二值化的算法有很多,大体分为两类: 全局阈值算法(如otsu算法)和局部阈值算法(如niblack)。而在汗牛充栋的局部阈值算法中,niblack 是实现方法较为简单,算法效果较为稳定的一种。但传统的niblack算法需要对图像中的每个像素计算其邻域的统计性质(均值与方差),算法复杂度为o(size *area) ,其中size 为图像像素尺寸大小,area 为邻域面积。作者尝试地对计算均值和方差的方法进行了改进,使得总体算法复杂度降低为 o(size),从而做到与邻域窗口大小基本无关。

       

      算法的关键是对每个像素的局部窗口求和过程的计算方式。图像实际上可看作是个二维数组,对二维数组中每个元素求局部窗口和的常规方式(C#代码)如下:

 

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// 常规的二维数组元素局部窗口求和程序  
  3. /// </summary>  
  4. /// <param name="array"> 输入二维数组</param>   
  5. /// <param name="winR">窗口半径</param>  
  6. /// <returns>输出结果</returns>  
  7.   
  8. public static int[,] LocalSum_Common(byte[,] array, int winR)  
  9. {  
  10.     int width = array.GetLength(0);  
  11.     int height = array.GetLength(1);  
  12.     int[,] sum = new int[width, height];  
  13.     int temp;  
  14.   
  15.     //不考虑边界情况,  
  16.       //水平方向:winR行至width-winR行,  
  17.       //垂直方向:winR列至width-winR列  
  18.   
  19.     for (int y = winR; y < height - winR; y++)  
  20.     {  
  21.         for (int x = winR; x < width - winR; x++)  
  22.         {  
  23.             temp =0;  
  24.             //对每个元素计算其周围半径为winR窗口内元素的累计和  
  25.             for (int k = -winR; k <= winR; k++)  
  26.             {  
  27.                 for (int j = -winR; j <= winR; j++)  
  28.                 {  
  29.                     temp += array[x + j, y + j];  
  30.                 }  
  31.             }  
  32.             sum[x, y] = temp;  
  33.         }  
  34.     }  
  35.     return sum;  
  36. }  

     

     上述求和的实现过程中包含了大量的重复运算。复杂度为 o(width*height*winR*winR)。由于二维数组中的相邻元素的局部窗口是相互交叉的,所以后一个元素的求和运算可以利用前一个元素的部分运算结果。为了达到这一目的,考虑将整个运算过程拆分为两个步骤,先进行垂直(水平)方向的求和再进行垂直(水平)方向的求和。代码如下

[c-sharp] view plaincopy
  1. /// <summary>  
  2. /// 快速的二维数组元素局部窗口求和程序  
  3. /// </summary>  
  4. /// <param name="array"> 输入二维数组</param>   
  5. /// <param name="winR">窗口半径</param>  
  6. /// <returns>输出结果</returns>  
  7. /// <summary>  
  8. public static int[,] LocalSum_Fast(byte[,] array, int winR)  
  9. {  
  10.     int width = array.GetLength(0);  
  11.     int height = array.GetLength(1);  
  12.     int[,] temp = new int[width, height];//保存中间结果的数组  
  13.     int[,] sum = new int[width, height];  
  14.   
  15.     //不考虑边界情况,  
  16.     //水平方向:winR行至width-winR行,  
  17.     //垂直方向:winR列至width-winR列  
  18.   
  19.     //对起始行winR在垂直方向求线性和  
  20.     for (int x = winR; x < width - winR; x++)  
  21.     {  
  22.         for (int k = -winR; k <= winR ; k++)  
  23.         {  
  24.             temp[x, winR] += array[x, winR + k];  
  25.         }  
  26.     }  
  27.     //从winR+1行至末尾行height-winR,依次基于前一行的求和结果进行计算。  
  28.     for (int y = winR + 1; y < height - winR; y++)  
  29.     {  
  30.         for (int x = winR; x < width - winR; x++)  
  31.         {  
  32.             temp[x, y] = temp[x, y - 1] + array[x, y + winR]   
  33.                          - array[x, y - 1 - winR];  
  34.         }  
  35.     }  
  36.       
  37.     //基于保存的垂直方向求和结果,进行水平方向求和  
  38.     //对起始列winR在水平方向求线性和  
  39.     for (int y = winR; y < height - winR; y++)  
  40.     {  
  41.         for (int k = -winR; k <= winR ; k++)  
  42.         {  
  43.             sum[winR, y] += temp[winR + k, y];  
  44.         }  
  45.     }  
  46.     //从winR+1列至末尾列height-winR,依次基于前一列的求和结果进行计算。  
  47.     for (int x = winR + 1; x < width - winR; x++)  
  48.     {  
  49.         for (int y = winR; y < height - winR; y++)  
  50.         {  
  51.             sum[x, y] = sum[x - 1, y] + temp[x + winR, y]   
  52.                         - temp[x - winR - 1, y];  
  53.         }  
  54.     }  
  55.     //运算完成,输出求和结果。  
  56.     return sum;  
  57. }  

 

       改进的求和实现,尽可能地利用了中间结果,计算复杂度降到了o(width*height),因此可实现快速运算。尤其在窗口尺寸要求较大时,两种算法在实现时间上的差异非常明显。