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

B站影视 电影资讯 2025-09-25 08:39 1

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

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;///
/// 原厂标签规避算法
///
///原箱标签
///返回坐标X
///返回坐标y
///异常信息
///电脑DPI
///
publicstatic Bitmap AvoidFactoryLabelAlgorithm(string imagepath, double boxWidthMm, double boxHeightMm, outint x, outint y, outstring 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;
}publicstaticvoidCalculateLabelGrayRange
{
var grayValues = new Listint>;

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);
}// 检测所有原厂面单位置
publicstatic List DetectOriginalLabelPositions(OpenCvSharp.Mat image)
{
var labelPositions = new List

// 二值化图像以分离面单区域
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, outvar 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基础网格)
publicstaticstringFindAvailableLabelPosition(OpenCvSharp.Mat image, List
{
// 获取图像尺寸
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
{
for (int baseCol = 0; 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";
}// 可视化结果
publicstatic Bitmap VisualizeResults(OpenCvSharp.Mat image, List
{
var colorImage = new OpenCvSharp.Mat;
Cv2.CvtColor(image, colorImage, ColorConversionCodes.GRAY2BGR);

int rows = image.Rows;
int cols = image.Cols;
// 绘制网格
for (int x = 0; x
{
Cv2.Line(colorImage, new OpenCvSharp.Point(x, 0), new OpenCvSharp.Point(x, rows), Scalar.Green, 5);
}
for (int y = 0; y
{
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)
{

new OpenCvSharp.Point(x, y),
new OpenCvSharp.Point(x + width, y + height),
Scalar.Yellow, 3);

// 添加标签文本

availablePosition,
new OpenCvSharp.Point(x + 10, y + 30),

1,
Scalar.Yellow,
3);
}
}
}

return colorImage.ToBitmap;
}

demo展示效果

结束语

来源:opendotnet

相关推荐