理想情况下,我想利用标签和背景之间的对比度来找到边缘并进行校正。否则,我可以要求用户以某种方式识别图像的角部和侧面。我的情况),可以使图像变平。当前,包裹在罐子或瓶子上的标签的图像将具有一些特征和文本,这些特征和文本在向图像的右侧或左侧后退时会缩小。同样,表示标签边缘的线将仅在图像中心平行,并且将在标签的左右两端相互偏斜。
处理完图像后,我想留下一个几乎完美的矩形,在该矩形上文本和特征的大小均一,就像我在不放在罐子上或罐子上时为标签拍照瓶子。
另外,如果该技术可以自动检测标签的边缘以进行适当的校正,我也希望它。否则,我将不得不要求我的用户指出标签边界。需求是带有简单曲线的标签。
#1 楼
在Mathematica.Stackexchange上也提出了类似的问题。我在那里的答案不断发展,并且最终还花了很长的时间,因此在这里我将对算法进行总结。摘要/>
查找标签。
查找标签的边界
查找将图像坐标映射到圆柱坐标的映射,以便将沿标签顶部边界的像素映射到([anything] / 0),沿标签的像素(1 / [任何])的右边界,依此类推。
使用此映射变换图像
该算法仅适用于以下情况的图像:
标签比背景亮(对于标签检测)
标签是矩形的(用于测量映射的质量)
罐子(几乎)是垂直的(用于使映射功能保持简单)
广口瓶是圆柱形的(用于使映射函数保持简单)
但是,该算法是模块化的。至少原则上,您可以编写自己的不需要深色背景的标签检测,也可以编写自己的质量测量功能以应对椭圆形或八边形的标签。
结果
这些图像是完全自动处理的,即该算法获取源图像,工作几秒钟,然后显示映射(左)和未失真的图像(右):
下一个图像是使用算法的修改版本处理的,因为用户选择了广口瓶的左右边框(而不是标签),因为标签不能从正面拍摄的图像中估计出来(即,全自动算法将返回稍微失真的图像):
/>实现方式:
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} -tuple ,其中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的符号工具箱,也许这使它更有用。)圆柱坐标映射。它只是边界像素的平方误差的总和:映射到([0 / [任何]),顶部边框上的像素映射到([任何] / 0),依此类推。现在我可以告诉Mathematica找到使该误差最小的系数功能。我可以对某些系数(例如图像中罐子的半径和中心)做出“有根据的猜测”。我将这些作为优化的起点:
errorFunction =
Flatten[{
(mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
(mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
(mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
(mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
}];
FindMinimum
查找我的映射函数的10个自由度的值,该值将误差函数最小化。结合通用映射和此解决方案,我从适合标签区域的X / Y图像坐标获取映射。我可以使用Mathematica的ContourPlot
函数形象化此映射: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]]
4。转换图像
最后,我使用Mathematica的
ImageForwardTransform
函数根据此映射对图像进行扭曲: 手动版本
以上算法是全自动的。无需调整。只要从上面或下面拍摄照片,它就可以正常工作。但是,如果是正面拍摄,则无法从标签的形状估算出罐子的半径。在这些情况下,如果让用户手动输入jar的左/右边界,并在映射中显式设置相应的自由度,则会得到更好的结果。选择左/右边框:
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]]]
这是替代的优化代码,其中明确给出了中心和半径。
ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]
评论
$ \ begingroup $
摘下太阳镜...上帝之母...
$ \ endgroup $
–太空
2012年5月18日18:37
$ \ begingroup $
您是否碰巧参考了圆柱映射?也许逆映射的方程式是? @ niki-estner
$ \ endgroup $
–意大利
18年6月4日在17:15
评论
Nikie似乎提供了无所不包的解决方案。但是,如果您知道相机始终与罐子成“正方形”且没有令人迷惑的背景,它将变得更加简单。然后,找到罐子的边缘,并应用简单的三角(arcsine?)变换,而无需进行任何其他操作。将图像展平后,您可以隔离标签本身。@Daniel这就是我在这里所做的。理想情况下,也应考虑并非完全平行的投影,但我没有考虑。
工作很好但是代码显示我的系统错误。我正在使用matlab 2017a兼容吗。谢谢
当我运行您的程序时,出现以下错误:C:/ Users / Michael Balcerzak / Documents / opencv-text-detection / opencv-text-detection / imageSticting.py:103:UserWarning:双二次插值行为已由于scikit-image实现中的错误。现在,新版本可作为SciPy插值函数的包装,而该函数本身未经验证是正确的实现。在确定skimage的实现之前,我们建议改用双线性或双三次插值。 warped = tf.warp(img,tform3,order = 2)如何解决此问题
@MichaelBalcerzak欢迎来到SE.SP!请不要添加评论作为答案。从目前的角度来看,您的问题并不适合该网站。您在询问编程问题。即使代码的原因是信号处理,我们也不在这里调试代码。请问您有关堆栈溢出本身的问题。