爱迪图蕃 發表於 2026-1-15 10:46:00

c#实现包裹扣面单的几种方式

<p><span>&nbsp; 无论是跨境电商还是制造业分拣设备,在包裹流转出入库的场景,为了保证包裹分拣计划和测量数据绑定真实性,经常会遇到面单扣取的需求,下面我就通过两种实现原理来实现这一功能。</span></p>
<p>&nbsp; &nbsp; 一:OpenCVSharp 通过面单轮廓/颜色/边缘等组合检测实现</p>
<p>&nbsp; &nbsp; 二:通过OCR识别面单内容,根据所有切割点坐标点最小外界矩形来定位面单位置(扣面单的场景需求是看清面单内容,当然想要扣取完整面单图片,可以添加面单尺寸,规则信息等维度计算或者直接用第三种方式)</p>
<p>&nbsp; &nbsp; 三:YOLO+Labelme标定工具,通过模型训练定位扣取(这个抽时间单独展开一篇解释)</p>
<p><strong><span style="font-family: 宋体, &quot;Songti SC&quot;; font-size: 16px">方式一:OpencvSharp 通过轮廓/颜色/边缘检测</span></strong></p>
<p><strong><span style="font-family: 宋体, &quot;Songti SC&quot;; font-size: 16px">  </span></strong>这种方式对于包裹和面单颜色有明显差异的场景很友好,对于包裹颜色和面单颜色接近的效果一般(建议考虑第二种方式),虽然可以根据面单样式或者文字聚集密度等多重维度来组合分析,但是过于复杂,并且定制化程度很高,废话少说,先看看效果:</p>
<p>&nbsp;</p>
<p><span>&nbsp; &nbsp; &nbsp; &nbsp; 原图:</span></p>
<p><span>  <img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185329554-1784692153.png" alt="333" width="439" height="328" loading="lazy"></span></p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp; &nbsp;通过显示增强后的效果图:</p>
<p>&nbsp; &nbsp; &nbsp; &nbsp;<img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185355689-1585898524.png" alt="zsvvyv" width="367" height="536" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/116076/202509/116076-20250924180326264-1688793031.png" alt="116076-20250310125032345-1793233350" width="519" height="244" loading="lazy"></p>
<p>废话少说,附上核心代码:</p>
<div class="cnblogs_code">
<pre>staticvoidProcessSingleImage(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> imagePath)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">File.Exists(imagePath))
    {
      Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">文件不存在!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      Console.ReadKey();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
    }

