如何用OpenCvSharp颜色反差避免FBA面单贴标自动识别?

摘要:01 规避原理 1.抠图,根据色差或者根据固定包裹位置以及包裹尺寸抠出纸箱图片 2.色差,获取纸箱上所有背景色的灰度值 3.采图,采集大量视野相同,光源相同面单的色差灰度值,整理区间 4.取反,所有非面单灰度值区间的,都认为是纸箱背景色 0
01 规避原理 1.抠图,根据色差或者根据固定包裹位置以及包裹尺寸抠出纸箱图片 2.色差,获取纸箱上所有背景色的灰度值 3.采图,采集大量视野相同,光源相同面单的色差灰度值,整理区间 4.取反,所有非面单灰度值区间的,都认为是纸箱背景色 02 根据DPI计算1mm对应像素点。 获取吸取的颜色,计算灰度值 // 300 DPI 计算:每毫米像素数 = 300 / 25.4 ≈ 11.81 private const double PixelsPerMm = 300.0 / 25.4 ; private static int LabelSizePixels = Convert.ToInt32(Math.Ceiling((int)(95 * PixelsPerMm) / 1.7)); // 100mm × 100mm // 面单颜色列表(十六进制格式) private static readonly List<string> LabelColors = new List<string> { "#E2E2E0", "#DEDEDC", "#E0E0DE", "#CCCCCA", "#B2B2B0", "#C2C2C0","#FFFFFF","#FEFEFE","#FCFCFC" ,"#ADADAD" }; // 计算出的面单灰度范围 private static int MinLabelGray; private static int MaxLabelGray; // 加载图像 var originalImage = Cv2.ImRead(@"D:\Users\steph\Pictures\1分4\Image_20250913210539498.jpg", OpenCvSharp.ImreadModes.Grayscale); // 计算面单灰度范围 CalculateLabelGrayRange(); Console.WriteLine($"计算出的面单灰度范围: {MinLabelGray}-{MaxLabelGray}"); if (originalImage.Empty()) { Console.WriteLine("无法加载图像"); return; } 此处用第二种最简单方式演示,视野固定包裹位置,根据计算包裹的尺寸,扣除原箱外观 // 从右下角裁剪图像 private static OpenCvSharp.Mat CropImageFromBottomRight(OpenCvSharp.Mat image, double widthMm, double heightMm) { // 将毫米转换为像素 int widthPixels = (int)(widthMm * PixelsPerMm/1.7); int heightPixels = (int)(heightMm * PixelsPerMm/1.7); // 获取图像尺寸 int imgWidth = image.Cols; int imgHeight = image.Rows; // 计算裁剪区域的左上角坐标 int x = Math.Max(0, imgWidth - widthPixels); int y = Math.Max(0, imgHeight - heightPixels); // 确保裁剪区域不超出图像边界 widthPixels = Math.Min(widthPixels, imgWidth - x); heightPixels = Math.Min(heightPixels, imgHeight - y); // 检查裁剪区域是否有效 if (widthPixels <= 0 || heightPixels <= 0) { Console.WriteLine($"无效的裁剪区域: x={x}, y={y}, width={widthPixels}, height={heightPixels}"); return image.Clone(); // 返回原始图像的副本 } Console.WriteLine($"裁剪区域: x={x}, y={y}, width={widthPixels}, height={heightPixels}"); // 裁剪图像 return new OpenCvSharp.Mat(image, new Rect(x, y, widthPixels, heightPixels)); } 根据裁切后的原箱外观,以及灰度值区间,定位原厂面单位置 // 检测所有原厂面单位置 public static List<LabelPosition> DetectOriginalLabelPositions(OpenCvSharp.Mat image) { var labelPositions = new List<LabelPosition>(); // 二值化图像以分离面单区域 var binary = new OpenCvSharp.Mat(); Cv2.Threshold(image, binary, MinLabelGray, 255, ThresholdTypes.Binary); // 形态学操作去除噪声 var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(5, 5)); Cv2.MorphologyEx(binary, binary, MorphTypes.Open, kernel); // 查找轮廓 Cv2.FindContours(binary, out var contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple); // 过滤轮廓(按面积) var filteredContours = contours.Where(c => Cv2.ContourArea(c) > 1000).ToList(); // 处理每个轮廓 foreach (var contour in filteredContours) { // 获取轮廓的边界矩形 var rect = Cv2.BoundingRect(contour); // 转换为网格坐标 string gridCoordinate = ConvertToGridCoordinate(rect, image.Rows, image.Cols); // 计算实际尺寸(毫米) double widthMm = rect.Width / PixelsPerMm; double heightMm = rect.Height / PixelsPerMm; // 添加到结果列表 labelPositions.Add(new LabelPosition { Rect = rect, GridCoordinate = gridCoordinate, WidthMm = widthMm, HeightMm = heightMm }); } return labelPositions; } 检查可贴标签位置是否与原厂标签有交集,检查可贴区域是否超过原箱尺寸,此处我们以新帖面单大小100mm*100mm为例。没有可贴标签位置默认选择1-1位置贴标 // 查找可贴标签的位置(与原厂标签无交集的网格) public static List<string> FindAvailableLabelPositions(OpenCvSharp.Mat image, List<LabelPosition> labelPositions) { var availablePositions = new List<string>(); // 获取图像尺寸 int rows = image.Rows; int cols = image.Cols; // 计算网格行列数 int gridCols = (int)Math.Ceiling((double)cols / LabelSizePixels); int gridRows = (int)Math.Ceiling((double)rows / LabelSizePixels); // 检查每个网格位置是否可用 for (int row = 1; row <= gridRows; row++) { for (int col = 1; col <= gridCols; col++) { // 计算当前网格的像素坐标 int x1 = Math.Max(0, cols - col * LabelSizePixels); int y1 = Math.Max(0, rows - row * LabelSizePixels); int x2 = Math.Min(cols, x1 + LabelSizePixels); int y2 = Math.Min(rows, y1 + LabelSizePixels); // 确保区域足够大 if (x2 - x1 < LabelSizePixels / 2 || y2 - y1 < LabelSizePixels / 2) continue; // 创建网格矩形 Rect gridRect = new Rect(x1, y1, x2 - x1, y2 - y1); // 检查网格是否与任何原厂标签相交 bool intersects = false; foreach (var labelPos in labelPositions) { if (gridRect.IntersectsWith(labelPos.Rect)) { intersects = true; break; } } // 如果不相交且区域颜色均匀,则添加到可用位置 if (!intersects && IsAreaUniform(image, x1, y1, x2, y2)) { availablePositions.Add($"{row}-{col}"); } } } return availablePositions; }    可视化结果,以绿色网格铺满原箱,以红色区域标定原厂标签位置,以蓝色网格标定可贴标签位置,返回可视化结果 // 可视化结果 public static OpenCvSharp.Mat VisualizeResults(OpenCvSharp.Mat image, List<LabelPosition> labelPositions, List<string> availablePositions) { var colorImage = new OpenCvSharp.Mat(); Cv2.CvtColor(image, colorImage, ColorConversionCodes.GRAY2BGR); int rows = image.Rows; int cols = image.Cols; // 绘制网格 for (int x = 0; x < cols; x += LabelSizePixels) { Cv2.Line(colorImage, new OpenCvSharp.Point(x, 0), new OpenCvSharp.Point(x, rows), Scalar.Green, 2); } for (int y = 0; y < rows; y += LabelSizePixels) { Cv2.Line(colorImage, new OpenCvSharp.Point(0, y), new OpenCvSharp.Point(cols, y), Scalar.Green, 2); } // 标记所有原厂面单位置(红色) foreach (var labelPos in labelPositions) { Cv2.Rectangle(colorImage, labelPos.Rect.TopLeft, labelPos.Rect.BottomRight, Scalar.Red, 2); // 添加标签文本 Cv2.PutText(colorImage, labelPos.GridCoordinate, new OpenCvSharp.Point(labelPos.Rect.X, labelPos.Rect.Y - 5), HersheyFonts.HersheySimplex, 0.5, Scalar.Red, 2); } // 标记可贴标签位置(蓝色) foreach (var pos in availablePositions) { var parts = pos.Split('-'); if (parts.Length != 2) continue; int row = int.Parse(parts[0]); int col = int.Parse(parts[1]); int x = Math.Max(0, cols - col * LabelSizePixels); int y = Math.Max(0, rows - row * LabelSizePixels); int width = Math.Min(LabelSizePixels, cols - x); int height = Math.Min(LabelSizePixels, rows - y); if (width > 0 && height > 0) { Cv2.Rectangle(colorImage, new OpenCvSharp.Point(x, y), new OpenCvSharp.Point(x + width, y + height), Scalar.Blue, 2); // 添加标签文本 Cv2.PutText(colorImage, pos, new OpenCvSharp.Point(x + 5, y + 15), HersheyFonts.HersheySimplex, 0.4, Scalar.Blue, 1); } } return colorImage; }    看看效果1,运行看看效果.(如下图纸箱长400,高190)白色原厂面单占据了前面6个网格,最后两个网格超过原箱尺寸无效,默认返回第一个网格(视情况自定义) 保持期待 奔赴山海KEEP LOOKING FORWARD TO GOING 效果2.原厂标签占据第一个和中间4个网格,可贴标签区域蓝色网格标识,并返回可贴坐标