list(contour)'的结果表示什么?

4 投票
2 回答
4595 浏览
提问于 2025-04-17 11:56

我想搞清楚“轮廓”是什么意思,以及当我们使用OpenCV中的cv.FindContours函数创建轮廓时,存储了什么值(我用的是OpenCV 2.3.1和Python)。我用了一张简单的图片来测试:

这里插入图片描述

找到轮廓后,我在ipython中执行了以下命令:

In [8]: contour
Out[8]: <cv2.cv.cvseq at 0x90a31a0>

In [10]: list(contour)
Out[10]: 
 [(256, 190),
  (255, 191),
  (112, 191),
  (255, 191),
  (256, 190),
  (257, 191),
  (257, 190)]

第一条命令显示,轮廓是一个cvSeq对象。

我在图片上标记了这些点,得到了如下图像(红色标记的圆圈是这些点):

这里插入图片描述

我不太明白这是什么意思。

所以我的问题是,第二条命令的结果中的值(也就是list(contour))表示什么?


编辑:以下是我使用的代码。

import cv
img = cv.LoadImage('simple.jpeg')
imgg = cv.LoadImage('simple.jpeg',cv.CV_LOAD_IMAGE_GRAYSCALE)
storage = cv.CreateMemStorage(0)
contours = cv.FindContours(imgg,storage,cv.CV_RETR_TREE,cv.CV_CHAIN_APPROX_SIMPLE,(0,0))
print list(contours)
for i in list(contours):
    cv.Circle(img,i,5,(0,0,255),1)
cv.ShowImage('img',img)
cv.WaitKey(0)

2 个回答

4

我对list(contour)的输出做了一些额外的工作,以便更好地理解轮廓,感谢数学咖啡的回答。

1) 我在测试图像时犯了个错误。我以为它是二值图像,实际上它是一个灰度图像,还包含其他颜色。(感谢数学咖啡)。所以我把图像改成了纯黑白图像,这样我就能得到一个轮廓,然后再次测试。这次,list(contour)给出了4个值,当我把它们画在图像上时,正好是那个框的四个角。

新图像输出

当我们使用'cv.DrawContours'函数时,会画出连接这些顶点的线。所以我推测cv.FindContours存储的是轮廓的顶点位置,实际上它是一个多边形。

2) 为了再次测试,我拿了一张T形的图像。

测试图像2

对此,我期待得到一个包含8个值的列表,这8个值是T形的8个角。

图像2的输出

`list(contour)`打印出的列表包含10个值。(多出的2个值可能是我绘制时的错误)

[(92, 58), (92, 108), (174, 108), (175, 109), (175, 239), (225, 239), (225, 109), (226, 108), (285, 108), (285, 58)]

这意味着cv.FindContours创建了一个cvseq对象。里面存储了我上面假设的值。

3) 上面的例子只找到了一个轮廓。那么在什么情况下会找到多个轮廓呢?我没有完全理解数学咖啡解释的多个链接序列的概念。为了测试这个,我拿了一张第三张图像。

测试图像3

现在cv.FindContours找到了三个轮廓。记住,每个轮廓都是一个框的4个角的列表。这三个列表存储在一个单一的cvseq对象中,而指针只指向第一个轮廓,也就是第一个框的顶点列表。因此,使用上面的代码,只画出了一个框的角。

要获取第二个顶点的列表,我们使用contour.h_next函数(感谢数学咖啡,我直到现在才知道它的功能)。现在它指向第二个框的轮廓。这样我们就可以遍历里面的所有列表。

所以我添加了一个简单的while循环,如下所示:

while contours:
   print list(contours)
   for i in list(contours):
       cv.Circle(img,i,5,(0,0,255),3)
   contours = contours.h_next()

我得到了三个列表,分别对应三个框的角:

[(196, 237), (196, 279), (357, 279), (357, 237)]
[(141, 136), (141, 201), (346, 201), (346, 136)]
[(33, 39), (33, 92), (206, 92), (206, 39)]

输出的图像:

图像3的输出

所以你可以想象,如果是一个圆形,"会有很多顶点"的输出会是什么样子。

现在一切都简单了。我之前没能理解轮廓的值。这就是为什么会有这么多混乱。谢谢。

更新 - 1:

关于新cv2模块中轮廓的更多细节可以在这里找到:轮廓 -1 : 入门

更新 - 2:

所有这些解释都是针对cv2.CHAIN_APPROX_SIMPLE的。但如果我们使用cv2.CHAIN_APPROX_NONE,则会得到轮廓上的所有点。这个文章中详细解释了这个问题,并提供了示例:轮廓 - 5 : 层次结构

4

好的,我看了你的图片,你确实得到了每个区域的顶点。花了我一段时间才搞明白,因为我用的是 cv2 接口,而不是 cv 的。

几点说明:

  • 你的输入图片 simple.jpeg 除了0和255,还有很多其他的灰度值,这很可能是因为JPEG压缩造成的。
  • 因此,你从 FindContours 得到了多个区域,都是因为不同的灰度级别。
  • cv.FindContours 返回的是多个相连的序列,你需要逐个遍历它们才能获取所有的轮廓。
  • 你在示例中得到的轮廓是边界上的一个。

为了演示,我们来画出所有的轮廓。

contours = cv.FindContours(imgg,storage,cv.CV_RETR_TREE,cv.CV_CHAIN_APPROX_SIMPLE,(0,0))
colours = [ (0,255,0,0),   # green
            (255,0,0,0),   # blue
            (255,255,0,0), # cyan
            (0,255,255,0), # yellow
            (255,0,255,0), # magenta
            (0,0,255,0)]   # red 
i=0
while contours:
    cv.DrawContours(img, contours, colours[i], colours[i], 0, thickness=-1)
    i = (i+1) % len(colours)
    contours = contours.h_next() # go to next contour
cv.ShowImage('img',img)
cv.WaitKey(0)

contours drawn

所以我们看到你在原问题中用 list(contours) 得到的第一个轮廓,就是方块底部那条小绿条,得到的点对应它的顶点。

在矩形边缘和角落周围出现这么多奇怪的小轮廓,可能是因为保存图片为JPEG时引入的压缩伪影,因为JPEG是有损的。如果你用无损格式保存你的方块(比如PNG或TIFF),你就只会得到一个由矩形角定义的轮廓。

总结经验:

  • cv.FindContours 确实给出了每个轮廓的“顶点”。
  • 你需要用 contours = contours.h_next() 来遍历每个轮廓。
  • 如果保存为JPEG,记得要期待伪影!用TIFF/PNG或其他无损格式吧!

撰写回答