如何使食品罐上的标签图像变平?

信息处理 图像处理 计算机视觉
2022-01-07 21:37:46

我想为一罐食物上的标签拍照,并能够将它们转换为扁平的标签,左右调整大小以与图像的中心对齐。

理想情况下,我想使用标签和背景之间的对比度来找到边缘并应用校正。否则,我可以要求用户以某种方式识别图像的角和边。


我正在寻找通用技术和算法来拍摄球形(在我的情况下为圆柱形)倾斜的图像并且可以使图像变平。目前,包裹在罐子或瓶子周围的标签图像将具有特征和文本,当它向图像的右侧或左侧后退时会缩小。此外,表示标签边缘的线条只会在图像的中心平行,并且在标签的左右两端相互倾斜。

处理完图像后,我想留下一个几乎完美的矩形,其中文本和特征大小一致,就好像我在标签不在罐子或瓶子上时拍了一张照片一样。

另外,如果该技术可以自动检测标签的边缘,以便应用适当的校正,我希望它。否则我将不得不让我的用户指出标签边界。

我已经在 Google 上搜索并找到了类似这样的文章: flattening curve documents,但我正在寻找更简单的东西,因为我需要具有简单曲线的标签。

1个回答

在 Mathematica.Stackexchange 上提出了类似的问题我在那边的答案演变并最终变得很长,所以我将在这里总结一下算法。

抽象的

基本思想是:

  1. 找到标签。
  2. 找到标签的边界
  3. 找到一个将图像坐标映射到圆柱坐标的映射,以便将标签上边界的像素映射到 ([anything] / 0),将右边界的像素映射到 (1 / [anything]),依此类推。
  4. 使用此映射转换图像

该算法仅适用于以下图像:

  1. 标签比背景亮(这是标签检测所必需的)
  2. 标签是矩形的(用于衡量映射的质量)
  3. 罐子(几乎)是垂直的(这用于保持映射功能简单)
  4. 罐子是圆柱形的(这是为了保持映射功能简单)

但是,该算法是模块化的。至少在原则上,您可以编写自己的不需要深色背景的标签检测,或者您可以编写自己的质量测量函数来处理椭圆或八角形标签。

结果

这些图像是完全自动处理的,即算法获取源图像,工作几秒钟,然后显示映射(左)和未失真的图像(右):

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

接下来的图像是用算法的修改版本处理的,如果用户选择罐子的左右边界(不是标签),因为标签的曲率无法从正面拍摄的图像中估计(即全自动算法将返回略微失真的图像):

在此处输入图像描述

在此处输入图像描述

执行:

1.找到标签

标签在深色背景前是明亮的,所以我可以使用二值化轻松找到它:

src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]

二值化图像

我只是选择最大的连接组件并假设这是标签:

labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]

最大的组件

2.找到标签的边框

下一步:使用简单的导数卷积掩码找到上/下/左/右边界:

topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];

在此处输入图像描述

这是一个小辅助函数,用于查找这四个图像之一中的所有白色像素并将索引转换为坐标(Position返回索引,并且索引是基于 1 的 {y,x} 元组,其中 y=1 位于顶部图像。但是所有图像处理函数都需要坐标,它们是基于 0 的 {x,y}-元组,其中 y=0 是图像的底部):

{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];

3. 找到从图像到圆柱坐标的映射

现在我有四个单独的标签顶部、底部、左侧、右侧边框坐标列表。我定义了从图像坐标到圆柱坐标的映射:

arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] := 
   {
    c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y, 
    top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
   }

这是一个圆柱映射,将源图像中的 X/Y 坐标映射到圆柱坐标。映射对于高度/半径/中心/透视/倾斜有 10 个自由度。我使用泰勒级数来近似反正弦,因为我无法直接使用 ArcSin 进行优化。Clip调用是我在优化过程中防止复数的临时尝试。这里有一个权衡:一方面,函数应该尽可能接近精确的圆柱映射,以提供尽可能低的失真。另一方面,如果它太复杂,自动找到自由度的最佳值变得更加困难。(使用 Mathematica 进行图像处理的好处是您可以非常轻松地使用这样的数学模型,为不同的失真引入附加项并使用相同的优化函数来获得最终结果。我从来没有做过任何事情就像使用 OpenCV 或 Matlab 一样。但我从未尝试过 Matlab 的符号工具箱,也许这使它更有用。)

接下来我定义了一个“误差函数”来衡量图像的质量 -> 圆柱坐标映射。它只是边界像素的平方误差之和:

errorFunction =
  Flatten[{
    (mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
    (mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
    (mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
    (mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
    }];

此误差函数衡量映射的“质量”:如果左边框上的点映射到 (0 / [anything]),上边框上的像素映射到 ([anything] / 0) 等,则它是最低的.

现在我可以告诉 Mathematica 找到最小化这个误差函数的系数。我可以对一些系数(例如图像中罐子的半径和中心)做出“有根据的猜测”。我使用这些作为优化的起点:

leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution = 
 FindMinimum[
   Total[errorFunction], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {cx, (leftMean + rightMean)/2}, 
     {top, topMean}, 
     {r, rightMean - leftMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]

FindMinimum找到最小化误差函数的映射函数的 10 个自由度的值。结合通用映射和这个解决方案,我从 X/Y 图像坐标中得到一个适合标签区域的映射。我可以使用 Mathematica 的ContourPlot函数可视化这个映射:

Show[src,
 ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.1], 
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
 ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.2],
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]

在此处输入图像描述

4.变换图像

最后,我使用 Mathematica 的ImageForwardTransform函数根据这个映射扭曲图像:

ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]

这给出了如上所示的结果。

手动辅助版本

上面的算法是全自动的。无需调整。只要图片是从上方或下方拍摄的,它就可以很好地工作。但如果是正面照,罐子的半径就无法从标签的形状来估计。在这些情况下,如果我让用户手动输入 jar 的左/右边界,并在映射中明确设置相应的自由度,我会得到更好的结果。

此代码允许用户选择左/右边框:

LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}], 
 Dynamic[Show[src, 
   Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}], 
     Line[{{xRight, 0}, {xRight, h}}]}]]]]

定位器窗格

这是替代优化代码,其中明确给出了中心和半径。

manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution = 
  FindMinimum[
   Total[minimize /. manualAdjustments], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {top, topMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]