</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
    {
      Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理: {Path.GetFileName(imagePath)}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> stopwatch =<span style="color: rgba(0, 0, 0, 1)"> Stopwatch.StartNew();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检测面单</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> results =<span style="color: rgba(0, 0, 0, 1)"> _detector.DetectLabels(imagePath);

      stopwatch.Stop();
      Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">检测耗时: {stopwatch.ElapsedMilliseconds}ms</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">找到 {results.Count} 个面单区域</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">if</span> (results.Count == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
      {
            Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未检测到面单!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            Console.ReadKey();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示结果</span>
<span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> result <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> results)
      {
            Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">- {result.DetectionMethod}: 置信度 {result.Confidence:F2}, </span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)">
$</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">位置 [{result.BoundingBox.X}, {result.BoundingBox.Y}, </span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)">
$</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{result.BoundingBox.Width}, {result.BoundingBox.Height}]</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建输出目录</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> outputDir =<span style="color: rgba(0, 0, 0, 1)"> _config.OutputDirectory;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">Directory.Exists(outputDir))
            Directory.CreateDirectory(outputDir);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> baseName =<span style="color: rgba(0, 0, 0, 1)"> Path.GetFileNameWithoutExtension(imagePath);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存可视化结果</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (_config.SaveVisualized)
      {
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> original = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap(imagePath))
            {
                Bitmap bitResult </span>=<span style="color: rgba(0, 0, 0, 1)"> ImageProcessor.DrawBoundingBoxesSafe(original, results);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> visPath = Path.Combine(outputDir, $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{baseName}_detected.png</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                ImageProcessor.SaveImage(bitResult, visPath);
                Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">可视化结果已保存: {visPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            }
      }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存抠图结果 </span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (_config.SaveCropped)
      {
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mat =<span style="color: rgba(0, 0, 0, 1)"> Cv2.ImRead(imagePath))
            {
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; results.Count; i++<span style="color: rgba(0, 0, 0, 1)">)
                {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cropped =<span style="color: rgba(0, 0, 0, 1)"> _detector.CropLabel(mat, results.BoundingBox);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (cropped != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
                  {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 图像增强</span>
                        _detector.EnhanceImage(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> cropped);

</span><span style="color: rgba(0, 0, 255, 1)">var</span> cropPath = Path.Combine(outputDir, $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{baseName}_label_{i + 1}.png</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                        Console.WriteLine(cropped);

                        ImageProcessor.SaveImage(cropped, cropPath);
                        Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">抠图已保存: {cropPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

                        cropped.Dispose();
                  }
                }
            }
      }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存检测结果到JSON</span>
      SaveResultsToJson(results, Path.Combine(outputDir, $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{baseName}_results.json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));

      Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n处理完成! 按任意键继续...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
    {
      Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理失败: {ex.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }
}</span></pre>
</div>
<p>通过轮廓检测、颜色检测和边缘检测三种方式组合定位面单位置</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> List&lt;DetectionResult&gt; DetectLabels(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> imagePath)
    {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> results = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)">();

</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mat =<span style="color: rgba(0, 0, 0, 1)"> Cv2.ImRead(imagePath, OpenCvSharp.ImreadModes.Color))
      {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (mat.Empty())
thrownew FileNotFoundException($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">无法加载图像: {imagePath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 方法1: 轮廓检测</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> contourResults =<span style="color: rgba(0, 0, 0, 1)"> DetectByContours(mat);
            results.AddRange(contourResults);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 方法2: 颜色检测</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> colorResults =<span style="color: rgba(0, 0, 0, 1)"> DetectByColor(mat);
            results.AddRange(colorResults);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 方法3: 边缘检测</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> edgeResults =<span style="color: rgba(0, 0, 0, 1)"> DetectByEdges(mat);
            results.AddRange(edgeResults);
      }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 合并和筛选结果</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> FilterResults(results);
    }</span></pre>
</div>
<p>轮廓检测</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)"> DetectByContours(OpenCvSharp.Mat src)
    {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> results = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> gray = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> binary = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
      {
            Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 二值化</span>
            Cv2.Threshold(gray, binary, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">255</span>, ThresholdTypes.Binary |<span style="color: rgba(0, 0, 0, 1)"> ThresholdTypes.Otsu);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 形态学操作</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> kernel = Cv2.GetStructuringElement(MorphShapes.Rect, <span style="color: rgba(0, 0, 255, 1)">new</span> OpenCvSharp.Size(<span style="color: rgba(128, 0, 128, 1)">3</span>, <span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">));
            Cv2.MorphologyEx(binary, binary, MorphTypes.Close, kernel);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查找轮廓</span>
<span style="color: rgba(0, 0, 0, 1)">            Cv2.FindContours(binary, outvar contours, outvar hierarchy,
                RetrievalModes.External, ContourApproximationModes.ApproxSimple);

</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> contour <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> contours)
            {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> area =<span style="color: rgba(0, 0, 0, 1)"> Cv2.ContourArea(contour);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (area &lt; _minArea || area &gt;<span style="color: rgba(0, 0, 0, 1)"> _maxArea)
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;

                Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">面积:{area}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> rect =<span style="color: rgba(0, 0, 0, 1)"> Cv2.BoundingRect(contour);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算宽高比</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> aspectRatio = (<span style="color: rgba(0, 0, 255, 1)">double</span>)rect.Width /<span style="color: rgba(0, 0, 0, 1)"> rect.Height;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 面单通常为矩形,宽高比在一定范围内</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (aspectRatio &gt; <span style="color: rgba(128, 0, 128, 1)">0.5</span> &amp;&amp; aspectRatio &lt; <span style="color: rgba(128, 0, 128, 1)">3.0</span><span style="color: rgba(0, 0, 0, 1)">)
                {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算矩形度</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> rectArea = rect.Width *<span style="color: rgba(0, 0, 0, 1)"> rect.Height;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> rectangularity = area /<span style="color: rgba(0, 0, 0, 1)"> rectArea;
Console.WriteLine(rectangularity);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (rectangularity &gt; <span style="color: rgba(128, 0, 128, 1)">0.55</span><span style="color: rgba(0, 0, 0, 1)">)
                  {
                        results.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DetectionResult
                        {
                            BoundingBox </span>=<span style="color: rgba(0, 0, 0, 1)"> rect.ToRectangle(),
                            Confidence </span>=<span style="color: rgba(0, 0, 0, 1)"> rectangularity,
                            DetectionMethod </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Contour</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
                        });
                  }
                }
            }
      }

