206 lines
6.7 KiB
C#
206 lines
6.7 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using Tesseract;
|
||
|
||
public static class OcrFinalHelper
|
||
{
|
||
private static readonly string _tessDataPath =
|
||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tessdata");
|
||
|
||
/// <summary>
|
||
/// 针对白底黑字/截图优化的中文识别(强化浅灰色文字处理)
|
||
/// </summary>
|
||
public static string RecognizeScreenshot(string imagePath)
|
||
{
|
||
if (!File.Exists(imagePath))
|
||
return "文件不存在";
|
||
|
||
try
|
||
{
|
||
using var original = new Bitmap(imagePath);
|
||
using var scaled = ScaleImage(original, original.Width * 2, original.Height * 2);
|
||
|
||
// 预处理:灰度 → 对比度拉伸 → OTSU 二值化
|
||
using var gray = ToGrayscale(scaled);
|
||
using var stretched = AutoContrast(gray);
|
||
using var binary = BinarizeOtsu(stretched);
|
||
|
||
using var engine = new TesseractEngine(
|
||
_tessDataPath,
|
||
"chi_sim+eng",
|
||
EngineMode.LstmOnly);
|
||
|
||
engine.SetVariable("preserve_interword_spaces", "1");
|
||
engine.SetVariable("tessedit_pageseg_mode", "6");
|
||
engine.SetVariable("textord_min_linesize", "2.5");
|
||
|
||
using var pix = BitmapToPix(binary);
|
||
using var result = engine.Process(pix);
|
||
|
||
return result.GetText()?.Trim() ?? "";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return $"识别失败:{ex.Message}";
|
||
}
|
||
}
|
||
|
||
#region 预处理
|
||
|
||
private static Bitmap ScaleImage(Bitmap original, int newWidth, int newHeight)
|
||
{
|
||
var scaled = new Bitmap(newWidth, newHeight);
|
||
using (var g = Graphics.FromImage(scaled))
|
||
{
|
||
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||
g.DrawImage(original, 0, 0, newWidth, newHeight);
|
||
}
|
||
return scaled;
|
||
}
|
||
|
||
private static Bitmap ToGrayscale(Bitmap bmp)
|
||
{
|
||
var gray = new Bitmap(bmp.Width, bmp.Height);
|
||
using (var g = Graphics.FromImage(gray))
|
||
{
|
||
var cm = new ColorMatrix(new float[][]
|
||
{
|
||
new float[]{0.299f,0.299f,0.299f,0,0},
|
||
new float[]{0.587f,0.587f,0.587f,0,0},
|
||
new float[]{0.114f,0.114f,0.114f,0,0},
|
||
new float[]{0,0,0,1,0},
|
||
new float[]{0,0,0,0,1}
|
||
});
|
||
using (var ia = new ImageAttributes())
|
||
{
|
||
ia.SetColorMatrix(cm);
|
||
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height),
|
||
0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, ia);
|
||
}
|
||
}
|
||
return gray;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 强化版对比度拉伸(专门优化:浅灰文字 + 低对比度文字)
|
||
/// 不破坏图像、不炸图、Tesseract 可正常识别
|
||
/// </summary>
|
||
private static Bitmap AutoContrast(Bitmap gray)
|
||
{
|
||
int min = 255, max = 0;
|
||
int width = gray.Width;
|
||
int height = gray.Height;
|
||
|
||
// 第一步:统计灰度(忽略纯白 255,避免背景干扰文字)
|
||
for (int y = 0; y < height; y++)
|
||
{
|
||
for (int x = 0; x < width; x++)
|
||
{
|
||
int val = gray.GetPixel(x, y).R;
|
||
if (val == 255) continue; // 跳过纯白背景
|
||
if (val < min) min = val;
|
||
if (val > max) max = val;
|
||
}
|
||
}
|
||
|
||
// 如果无有效内容,返回原图
|
||
if (min >= max || max - min < 10)
|
||
return (Bitmap)gray.Clone();
|
||
|
||
// 第二步:强化拉伸(专门强化 180~240 浅灰色文字)
|
||
Bitmap result = new Bitmap(width, height);
|
||
for (int y = 0; y < height; y++)
|
||
{
|
||
for (int x = 0; x < width; x++)
|
||
{
|
||
int val = gray.GetPixel(x, y).R;
|
||
|
||
// 核心:浅灰色文字强制增强
|
||
if (val >= 180 && val <= 245)
|
||
{
|
||
val = val - 30; // 把浅灰 → 变深灰
|
||
}
|
||
|
||
// 标准对比度拉伸(强化文字边缘)
|
||
int newVal = (int)(((val - min) / (double)(max - min)) * 220 + 20);
|
||
newVal = Math.Clamp(newVal, 0, 255);
|
||
|
||
result.SetPixel(x, y, Color.FromArgb(newVal, newVal, newVal));
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
/// <summary>
|
||
/// OTSU 大津法二值化,自动寻找最佳阈值,适应各种灰度文字
|
||
/// </summary>
|
||
private static Bitmap BinarizeOtsu(Bitmap gray)
|
||
{
|
||
int[] histogram = new int[256];
|
||
int totalPixels = gray.Width * gray.Height;
|
||
|
||
// 统计直方图
|
||
for (int y = 0; y < gray.Height; y++)
|
||
{
|
||
for (int x = 0; x < gray.Width; x++)
|
||
{
|
||
int val = gray.GetPixel(x, y).R;
|
||
histogram[val]++;
|
||
}
|
||
}
|
||
|
||
// 计算 OTSU 阈值
|
||
double sum = 0;
|
||
for (int i = 0; i < 256; i++)
|
||
sum += i * histogram[i];
|
||
|
||
double sumB = 0;
|
||
int wB = 0, wF = 0;
|
||
double maxVariance = 0;
|
||
int threshold = 128; // 默认
|
||
|
||
for (int t = 0; t < 256; t++)
|
||
{
|
||
wB += histogram[t]; // 背景像素数
|
||
if (wB == 0) continue;
|
||
wF = totalPixels - wB; // 前景像素数
|
||
if (wF == 0) break;
|
||
|
||
sumB += t * histogram[t];
|
||
double mB = sumB / wB; // 背景平均灰度
|
||
double mF = (sum - sumB) / wF; // 前景平均灰度
|
||
|
||
double variance = wB * (double)wF * (mB - mF) * (mB - mF);
|
||
if (variance > maxVariance)
|
||
{
|
||
maxVariance = variance;
|
||
threshold = t;
|
||
}
|
||
}
|
||
|
||
// 白底黑字:大于阈值 -> 白,否则 -> 黑
|
||
var bin = new Bitmap(gray.Width, gray.Height);
|
||
for (int y = 0; y < gray.Height; y++)
|
||
{
|
||
for (int x = 0; x < gray.Width; x++)
|
||
{
|
||
int val = gray.GetPixel(x, y).R;
|
||
bin.SetPixel(x, y, val > threshold ? Color.White : Color.Black);
|
||
}
|
||
}
|
||
return bin;
|
||
}
|
||
|
||
private static Pix BitmapToPix(Bitmap bmp)
|
||
{
|
||
using var ms = new MemoryStream();
|
||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
|
||
ms.Position = 0;
|
||
return Pix.LoadFromMemory(ms.ToArray());
|
||
}
|
||
|
||
#endregion
|
||
} |