替换边缘扫描为形态学大面积暗区域检测
边缘扫描只能处理从边缘开始的连续暗像素,无法处理折角三角形 阴影等不规则形状。改用形态学方法: 1. 灰度二值化找所有暗像素(<纸面亮度65%) 2. 大核腐蚀去掉文字笔画(小面积暗区域) 3. 膨胀回来得到大面积阴影掩码 4. 掩码内低方差像素推白,高方差像素(文字)保留 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
100
CamScanner.cs
100
CamScanner.cs
@@ -536,61 +536,59 @@ public static class DocumentScanner
|
||||
}
|
||||
}
|
||||
|
||||
// --- 边缘阴影修复:从边缘向内扫描连续暗像素 ---
|
||||
byte[] shadowFlag = new byte[w * h];
|
||||
int darkThresh = (int)(paperBright * 0.55);
|
||||
int maxScanDepth = Math.Max(w, h) / 8;
|
||||
// --- 大面积阴影检测(形态学方法)---
|
||||
// 思路:
|
||||
// 1. 对原始灰度二值化,找出所有暗像素
|
||||
// 2. 用大核腐蚀,去掉小面积暗区域(文字笔画)
|
||||
// 3. 再膨胀回来,剩下的就是大面积暗区域(阴影/折痕)
|
||||
// 4. 在这些区域内,只保留高方差像素(文字),其余推白
|
||||
Mat grayForShadow = new Mat(h, w, MatType.CV_8U, grayData);
|
||||
|
||||
// 上边缘
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
for (int y = 0; y < Math.Min(maxScanDepth, h); y++)
|
||||
{
|
||||
if (grayData[y * w + x] < darkThresh)
|
||||
shadowFlag[y * w + x] = 1;
|
||||
else
|
||||
break; // 遇到亮像素就停止
|
||||
}
|
||||
}
|
||||
// 下边缘
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
for (int y = h - 1; y >= Math.Max(0, h - maxScanDepth); y--)
|
||||
{
|
||||
if (grayData[y * w + x] < darkThresh)
|
||||
shadowFlag[y * w + x] = 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 左边缘
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < Math.Min(maxScanDepth, w); x++)
|
||||
{
|
||||
if (grayData[y * w + x] < darkThresh)
|
||||
shadowFlag[y * w + x] = 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 右边缘
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = w - 1; x >= Math.Max(0, w - maxScanDepth); x--)
|
||||
{
|
||||
if (grayData[y * w + x] < darkThresh)
|
||||
shadowFlag[y * w + x] = 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 二值化:暗像素=255, 亮像素=0
|
||||
int shadowBinThresh = (int)(paperBright * 0.65);
|
||||
Mat darkBin = new Mat();
|
||||
Cv2.Threshold(grayForShadow, darkBin, shadowBinThresh, 255, ThresholdTypes.BinaryInv);
|
||||
grayForShadow.Dispose();
|
||||
|
||||
// 把阴影区域的结果设为白色
|
||||
// 腐蚀:去掉小面积暗区域(文字笔画宽度一般<10px)
|
||||
// 腐蚀核要大于文字笔画宽度
|
||||
int erodeSize = Math.Max(w, h) / 80;
|
||||
if (erodeSize < 15) erodeSize = 15;
|
||||
if (erodeSize % 2 == 0) erodeSize++;
|
||||
Mat erodeK = Cv2.GetStructuringElement(MorphShapes.Ellipse,
|
||||
new OpenCvSharp.Size(erodeSize, erodeSize));
|
||||
Mat eroded = new Mat();
|
||||
Cv2.Erode(darkBin, eroded, erodeK);
|
||||
darkBin.Dispose();
|
||||
|
||||
// 膨胀回来(比腐蚀核稍大,确保覆盖阴影边缘)
|
||||
int dilateSize = erodeSize + 10;
|
||||
if (dilateSize % 2 == 0) dilateSize++;
|
||||
Mat dilateK = Cv2.GetStructuringElement(MorphShapes.Ellipse,
|
||||
new OpenCvSharp.Size(dilateSize, dilateSize));
|
||||
Mat shadowMask = new Mat();
|
||||
Cv2.Dilate(eroded, shadowMask, dilateK);
|
||||
eroded.Dispose();
|
||||
erodeK.Dispose();
|
||||
dilateK.Dispose();
|
||||
|
||||
byte[] shadowMaskData = new byte[w * h];
|
||||
Marshal.Copy(shadowMask.Data, shadowMaskData, 0, shadowMaskData.Length);
|
||||
shadowMask.Dispose();
|
||||
|
||||
// 在阴影区域内:高方差像素保留(文字),低方差推白
|
||||
for (int i = 0; i < resultData.Length; i++)
|
||||
{
|
||||
if (shadowFlag[i] == 1)
|
||||
resultData[i] = 255;
|
||||
if (shadowMaskData[i] > 128)
|
||||
{
|
||||
// 在大面积暗区域内
|
||||
if (varDataS[i] < 12)
|
||||
{
|
||||
// 低方差 → 阴影本身 → 白色
|
||||
resultData[i] = 255;
|
||||
}
|
||||
// 高方差 → 文字 → 保留
|
||||
}
|
||||
}
|
||||
|
||||
Mat enhanced = new Mat(h, w, MatType.CV_8U);
|
||||
|
||||
Reference in New Issue
Block a user