在许多文本图像的预处理过程中, 二值化过程是至为关键的一个环节。二值化算法的效果会对后续的处理如版面分析,字符定位以及识别等产生决定性的影响。
二值化的算法有很多,大体分为两类: 全局阈值算法(如otsu算法)和局部阈值算法(如niblack)。而在汗牛充栋的局部阈值算法中,niblack 是实现方法较为简单,算法效果较为稳定的一种。但传统的niblack算法需要对图像中的每个像素计算其邻域的统计性质(均值与方差),算法复杂度为o(size *area) ,其中size 为图像像素尺寸大小,area 为邻域面积。作者尝试地对计算均值和方差的方法进行了改进,使得总体算法复杂度降低为 o(size),从而做到与邻域窗口大小基本无关。
算法的关键是对每个像素的局部窗口求和过程的计算方式。图像实际上可看作是个二维数组,对二维数组中每个元素求局部窗口和的常规方式(C#代码)如下:
/// /// 常规的二维数组元素局部窗口求和程序 /// /// 输入二维数组 /// 窗口半径 /// 输出结果 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)。由于二维数组中的相邻元素的局部窗口是相互交叉的,所以后一个元素的求和运算可以利用前一个元素的部分运算结果。为了达到这一目的,考虑将整个运算过程拆分为两个步骤,先进行垂直(水平)方向的求和再进行垂直(水平)方向的求和。代码如下
/// /// 快速的二维数组元素局部窗口求和程序 /// /// 输入二维数组 /// 窗口半径 /// 输出结果 /// 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),因此可实现快速运算。尤其在窗口尺寸要求较大时,两种算法在实现时间上的差异非常明显。