我如何 select 落在由 numpy 数组表示的图像中的轮廓内的像素?

How can I select the pixels that fall within a contour in an image represented by a numpy array?

我在存储为二维 numpy 数组的图像上绘制了一组轮廓点。轮廓由 2 个浮点值的 numpy 数组表示,每个 x 和 y 坐标。 These coordinates 不是整数,不能与像素完美对齐,但它们确实会告诉您轮廓点相对于像素的位置。

我希望能够 select 落在轮廓内的像素。我写了一些与此处给出的答案几乎相同的代码:

temp_list = []
for a, b in zip(x_pixel_nos, y_pixel_nos):
    temp_list.append([[a, b]]) # 2D array of shape 1x2
temp_array = np.array(temp_list)
contour_array_list = []
contour_array_list.append(temp_array)



lst_intensities = []

# For each list of contour points...
for i in range(len(contour_array_list)):
    # Create a mask image that contains the contour filled in
    cimg = np.zeros_like(pixel_array)
    cv2.drawContours(cimg, contour_array_list, 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(pixel_array[pts[0], pts[1]])

当我运行这个的时候,我得到一个错误error: OpenCV(3.4.1) /opt/conda/conda-bld/opencv-suite_1527005509093/work/modules/imgproc/src/drawing.cpp:2515: error: (-215) npoints > 0 in function drawContours

我猜测此时 openCV 对我不起作用,因为我的轮廓是浮点数,而不是整数,openCV 无法处理 drawContours。如果我将轮廓的坐标转换为整数,我会失去很多精度。

那么我怎样才能得到落在轮廓内的像素呢?

这应该是一项微不足道的任务,但到目前为止我还没有找到一种简单的方法来完成它。

我认为找到落在轮廓内的所有像素的最简单方法如下。

轮廓由一组非整数点描述。我们可以把这些点看成是一个多边形的顶点,等高线就是一个多边形。

我们首先找到多边形的边界框。此边界框外的任何像素都不在多边形内,不需要考虑。

对于边界框内的像素,我们使用经典测试来测试它们是否在多边形内部:从无限远的某个点到该点跟踪一条线,并计算交叉的多边形边(线段)的数量.如果此数字为奇数,则该点位于多边形内。结果是Matplotlib contains .

我还在习惯 Python 和 Numpy,如果您是 Python 专家,这可能是一个有点笨拙的代码。但我认为它的作用是直截了当的。首先,它计算多边形的边界框,然后创建一个数组 points,其中包含落在该边界框内的所有像素的坐标(我假设像素质心才是最重要的)。它将 matplotlib.path.contains_points 方法应用到这个数组,产生一个布尔数组 mask。最后,它重塑这个数组以匹配边界框。

import math
import matplotlib.path
import numpy as np

x_pixel_nos = [...]
y_pixel_nos = [...] # Data from https://gist.github.com/sdoken/173fae1f9d8673ffff5b481b3872a69d

temp_list = []
for a, b in zip(x_pixel_nos, y_pixel_nos):
   temp_list.append([a, b])

polygon = np.array(temp_list)
left = np.min(polygon, axis=0)
right = np.max(polygon, axis=0)
x = np.arange(math.ceil(left[0]), math.floor(right[0])+1)
y = np.arange(math.ceil(left[1]), math.floor(right[1])+1)
xv, yv = np.meshgrid(x, y, indexing='xy')
points = np.hstack((xv.reshape((-1,1)), yv.reshape((-1,1))))

path = matplotlib.path.Path(polygon)
mask = path.contains_points(points)
mask.shape = xv.shape

在这段代码之后,需要做的是定位图像中的边界框,并对像素进行着色。 left 包含图像中对应于 mask.

左上角像素的像素

可以提高该算法的性能。如果为测试像素而追踪的光线是水平的,您可以想象沿水平线的所有像素都可以从对左侧像素所做的工作中受益。也就是说,计算图像行上所有像素的 in/out 状态比计算单个像素的成本稍微多一点是可能的。

matplotlib.path.contains_points算法比对所有点进行单点测试更有效,因为适当地对多边形边和顶点进行排序可以使每次测试成本更低,而且排序只需要进行一次一次测试多个点时。但是这个算法没有考虑到我们要在同一条线上测试很多点。


这些是我做的时候看到的

pp.plot(x_pixel_nos, y_pixel_nos)
pp.imshow(mask)

在 运行 上面的代码和您的数据之后。请注意,y 轴与 imshow 反转,因此是垂直镜像的形状。

借助 python 中的 Shapely 库,可以轻松完成:

从 shapely.geometry 导入点、多边形

将所有的 x,y 坐标转换为匀称的多边形:

coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)]
pl = Polygon(coords)

现在在每个多边形中找到像素为:

minx, miny, maxx, maxy = pl.bounds
minx, miny, maxx, maxy = int(minx), int(miny), int(maxx), int(maxy)
box_patch = [[x,y] for x in range(minx,maxx+1) for y in range(miny,maxy+1)]
pixels = []
for pb in box_patch: 
  pt = Point(pb[0],pb[1])
  if(pl.contains(pt)):
    pixels.append([int(pb[0]), int(pb[1])])
return pixels

为每组坐标放置此循环,然后为每个多边形放置此循环。

很高兴:)

skimage.draw.polygon 可以处理这个 1,请参阅该页面上此函数的示例代码。

如果你只想要轮廓,你可以skimage.segmentation.find_boundaries 2