2015年3月10日
C#实现图像竖直投影
近期的学习重点的是如何让Tesseract识别粘连倾斜的字符(如下图所示),把这种图片直接丢给Tesseract肯定是不行的,我需要先将图片中的字符分开,然后再将倾斜的字符摆正,然后才可以给Tesseract呈上。那么现在第一个问题来了,怎样才能正确的将粘连字符分割呢?相关参考资料显示,通过竖直投影直方图的投影极小值,再辅以滴水算法的起始滴落点即可实现正确率较高的分割。
因此,我首先要得到图片的竖直投影直方图,何谓竖直投影直方图呢?图像的直方图通常用于统计图像的色彩分布,横轴是像素点的数值(0~255),纵轴则是像素值在这张图片中有多少一样的像素点。而图像的垂直(水平)投影直方图则是将样本图的每一列(每一行),投影到直方图对应水平(垂直)坐标上。这是我个人的理解,可能有误,请谨慎参考,若有此领域大牛愿意指点一二,不甚感激。
如下图所示,蓝色字体部分为原图,其下方的黑色部分为各自二值化之后对应的竖直投影图,源码如下:
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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
/// <summary> /// Get the vertical projection of the image /// </summary> /// <param name="imageSrc">the path of src image</param> public static void VerticalProjection(string imageSrcPath, string imageDestPath) { Bitmap bmp = new Bitmap(imageSrcPath); Int32 imgWidth = bmp.Width; Int32 imgHeight = bmp.Height; //用于存储当前横坐标垂直方向上的有效像素点数量(组成字符的像素点) Int32[] verticalPoints = new Int32[imgWidth]; Array.Clear(verticalPoints, 0, imgWidth); Int32 threshold = 0; //为增强本函数的通用性,先将原图像进行二值化,得到其二值化的数组 Byte[,] BinaryArray = bmp.ToBinaryArray(BinarizationMethods.Otsu,out threshold); //用于存储竖直投影后的二值化数组 Byte[,] verticalProArray = new Byte[imgHeight,imgWidth]; //先将该二值化数组初始化为白色 for (Int32 x = 0; x < imgWidth; x++) { for (Int32 y = 0; y < imgHeight; y++) { verticalProArray[y, x] = 255; } } //统计源图像的二值化数组中在每一个横坐标的垂直方向所包含的像素点数 for (Int32 x = 0; x < imgWidth; x++) { for (Int32 y = 0; y < imgHeight; y++) { if (0 == BinaryArray[y,x]) { verticalPoints[x] ++; } } } //将源图像中横坐标垂直方向上所包含的像素点按垂直方向依次从imgWidth开始叠放在竖直投影二值化数组中 for (Int32 x = 0; x < imgWidth; x++) { for (Int32 y = (imgHeight - 1); y >(imgHeight - verticalPoints[x] - 1); y--) { verticalProArray[y,x] = 0; } } //将竖直投影的二值化数组转换为二值化图像并保存 Bitmap verBmp = BinaryArrayToBinaryBitmap(verticalProArray); verBmp.Save(imageDestPath, System.Drawing.Imaging.ImageFormat.Jpeg); } /// <summary> /// 全局阈值图像二值化 /// </summary> /// <param name="bmp">原始图像</param> /// <param name="method">二值化方法</param> /// <param name="threshold">输出:全局阈值</param> /// <returns>二值化后的图像数组</returns> public static Byte[,] ToBinaryArray(this Bitmap bmp, BinarizationMethods method, out Int32 threshold) { // 位图转换为灰度数组 Byte[,] GrayArray = bmp.ToGrayArray(); // 计算全局阈值 if (method == BinarizationMethods.Otsu) threshold = OtsuThreshold(GrayArray); else threshold = IterativeThreshold(GrayArray); // 根据阈值进行二值化 Int32 PixelHeight = bmp.Height; Int32 PixelWidth = bmp.Width; Byte[,] BinaryArray = new Byte[PixelHeight, PixelWidth]; for (Int32 i = 0; i < PixelHeight; i++) { for (Int32 j = 0; j < PixelWidth; j++) { BinaryArray[i, j] = Convert.ToByte((GrayArray[i, j] > threshold) ? 255 : 0); } } return BinaryArray; } /// <summary> /// 大津法计算阈值 /// </summary> /// <param name="grayArray">灰度数组</param> /// <returns>二值化阈值</returns> public static Int32 OtsuThreshold(Byte[,] grayArray) { // 建立统计直方图 Int32[] Histogram = new Int32[256]; Array.Clear(Histogram, 0, 256); // 初始化 foreach (Byte b in grayArray) { Histogram[b]++; // 统计直方图 } // 总的质量矩和图像点数 Int32 SumC = grayArray.Length; // 总的图像点数 Double SumU = 0; // 双精度避免方差运算中数据溢出 for (Int32 i = 1; i < 256; i++) { SumU += i * Histogram[i]; // 总的质量矩 } // 灰度区间 Int32 MinGrayLevel = Array.FindIndex(Histogram, NonZero); // 最小灰度值 Int32 MaxGrayLevel = Array.FindLastIndex(Histogram, NonZero); // 最大灰度值 // 计算最大类间方差 Int32 Threshold = MinGrayLevel; Double MaxVariance = 0.0; // 初始最大方差 Double U0 = 0; // 初始目标质量矩 Int32 C0 = 0; // 初始目标点数 for (Int32 i = MinGrayLevel; i < MaxGrayLevel; i++) { if (Histogram[i] == 0) continue; // 目标的质量矩和点数 U0 += i * Histogram[i]; C0 += Histogram[i]; // 计算目标和背景的类间方差 Double Diference = U0 * SumC - SumU * C0; Double Variance = Diference * Diference / C0 / (SumC - C0); // 方差 if (Variance > MaxVariance) { MaxVariance = Variance; Threshold = i; } } // 返回类间方差最大阈值 return Threshold; } /// <summary> /// 将二值化数组转换为二值化图像 /// </summary> /// <param name="binaryArray">二值化数组</param> /// <returns>二值化图像</returns> public static Bitmap BinaryArrayToBinaryBitmap(Byte[,] binaryArray) { // 将二值化数组转换为二值化数据 Int32 PixelHeight = binaryArray.GetLength(0); Int32 PixelWidth = binaryArray.GetLength(1); Int32 Stride = ((PixelWidth + 31) >> 5) << 2; Byte[] Pixels = new Byte[PixelHeight * Stride]; for (Int32 i = 0; i < PixelHeight; i++) { Int32 Base = i * Stride; for (Int32 j = 0; j < PixelWidth; j++) { if (binaryArray[i, j] != 0) { Pixels[Base + (j >> 3)] |= Convert.ToByte(0x80 >> (j & 0x7)); } } } // 创建黑白图像 Bitmap BinaryBmp = new Bitmap(PixelWidth, PixelHeight, PixelFormat.Format1bppIndexed); // 设置调色表 ColorPalette cp = BinaryBmp.Palette; cp.Entries[0] = Color.Black; // 黑色 cp.Entries[1] = Color.White; // 白色 BinaryBmp.Palette = cp; // 设置位图图像特性 BitmapData BinaryBmpData = BinaryBmp.LockBits(new Rectangle(0, 0, PixelWidth, PixelHeight), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed); Marshal.Copy(Pixels, 0, BinaryBmpData.Scan0, Pixels.Length); BinaryBmp.UnlockBits(BinaryBmpData); return BinaryBmp; } |