优化折痕检测:双级方差保护文字
单级方差核(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=纯黑
|
inkData[i] = (byte)(255 - val); // 墨迹强度:0=无墨, 255=纯黑
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算局部方差图(用灰度图,不是墨迹图)
|
// 计算两级局部方差:
|
||||||
// 文字区域:灰度变化大(笔画边缘),方差高
|
// 小核(7):检测文字笔画(高频),文字处方差高
|
||||||
// 折痕区域:灰度缓慢变化,方差低
|
// 大核(31):检测折痕渐变(低频),折痕处方差低
|
||||||
// 用简化方法:|gray - 局部均值| 的局部均值 ≈ 局部标准差
|
// 只有两级方差都低时才抑制,避免误伤折痕附近的文字
|
||||||
Mat grayMat = new Mat(h, w, MatType.CV_8U, grayData);
|
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();
|
Mat localMeanS = new Mat();
|
||||||
Cv2.Absdiff(grayMat, localMean, diff);
|
Cv2.Blur(grayMat, localMeanS, new OpenCvSharp.Size(7, 7));
|
||||||
localMean.Dispose();
|
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();
|
Mat localMeanL = new Mat();
|
||||||
Cv2.Blur(diff, localVar, new OpenCvSharp.Size(varKernSize, varKernSize));
|
Cv2.Blur(grayMat, localMeanL, new OpenCvSharp.Size(31, 31));
|
||||||
diff.Dispose();
|
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();
|
grayMat.Dispose();
|
||||||
|
|
||||||
byte[] varData = new byte[w * h];
|
byte[] varDataS = new byte[w * h];
|
||||||
Marshal.Copy(localVar.Data, varData, 0, varData.Length);
|
byte[] varDataL = new byte[w * h];
|
||||||
localVar.Dispose();
|
Marshal.Copy(localVarS.Data, varDataS, 0, varDataS.Length);
|
||||||
|
Marshal.Copy(localVarL.Data, varDataL, 0, varDataL.Length);
|
||||||
// 用局部方差来决定是否抑制
|
localVarS.Dispose();
|
||||||
// 高方差(> 阈值)= 文字,保留
|
localVarL.Dispose();
|
||||||
// 低方差 + 有墨迹 = 折痕/阴影,抑制
|
|
||||||
int varThresh = 8; // 方差阈值,低于此值认为是平坦区域
|
|
||||||
|
|
||||||
|
// 抑制逻辑:
|
||||||
|
// 小核方差 < 5 且 大核方差 < 12 → 确定是平坦渐变区域(折痕/阴影)
|
||||||
|
// 小核方差 >= 5 → 有文字笔画,不抑制(即使大核方差低)
|
||||||
for (int i = 0; i < resultData.Length; i++)
|
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)varDataS[i] / 5.0;
|
||||||
double suppressRatio = (double)varData[i] / varThresh;
|
|
||||||
int origVal = resultData[i];
|
int origVal = resultData[i];
|
||||||
resultData[i] = (byte)(origVal + (int)((255 - origVal) * (1.0 - suppressRatio)));
|
resultData[i] = (byte)(origVal + (int)((255 - origVal) * (1.0 - suppressRatio)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user