OpenCvSharp基于颜色反差规避FBA面单贴标2

第一版的劣势

原理同上一边博客记录,在基础上改造的更加细致些,100*100的贴标区域,很容易让原本就不大的FBA纸箱,留下更多空白区域,并且空白区域和原厂标签空隙不足贴下一张新的标签,导致东一张西一张,虽然能够满足规避原厂标签的初衷,但是如果客户需要贴多张标签,就会捉襟见肘

解决办法-提升精度

既然100*100的匹配,容易造成可贴标区域浪费,那么何不把精度提升到100倍呢?

把原来100*100的网格,细分为由10个10*10的网格组成,每次匹配可贴标区域,偏移一个10*10网格的网格,然后根据占用的这个10*10的网格,按照偏移的方向,向左向上分别获取相邻的10个网格,那不就组成了一个100*100的可贴标区域了吗?(当然如果需要可贴标区域利用率更高,可以缩小100倍,比如1*1的网格,获取相邻横向和纵向100个这样1*1网格,也可以组成100*100的可贴标区域,本文已经把网格大小提取出来,可用作扩展配置,本文抛砖引玉,有更好的想法可以一起交流完善)。

无图言屌,用一张粗糙的动态图,来说明第二版本提升精度的慢动作(最下面红色区域是硬件的物理钣金,已经根据上篇博客当作原厂标签标记了,所以标记为干扰区域)

先看最终效果

避免文字无趣,先看下实际的定位效果(红色标记原厂标签,黄色标记可贴区域坐标)

下面是模拟效果(红色区域是人工制造的FBA原厂标签),旋转纸箱不同方向的贴标效果

可以看到,无论纸箱如何旋转,新帖的标签,都可以完全避开.

废话少说,上源码

大部分源码在上个博文已经分享出来,以下附上改动点。(文章最后会附上不同纸箱的定位效果)

复制代码
   // 裁剪图像(从右下角开始保留指定尺寸)
   var croppedImage = AvoidFactoryLabelSDK.CropImageFromBottomRight(originalImage, boxWidthMm, boxHeightMm);

   if (croppedImage.Empty())
   {
       Console.WriteLine("裁剪后的图像为空");
       return;
   }

   // 检测所有原厂面单位置
   var labelPositions = AvoidFactoryLabelSDK.DetectOriginalLabelPositions(croppedImage);
   Console.WriteLine($"检测到 {labelPositions.Count} 个原厂面单:");
   foreach (var pos in labelPositions)
   {
       Console.WriteLine($"位置: {pos.GridCoordinate}, 尺寸: {pos.WidthMm:F1}mm × {pos.HeightMm:F1}mm");
   }

   // 查找可贴标签的位置
   string availablePosition = AvoidFactoryLabelSDK.FindAvailableLabelPosition(croppedImage, labelPositions);
   Console.WriteLine($"可贴标签的位置: {availablePosition}");

   // 可视化结果(可选)
   Bitmap resultbm = AvoidFactoryLabelSDK.VisualizeResults(croppedImage, labelPositions, availablePosition);
   lblStatus.Text = availablePosition;
   pictureBox1.Image = resultbm;