</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> results;
    }</span></pre>
</div>
<p>2.颜色检测</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)"> DetectByColor(OpenCvSharp.Mat src)
    {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> results = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)">();

</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> hsv = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mask = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
      {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换到HSV色彩空间</span>
<span style="color: rgba(0, 0, 0, 1)">            Cv2.CvtColor(src, hsv, ColorConversionCodes.BGR2HSV);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义白色/浅色范围</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> lowerWhite1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Scalar(<span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> upperWhite1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Scalar(<span style="color: rgba(128, 0, 128, 1)">180</span>, <span style="color: rgba(128, 0, 128, 1)">30</span>, <span style="color: rgba(128, 0, 128, 1)">255</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> lowerWhite2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Scalar(<span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">180</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> upperWhite2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Scalar(<span style="color: rgba(128, 0, 128, 1)">180</span>, <span style="color: rgba(128, 0, 128, 1)">80</span>, <span style="color: rgba(128, 0, 128, 1)">255</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mask1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mask2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
            {
                Cv2.InRange(hsv, lowerWhite1, upperWhite1, mask1);
                Cv2.InRange(hsv, lowerWhite2, upperWhite2, mask2);
                Cv2.BitwiseOr(mask1, mask2, mask);
            }

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 形态学操作</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> kernel = Cv2.GetStructuringElement(MorphShapes.Rect, <span style="color: rgba(0, 0, 255, 1)">new</span> OpenCvSharp.Size(<span style="color: rgba(128, 0, 128, 1)">5</span>, <span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)">));
            Cv2.MorphologyEx(mask, mask, MorphTypes.Close, kernel);
            Cv2.MorphologyEx(mask, mask, MorphTypes.Open, kernel);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查找轮廓</span>
<span style="color: rgba(0, 0, 0, 1)">            Cv2.FindContours(mask, outvar contours, outvar hierarchy,
                RetrievalModes.External, ContourApproximationModes.ApproxSimple);

</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> contour <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> contours)
            {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> area =<span style="color: rgba(0, 0, 0, 1)"> Cv2.ContourArea(contour);

</span><span style="color: rgba(0, 0, 255, 1)">if</span> (area &lt; _minArea || area &gt;<span style="color: rgba(0, 0, 0, 1)"> _maxArea)
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> rect =<span style="color: rgba(0, 0, 0, 1)"> Cv2.BoundingRect(contour);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算颜色均匀度</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> uniformity =<span style="color: rgba(0, 0, 0, 1)"> CalculateColorUniformity(src, rect);

</span><span style="color: rgba(0, 0, 255, 1)">if</span> (uniformity &gt;<span style="color: rgba(0, 0, 0, 1)"> _confidenceThreshold)
                {
                  results.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DetectionResult
                  {
                        BoundingBox </span>=<span style="color: rgba(0, 0, 0, 1)"> rect.ToRectangle(),
                        Confidence </span>=<span style="color: rgba(0, 0, 0, 1)"> uniformity,
                        DetectionMethod </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Color</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
                  });
                }
            }
      }

</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> results;
    }
</span><span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">.边缘检测

</span><span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)"> DetectByEdges(OpenCvSharp.Mat src)
    {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> results = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;DetectionResult&gt;<span style="color: rgba(0, 0, 0, 1)">();

</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> gray = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> edges = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
      {
            Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 降噪</span>
            Cv2.GaussianBlur(gray, gray, <span style="color: rgba(0, 0, 255, 1)">new</span> OpenCvSharp.Size(<span style="color: rgba(128, 0, 128, 1)">5</span>, <span style="color: rgba(128, 0, 128, 1)">5</span>), <span style="color: rgba(128, 0, 128, 1)">1.5</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 边缘检测</span>
            Cv2.Canny(gray, edges, <span style="color: rgba(128, 0, 128, 1)">50</span>, <span style="color: rgba(128, 0, 128, 1)">150</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 膨胀</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> kernel = Cv2.GetStructuringElement(MorphShapes.Rect, <span style="color: rgba(0, 0, 255, 1)">new</span> OpenCvSharp.Size(<span style="color: rgba(128, 0, 128, 1)">3</span>, <span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">));
            Cv2.Dilate(edges, edges, kernel, iterations: </span><span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查找轮廓</span>
<span style="color: rgba(0, 0, 0, 1)">            Cv2.FindContours(edges, outvar contours, outvar hierarchy,
                RetrievalModes.External, ContourApproximationModes.ApproxSimple);

</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> contour <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> contours)
            {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> area =<span style="color: rgba(0, 0, 0, 1)"> Cv2.ContourArea(contour);

</span><span style="color: rgba(0, 0, 255, 1)">if</span> (area &lt; _minArea || area &gt;<span style="color: rgba(0, 0, 0, 1)"> _maxArea)
</span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">var</span> rect =<span style="color: rgba(0, 0, 0, 1)"> Cv2.BoundingRect(contour);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算边缘密度</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> roi = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat(edges, rect))
                {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> totalPixels = roi.Rows *<span style="color: rgba(0, 0, 0, 1)"> roi.Cols;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> edgePixels =<span style="color: rgba(0, 0, 0, 1)"> Cv2.CountNonZero(roi);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> edgeDensity = (<span style="color: rgba(0, 0, 255, 1)">double</span>)edgePixels /<span style="color: rgba(0, 0, 0, 1)"> totalPixels;

</span><span style="color: rgba(0, 0, 255, 1)">if</span> (edgeDensity &gt; <span style="color: rgba(128, 0, 128, 1)">0.1</span> &amp;&amp; rect.Width &gt; <span style="color: rgba(128, 0, 128, 1)">100</span> &amp;&amp; rect.Height &gt; <span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">)
                  {
                        results.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DetectionResult
                        {
                            BoundingBox </span>=<span style="color: rgba(0, 0, 0, 1)"> rect.ToRectangle(),
                            Confidence </span>=<span style="color: rgba(0, 0, 0, 1)"> edgeDensity,
                            DetectionMethod </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Edge</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
                        });
                  }
                }
            }
      }

</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> results;
    }</span></pre>
</div>
<p><strong data-brushtype="text"><span>方式二:通过OCR识别面单内容,根据所有切割点坐标点最小外界矩形来定位面单位置</span></strong></p>
<p><span>&nbsp; &nbsp; OCR基础模型用的是SVTR-LCNet这个架构的网络模型,论文是公开的,我们在这个基础上做的复现与调优。话不多说,先看效果</span></p>
<p><span>&nbsp; &nbsp; 相机拍照原始包裹图片</span></p>
<p><span>&nbsp; &nbsp;&nbsp;<img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185618097-2092867094.png" alt="ScreenShot_2026-01-14_185601_117" width="458" height="383" loading="lazy"></span></p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; OCR识别切割效果(根据识别文字角度自动校正)</p>
<p>  <img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185658596-1401802679.png" alt="640" loading="lazy"></p>
<p>  定位到每个识别内容的矩形坐标,获取所有当前图片所有切割矩形的最小外接矩形,然后裁切,就可以得到包含所有面单内容的图片</p>
<p>  <img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185730759-1515244179.png" alt="640 (1)" width="840" height="547" loading="lazy"></p>
<p>  抠面单效果(实际会比面单小,但是满足客户需求,包含了所有面单内容)</p>
<p>  <img src="https://img2024.cnblogs.com/blog/116076/202601/116076-20260114185752661-1398249190.png" alt="640 (2)" width="849" height="536" loading="lazy"></p>
<p>  <img src="https://img2024.cnblogs.com/blog/116076/202509/116076-20250924180326264-1688793031.png" alt="116076-20250310125032345-1793233350" loading="lazy"></p>
<p>  废话不多说,附上代码</p>
<p>  </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 返回面单图片
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;param name="errorMsg"&gt;</span><span style="color: rgba(0, 128, 0, 1)">异常信息</span><span style="color: rgba(128, 128, 128, 1)">&lt;/param&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;param name="IsEnhanceImage"&gt;</span><span style="color: rgba(0, 128, 0, 1)">面单是否增强</span><span style="color: rgba(128, 128, 128, 1)">&lt;/param&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;param name="IsSaveLocl"&gt;</span><span style="color: rgba(0, 128, 0, 1)">是否本地保存</span><span style="color: rgba(128, 128, 128, 1)">&lt;/param&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;returns&gt;&lt;/returns&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> Bitmap GetLabelImageByBitmap(outstring errorMsg, <span style="color: rgba(0, 0, 255, 1)">bool</span> IsEnhanceImage = <span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 0, 255, 1)">bool</span> IsSaveLocl = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
{
   Bitmap croppedImage </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
   errorMsg </span>= <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
   </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
   {
         </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">File.Exists(imagePath))
         {
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">请确保 {imagePath} 存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             errorMsg </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">请确保 {imagePath} 存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
             returnnew Bitmap(</span><span style="color: rgba(128, 0, 128, 1)">10</span>, <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">);
         }
         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">图片目录</span>
         <span style="color: rgba(0, 0, 255, 1)">string</span> imageDir =<span style="color: rgba(0, 0, 0, 1)"> Path.GetDirectoryName(debugImagePath);
         </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (Directory.Exists(imageDir))
         {
             Directory.CreateDirectory(imageDir);
         }
         Bitmap bitmap1 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap(imagePath);
         </span><span style="color: rgba(0, 0, 255, 1)">var</span> rr =<span style="color: rgba(0, 0, 0, 1)"> oCR.GetOCRDataStr(bitmap1, debugImagePath);

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 读取JSON文件</span>
         <span style="color: rgba(0, 0, 255, 1)">string</span> jsonFilePath = imageDir + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\\content.json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

         </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">File.Exists(jsonFilePath))
         {
             errorMsg </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未找到JSON文件,请确保 {jsonFilePath} 存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未找到JSON文件,请确保 {jsonFilePath} 存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             returnnew Bitmap(imagePath);
         }

         </span><span style="color: rgba(0, 0, 255, 1)">string</span> preRotatedImage = imageDir + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\\preRotatedImg.jpg</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
         </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">File.Exists(preRotatedImage))
         {
             errorMsg </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未找到面单文件,请确保包裹面单清晰且存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未找到面单文件,请确保包裹面单清晰且存在</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             returnnew Bitmap(imagePath);
         }

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析矩形数据并计算最小外接矩形</span>
         List&lt;Rectangle&gt; rectangles =<span style="color: rgba(0, 0, 0, 1)"> ParseRectanglesFromJson(jsonFilePath);
         </span><span style="color: rgba(0, 0, 255, 1)">if</span> (rectangles.Count == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
         {
             errorMsg </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未在JSON文件中找到有效的矩形数据</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
             ShellLine.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">未在JSON文件中找到有效的矩形数据</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             returnnew Bitmap(imagePath);
         }

         Rectangle boundingRect </span>=<span style="color: rgba(0, 0, 0, 1)"> CalculateBoundingRectangle(rectangles);
         ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">最小外接矩形: X={boundingRect.X}, Y={boundingRect.Y}, Width={boundingRect.Width}, Height={boundingRect.Height}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
         ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">包含 {rectangles.Count} 个元素</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加载图片并进行裁剪</span>
         <span style="color: rgba(0, 0, 255, 1)">using</span> (Bitmap originalImage = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap(preRotatedImage))
         {
             </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确保矩形在图片范围内</span>
             Rectangle safeRect =<span style="color: rgba(0, 0, 0, 1)"> GetSafeRectangle(boundingRect, originalImage);
             </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 裁剪图片</span>
             croppedImage =<span style="color: rgba(0, 0, 0, 1)"> CropImage(originalImage, safeRect);
             </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (IsEnhanceImage)
             {
               </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 增强显示 </span>
               EnhanceImage(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> croppedImage);
             }
             </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (IsSaveLocl)
             {
               </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存结果</span>
               <span style="color: rgba(0, 0, 255, 1)">string</span> outputPath =<span style="color: rgba(0, 0, 0, 1)"> Path.Combine(
                     Path.GetDirectoryName(preRotatedImage),
                     Path.GetFileNameWithoutExtension(preRotatedImage) </span>+ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">_cropped_enhanced.jpg</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

               croppedImage.Save(outputPath, ImageFormat.Jpeg);
               ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理完成!结果已保存到: {outputPath}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             }
             </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 显示裁剪区域信息</span>
             ShellLine.WriteLine($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n裁剪区域信息:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">原始图片尺寸: {originalImage.Width}x{originalImage.Height}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">裁剪区域: {safeRect.X}, {safeRect.Y}, {safeRect.Width}x{safeRect.Height}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">增强后图片尺寸: {croppedImage.Width}x{croppedImage.Height}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
             </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> croppedImage;
         }
   }
   </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
   {
         errorMsg </span>= $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理过程中出现错误: {ex.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
         ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">处理过程中出现错误: {ex.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
         ShellLine.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">堆栈跟踪: {ex.StackTrace}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
         returnnew Bitmap(imagePath);
   }
   </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)">
   {
         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 释放资源</span>
         croppedImage?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
   }
}</span></pre>
</div>
<p>  图片增强显示,有需要可以调用</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;summary&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 图片增强显示
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;/summary&gt;</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)">&lt;param name="image"&gt;&lt;/param&gt;</span>
publicvoidEnhanceImage(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap image)
{
   </span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> mat =<span style="color: rgba(0, 0, 0, 1)"> image.ToMat())
   </span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> lab = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OpenCvSharp.Mat())
   {
         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换为Lab色彩空间</span>
<span style="color: rgba(0, 0, 0, 1)">         Cv2.CvtColor(mat, lab, ColorConversionCodes.BGR2Lab);

         Cv2.Split(lab, outvar labChannels);

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 对亮度通道进行直方图均衡化</span>
         Cv2.EqualizeHist(labChannels[<span style="color: rgba(128, 0, 128, 1)">0</span>], labChannels[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">]);

         Cv2.Merge(labChannels, lab);
         Cv2.CvtColor(lab, mat, ColorConversionCodes.Lab2BGR);

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 释放通道</span>
         <span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> channel <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> labChannels)
             channel.Dispose();

         </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更新图像</span>
<span style="color: rgba(0, 0, 0, 1)">         image.Dispose();
         image </span>=<span style="color: rgba(0, 0, 0, 1)"> mat.ToBitmap();
   }
}</span></pre>
</div>
<p>  获取包含所有切割字符的最小外接矩形</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 计算包含所有矩形的最小外接矩形</span>
<span style="color: rgba(0, 0, 255, 1)">static</span> Rectangle CalculateBoundingRectangle(List&lt;Rectangle&gt;<span style="color: rgba(0, 0, 0, 1)"> rectangles)
{
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (rectangles.Count == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
          thrownew ArgumentException(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">矩形列表为空</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 0, 255, 1)">int</span> minX = <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">.MaxValue;
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> minY = <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">.MaxValue;
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> maxX = <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">.MinValue;
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> maxY = <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">.MinValue;

      </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (Rectangle rect <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> rectangles)
      {
          minX </span>=<span style="color: rgba(0, 0, 0, 1)"> Math.Min(minX, rect.X);
          minY </span>=<span style="color: rgba(0, 0, 0, 1)"> Math.Min(minY, rect.Y);
          maxX </span>= Math.Max(maxX, rect.X +<span style="color: rgba(0, 0, 0, 1)"> rect.Width);
          maxY </span>= Math.Max(maxY, rect.Y +<span style="color: rgba(0, 0, 0, 1)"> rect.Height);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加一些边距,使裁剪更美观</span>
      <span style="color: rgba(0, 0, 255, 1)">int</span> margin = <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">;
      minX </span>= Math.Max(<span style="color: rgba(128, 0, 128, 1)">0</span>, minX -<span style="color: rgba(0, 0, 0, 1)"> margin);
      minY </span>= Math.Max(<span style="color: rgba(128, 0, 128, 1)">0</span>, minY -<span style="color: rgba(0, 0, 0, 1)"> margin);
      maxX </span>= maxX +<span style="color: rgba(0, 0, 0, 1)"> margin;
      maxY </span>= maxY +<span style="color: rgba(0, 0, 0, 1)"> margin;

      returnnew Rectangle(minX, minY, maxX </span>- minX, maxY -<span style="color: rgba(0, 0, 0, 1)"> minY);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确保矩形在图片范围内</span>
<span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> Rectangle GetSafeRectangle(Rectangle rect, Bitmap image)
{
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> x = Math.Max(<span style="color: rgba(128, 0, 128, 1)">0</span>, Math.Min(rect.X, image.Width - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">));
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> y = Math.Max(<span style="color: rgba(128, 0, 128, 1)">0</span>, Math.Min(rect.Y, image.Height - <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">));
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> width = Math.Min(rect.Width, image.Width -<span style="color: rgba(0, 0, 0, 1)"> x);
      </span><span style="color: rgba(0, 0, 255, 1)">int</span> height = Math.Min(rect.Height, image.Height -<span style="color: rgba(0, 0, 0, 1)"> y);

      returnnew Rectangle(x, y, width, height);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 裁剪图片</span>
<span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap CropImage(Bitmap source, Rectangle cropArea)
{
      Bitmap target </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Bitmap(cropArea.Width, cropArea.Height);

      </span><span style="color: rgba(0, 0, 255, 1)">using</span> (Graphics g =<span style="color: rgba(0, 0, 0, 1)"> Graphics.FromImage(target))
      {
          g.DrawImage(source, </span><span style="color: rgba(0, 0, 255, 1)">new</span> Rectangle(<span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, cropArea.Width, cropArea.Height),
            cropArea, GraphicsUnit.Pixel);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> target;
}</span></pre>
</div>
<p><strong><span>  结束语</span></strong></p>
<p><strong>&nbsp;&nbsp; &nbsp;  感谢各位耐心查阅! &nbsp;如果您有更好的想法欢迎一起交流,有不懂的也可以微信公众号联系博主,作者公众号会经常发一些实用的小工具和demo源码,需要的可以去看看!另外,如果觉得本篇博文对您或者身边朋友有帮助的,麻烦点个关注!赠人玫瑰,手留余香,您的支持就是我写作最大的动力,感谢您的关注,期待和您一起探讨!再会!</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/116076/202509/116076-20250924180748850-573212949.gif" alt="640" loading="lazy"></p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <div id="AllanboltSignature1" style="color:green;">      
<p id="PSignature" style="border-top: red 1px dashed; border-right: red 1px dashed; border-bottom: red 1px dashed; border-left:red 1px dashed;            padding-top: 50px;padding-right: 10px;padding-bottom: 10px;padding-left: 250px;            background: url(https://images.cnblogs.com/cnblogs_com/axing/1657506/o_2002281642338.jpg) #e5f1f4 no-repeat 1% 20%;         font-family: 微软雅黑; font-size:11px;">            
<br />
作者:Stephen-kzx            
<br />
出处:http://www.cnblogs.com/axing/
<br />
公众号:<font color="red">会定时分享写工作中或者生活中遇到的小游戏和小工具源码。有兴趣的帮忙点下关注!感恩!</font>
<br />本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

</p>
</div><br><br>
来源:https://www.cnblogs.com/axing/p/19483856
頁: [1]
查看完整版本: c#实现包裹扣面单的几种方式