You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用OpenCvSharp去除票据照片四周的背景?

票据照片背景去除的OpenCvSharp解决方案

问题回顾

尝试用GrabCut去除票据背景时,因初始化矩形几乎覆盖全图,仅左侧背景被去除,增加迭代次数无改善;改用Canny+轮廓检测时,未筛选四边形轮廓导致选中错误区域,强光也干扰了边缘识别。

原始GrabCut代码问题

static Mat Grabcut(Mat imageMat) {
    Mat result = new(imageMat.Size(),MatType.CV_8UC1);
     OpenCvSharp.Rect rect = new(2,2,imageMat.Width - 2,imageMat.Height - 2);
    Cv2.GrabCut(imageMat,result,rect,new Mat(),new Mat(),3,GrabCutModes.InitWithRect);
    Mat newResult = (result & 1) * 255;
    Mat dokument = new();
    imageMat.CopyTo(dokument,newResult);
        return dokument;
  }

问题核心:初始化矩形几乎等于原图范围,GrabCut无法区分前景(票据)和背景,算法没有明确的前景参考区域。

原始轮廓检测代码问题

static Mat? Canny(Mat src) {
    Mat small = new();
    double scale = 200.0 /Math.Max(src.Rows,src.Cols);
    Cv2.Resize(src,small,new Size((int)(src.Cols * scale),(int)(src.Rows * scale)));
Cv2.CvtColor(small,small,ColorConversionCodes.BGR2GRAY);
    Cv2.GaussianBlur(small,small,new Size(5,5),0);
Cv2.Threshold(small,small,0,255,ThresholdTypes.Binary | ThresholdTypes.Otsu);
    Cv2.Canny(small,small,50,150);
Cv2.Dilate(small,small,Cv2.GetStructuringElement(MorphShapes.Rect,new Size(3,3)));
    Cv2.FindContours(small,out Point[][] contours,out _,
      RetrievalModes.External,ContourApproximationModes.ApproxSimple);

    contours = [.. contours.Where(cnt => Cv2.ContourArea(cnt) > 1).OrderByDescending(cnt => Cv2.ContourArea(cnt))];
    Point[]? doc_contour = null;
    foreach(Point[] cnt in contours.Take(10)) {
      double peri = Cv2.ArcLength(cnt,true);
      Point[] approx = Cv2.ApproxPolyDP(cnt,0.02 * peri,true);
      doc_contour = [.. approx.Select(p => new Point(p.X / (float)scale,p.Y / (float)scale))];
      break;
      }
    }

    OpenCvSharp.Rect biggestContourRect = Cv2.BoundingRect(doc_contour);
    src = new Mat(src,biggestContourRect);
    Cv2.DrawContours(mask_image,[doc_contour],-1,new Scalar(255),-1,
     offset: new Point(-biggestContourRect.X,-biggestContourRect.Y));
    Mat dokument = new();
    src.CopyTo(dokument,mask_image);
    return dokument;
  }

问题核心:未筛选四边形轮廓,直接取前10个轮廓的第一个,强光导致边缘断裂,错误轮廓被选中;缩放后精度损失也影响结果。

改进方案一:优化GrabCut分割

代码实现

