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


40

我想在一罐食品上拍摄标签的图片,并能够对其进行变换,以使标签平坦,左右两侧的大小均调整为与图像中心对齐。

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


我正在寻找一般技术和算法来拍摄球形偏斜(在我的情况下为圆柱形)并且可以使图像变平的图像。当前,包裹在广口瓶或瓶子上的标签图像将具有一些特征和文本,这些特征和文本在向图像的右侧或左侧后退时会缩小。同样,表示标签边缘的线将仅在图像中心平行,并且将在标签的左右两端彼此偏斜。

处理完图像后,我希望留下一个几乎完美的矩形,在该矩形上,文本和特征的大小均要统一,就像我在不在罐子或瓶子上的时候给标签拍照一样。

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

我已经用Google搜索并找到了类似这样的文章: 弄平弯曲的文档,但是我正在寻找更简单的东西,因为我需要的是带有简单曲线的标签。


Nikie似乎提供了无所不包的解决方案。但是,如果您知道相机始终相对于广口瓶而言是“正方形”且没有令人迷惑的背景,则它将变得更加简单。然后,找到罐子的边缘,并应用简单的三角(arcsine?)变换,而无需进行任何其他操作。将图像展平后,您可以隔离标签本身。
Daniel R Hicks

@Daniel这就是我在这里所做的。理想情况下,也应考虑并非完全平行的投影,但我没有考虑。
Szabolcs 2012年

工作很好 但是代码显示我的系统错误。我正在使用matlab 2017a兼容吗。谢谢,
Satish Kumar,

Answers:


60

一个类似的问题,有人问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] / 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]

11
摘下太阳镜 ……上帝之母……
Spacey 2012年

您是否碰巧参考了圆柱映射?也许逆映射的方程式是?@ niki-estner
Ita
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.