处理图像以找到外部轮廓

7
我有数百张PNG图像需要生成对应的黑白图像,显示对象的外轮廓。源PNG图像具有alpha通道,因此图像的“无对象”部分为100%透明。
棘手的部分是,如果对象有孔洞,则轮廓中不应该看到它们。因此,如果源图像是甜甜圈,则相应的轮廓图像将是一个带有没有同心较小线条的锯齿形圆形线。
这是一个示例图像,源图像及其轮廓:enter image description here 是否有任何库或命令行工具可以做到这一点?理想情况下,可以从Python使用的工具。
4个回答

18

我同意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 opencvbrew install numpy来安装。
  • 如果您使用的是Windows,则使其正常工作的最佳方法是通过Christoph Gohlke的非官方Python软件包 for Windows:http://www.lfd.uci.edu/~gohlke/pythonlibs/ - 检查OpenCV软件包并安装它正在请求的所有依赖项,包括您可以在此页面上找到的NumPy

无论如何,我拿了你的甜甜圈图像,并提取了仅带有甜甜圈图像的部分。 换句话说,我创建了这张图片:

enter image description here

至于您的图像是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 来展示原始甜甜圈图像(灰度)和输出轮廓图像。但这并不是全部。除非你调用 cv2.waitKey(0),否则你看不到任何输出。现在,这意味着你可以无限期地显示图像,直到你按下一个键。一旦按下键,cv2.destroyAllWindows() 调用将关闭生成的所有窗口。

这是我得到的结果(一旦你将窗口重新排列成并排状态):

enter image description here


作为额外的奖励,如果你想保存这个图像,只需运行 imwrite 来保存图像。您需要指定要写入的图像的名称和要访问的变量。因此,你可以做如下的操作:

cv2.imwrite('contour.png', out)

您将把这个轮廓图像保存到名为contour.png的文件中。


这应该足以让您开始了。

祝你好运!


如果我有OpenCV安装和编写示例的时间,我的答案将会是这样的。干得好! - amgaera
@amgaera - 谢谢 :) 希望你不介意我借用了你的想法。实际上,我回答更多是为了自己,因为我对OpenCV和Python相对较新。我这样做更多是为了学习,但如果我能在途中帮助到某人,为什么不呢?顺便说一句,我+1了你的帖子,因为最初是你想出了这个想法! - rayryeng
2
哦,我一点也不介意 :) 你花时间提供了一个值得被点赞的正确答案。 - amgaera
1
当然。谢谢!我会试一下的。 - user1652054
1
我成功地得到了轮廓,在将灰色/白色像素替换为黑色等深色背景后。再次感谢。 - user1652054
显示剩余9条评论

6

OpenCV有一个名为findContours的函数,可以完全满足你的需求。你需要将轮廓检索模式设置为CV_RETR_EXTERNAL。要加载图像,请使用imread函数。


6
我建议使用ImageMagick,可以从这里免费获取。它已经包含在许多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 是一个自定义函数(运算符),其中我将输出图像的每个像素设置为修改后阿尔法通道中的Alpha值a编辑后 以下方法可能比使用上述的 fx 操作符更快:
convert donut.png -channel RGBA -separate -delete 0-2 -morphology EdgeOut Diamond -negate output.png

结果:

在此输入图片描述

如果您需要描绘许多数百(或上千)图像,我建议使用 GNU Parallel,可以从这里下载。然后它将利用所有 CPU 核心快速完成作业。您的命令将类似于这样 - 但请务必先备份并在副本上操作,直到您掌握了它的使用方法!

parallel convert {} -channel A -morphology EdgeOut Diamond +channel -fx 'a' -negate {.}.jpg ::: *.png

这段内容的意思是使用:::后面的所有内容作为需要处理的文件。然后并行地利用所有可用的核心,将每个PNG文件转换成相应的JPEG文件,并将其名称更改为输出文件名。


@MarkSetchell,您的建议让我认识到了ImageMagick的强大之处。但是,当输入图像的形状由Alpha通道确定时,我似乎找不到如何获取没有中间孔的轮廓的方法。 - Passiday

-3

我找到了一个非常有用的轮廓追踪REST API,比使用其他程序更简单。 我在Ruby项目中使用了API,现在也通过使用CURL;它运行得非常好!

http://tracecontour.com/

这里有一个演示和API文档。 所以我使用了你的甜甜圈图片作为测试的源图像。唯一的问题是你的图片没有透明背景颜色。所以我使用Gimp制作了这个带有透明背景的图片,其中你的图片是黑色的部分。所以当你调用API时,如果matching_not_color=0,你引用的是不透明颜色之外的任何图像部分。

带有背景透明颜色的甜甜圈图片

现在我多次使用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)。

返回的API示例PNG图像

正如你在这里看到的匹配区域是蓝色的。每个外部折线是红色的,每个内部折线是绿色的。我尝试了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

结果在这里,非常精确且获取速度非常快

返回的API最精确的轮廓

非常有用的API!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接