复制代码
 /// <summary>
 /// 原厂标签规避算法
 /// </summary>
 /// <param name="bmSource">原箱标签</param>
 /// <param name="x">返回坐标X</param>
 /// <param name="y">返回坐标y</param>
 /// <param name="message">异常信息</param>
 /// <param name="dpi">电脑DPI</param>
 /// <returns></returns>
 public static Bitmap AvoidFactoryLabelAlgorithm(string imagepath, double boxWidthMm, double boxHeightMm, out int x, out int y, out string message, double sizeF = 1.7, double dpi = 300)
 {
     message = string.Empty;
     x = y = 1;

     // 加载图像 
     var originalImage = Cv2.ImRead(imagepath, OpenCvSharp.ImreadModes.Grayscale);
     //计算每毫米像素数 (基于300 DPI)
     double PixelsPerMm = dpi / 25.4; // 约等于 11.811 
                                      // 计算面单灰度范围
     CalculateLabelGrayRange();
     ShellLine.WriteLine($"计算出的面单灰度范围: {MinLabelGray}-{MaxLabelGray}");

     // 裁剪图像(从右下角开始保留指定尺寸)
     var croppedImage = CropImageFromBottomRight(originalImage, boxWidthMm, boxHeightMm);

     if (croppedImage.Empty())
     {
         ShellLine.WriteLine("裁剪后的图像为空");
         return croppedImage.ToBitmap();
     }
      
     // 检测所有原厂面单位置
     var labelPositions = DetectOriginalLabelPositions(croppedImage);
     ShellLine.WriteLine($"检测到 {labelPositions.Count} 个原厂面单:");
     foreach (var pos in labelPositions)
     {
         ShellLine.WriteLine($"位置: {pos.GridCoordinate}, 尺寸: {pos.WidthMm:F1}mm × {pos.HeightMm:F1}mm");
     } 
     // 查找可贴标签的位置
     string availablePosition = FindAvailableLabelPosition(croppedImage, labelPositions);
     ShellLine.WriteLine($"可贴标签的位置: {availablePosition}"); 
     x = availablePosition.Split('-')[0].ToIntExt();
     y = availablePosition.Split('-')[1].ToIntExt();
     // 可视化结果(可选)
     Bitmap resultMap =  VisualizeResults(croppedImage, labelPositions, availablePosition);
     
     return resultMap; 
 }

复制代码
  // 根据颜色列表计算面单灰度范围
  public static void CalculateLabelGrayRange()
  {
      var grayValues = new List<int>();

      foreach (var colorHex in LabelColors)
      {
          // 将十六进制颜色转换为RGB
          System.Drawing.Color color = ColorTranslator.FromHtml(colorHex);

          // 计算灰度值 (使用标准公式: 0.299*R + 0.587*G + 0.114*B)
          int grayValue = (int)(0.299 * color.R + 0.587 * color.G + 0.114 * color.B);
          grayValues.Add(grayValue);

          Console.WriteLine($"颜色 {colorHex} 的灰度值: {grayValue}");
      }

      // 计算最小和最大灰度值,并扩展范围以容纳类似颜色
      MinLabelGray = grayValues.Min() - 10;
      MaxLabelGray = grayValues.Max() + 10;

      // 确保范围在0-255之间
      MinLabelGray = Math.Max(0, MinLabelGray);
      MaxLabelGray = Math.Min(255, MaxLabelGray);
  }

复制代码
 // 检测所有原厂面单位置
 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;
 }

复制代码
   // 查找可贴标签的位置(使用10mm×10mm基础网格)
   public static string FindAvailableLabelPosition(OpenCvSharp.Mat image, List<LabelPosition> labelPositions)
   {
       // 获取图像尺寸
       int rows = image.Rows;
       int cols = image.Cols;

       // 计算基础网格行列数
       int baseGridCols = (int)Math.Ceiling((double)cols / BaseGridSizePixels);
       int baseGridRows = (int)Math.Ceiling((double)rows / BaseGridSizePixels);

       // 从右下角开始查找(先横向,再纵向)
       for (int baseRow = 0; baseRow < baseGridRows; baseRow++)
       {
           for (int baseCol = 0; baseCol < baseGridCols; baseCol++)
           {
               // 计算当前基础网格的像素坐标(右下角)
               int baseX = cols - baseCol * BaseGridSizePixels;
               int baseY = rows - baseRow * BaseGridSizePixels;

               // 计算100mm×100mm区域的像素坐标
               int labelX = baseX - LabelSizePixels;
               int labelY = baseY - LabelSizePixels;

               // 检查区域是否在图像范围内
               if (labelX < 0 || labelY < 0)
                   continue;

               // 创建100mm×100mm区域矩形
               Rect labelRect = new Rect(labelX, labelY, LabelSizePixels, LabelSizePixels);

               // 检查区域是否与任何原厂标签相交
               bool intersects = false;
               foreach (var labelPos in labelPositions)
               {
                   if (labelRect.IntersectsWith(labelPos.Rect))
                   {
                       intersects = true;
                       break;
                   }
               }

               // 如果不相交,则返回当前位置
               if (!intersects)
               {
                   // 转换为网格坐标 (baseRow+1, baseCol+1)
                   return $"{baseRow + 1}-{baseCol + 1}";
               }
           }
       }

       // 如果没有找到可用位置,返回默认位置
       return "1-1";
   }

