重写折痕/阴影检测:用局部方差区分文字和折痕
放弃基于背景亮度阈值的方案(会误伤折痕附近的文字)。 改用频域特征区分: - 文字 = 高频信号,笔画边缘锐利,局部灰度方差高 - 折痕/阴影 = 低频信号,缓慢渐变,局部灰度方差低 计算灰度图的局部方差(|gray-localMean|的局部均值), 低方差+有墨迹的像素判定为折痕/阴影,按方差比例推向白色。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -391,15 +391,18 @@ public static class DocumentScanner
|
|||||||
}
|
}
|
||||||
int paperBright = (int)(centerSum / Math.Max(centerCount, 1));
|
int paperBright = (int)(centerSum / Math.Max(centerCount, 1));
|
||||||
|
|
||||||
// 墨迹映射(带阴影/折痕保护)
|
// 墨迹映射(带折痕/阴影抑制)
|
||||||
// 文字特征:背景亮(接近paperBright),gray比背景低 → ink大
|
//
|
||||||
// 折痕/阴影特征:gray本身就暗,背景估计也被拉低 → ink也可能大
|
// 核心区分逻辑:文字 vs 折痕/阴影
|
||||||
// 区分方法:
|
// 文字 = 高频信号,笔画边缘锐利,局部灰度变化剧烈
|
||||||
// 1. 看背景估计值是否接近正常纸面亮度(抑制阴影区域的墨迹)
|
// 折痕/阴影 = 低频信号,缓慢渐变,局部灰度变化平缓
|
||||||
// 2. 看原始灰度值本身是否偏暗(折痕处gray很低)
|
//
|
||||||
double bgThreshHigh = 0.92; // 背景低于纸面亮度92%就开始轻微抑制
|
// 方法:先正常生成墨迹图,然后用"墨迹图的局部方差"来区分
|
||||||
double bgThreshLow = 0.60; // 低于60%时完全抑制为白色
|
// 高方差区域 = 文字(保留)
|
||||||
|
// 低方差但偏暗区域 = 折痕/阴影(推向白色)
|
||||||
|
|
||||||
|
// 先生成原始墨迹图(不做任何抑制)
|
||||||
|
byte[] inkData = new byte[w * h];
|
||||||
for (int i = 0; i < grayData.Length; i++)
|
for (int i = 0; i < grayData.Length; i++)
|
||||||
{
|
{
|
||||||
int ink = bgData[i] - grayData[i];
|
int ink = bgData[i] - grayData[i];
|
||||||
@@ -410,40 +413,52 @@ public static class DocumentScanner
|
|||||||
|
|
||||||
double darkness = Math.Pow(inkNorm, 0.45);
|
double darkness = Math.Pow(inkNorm, 0.45);
|
||||||
|
|
||||||
// 阴影/折痕抑制
|
|
||||||
double bgRatio = (double)bgData[i] / Math.Max(paperBright, 1);
|
|
||||||
|
|
||||||
if (bgRatio < bgThreshHigh)
|
|
||||||
{
|
|
||||||
if (bgRatio <= bgThreshLow)
|
|
||||||
{
|
|
||||||
// 极暗区域,完全抑制
|
|
||||||
darkness = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 平滑衰减:从bgThreshHigh到bgThreshLow,darkness从原值→0
|
|
||||||
double t = (bgRatio - bgThreshLow) / (bgThreshHigh - bgThreshLow);
|
|
||||||
// 用三次曲线平滑过渡
|
|
||||||
t = t * t * (3.0 - 2.0 * t);
|
|
||||||
darkness = darkness * t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 额外保护:原始灰度本身很暗且不是文字(ink占gray比例小)
|
|
||||||
// 折痕:gray很低(比如100),ink可能只有20-30,ink/gray比例小
|
|
||||||
// 文字:gray中等(比如150),ink可能50-80,ink/gray比例大
|
|
||||||
double grayRatio = (double)grayData[i] / Math.Max(paperBright, 1);
|
|
||||||
if (grayRatio < 0.5 && ink < bgData[i] * 0.3)
|
|
||||||
{
|
|
||||||
// 原始灰度很暗,但墨迹占比小 → 不是文字,是阴影/折痕
|
|
||||||
darkness = darkness * 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int val = (int)(255.0 * (1.0 - darkness));
|
int val = (int)(255.0 * (1.0 - darkness));
|
||||||
if (val < 0) val = 0;
|
if (val < 0) val = 0;
|
||||||
if (val > 255) val = 255;
|
if (val > 255) val = 255;
|
||||||
resultData[i] = (byte)val;
|
resultData[i] = (byte)val;
|
||||||
|
inkData[i] = (byte)(255 - val); // 墨迹强度:0=无墨, 255=纯黑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算局部方差图(用灰度图,不是墨迹图)
|
||||||
|
// 文字区域:灰度变化大(笔画边缘),方差高
|
||||||
|
// 折痕区域:灰度缓慢变化,方差低
|
||||||
|
// 用简化方法:|gray - 局部均值| 的局部均值 ≈ 局部标准差
|
||||||
|
Mat grayMat = new Mat(h, w, MatType.CV_8U, grayData);
|
||||||
|
Mat localMean = new Mat();
|
||||||
|
int varKernSize = 15;
|
||||||
|
Cv2.Blur(grayMat, localMean, new OpenCvSharp.Size(varKernSize, varKernSize));
|
||||||
|
|
||||||
|
// |gray - localMean|
|
||||||
|
Mat diff = new Mat();
|
||||||
|
Cv2.Absdiff(grayMat, localMean, diff);
|
||||||
|
localMean.Dispose();
|
||||||
|
|
||||||
|
// 对 diff 再做一次均值模糊,得到局部方差的近似
|
||||||
|
Mat localVar = new Mat();
|
||||||
|
Cv2.Blur(diff, localVar, new OpenCvSharp.Size(varKernSize, varKernSize));
|
||||||
|
diff.Dispose();
|
||||||
|
grayMat.Dispose();
|
||||||
|
|
||||||
|
byte[] varData = new byte[w * h];
|
||||||
|
Marshal.Copy(localVar.Data, varData, 0, varData.Length);
|
||||||
|
localVar.Dispose();
|
||||||
|
|
||||||
|
// 用局部方差来决定是否抑制
|
||||||
|
// 高方差(> 阈值)= 文字,保留
|
||||||
|
// 低方差 + 有墨迹 = 折痕/阴影,抑制
|
||||||
|
int varThresh = 8; // 方差阈值,低于此值认为是平坦区域
|
||||||
|
|
||||||
|
for (int i = 0; i < resultData.Length; i++)
|
||||||
|
{
|
||||||
|
if (inkData[i] > 10 && varData[i] < varThresh)
|
||||||
|
{
|
||||||
|
// 有墨迹但局部方差低 → 折痕/阴影,推向白色
|
||||||
|
// 抑制程度和方差成正比:方差越低抑制越强
|
||||||
|
double suppressRatio = (double)varData[i] / varThresh;
|
||||||
|
int origVal = resultData[i];
|
||||||
|
resultData[i] = (byte)(origVal + (int)((255 - origVal) * (1.0 - suppressRatio)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 边缘阴影修复:从边缘向内扫描连续暗像素 ---
|
// --- 边缘阴影修复:从边缘向内扫描连续暗像素 ---
|
||||||
|
|||||||
Reference in New Issue
Block a user