本文非作者原创,转载于http://blog.csdn.net/ieogxw/article/details/3871750#comments
二值化的算法有很多,大体分为两类: 全局阈值算法(如otsu算法)和局部阈值算法(如niblack)。而在汗牛充栋的局部阈值算法中,niblack 是实现方法较为简单,算法效果较为稳定的一种。但传统的niblack算法需要对图像中的每个像素计算其邻域的统计性质(均值与方差),算法复杂度为o(size *area) ,其中size 为图像像素尺寸大小,area 为邻域面积。作者尝试地对计算均值和方差的方法进行了改进,使得总体算法复杂度降低为 o(size),从而做到与邻域窗口大小基本无关。
算法的关键是对每个像素的局部窗口求和过程的计算方式。图像实际上可看作是个二维数组,对二维数组中每个元素求局部窗口和的常规方式(C#代码)如下:
- /// <summary>
- /// 常规的二维数组元素局部窗口求和程序
- /// </summary>
- /// <param name="array"> 输入二维数组</param>
- /// <param name="winR">窗口半径</param>
- /// <returns>输出结果</returns>
- public static int[,] LocalSum_Common(byte[,] array, int winR)
- {
- int width = array.GetLength(0);
- int height = array.GetLength(1);
- int[,] sum = new int[width, height];
- int temp;
- //不考虑边界情况,
- //水平方向:winR行至width-winR行,
- //垂直方向:winR列至width-winR列
- for (int y = winR; y < height - winR; y++)
- {
- for (int x = winR; x < width - winR; x++)
- {
- temp =0;
- //对每个元素计算其周围半径为winR窗口内元素的累计和
- for (int k = -winR; k <= winR; k++)
- {
- for (int j = -winR; j <= winR; j++)
- {
- temp += array[x + j, y + j];
- }
- }
- sum[x, y] = temp;
- }
- }
- return sum;
- }
上述求和的实现过程中包含了大量的重复运算。复杂度为 o(width*height*winR*winR)。由于二维数组中的相邻元素的局部窗口是相互交叉的,所以后一个元素的求和运算可以利用前一个元素的部分运算结果。为了达到这一目的,考虑将整个运算过程拆分为两个步骤,先进行垂直(水平)方向的求和再进行垂直(水平)方向的求和。代码如下
- /// <summary>
- /// 快速的二维数组元素局部窗口求和程序
- /// </summary>
- /// <param name="array"> 输入二维数组</param>
- /// <param name="winR">窗口半径</param>
- /// <returns>输出结果</returns>
- /// <summary>
- public static int[,] LocalSum_Fast(byte[,] array, int winR)
- {
- int width = array.GetLength(0);
- int height = array.GetLength(1);
- int[,] temp = new int[width, height];//保存中间结果的数组
- int[,] sum = new int[width, height];
- //不考虑边界情况,
- //水平方向:winR行至width-winR行,
- //垂直方向:winR列至width-winR列
- //对起始行winR在垂直方向求线性和
- for (int x = winR; x < width - winR; x++)
- {
- for (int k = -winR; k <= winR ; k++)
- {
- temp[x, winR] += array[x, winR + k];
- }
- }
- //从winR+1行至末尾行height-winR,依次基于前一行的求和结果进行计算。
- for (int y = winR + 1; y < height - winR; y++)
- {
- for (int x = winR; x < width - winR; x++)
- {
- temp[x, y] = temp[x, y - 1] + array[x, y + winR]
- - array[x, y - 1 - winR];
- }
- }
- //基于保存的垂直方向求和结果,进行水平方向求和
- //对起始列winR在水平方向求线性和
- for (int y = winR; y < height - winR; y++)
- {
- for (int k = -winR; k <= winR ; k++)
- {
- sum[winR, y] += temp[winR + k, y];
- }
- }
- //从winR+1列至末尾列height-winR,依次基于前一列的求和结果进行计算。
- for (int x = winR + 1; x < width - winR; x++)
- {
- for (int y = winR; y < height - winR; y++)
- {
- sum[x, y] = sum[x - 1, y] + temp[x + winR, y]
- - temp[x - winR - 1, y];
- }
- }
- //运算完成,输出求和结果。
- return sum;
- }
改进的求和实现,尽可能地利用了中间结果,计算复杂度降到了o(width*height),因此可实现快速运算。尤其在窗口尺寸要求较大时,两种算法在实现时间上的差异非常明显。