Program.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Drawing;
  5. using System.Drawing.Imaging;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using static SauvolaTest.SauvolaBinarization;
  11. namespace SauvolaTest
  12. {
  13. internal class Program
  14. {
  15. static void Main(string[] args)
  16. {
  17. // 1. 配置路径
  18. string inputPath = "ScreenShot_2026-04-16_102501_796.png"; // 请替换为你本地的图片路径
  19. string outputPath = "result_sauvola.bmp";
  20. if (!File.Exists(inputPath))
  21. {
  22. Console.WriteLine($"错误: 找不到文件 {inputPath}");
  23. Console.WriteLine("请将一张图片放在程序目录下,并修改 inputPath 变量。");
  24. return;
  25. }
  26. try
  27. {
  28. Console.WriteLine("正在加载图片...");
  29. using (Bitmap original = new Bitmap(inputPath))
  30. {
  31. // 2. 转换为灰度图 (修复了 Graphics 报错的问题)
  32. Console.WriteLine("正在转换为灰度图...");
  33. using (Bitmap grayImage = ConvertToGrayscaleFast(original))
  34. {
  35. // 3. 执行 Sauvola 二值化
  36. Console.WriteLine("正在执行 Sauvola 二值化 (这可能需要几秒钟)...");
  37. // 参数建议:
  38. // windowSize: 15-50 (奇数)。越小细节越多,越大背景越平滑。
  39. // k: 0.2 - 0.5。控制对标准差的敏感度。
  40. // r: 128。动态范围常数。
  41. Stopwatch stopwatch = Stopwatch.StartNew();
  42. using (Bitmap binaryImage = SauvolaBinarizer.ApplyFast(grayImage, windowSize: 15, k: 0.3, r: 100.0))
  43. {
  44. stopwatch.Stop();
  45. Console.WriteLine("耗时:{0}", stopwatch.ElapsedMilliseconds);
  46. // 4. 保存结果
  47. binaryImage.Save(outputPath, ImageFormat.Bmp);
  48. Console.WriteLine($"成功!结果已保存至: {outputPath}");
  49. }
  50. }
  51. }
  52. }
  53. catch (Exception ex)
  54. {
  55. Console.WriteLine($"发生异常: {ex.GetType().Name}");
  56. Console.WriteLine($"消息: {ex.Message}");
  57. Console.WriteLine($"堆栈: {ex.StackTrace}");
  58. }
  59. Console.WriteLine("按任意键退出...");
  60. Console.ReadKey();
  61. }
  62. /// <summary>
  63. /// 高效将任意 Bitmap 转换为 8bpp 灰度 Bitmap
  64. /// 不使用 Graphics 对象,避免索引格式异常
  65. /// </summary>
  66. private static Bitmap ConvertToGrayscaleFast(Bitmap source)
  67. {
  68. int width = source.Width;
  69. int height = source.Height;
  70. // 创建目标 8bpp 图像
  71. Bitmap grayBitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
  72. // 设置灰度调色板
  73. ColorPalette palette = grayBitmap.Palette;
  74. for (int i = 0; i < 256; i++)
  75. {
  76. palette.Entries[i] = Color.FromArgb(i, i, i);
  77. }
  78. grayBitmap.Palette = palette;
  79. // 锁定源图像 (假设源图像可能是 24bpp 或 32bpp)
  80. // 如果源图像已经是 8bpp,逻辑也兼容,但通常我们处理的是彩色图
  81. BitmapData srcData = source.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, source.PixelFormat);
  82. BitmapData dstData = grayBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
  83. try
  84. {
  85. unsafe
  86. {
  87. byte* srcPtr = (byte*)srcData.Scan0;
  88. byte* dstPtr = (byte*)dstData.Scan0;
  89. int srcStride = srcData.Stride;
  90. int dstStride = dstData.Stride;
  91. // 获取源图像的每个像素字节数
  92. int bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) / 8;
  93. // 遍历每一行
  94. for (int y = 0; y < height; y++)
  95. {
  96. byte* srcRow = srcPtr + y * srcStride;
  97. byte* dstRow = dstPtr + y * dstStride;
  98. for (int x = 0; x < width; x++)
  99. {
  100. byte b, g, r;
  101. // 根据源格式读取像素
  102. // 注意:Bitmap 在内存中通常是 BGR 顺序
  103. if (bytesPerPixel >= 3)
  104. {
  105. b = srcRow[x * bytesPerPixel];
  106. g = srcRow[x * bytesPerPixel + 1];
  107. r = srcRow[x * bytesPerPixel + 2];
  108. }
  109. else if (bytesPerPixel == 1)
  110. {
  111. // 如果源已经是灰度图
  112. b = g = r = srcRow[x];
  113. }
  114. else
  115. {
  116. // 其他格式默认处理
  117. b = g = r = srcRow[x * bytesPerPixel];
  118. }
  119. // 计算灰度值 (ITU-R BT.601 标准)
  120. // Gray = 0.299*R + 0.587*G + 0.114*B
  121. // 使用整数运算加速: (299*R + 587*G + 114*B) / 1000
  122. int grayVal = (299 * r + 587 * g + 114 * b) / 1000;
  123. // 确保范围在 0-255
  124. if (grayVal > 255) grayVal = 255;
  125. if (grayVal < 0) grayVal = 0;
  126. dstRow[x] = (byte)grayVal;
  127. }
  128. }
  129. }
  130. }
  131. finally
  132. {
  133. source.UnlockBits(srcData);
  134. grayBitmap.UnlockBits(dstData);
  135. }
  136. return grayBitmap;
  137. }
  138. }
  139. }