static Mat ImprovedGrabcut(Mat imageMat)
{
    // 预处理:自适应阈值提取票据大致区域
    Mat gray = new();
    Cv2.CvtColor(imageMat, gray, ColorConversionCodes.BGR2GRAY);
    Cv2.GaussianBlur(gray, gray, new Size(5, 5), 0);
    Cv2.AdaptiveThreshold(gray, gray, 255, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.BinaryInv, 11, 2);
    
    // 提取最大轮廓作为票据初始范围
    Cv2.FindContours(gray, out Point[][] contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
    if (contours.Length == 0) return imageMat.Clone();
    
    var sortedContours = contours.OrderByDescending(c => Cv2.ContourArea(c)).ToArray();
    Rect ticketRect = Cv2.BoundingRect(sortedContours[0]);
    // 给矩形留余量,避免裁切票据内容
    ticketRect = new Rect(
        Math.Max(0, ticketRect.X - 10), 
        Math.Max(0, ticketRect.Y - 10), 
        Math.Min(imageMat.Width - ticketRect.X, ticketRect.Width + 20), 
        Math.Min(imageMat.Height - ticketRect.Y, ticketRect.Height + 20)
    );

    // GrabCut细化分割
    Mat mask = new(imageMat.Size(), MatType.CV_8UC1);
    Mat bgdModel = new(), fgdModel = new();
    Cv2.GrabCut(imageMat, mask, ticketRect, bgdModel, fgdModel, 5, GrabCutModes.InitWithRect);
    
    // 合并确定前景与可能前景,生成最终掩码
    Mat finalMask = (mask == GrabCutClasses.Fgd) | (mask == GrabCutClasses.PossibleFgd);
    finalMask.ConvertTo(finalMask, MatType.CV_8UC1, 255);
    
    // 提取票据
    Mat result = new();
    imageMat.CopyTo(result, finalMask);
    return result;
}

改进点

  1. 先通过自适应阈值+轮廓定位票据的大致矩形,给GrabCut明确的前景参考范围;
  2. 合并确定前景和可能前景,避免遗漏票据边缘;
  3. 给初始矩形留余量,防止裁切票据内容。

改进方案二:精准四边形轮廓检测

代码实现

static Mat? ImprovedContourBasedCut(Mat src)
{
    // 预处理:双边滤波抑制强光噪声,保留边缘
    Mat gray = new();
    Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
    Cv2.BilateralFilter(gray, gray, 9, 75, 75);
    
    // 边缘检测+形态学修复断裂边缘
    Cv2.Canny(gray, gray, 30, 100);
    Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 5));
    Cv2.Dilate(gray, gray, kernel);
    Cv2.MorphologyEx(gray, gray, MorphTypes.Close, kernel);
    
    // 提取轮廓并筛选四边形
    Cv2.FindContours(gray, out Point[][] contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
    if (contours.Length == 0) return null;

    Point[]? ticketContour = null;
    double maxArea = 0;
    foreach (var cnt in contours)
    {
        double area = Cv2.ContourArea(cnt);
        // 过滤过小轮廓,排除干扰
        if (area < src.Total() * 0.1) continue;
        
        double peri = Cv2.ArcLength(cnt, true);
        Point[] approx = Cv2.ApproxPolyDP(cnt, 0.02 * peri, true);
        // 筛选面积最大的四边形轮廓(票据为矩形)
        if (approx.Length == 4 && area > maxArea)
        {
            maxArea = area;
            ticketContour = approx;
        }
    }

    if (ticketContour == null) return null;

    // 生成掩码并提取票据
    Mat mask = Mat.Zeros(src.Size(), MatType.CV_8UC1);
    Cv2.DrawContours(mask, new[] { ticketContour }, -1, new Scalar(255), -1);
    
    // 膨胀掩码,修复边缘裁切
    Cv2.Dilate(mask, mask, Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3)));
    
    Mat result = new();
    src.CopyTo(result, mask);
    return result;
}

改进点

  1. 用双边滤波替代高斯Blur,在抑制强光噪声的同时保留边缘细节;
  2. 增加四边形轮廓筛选逻辑,确保选中的是票据的矩形轮廓;
  3. 过滤过小轮廓,排除无关干扰;
  4. 形态学闭合操作修复强光导致的边缘断裂。

参考效果示例

  • 原始票据照片:票据原始照片
  • 原GrabCut处理结果:GrabCut处理结果
  • 原轮廓检测结果:轮廓检测结果
  • 原错误轮廓处理结果:错误的轮廓处理结果

内容的提问来源于stack exchange,提问作者Andrus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:35:55