如何使用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; }
改进点
- 先通过自适应阈值+轮廓定位票据的大致矩形,给GrabCut明确的前景参考范围;
- 合并确定前景和可能前景,避免遗漏票据边缘;
- 给初始矩形留余量,防止裁切票据内容。
改进方案二:精准四边形轮廓检测
代码实现
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; }
改进点
- 用双边滤波替代高斯Blur,在抑制强光噪声的同时保留边缘细节;
- 增加四边形轮廓筛选逻辑,确保选中的是票据的矩形轮廓;
- 过滤过小轮廓,排除无关干扰;
- 形态学闭合操作修复强光导致的边缘断裂。
参考效果示例
- 原始票据照片:

- 原GrabCut处理结果:

- 原轮廓检测结果:

- 原错误轮廓处理结果:

内容的提问来源于stack exchange,提问作者Andrus
相关产品推荐
相关产品推荐

