| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- 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();
- }
- /// <summary>
- /// 高效将任意 Bitmap 转换为 8bpp 灰度 Bitmap
- /// 不使用 Graphics 对象,避免索引格式异常
- /// </summary>
- 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;
- }
- }
- }
|