优化折痕检测:双级方差保护文字
单级方差核(15)太大,折痕附近的文字方差被稀释导致误伤。 改为双级方差: - 小核(7):精确检测文字笔画高频特征,>=5即保护 - 大核(31):检测折痕低频渐变,<12才触发抑制 两级方差都低时才判定为折痕,大幅减少文字误伤。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -420,42 +420,49 @@ public static class DocumentScanner
|
||||
inkData[i] = (byte)(255 - val); // 墨迹强度:0=无墨, 255=纯黑
|
||||
}
|
||||
|
||||
// 计算局部方差图(用灰度图,不是墨迹图)
|
||||
// 文字区域:灰度变化大(笔画边缘),方差高
|
||||
// 折痕区域:灰度缓慢变化,方差低
|
||||
// 用简化方法:|gray - 局部均值| 的局部均值 ≈ 局部标准差
|
||||
// 计算两级局部方差:
|
||||
// 小核(7):检测文字笔画(高频),文字处方差高
|
||||
// 大核(31):检测折痕渐变(低频),折痕处方差低
|
||||
// 只有两级方差都低时才抑制,避免误伤折痕附近的文字
|
||||
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();
|
||||
// 小核方差(保护文字)
|
||||
Mat localMeanS = new Mat();
|
||||
Cv2.Blur(grayMat, localMeanS, new OpenCvSharp.Size(7, 7));
|
||||
Mat diffS = new Mat();
|
||||
Cv2.Absdiff(grayMat, localMeanS, diffS);
|
||||
localMeanS.Dispose();
|
||||
Mat localVarS = new Mat();
|
||||
Cv2.Blur(diffS, localVarS, new OpenCvSharp.Size(7, 7));
|
||||
diffS.Dispose();
|
||||
|
||||
// 对 diff 再做一次均值模糊,得到局部方差的近似
|
||||
Mat localVar = new Mat();
|
||||
Cv2.Blur(diff, localVar, new OpenCvSharp.Size(varKernSize, varKernSize));
|
||||
diff.Dispose();
|
||||
// 大核方差(检测折痕)
|
||||
Mat localMeanL = new Mat();
|
||||
Cv2.Blur(grayMat, localMeanL, new OpenCvSharp.Size(31, 31));
|
||||
Mat diffL = new Mat();
|
||||
Cv2.Absdiff(grayMat, localMeanL, diffL);
|
||||
localMeanL.Dispose();
|
||||
Mat localVarL = new Mat();
|
||||
Cv2.Blur(diffL, localVarL, new OpenCvSharp.Size(31, 31));
|
||||
diffL.Dispose();
|
||||
grayMat.Dispose();
|
||||
|
||||
byte[] varData = new byte[w * h];
|
||||
Marshal.Copy(localVar.Data, varData, 0, varData.Length);
|
||||
localVar.Dispose();
|
||||
|
||||
// 用局部方差来决定是否抑制
|
||||
// 高方差(> 阈值)= 文字,保留
|
||||
// 低方差 + 有墨迹 = 折痕/阴影,抑制
|
||||
int varThresh = 8; // 方差阈值,低于此值认为是平坦区域
|
||||
byte[] varDataS = new byte[w * h];
|
||||
byte[] varDataL = new byte[w * h];
|
||||
Marshal.Copy(localVarS.Data, varDataS, 0, varDataS.Length);
|
||||
Marshal.Copy(localVarL.Data, varDataL, 0, varDataL.Length);
|
||||
localVarS.Dispose();
|
||||
localVarL.Dispose();
|
||||
|
||||
// 抑制逻辑:
|
||||
// 小核方差 < 5 且 大核方差 < 12 → 确定是平坦渐变区域(折痕/阴影)
|
||||
// 小核方差 >= 5 → 有文字笔画,不抑制(即使大核方差低)
|
||||
for (int i = 0; i < resultData.Length; i++)
|
||||
{
|
||||
if (inkData[i] > 10 && varData[i] < varThresh)
|
||||
if (inkData[i] > 10 && varDataS[i] < 5 && varDataL[i] < 12)
|
||||
{
|
||||
// 有墨迹但局部方差低 → 折痕/阴影,推向白色
|
||||
// 抑制程度和方差成正比:方差越低抑制越强
|
||||
double suppressRatio = (double)varData[i] / varThresh;
|
||||
// 两级方差都低 → 折痕/阴影
|
||||
double suppressRatio = (double)varDataS[i] / 5.0;
|
||||
int origVal = resultData[i];
|
||||
resultData[i] = (byte)(origVal + (int)((255 - origVal) * (1.0 - suppressRatio)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user