复制代码
    // 可视化结果
    public static Bitmap VisualizeResults(OpenCvSharp.Mat image, List<LabelPosition> labelPositions, string availablePosition)
    {
        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 += BaseGridSizePixels)
        {
            Cv2.Line(colorImage, new OpenCvSharp.Point(x, 0), new OpenCvSharp.Point(x, rows), Scalar.Green, 5);
        }
        for (int y = 0; y < rows; y += BaseGridSizePixels)
        {
            Cv2.Line(colorImage, new OpenCvSharp.Point(0, y), new OpenCvSharp.Point(cols, y), Scalar.Green, 5);
        }
        // 标记所有原厂面单位置(红色)
        foreach (var labelPos in labelPositions)
        {
            Cv2.Rectangle(colorImage,
                         labelPos.Rect.TopLeft,
                         labelPos.Rect.BottomRight,
                         Scalar.Red, 3);

            // 添加标签文本
            Cv2.PutText(colorImage,
                       labelPos.GridCoordinate,
                       new OpenCvSharp.Point(labelPos.Rect.X, labelPos.Rect.Y - 5),
                       HersheyFonts.HersheySimplex,
                       0.5,
                       Scalar.Red,
                       3);
        }

        // 标记可贴标签位置(黄色)
        if (!string.IsNullOrEmpty(availablePosition) && availablePosition != "1-1")
        {
            var parts = availablePosition.Split('-');
            if (parts.Length == 2)
            {
                int row = int.Parse(parts[0]);
                int col = int.Parse(parts[1]);

                // 计算100mm×100mm区域的像素坐标
                int x = cols - col * BaseGridSizePixels - LabelSizePixels;
                int y = rows - row * BaseGridSizePixels - LabelSizePixels;

                // 确保区域在图像范围内
                if (x < 0) x = 0;
                if (y < 0) y = 0;

                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.Yellow, 3);

                    // 添加标签文本
                    Cv2.PutText(colorImage,
                               availablePosition,
                               new OpenCvSharp.Point(x + 10, y + 30),
                               HersheyFonts.HersheySimplex,
                               1,
                               Scalar.Yellow,
                               3);
                }
            }
        }

        return colorImage.ToBitmap();
    }
}

demo展示效果

结束语

感谢各位耐心查阅! 如果您有更好的想法欢迎一起交流,有不懂的也可以微信公众号联系博主,作者公众号会经常发一些实用的小工具和demo源码,需要的可以去看看!另外,如果觉得本篇博文对您或者身边朋友有帮助的,麻烦点个关注!赠人玫瑰,手留余香,您的支持就是我写作最大的动力,感谢您的关注,期待和您一起探讨!再会!

相关推荐
weixin_445251833 小时前
透视与逆透视,及opencv中的warp
人工智能·opencv·计算机视觉
山烛4 小时前
OpenCV :基于 Lucas-Kanade 算法的视频光流估计实现
人工智能·opencv·计算机视觉·音视频·图像识别·特征提取·光流估计
qq_251616196 小时前
在目标图像中查找带 Alpha 掩码的 PNG 图标
人工智能·opencv·计算机视觉
星期天要睡觉7 小时前
计算机视觉(opencv)实战三十二——CascadeClassifier 人脸微笑检测(摄像头)
人工智能·opencv·计算机视觉
星期天要睡觉7 小时前
计算机视觉(opencv)实战二十九——图像风格迁移
人工智能·opencv·计算机视觉
蜉蝣之翼❉7 小时前
编译OpenCV 无法解析的外部符号 cv::xfeatures2d::VGG::getDef
opencv
Monkey的自我迭代7 小时前
图像拼接(反向拼接巨难,求指教!)
图像处理·人工智能·python·opencv·算法·计算机视觉
何以解忧唯有撸码6 天前
OpenCvSharp基于颜色反差规避FBA面单贴标
opencv
xiaohouzi1122338 天前
OpenCV的cv2.VideoCapture如何加GStreamer后端
人工智能·opencv·计算机视觉