处理图像以查找外部轮廓
Posted
技术标签:
【中文标题】处理图像以查找外部轮廓【英文标题】:Process image to find external contour 【发布时间】:2014-11-02 05:30:47 【问题描述】:我有数百个 PNG 图像,我必须为其生成相应的黑白图像,以显示对象的外部轮廓。源 PNG 图像具有 alpha 通道,因此图像的“无对象”部分是 100% 透明的。
棘手的部分是,如果对象中有孔,则不应在轮廓中看到这些孔。因此,如果源图像是甜甜圈,则相应的轮廓图像将是一条锯齿状的圆形线,中间没有同心的较小线。
这是一个示例图像、来源及其轮廓:
是否有任何库或命令行工具可以做到这一点?理想情况下,可以从 Python 中使用。
【问题讨论】:
【参考方案1】:我发现了一个非常有用的 REST API 用于轮廓跟踪,比使用其他程序更简单。我在一个 ruby 项目中使用了 API,现在也使用了 CURL;效果很好!
http://tracecontour.com/
有一个演示和 API 文档。 所以我用你的甜甜圈图像作为测试的源图像。唯一的问题是您的图像没有透明的背景颜色。所以我用 Gimp 制作了这个,它有透明背景,你的图像是黑色的。因此,当您使用 matching_not_color=0 调用 API 时,您将引用图像中不是透明颜色的任何部分。
donut image with background transparent color
现在我多次使用 API 进行一些测试。如您所见,您可以获得 JSON 数据(轮廓折线)或内部绘制轮廓的示例图像。所以我用 CURL 来调用这个命令的 API:
curl -v -H "Accept: application/json" -X POST -F "png_image=@rRBal.png" -F "match_not_color=0" -F
"options[compress][linear]=true" -F
"options[compress][visvalingam]=true" -o output.png
http://tracecontour.com/outlines_image
我使用了 match_not_color=0。通过这种方式,我要求考虑任何像素不是背景透明色。结果我得到了这个 png 图像(作为 curl 命令状态的 output.png)。
returned api sample png image
如您所见,这里的匹配区域是蓝色的。每条外折线为红色,每条内折线为绿色。我使用 visvalingam 选项来获得不太精确的轮廓,但您可以要求最精确的。
如果你这样调用,你会得到坐标为 (x,y) 的 JSON 数据
curl -v -H "Accept: application/json" -X POST -F
"png_image=@rRBal.png" -F "match_not_color=0" -F
"options[compress][linear]=true" -F
"options[compress][visvalingam]=true" http://tracecontour.com/outlines
&> /dev/stdout
在这里您可以获得最精确的结果。这里的命令(没有 visvalingam 选项):
curl -v -H "Accept: application/json" -X POST -F
"png_image=@rRBal.png" -F "match_not_color=0" -F
"options[compress][linear]=true" -o output.png
http://tracecontour.com/outlines_image
结果就在这里,很精确,很快就能得到
returned api most precise contour
非常有用的 API!
【讨论】:
【参考方案2】:我同意 amgaera。如果要查找轮廓,Python 中的 OpenCV 是可以使用的最佳工具之一。与他/她的帖子一样,使用findContours
方法并使用RETR_EXTERNAL
标志来获取形状的最外层轮廓。这里有一些可重现的代码来说明这一点。您首先需要安装 OpenCV 和 NumPy
才能进行此操作。
我不确定您使用的是什么平台,但是:
如果您使用的是 Linux,只需在libopencv-dev
和 python-numpy
上执行 apt-get
(即 sudo apt-get install libopencv-dev python-numpy
)。
如果您使用的是 Mac OS,请安装 Homebrew,然后通过 brew install opencv
安装,然后通过 brew install numpy
安装。
如果您使用的是 Windows,最好的方法是通过 Christoph Gohlke 的 Windows 非官方 Python 包:http://www.lfd.uci.edu/~gohlke/pythonlibs/
- 检查 OpenCV 包并安装它要求的所有依赖项,包括 @987654339 @ 您可以在此页面上找到。
无论如何,我拍了你的甜甜圈图像,然后只提取了带有甜甜圈的图像。换句话说,我创建了这个图像:
至于您的图像是 PNG 并具有 alpha 通道,这实际上并不重要。只要您在此图像中只包含一个对象,我们实际上根本不需要访问 Alpha 通道。下载此图像后,将其保存为 donut.png
,然后继续运行此代码:
import cv2 # Import OpenCV
import numpy as np # Import NumPy
# Read in the image as grayscale - Note the 0 flag
im = cv2.imread('donut.png', 0)
# Run findContours - Note the RETR_EXTERNAL flag
# Also, we want to find the best contour possible with CHAIN_APPROX_NONE
contours, hierarchy = cv2.findContours(im.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Create an output of all zeroes that has the same shape as the input
# image
out = np.zeros_like(im)
# On this output, draw all of the contours that we have detected
# in white, and set the thickness to be 3 pixels
cv2.drawContours(out, contours, -1, 255, 3)
# Spawn new windows that shows us the donut
# (in grayscale) and the detected contour
cv2.imshow('Donut', im)
cv2.imshow('Output Contour', out)
# Wait indefinitely until you push a key. Once you do, close the windows
cv2.waitKey(0)
cv2.destroyAllWindows()
让我们慢慢看代码。首先我们导入 OpenCV 和 NumPy 包。我将 NumPy 导入为 np
,如果您到处查看 numpy
文档和教程,他们这样做是为了尽量减少打字。 OpenCV 和 NumPy 相互配合,这就是为什么你需要同时安装这两个包的原因。然后我们使用imread
读取图像。我将标志设置为0
以使图像灰度化以使事情变得简单。一旦我加载了图像,我就运行findContours
,这个函数的输出输出了一个包含两个东西的元组:
contours
- 这是一个数组结构,可为您提供图像中检测到的每个轮廓的 (x,y)
坐标。
hierarchy
- 这包含有关您检测到的轮廓的其他信息,例如拓扑,但为了这篇文章,我们跳过它。
请注意,我指定了RETR_EXTERNAL
来检测对象的最外层轮廓。我还指定了CHAIN_APPROX_NONE
标志,以确保我们获得完整的轮廓而没有任何近似值。一旦我们检测到轮廓,我们就会创建一个全黑的新输出图像。这将包含我们检测到的甜甜圈外轮廓。创建此图像后,我们运行drawContours
方法。您指定要在其中显示轮廓的图像、之前创建的轮廓结构,并且 -1
标志表示要绘制图像中的所有轮廓。如果一切顺利,您应该只检测到一个轮廓。然后,您指定您希望轮廓看起来像什么颜色。在我们的例子中,我们希望它是白色的。之后,您指定要绘制轮廓的厚度。我选择了 3 像素的厚度。
我们要做的最后一件事是显示结果的样子。我调用imshow
来显示原始甜甜圈图像的外观(灰度)以及输出轮廓的外观。 imshow
不是故事的结局。在调用cv2.waitKey(0)
之前,您不会看到任何输出。现在的意思是你可以无限期地显示图像,直到你按下一个键。按下某个键后,cv2.destroyAllWindows()
调用将关闭所有生成的窗口。
这就是我得到的(一旦你重新排列窗口,使它们并排):
另外,如果您想保存图像,只需运行imwrite
即可保存图像。您指定要写入的图像的名称以及要访问的变量。因此,您可以执行以下操作:
cv2.imwrite('contour.png', out)
然后您将此轮廓图像保存到名为 contour.png
的文件中。
这应该足以让您入门。
祝你好运!
【讨论】:
如果我可以访问 OpenCV 安装并有时间编写示例代码,这就是我的答案。干得好! @amgaera - 谢谢 :) 如果我借用了你的想法,我希望你不介意。实际上,我为我回答了更多问题,因为我对 OpenCV 和 Python 还比较陌生。我这样做更像是一种学习练习,但如果我能一路帮助别人,为什么不呢?顺便说一句,我为你的帖子 +1 了你最初的想法! 哦,我一点也不介意 :) 你花时间提供了一个值得支持的正确答案。 当然。谢谢!。会试试看。 在将灰色/白色像素替换为黑色等深色背景后,我能够获得轮廓。再次感谢。【参考方案3】:我会推荐 ImageMagick,它可以从 here 免费获得。无论如何,它包含在许多 Linux 发行版中。它还提供 Python、Perl、php、C/C++ 绑定。
我只是从下面的命令行中使用它。
convert donut.png -channel A -morphology EdgeOut Diamond +channel -fx 'a' -negate output.jpg
基本上,-channel A
选择 alpha(透明度)并应用形态学来提取不透明区域的轮廓。然后+channel
告诉 ImageMagick 我现在再次寻址所有频道。 -fx
是一个自定义函数(运算符),我在其中将输出图像的每个像素设置为 a
- 修改后的 Alpha 通道中的 Alpha 值。
已编辑
以下可能比使用上述fx
运算符更快:
convert donut.png -channel RGBA -separate -delete 0-2 -morphology EdgeOut Diamond -negate output.png
结果:
如果您要勾勒出数百(或数千)张图像,我会推荐 GNU Parallel,可从here 获得。然后它将使用您所有的 CPU 内核来快速完成工作。您的命令将如下所示 - 但请先备份并处理副本,直到您掌握它!
parallel convert -channel A -morphology EdgeOut Diamond +channel -fx 'a' -negate ..jpg ::: *.png
这表示使用:::
之后的所有内容作为要处理的文件。然后并行,使用所有可用的内核,转换每个 PNG 文件并将其名称更改为相应的 JPEG 文件作为输出文件名。
【讨论】:
@MarkSetchell 你的建议让我大开眼界,imagemagick 有多么强大。但是当输入图像的形状由 alpha 通道确定时,我似乎无法找到没有中间孔的轮廓。【参考方案4】:OpenCV 有一个 findContours 函数,可以完全满足您的需求。您需要将轮廓检索模式设置为CV_RETR_EXTERNAL
。要加载图像,请使用 imread 函数。
【讨论】:
以上是关于处理图像以查找外部轮廓的主要内容,如果未能解决你的问题,请参考以下文章