记录一个优秀的图像二值化代码
#region 二值化02 public Bitmap binaryzation(Bitmap srcBitmap, Bitmap dstBitmap) { int threshold = 0; Byte[,] BinaryArray = ToBinaryArray(srcBitmap, out threshold); dstBitmap = BinaryArrayToBinaryBitmap(BinaryArray); return dstBitmap; } /// <summary> /// 全局阈值图像二值化 /// </summary> /// <param name="bmp">原始图像</param> /// <param name="method">二值化方法</param> /// <param name="threshold">输出:全局阈值</param> /// <returns>二值化后的图像数组</returns> public static Byte[,] ToBinaryArray(Bitmap bmp, out int threshold) { // 位图转换为灰度数组 Byte[,] GrayArray = ToGrayArray(bmp); // 计算全局阈值 threshold = OtsuThreshold(GrayArray); // 根据阈值进行二值化 int PixelHeight = bmp.Height; int PixelWidth = bmp.Width; Byte[,] BinaryArray = new Byte[PixelHeight, PixelWidth]; for (int i = 0; i < PixelHeight; i++) { for (int 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 int OtsuThreshold(Byte[,] grayArray) { // 建立统计直方图 int[] Histogram = new int[256]; Array.Clear(Histogram, 0, 256); // 初始化 foreach (Byte b in grayArray) { Histogram[b]++; // 统计直方图 } // 总的质量矩和图像点数 int SumC = grayArray.Length; // 总的图像点数 Double SumU = 0; // 双精度避免方差运算中数据溢出 for (int i = 1; i < 256; i++) { SumU += i * Histogram[i]; // 总的质量矩 } // 灰度区间 int MinGrayLevel = Array.FindIndex(Histogram, NonZero); // 最小灰度值 int MaxGrayLevel = Array.FindLastIndex(Histogram, NonZero); // 最大灰度值 // 计算最大类间方差 int Threshold = MinGrayLevel; Double MaxVariance = 0.0; // 初始最大方差 Double U0 = 0; // 初始目标质量矩 int C0 = 0; // 初始目标点数 for (int 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="value">要检测的数值</param> /// <returns> /// true:非零 /// false:零 /// </returns> private static Boolean NonZero(int value) { return (value != 0) ? true : false; } /// <summary> /// 将位图转换为灰度数组(256级灰度) /// </summary> /// <param name="bmp">原始位图</param> /// <returns>灰度数组</returns> public static Byte[,] ToGrayArray(Bitmap bmp) { int PixelHeight = bmp.Height; // 图像高度 int PixelWidth = bmp.Width; // 图像宽度 int Stride = ((PixelWidth * 3 + 3) >> 2) << 2; // 跨距宽度 Byte[] Pixels = new Byte[PixelHeight * Stride]; // 锁定位图到系统内存 BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, PixelWidth, PixelHeight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); Marshal.Copy(bmpData.Scan0, Pixels, 0, Pixels.Length); // 从非托管内存拷贝数据到托管内存 bmp.UnlockBits(bmpData); // 从系统内存解锁位图 // 将像素数据转换为灰度数组 Byte[,] GrayArray = new Byte[PixelHeight, PixelWidth]; for (int i = 0; i < PixelHeight; i++) { int Index = i * Stride; for (int j = 0; j < PixelWidth; j++) { GrayArray[i, j] = Convert.ToByte((Pixels[Index + 2] * 19595 + Pixels[Index + 1] * 38469 + Pixels[Index] * 7471 + 32768) >> 16); Index += 3; } } return GrayArray; } /// <summary> /// 将二值化数组转换为二值化图像 /// </summary> /// <param name="binaryArray">二值化数组</param> /// <returns>二值化图像</returns> public static Bitmap BinaryArrayToBinaryBitmap(Byte[,] binaryArray) { // 将二值化数组转换为二值化数据 int PixelHeight = binaryArray.GetLength(0); int PixelWidth = binaryArray.GetLength(1); int Stride = ((PixelWidth + 31) >> 5) << 2; //int Stride = PixelWidth/8+(4- (PixelWidth / 8)%4); Byte[] Pixels = new Byte[PixelHeight * Stride]; for (int i = 0; i < PixelHeight; i++) { int Base = i * Stride; for (int 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; } #endregion
其中将二值化数组转换为二值化图像的算法很有意思
求跨距宽度这一步怎么也想不通为什么 int Stride = ((PixelWidth + 31) >> 5) << 2;
下面这个是一般的写法,很容易懂 参考https://www.cnblogs.com/dearzhoubi/p/8655326.html //int Stride = PixelWidth/8+(4- (PixelWidth / 8)%4);
Pixels[Base + (j >> 3)] |= Convert.ToByte(0x80 >> (j & 0x7));
****j&0x7实际上就是对j进行对8取余
****0x7二进制就是0000 0111
****比如j=18,二进制j=0001 0010,j&0x7=0000 0010,j=18对8取余就是2,也就是说二值数组中j=18这个位置的数据在二值图像中的存储位置是2个字节后第三个字节的第2个位置
****也就是要把0x80向右移动2位,0x80二进制是1000 0000,右移2位是0010 0000,
****然后和当前正在填充的字节进行按位或|,就可以把这一位数据填充进去
****实在是不熟悉这种二进制的这种移位操作,很神奇。