2015年2月22日
Sauvola 实现图像二值化
Sauvola 算法作为局部二值化算法,更加注重图像的局部特征,初次听闻Sauvola算法是在《基于背景估计和边缘检测的文档图像二值化》一文中,据说该算法是最好的二值化算法之一。为了更好的实现图像二值化,近期我孜孜不倦的在github中查找相关源码与文献。功夫不负有心人,我终于在github中找到了可用的Sauvola算法的源代码,并成功进行了实验验证。
————————————
Sauvola算法公式推导一文中介绍了3篇与Sauvola 算法相关的文献,对代码中所使用的公式进行了简单的推导,有兴趣的同学可以前往围观,谢谢。
————————————
该代码来源于github中名为nikun/OCRonet的repository,我做了小小的改动,让Sauvola算法在不引用OCRonet的前提下可以用于普通的C#工程。我们先来看看实验结果。
由实验结果可知,针对我最近正在研究的图片类型,Sauvola算法比OSTU的效果要好太多了,除了4张图有少量损失外,其他图片基本可以满足tessnet2的识别要求。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/// <summary> /// 实现Sauvola算法实现图像二值化 /// </summary> /// <param name="bin_image">用于存储二值化完成的图像</param> /// <param name="gray_image">用于存储等待二值化完成的灰度图像</param> public static void Sauvola(Byte[,] bin_image, Byte[,] gray_image) { int w = 40; double k = 0.3; int whalf = w >> 1; int MAXVAL = 256; int image_width = gray_image.GetLength(0); int image_height = gray_image.GetLength(1); int[,] integral_image = new int[image_width, image_height]; int[,] integral_sqimg = new int[image_width, image_height]; int[,] rowsum_image = new int[image_width, image_height]; int[,] rowsum_sqimg = new int[image_width, image_height]; int xmin,ymin,xmax,ymax; double diagsum,idiagsum,diff,sqdiagsum,sqidiagsum,sqdiff,area; double mean,std,threshold; for (int j = 0; j < image_height; j++) { rowsum_image[0, j] = gray_image[0, j]; rowsum_sqimg[0, j] = gray_image[0, j] * gray_image[0, j]; } for (int i = 1; i < image_width; i++) { for (int j = 0; j < image_height; j++) { rowsum_image[i, j] = rowsum_image[i - 1, j] + gray_image[i, j]; rowsum_sqimg[i, j] = rowsum_sqimg[i - 1, j] + gray_image[i, j] * gray_image[i, j]; } } for (int i = 0; i < image_width; i++) { integral_image[i, 0] = rowsum_image[i, 0]; integral_sqimg[i, 0] = rowsum_sqimg[i, 0]; } for (int i = 0; i < image_width; i++) { for (int j = 1; j < image_height; j++) { integral_image[i, j] = integral_image[i, j - 1] + rowsum_image[i, j]; integral_sqimg[i, j] = integral_sqimg[i, j - 1] + rowsum_sqimg[i, j]; } } //Calculate the mean and standard deviation using the integral image for(int i=0; i<image_width; i++){ for(int j=0; j<image_height; j++){ xmin = Math.Max(0,i-whalf); ymin = Math.Max(0, j - whalf); xmax = Math.Min(image_width - 1, i + whalf); ymax = Math.Min(image_height - 1, j + whalf); area = (xmax-xmin+1)*(ymax-ymin+1); // area can't be 0 here // proof (assuming whalf >= 0): // we'll prove that (xmax-xmin+1) > 0, // (ymax-ymin+1) is analogous // It's the same as to prove: xmax >= xmin // image_width - 1 >= 0 since image_width > i >= 0 // i + whalf >= 0 since i >= 0, whalf >= 0 // i + whalf >= i - whalf since whalf >= 0 // image_width - 1 >= i - whalf since image_width > i // --IM if (area <= 0) throw new Exception("Binarize: area can't be 0 here"); if (xmin == 0 && ymin == 0) { // Point at origin diff = integral_image[xmax, ymax]; sqdiff = integral_sqimg[xmax, ymax]; } else if (xmin == 0 && ymin > 0) { // first column diff = integral_image[xmax, ymax] - integral_image[xmax, ymin - 1]; sqdiff = integral_sqimg[xmax, ymax] - integral_sqimg[xmax, ymin - 1]; } else if (xmin > 0 && ymin == 0) { // first row diff = integral_image[xmax, ymax] - integral_image[xmin - 1, ymax]; sqdiff = integral_sqimg[xmax, ymax] - integral_sqimg[xmin - 1, ymax]; } else { // rest of the image diagsum = integral_image[xmax, ymax] + integral_image[xmin - 1, ymin - 1]; idiagsum = integral_image[xmax, ymin - 1] + integral_image[xmin - 1, ymax]; diff = diagsum - idiagsum; sqdiagsum = integral_sqimg[xmax, ymax] + integral_sqimg[xmin - 1, ymin - 1]; sqidiagsum = integral_sqimg[xmax, ymin - 1] + integral_sqimg[xmin - 1, ymax]; sqdiff = sqdiagsum - sqidiagsum; } mean = diff/area; std = Math.Sqrt((sqdiff - diff*diff/area)/(area-1)); threshold = mean*(1+k*((std/128)-1)); if(gray_image[i,j] < threshold) bin_image[i,j] = 0; else bin_image[i,j] = (byte)(MAXVAL-1); } } } |
这个算法好像不是Sauvola, 是Niblack。
Sauvola算法是Niblack算法的改进版本,看起来会比较像。[呵呵]