using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using static SauvolaTest.SauvolaBinarization; namespace SauvolaTest { internal class Program { static void Main(string[] args) { // 1. 配置路径 string inputPath = "ScreenShot_2026-04-16_102501_796.png"; // 请替换为你本地的图片路径 string outputPath = "result_sauvola.bmp"; if (!File.Exists(inputPath)) { Console.WriteLine($"错误: 找不到文件 {inputPath}"); Console.WriteLine("请将一张图片放在程序目录下,并修改 inputPath 变量。"); return; } try { Console.WriteLine("正在加载图片..."); using (Bitmap original = new Bitmap(inputPath)) { // 2. 转换为灰度图 (修复了 Graphics 报错的问题) Console.WriteLine("正在转换为灰度图..."); using (Bitmap grayImage = ConvertToGrayscaleFast(original)) { // 3. 执行 Sauvola 二值化 Console.WriteLine("正在执行 Sauvola 二值化 (这可能需要几秒钟)..."); // 参数建议: // windowSize: 15-50 (奇数)。越小细节越多,越大背景越平滑。 // k: 0.2 - 0.5。控制对标准差的敏感度。 // r: 128。动态范围常数。 Stopwatch stopwatch = Stopwatch.StartNew(); using (Bitmap binaryImage = SauvolaBinarizer.ApplyFast(grayImage, windowSize: 15, k: 0.3, r: 100.0)) { stopwatch.Stop(); Console.WriteLine("耗时:{0}", stopwatch.ElapsedMilliseconds); // 4. 保存结果 binaryImage.Save(outputPath, ImageFormat.Bmp); Console.WriteLine($"成功!结果已保存至: {outputPath}"); } } } } catch (Exception ex) { Console.WriteLine($"发生异常: {ex.GetType().Name}"); Console.WriteLine($"消息: {ex.Message}"); Console.WriteLine($"堆栈: {ex.StackTrace}"); } Console.WriteLine("按任意键退出..."); Console.ReadKey(); } /// /// 高效将任意 Bitmap 转换为 8bpp 灰度 Bitmap /// 不使用 Graphics 对象,避免索引格式异常 /// private static Bitmap ConvertToGrayscaleFast(Bitmap source) { int width = source.Width; int height = source.Height; // 创建目标 8bpp 图像 Bitmap grayBitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed); // 设置灰度调色板 ColorPalette palette = grayBitmap.Palette; for (int i = 0; i < 256; i++) { palette.Entries[i] = Color.FromArgb(i, i, i); } grayBitmap.Palette = palette; // 锁定源图像 (假设源图像可能是 24bpp 或 32bpp) // 如果源图像已经是 8bpp,逻辑也兼容,但通常我们处理的是彩色图 BitmapData srcData = source.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, source.PixelFormat); BitmapData dstData = grayBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); try { unsafe { byte* srcPtr = (byte*)srcData.Scan0; byte* dstPtr = (byte*)dstData.Scan0; int srcStride = srcData.Stride; int dstStride = dstData.Stride; // 获取源图像的每个像素字节数 int bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) / 8; // 遍历每一行 for (int y = 0; y < height; y++) { byte* srcRow = srcPtr + y * srcStride; byte* dstRow = dstPtr + y * dstStride; for (int x = 0; x < width; x++) { byte b, g, r; // 根据源格式读取像素 // 注意:Bitmap 在内存中通常是 BGR 顺序 if (bytesPerPixel >= 3) { b = srcRow[x * bytesPerPixel]; g = srcRow[x * bytesPerPixel + 1]; r = srcRow[x * bytesPerPixel + 2]; } else if (bytesPerPixel == 1) { // 如果源已经是灰度图 b = g = r = srcRow[x]; } else { // 其他格式默认处理 b = g = r = srcRow[x * bytesPerPixel]; } // 计算灰度值 (ITU-R BT.601 标准) // Gray = 0.299*R + 0.587*G + 0.114*B // 使用整数运算加速: (299*R + 587*G + 114*B) / 1000 int grayVal = (299 * r + 587 * g + 114 * b) / 1000; // 确保范围在 0-255 if (grayVal > 255) grayVal = 255; if (grayVal < 0) grayVal = 0; dstRow[x] = (byte)grayVal; } } } } finally { source.UnlockBits(srcData); grayBitmap.UnlockBits(dstData); } return grayBitmap; } } }