diff --git a/CamScanner.cs b/CamScanner.cs index 356c5f3..447d216 100644 --- a/CamScanner.cs +++ b/CamScanner.cs @@ -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))); }