处理图像以查找外部轮廓

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-devpython-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 函数。

【讨论】:

以上是关于处理图像以查找外部轮廓的主要内容,如果未能解决你的问题,请参考以下文章

18KW15/1-OpenCV入门-初识轮廓

图像轮廓之查找并绘制轮廓

从轮廓位置生成边界框

深度好文Python图像处理之目标物体轮廓提取

如何将轮廓层次结构从 python openCV 转换为 emgu cv 以查找封闭轮廓

OpenCV图像处理应用(面向Python)之图像轮廓