#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;
下面这个是一般的写法,很容易懂 参考
//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,
****然后和当前正在填充的字节进行按位或|,就可以把这一位数据填充进去
****实在是不熟悉这种二进制的这种移位操作,很神奇。