使用Python中的OpenCV在轮廓边界内访问像素值

20
我正在使用Python 2.7.9上的OpenCV 3.0.0。我试图在静态背景下的视频中跟踪一个物体并估计它的一些属性。由于图像中可能会有多个移动的物体,因此我希望能够区分它们并在视频的其余帧中单独跟踪它们。
我想到的一种方法是将图像转换为二进制,获取斑点(在这种情况下是跟踪对象)的轮廓,并获取对象边界的坐标。然后,我可以在灰度图像中转到这些边界坐标,获取该边界周围的像素强度,并在其他帧中跟踪这种颜色梯度/像素强度。通过这种方式,我可以将两个对象分开,使它们不被认为是下一帧中的新对象。
我已经有了轮廓边界的坐标,但我不知道如何检索该边界内的像素强度。请问有人能帮助我吗?
谢谢!

cv2.findContours 肯定能为您完成工作,并返回每个轮廓的 (x,y) 坐标列表。然后,您可以使用这些坐标来索引图像并获取正确的强度值。但是,我不确定您想要如何存储这些强度值。您只想要一个单一的一维强度数组吗?您想将它们放入某种掩模中吗?您能详细说明一下您希望如何存储这些强度值吗? - rayryeng
我使用cv2.findContours找到了轮廓点,并将它们存储在一个数组中。我只想要该边界内所有像素的强度值的一维数组。此外,我不确定应该如何进行索引。您能否解释一下? - Kaya311
我使用了一个 for 循环来索引 cv2.findContours 的输出。然后,我只是使用 pxlVal = img[x, y] 来获取对象边界(轮廓)上的像素值。如何获取边界内的像素? - Kaya311
请看一下我的答案。 - rayryeng
嘿@rayryreg,请在这里检查我的问题[http://stackoverflow.com/questions/36861334/i-want-to-plot-the-distance-between-centroid-of-each-contour-and-respective-boun?noredirect=1#comment61291472_36861334]。 - PJL
3个回答

33

根据我们的评论,您可以创建一个numpy数组列表,其中每个元素是描述每个对象轮廓内部强度的强度值。具体来说,对于每个轮廓,创建一个二进制掩码填充轮廓内部,找到填充对象的(x,y)坐标,然后索引到您的图像并获取强度。

我不确定您如何设置代码,但假设您有一张名为img的灰度图像。您可能需要将图像转换为灰度图像,因为cv2.findContours适用于灰度图像。使用此功能,正常调用cv2.findContours

import cv2
import numpy as np

#... Put your other code here....
#....

# Call if necessary
#img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Call cv2.findContours
contours,_ = cv2.findContours(img, cv2.RETR_LIST, cv2.cv.CV_CHAIN_APPROX_NONE)

contours现在是一个由3D的numpy数组组成的列表,每个数组的大小为N x 1 x 2,其中N是每个对象的轮廓点的总数。

因此,您可以像这样创建我们的列表:

# Initialize empty list
lst_intensities = []

# For each list of contour points...
for i in range(len(contours)):
    # Create a mask image that contains the contour filled in
    cimg = np.zeros_like(img)
    cv2.drawContours(cimg, contours, i, color=255, thickness=-1)

    # Access the image pixels and create a 1D numpy array then add to list
    pts = np.where(cimg == 255)
    lst_intensities.append(img[pts[0], pts[1]])
对于每个轮廓,我们创建一个空白图像,然后在这个空白图像中绘制填充的轮廓。您可以通过将 thickness 参数设置为 -1 来填充轮廓所占据的区域。我将轮廓的内部设为 255。之后,我们使用 numpy.where 查找与某个条件匹配的数组中所有行和列的位置。在我们的情况下,我们想要找到等于 255 的值。之后,我们使用这些点来索引进入图像以获取位于轮廓内部的像素强度。 lst_intensities 包含了一个由 1D numpy 数组组成的列表,其中每个元素都给出了属于每个对象轮廓内部的强度。要访问每个数组,只需执行 lst_intensities[i],其中 i 是要访问的轮廓。

@Kaya311 - 没问题。祝你好运! - rayryeng
嗨,我正在做完全相同的事情,但是出现了错误 error: (-215) npoints > 0 in function drawContours。这是因为我的轮廓点是浮点数吗? - Semihcan Doken
1
@itsnotme 可能是这样。绘图函数直接在图像坐标上工作,因此浮点值没有意义。 - rayryeng
@rayryeng 我猜这意味着我不能使用openCV来完成选择位于轮廓内的像素的目标。你有没有什么提示,可以让我用其他方式实现同样的功能? - Semihcan Doken
@itsnotme,如果你能让我看到你已经做了什么,那也会有所帮助。你有问题或者代码可以给我看吗? - rayryeng
显示剩余2条评论

3

@rayryeng的回答非常好!

在我的实现中有一点需要注意:np.where()返回一个元组,其中包含一组行索引和一组列索引。因此,pts[0] 包括一个 行索引 列表,对应于图像的高度,pts[1] 包括一个 列索引 列表,对应于图像的宽度。而 img.shape 返回的是 (行数, 列数, 通道数)。因此,我认为应该使用 img[pts[0], pts[1]] 来切片 img 后面的 ndarray


OpenCV中使用的约定是保持方法中的x坐标为水平,而y坐标为垂直。这就是为什么上面代码中的符号被翻转的原因。这更多是为了与OpenCV兼容,而不是访问实际像素本身。 - rayryeng
感谢您的评论,@rayryeng! - Jundong
感谢您的评论,@rayryeng!当您说x坐标是水平的时候,这是否意味着返回的轮廓包含元组中的“列索引”作为第一个元素?当我使用了您的想法后,img[pts[0], pts[1]]对我有效;但是img[pts[1], pts[0]]引发了“超出范围”的索引错误。可能的原因是什么? - Jundong
1
实际上,你是正确的。我需要修改我的帖子。我道歉。我投了一票。 - rayryeng

1
抱歉,由于我声望不够,无法将此评论添加到第一个正确答案中。
实际上,上面的好代码有些改进:我们可以跳过获取分数的那一行,因为灰度图像和np.zeros临时图像具有相同的形状,我们可以直接在括号内使用“where”。就像这样:
# (...) opening image, converting into grayscale, detect contours (...)
intensityPer = 0.15
for c in contours:
    temp = np.zeros_like(grayImg)
    cv2.drawContours(temp, [c], 0, (255,255,255), -1)
    if np.mean(grayImg[temp==255]) > intensityPer*255:
        pass # here your code

通过此示例,我们保证轮廓内区域的平均强度将至少为最大强度的15%。

如果我错了,请告诉我,同时降低答案的评级,这样我也可以修复我的代码,谢谢! - Jaime Finat
作为最佳答案甚至被接受的答案可能会发生变化,请参考@rayryeng的答案。这样,添加一个建立在其他答案基础上的答案就可以完全没有问题。一些复杂的开发过程是无法用一个简短的答案来回答的。祝好! - cmbarbu

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