最近找到一个不错的文档图片自动纠偏跟大家分享一下。
纠偏算法:
1 public class Deskew 2 { 3 // Representation of a line in the image. 4 private class HougLine 5 { 6 // Count of points in the line. 7 public int Count; 8 // Index in Matrix. 9 public int Index; 10 // The line is represented as all x,y that solve y*cos(alpha)-x*sin(alpha)=d 11 public double Alpha; 12 } 13 14 15 // The Bitmap 16 public Bitmap _internalBmp; 17 18 // The range of angles to search for lines 19 const double ALPHA_START = -20; 20 const double ALPHA_STEP = 0.2; 21 const int STEPS = 40 * 5; 22 const double STEP = 1; 23 24 // Precalculation of sin and cos. 25 double[] _sinA; 26 double[] _cosA; 27 28 // Range of d 29 double _min; 30 31 32 int _count; 33 // Count of points that fit in a line. 34 int[] _hMatrix; 35 36 // Calculate the skew angle of the image cBmp. 37 public double GetSkewAngle() 38 { 39 // Hough Transformation 40 Calc(); 41 42 // Top 20 of the detected lines in the image. 43 HougLine[] hl = GetTop(20); 44 45 // Average angle of the lines 46 double sum = 0; 47 int count = 0; 48 for (int i = 0; i <= 19; i++) 49 { 50 sum += hl[i].Alpha; 51 count += 1; 52 } 53 return sum / count; 54 } 55 56 // Calculate the Count lines in the image with most points. 57 private HougLine[] GetTop(int count) 58 { 59 HougLine[] hl = new HougLine[count]; 60 61 for (int i = 0; i <= count - 1; i++) 62 { 63 hl[i] = new HougLine(); 64 } 65 for (int i = 0; i <= _hMatrix.Length - 1; i++) 66 { 67 if (_hMatrix[i] > hl[count - 1].Count) 68 { 69 hl[count - 1].Count = _hMatrix[i]; 70 hl[count - 1].Index = i; 71 int j = count - 1; 72 while (j > 0 && hl[j].Count > hl[j - 1].Count) 73 { 74 HougLine tmp = hl[j]; 75 hl[j] = hl[j - 1]; 76 hl[j - 1] = tmp; 77 j -= 1; 78 } 79 } 80 } 81 82 for (int i = 0; i <= count - 1; i++) 83 { 84 int dIndex = hl[i].Index / STEPS; 85 int alphaIndex = hl[i].Index - dIndex * STEPS; 86 hl[i].Alpha = GetAlpha(alphaIndex); 87 //hl[i].D = dIndex + _min; 88 } 89 90 return hl; 91 } 92 93 94 // Hough Transforamtion: 95 private void Calc() 96 { 97 int hMin = _internalBmp.Height / 4; 98 int hMax = _internalBmp.Height * 3 / 4; 99 100 Init(); 101 for (int y = hMin; y <= hMax; y++) 102 { 103 for (int x = 1; x <= _internalBmp.Width - 2; x++) 104 { 105 // Only lower edges are considered. 106 if (IsBlack(x, y)) 107 { 108 if (!IsBlack(x, y + 1)) 109 { 110 Calc(x, y); 111 } 112 } 113 } 114 } 115 } 116 117 // Calculate all lines through the point (x,y). 118 private void Calc(int x, int y) 119 { 120 int alpha; 121 122 for (alpha = 0; alpha <= STEPS - 1; alpha++) 123 { 124 double d = y * _cosA[alpha] - x * _sinA[alpha]; 125 int calculatedIndex = (int)CalcDIndex(d); 126 int index = calculatedIndex * STEPS + alpha; 127 try 128 { 129 _hMatrix[index] += 1; 130 } 131 catch (Exception ex) 132 { 133 System.Diagnostics.Debug.WriteLine(ex.ToString()); 134 } 135 } 136 } 137 private double CalcDIndex(double d) 138 { 139 return Convert.ToInt32(d - _min); 140 } 141 private bool IsBlack(int x, int y) 142 { 143 Color c = _internalBmp.GetPixel(x, y); 144 double luminance = (c.R * 0.299) + (c.G * 0.587) + (c.B * 0.114); 145 return luminance < 140; 146 } 147 148 private void Init() 149 { 150 // Precalculation of sin and cos. 151 _cosA = new double[STEPS]; 152 _sinA = new double[STEPS]; 153 154 for (int i = 0; i < STEPS; i++) 155 { 156 double angle = GetAlpha(i) * Math.PI / 180.0; 157 _sinA[i] = Math.Sin(angle); 158 _cosA[i] = Math.Cos(angle); 159 } 160 161 // Range of d: 162 _min = -_internalBmp.Width; 163 _count = (int)(2 * (_internalBmp.Width + _internalBmp.Height) / STEP); 164 _hMatrix = new int[_count * STEPS]; 165 166 } 167 168 private static double GetAlpha(int index) 169 { 170 return ALPHA_START + index * ALPHA_STEP; 171 } 172 }
自己写的调用方法
1 public static void DeskewImage(string fileName, byte binarizeThreshold) 2 { 3 //打开图像 4 Bitmap bmp = OpenImage(fileName); 5 6 Deskew deskew = new Deskew(); 7 Bitmap tempBmp = CropImage(bmp, bmp.Width / 4, bmp.Height / 4, bmp.Width / 2, bmp.Height / 2); 8 deskew._internalBmp = BinarizeImage(tempBmp, binarizeThreshold); 9 double angle = deskew.GetSkewAngle(); 10 bmp = RotateImage(bmp, (float)(-angle)); 11 12 //保存图像(需要再还原图片原本的位深度) 13 SaveImage(bmp, fileName); 14 } 15 16 /// <summary> 17 /// 图像剪切 18 /// </summary> 19 /// <param name="bmp"></param> 20 /// <param name="StartX"></param> 21 /// <param name="StartY"></param> 22 /// <param name="w"></param> 23 /// <param name="h"></param> 24 /// <returns></returns> 25 private static Bitmap CropImage(Bitmap bmp, int StartX, int StartY, int w, int h) 26 { 27 try 28 { 29 Bitmap bmpOut = new Bitmap(w, h, PixelFormat.Format32bppArgb); 30 31 Graphics g = Graphics.FromImage(bmpOut); 32 g.DrawImage(bmp, new Rectangle(0, 0, w, h), new Rectangle(StartX, StartY, w, h), GraphicsUnit.Pixel); 33 g.Dispose(); 34 35 return bmpOut; 36 } 37 catch 38 { 39 return null; 40 } 41 } 42 43 44 /// <summary> 45 /// 图像二值化 46 /// </summary> 47 /// <param name="b"></param> 48 /// <param name="threshold">阈值</param> 49 private static Bitmap BinarizeImage(Bitmap b, byte threshold) 50 { 51 int width = b.Width; 52 int height = b.Height; 53 BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 54 unsafe 55 { 56 byte* p = (byte*)data.Scan0; 57 int offset = data.Stride - width * 4; 58 byte R, G, B, gray; 59 for (int y = 0; y < height; y++) 60 { 61 for (int x = 0; x < width; x++) 62 { 63 R = p[2]; 64 G = p[1]; 65 B = p[0]; 66 gray = (byte)((R * 19595 + G * 38469 + B * 7472) >> 16); 67 if (gray >= threshold) 68 { 69 p[0] = p[1] = p[2] = 255; 70 } 71 else 72 { 73 p[0] = p[1] = p[2] = 0; 74 } 75 p += 4; 76 } 77 p += offset; 78 } 79 b.UnlockBits(data); 80 return b; 81 } 82 } 83 84 /// <summary> 85 /// 图像旋转 86 /// </summary> 87 /// <param name="bmp"></param> 88 /// <param name="angle">角度</param> 89 /// <returns></returns> 90 private static Bitmap RotateImage(Bitmap bmp, float angle) 91 { 92 PixelFormat pixelFormat = bmp.PixelFormat; 93 PixelFormat pixelFormatOld = pixelFormat; 94 if (bmp.Palette.Entries.Count() > 0) 95 { 96 pixelFormat = PixelFormat.Format24bppRgb; 97 } 98 99 Bitmap tmpBitmap = new Bitmap(bmp.Width, bmp.Height, pixelFormat); 100 tmpBitmap.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); 101 Graphics g = Graphics.FromImage(tmpBitmap); 102 try 103 { 104 g.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height); 105 g.RotateTransform(angle); 106 g.DrawImage(bmp, 0, 0); 107 } 108 catch 109 { 110 } 111 finally 112 { 113 g.Dispose(); 114 } 115 116 if (pixelFormatOld == PixelFormat.Format8bppIndexed) tmpBitmap = CopyTo8bpp(tmpBitmap); 117 else if (pixelFormatOld == PixelFormat.Format1bppIndexed) tmpBitmap = CopyTo1bpp(tmpBitmap); 118 119 return tmpBitmap; 120 }
在最后进行图片选择时,位深度为1、4、8的索引图片是没办法直接用Graphics进行旋转操作的,需要图像的PixelFormat再做旋转。
现在只实现位深度为1和8的索引图片还原。
1 private static Bitmap CopyTo1bpp(Bitmap b) 2 { 3 int w = b.Width, h = b.Height; Rectangle r = new Rectangle(0, 0, w, h); 4 if (b.PixelFormat != PixelFormat.Format32bppPArgb) 5 { 6 Bitmap temp = new Bitmap(w, h, PixelFormat.Format32bppPArgb); 7 temp.SetResolution(b.HorizontalResolution, b.VerticalResolution); 8 Graphics g = Graphics.FromImage(temp); 9 g.DrawImage(b, r, 0, 0, w, h, GraphicsUnit.Pixel); 10 g.Dispose(); 11 b = temp; 12 } 13 BitmapData bdat = b.LockBits(r, ImageLockMode.ReadOnly, b.PixelFormat); 14 Bitmap b0 = new Bitmap(w, h, PixelFormat.Format1bppIndexed); 15 b0.SetResolution(b.HorizontalResolution, b.VerticalResolution); 16 BitmapData b0dat = b0.LockBits(r, ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed); 17 for (int y = 0; y < h; y++) 18 { 19 for (int x = 0; x < w; x++) 20 { 21 int index = y * bdat.Stride + (x * 4); 22 if (Color.FromArgb(Marshal.ReadByte(bdat.Scan0, index + 2), Marshal.ReadByte(bdat.Scan0, index + 1), Marshal.ReadByte(bdat.Scan0, index)).GetBrightness() > 0.5f) 23 { 24 int index0 = y * b0dat.Stride + (x >> 3); 25 byte p = Marshal.ReadByte(b0dat.Scan0, index0); 26 byte mask = (byte)(0x80 >> (x & 0x7)); 27 Marshal.WriteByte(b0dat.Scan0, index0, (byte)(p | mask)); 28 } 29 } 30 } 31 b0.UnlockBits(b0dat); 32 b.UnlockBits(bdat); 33 return b0; 34 } 35 36 private static Bitmap CopyTo8bpp(Bitmap bmp) 37 { 38 if (bmp == null) return null; 39 40 Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 41 BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); 42 43 int width = bmpData.Width; 44 int height = bmpData.Height; 45 int stride = bmpData.Stride; 46 int offset = stride - width * 3; 47 IntPtr ptr = bmpData.Scan0; 48 int scanBytes = stride * height; 49 50 int posScan = 0, posDst = 0; 51 byte[] rgbValues = new byte[scanBytes]; 52 Marshal.Copy(ptr, rgbValues, 0, scanBytes); 53 byte[] grayValues = new byte[width * height]; 54 55 for (int i = 0; i < height; i++) 56 { 57 for (int j = 0; j < width; j++) 58 { 59 double temp = rgbValues[posScan++] * 0.11 + 60 rgbValues[posScan++] * 0.59 + 61 rgbValues[posScan++] * 0.3; 62 grayValues[posDst++] = (byte)temp; 63 } 64 posScan += offset; 65 } 66 67 Marshal.Copy(rgbValues, 0, ptr, scanBytes); 68 bmp.UnlockBits(bmpData); 69 70 Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed); 71 bitmap.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution); 72 BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); 73 74 int offset0 = bitmapData.Stride - bitmapData.Width; 75 int scanBytes0 = bitmapData.Stride * bitmapData.Height; 76 byte[] rawValues = new byte[scanBytes0]; 77 78 int posSrc = 0; 79 posScan = 0; 80 for (int i = 0; i < height; i++) 81 { 82 for (int j = 0; j < width; j++) 83 { 84 rawValues[posScan++] = grayValues[posSrc++]; 85 } 86 posScan += offset0; 87 } 88 89 Marshal.Copy(rawValues, 0, bitmapData.Scan0, scanBytes0); 90 bitmap.UnlockBits(bitmapData); 91 92 ColorPalette palette; 93 using (Bitmap bmp0 = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) 94 { 95 palette = bmp0.Palette; 96 } 97 for (int i = 0; i < 256; i++) 98 { 99 palette.Entries[i] = Color.FromArgb(i, i, i); 100 } 101 bitmap.Palette = palette; 102 103 return bitmap; 104 }
第一次发博,如有误的地方请大湿们多多指点。