如何为 numpy 数组创建圆形掩码?

Posted

技术标签:

【中文标题】如何为 numpy 数组创建圆形掩码?【英文标题】:How can I create a circular mask for a numpy array? 【发布时间】:2017-12-05 12:13:45 【问题描述】:

我正在尝试在 Python 中对图像进行圆形遮罩。我在网上找到了一些示例代码,但我不确定如何更改数学以使我的圈子出现在正确的位置。

我有一张image_data 类型为numpy.ndarray 的图片,形状为(3725, 4797, 3)

total_rows, total_cols, total_layers = image_data.shape
X, Y = np.ogrid[:total_rows, :total_cols]
center_row, center_col = total_rows/2, total_cols/2
dist_from_center = (X - total_rows)**2 + (Y - total_cols)**2
radius = (total_rows/2)**2
circular_mask = (dist_from_center > radius)

我看到这段代码应用 欧式距离 来计算 dist_from_center,但我不明白 X - total_rowsY - total_cols 部分。这会生成一个四分之一圆的蒙版,以图像的左上角为中心。

XY 在圈子里扮演什么角色?以及如何修改此代码以生成以图像其他位置为中心的蒙版?

【问题讨论】:

这实际上不是欧几里得距离。应该是那个总和的平方根。是的,我认为你是对的,应该是 center_rowcenter_col 而不是 total... 我认为这段代码会产生一个以左上角为中心的四分之一圆蒙版,而不是图像中的居中圆蒙版(注意 @987654333 @ 在这种情况下仍然是错误的)。 @AlexanderReynolds 是的,它正在形成一个四分之一圆形掩码,您能否解释一下您是如何确定的?请您用更简单的术语解释一下np.ogrid,网络上所有关于np.ogrid 的解释都在进行中远在我头顶。 当然,当我看到这个时我正在使用手机,所以没有写完整的答案,我想当我回到家时会有其他人有,但我想不会。我会去的。 @AlexanderReynolds 谢谢,我将等待您的回复。 【参考方案1】:

您上网的算法有一部分是错误的,至少对您而言是这样。如果我们有以下图像,我们希望它像这样被遮罩:

创建这样的蒙版最简单的方法是您的算法如何处理它,但它并没有以您想要的方式呈现,也没有让您能够以简单的方式修改它。我们需要做的是查看图像中每个像素的坐标,并获取该像素是否在半径内的真/假值。例如,这是一张放大的图片,显示了圆的半径和严格在该半径内的像素:

现在,要确定哪些像素位于圆圈内,我们需要图像中每个像素的索引。函数np.ogrid() 给出了两个向量,每个向量都包含像素位置(或索引):列索引有一个列向量,行索引有一个行向量:

>>> np.ogrid[:4,:5]
[array([[0],
       [1],
       [2],
       [3]]), array([[0, 1, 2, 3, 4]])]

这种格式对broadcasting 很有用,因此如果我们在某些函数中使用它们,它实际上会创建一个包含所有索引的网格,而不仅仅是这两个向量。因此,我们可以使用np.ogrid() 创建图像的索引(或像素坐标),然后检查每个像素坐标以查看它是在圆圈内还是在圆圈外。为了判断它是否在中心内,我们可以简单地找到从中心到每个像素位置的欧几里得距离,然后如果该距离小于圆半径,我们将其标记为包含 在掩码中,如果大于此值,我们会将其从掩码中排除

现在我们已经有了创建这个掩码的函数所需的一切。此外,我们将为其添加一些不错的功能;我们可以发送中心和半径,或者让它自动计算它们。

def create_circular_mask(h, w, center=None, radius=None):

    if center is None: # use the middle of the image
        center = (int(w/2), int(h/2))
    if radius is None: # use the smallest distance between the center and image walls
        radius = min(center[0], center[1], w-center[0], h-center[1])

    Y, X = np.ogrid[:h, :w]
    dist_from_center = np.sqrt((X - center[0])**2 + (Y-center[1])**2)

    mask = dist_from_center <= radius
    return mask

在这种情况下,dist_from_center 是一个与指定高度和宽度相同的矩阵。它将列和行索引向量广播到一个矩阵中,其中每个位置的值是到中心的距离。如果我们将此矩阵可视化为图像(将其缩放到适当的范围),那么它将是从我们指定的中心辐射的渐变:

因此,当我们将其与radius 进行比较时,它与对这个梯度图像进行阈值处理相同。

请注意,最终的掩码是一个布尔矩阵; True 如果该位置在指定中心的半径内,False 否则。因此,我们可以使用这个掩码作为我们关心的像素区域的指示符,或者我们可以采用与该布尔值相反的值(numpy 中的~)来选择该区域之外的像素。因此,使用此函数将圆圈外的像素着色为黑色,就像我在这篇文章顶部所做的那样,非常简单:

h, w = img.shape[:2]
mask = create_circular_mask(h, w)
masked_img = img.copy()
masked_img[~mask] = 0

但是如果我们想在与中心不同的点创建一个圆形遮罩,我们可以指定它(注意,该函数需要x, y 顺序的中心坐标,而不是索引row, col = y, x 顺序):

center = (int(w/4), int(h/4))
mask = create_circular_mask(h, w, center=center)

因为我们没有给出半径,所以它会给出最大的半径,这样圆仍然适合图像边界:

或者我们可以让它计算中心但使用指定的半径:

radius = h/4
mask = create_circular_mask(h, w, radius=radius)

给我们一个半径不完全延伸到最小尺寸的圆心圆:

最后,我们可以指定任何我们想要的半径和中心,包括延伸到图像边界之外的半径(中心甚至可以在图像边界之外!):

center = (int(w/4), int(h/4))
radius = h/2
mask = create_circular_mask(h, w, center=center, radius=radius)

你在网上找到的算法做的相当于将中心设置为(0, 0),将半径设置为h

mask = create_circular_mask(h, w, center=(0, 0), radius=h)

【讨论】:

+1 。我在顶部添加了if h % 2 == 0: h=h+1if w % 2 == 0: w=w+1,因此它不会削减偶数的最后一列和一行。【参考方案2】:

我想提供一种不涉及 np.ogrid() 函数的方法。我将裁剪一个名为“robot.jpg”的图像,它是 491 x 491 像素。为了便于阅读,我不会像在实际程序中那样定义尽可能多的变量:

导入库:

import matplotlib.pyplot as plt
from matplotlib import image
import numpy as np

导入图像,我将其称为“z”。这是一张彩色图像,所以我也只提取了一个颜色通道。接下来,我将显示它:

z = image.imread('robot.jpg')  
z = z[:,:,1]

zimg = plt.imshow(z,cmap="gray")
plt.show()

robot.jpg as displayed by matplotlib.pyplot

要使用一个带有圆圈的 numpy 数组(图像矩阵)作为遮罩,我将从以下开始:

x = np.linspace(-10, 10, 491)
y = np.linspace(-10, 10, 491)
x, y = np.meshgrid(x, y)
x_0 = -3
y_0 = -6
mask = np.sqrt((x-x_0)**2+(y-y_0)**2)

注意最后一行上的圆方程,其中 x_0 和 y_0 定义了网格中圆的中心点,该网格有 491 个元素高和宽。因为我将网格定义为在 x 和 y 中从 -10 到 10,所以 x_0 和 x_y 就是在该单位系统内设置圆的中心点相对于图像的中心。

要查看我运行的结果:

maskimg = plt.imshow(mask,cmap="gray")
plt.show()

Our "proto" masking circle

为了将其转换为实际的二进制值掩码,我只需将低于某个值的每个像素设置为 0,然后将高于某个值的每个像素设置为 256。“确定value" 将以上面定义的相同单位确定圆的半径,因此我将其称为 'r'。在这里,我将 'r' 设置为某个值,然后循环遍历遮罩中的每个像素以确定它应该是“开”还是“关”:

r = 7
for x in range(0,490):
        for y in range(0,490):
                if mask[x,y] < r:
                        mask[x,y] = 0
                elif mask[x,y] >= r:
                        mask[x,y] = 256

maskimg = plt.imshow(mask,cmap="gray")
plt.show()

The mask

现在我将蒙版乘以图像元素,然后显示结果:

z_masked = np.multiply(z,mask)

zimg_masked = plt.imshow(z_masked,cmap="gray")
plt.show()

要反转掩码,我可以在上面的阈值循环中交换 0 和 256,如果这样做,我会得到:

Masked version of robot.jpg

【讨论】:

【参考方案3】:

其他答案有效,但速度较慢,因此我将使用skimage.draw.circle 提出答案。使用它更快,我发现它使用起来很简单。只需指定圆心和半径,然后使用输出创建蒙版

from skimage.draw import circle
mask = np.zeros((10, 10), dtype=np.uint8)
row = 4
col = 5
radius = 5
rr, cc = circle(row, col, radius)
mask[rr, cc] = 1

运行它会创建一个快速掩码,但它也带有警告“FutureWarning: circle is deprecated in support disk.circle will be removed in version 0.19”

【讨论】:

以上是关于如何为 numpy 数组创建圆形掩码?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Google 课堂分享按钮使用圆形徽标?

圆形numpy数组索引

具有边界比率的圆形掩模

iOS UIImageView如何为圆形ImageView添加填充

xml Android - 如何为ProgressBar定义水平/圆形样式

xml Android - 如何为ProgressBar定义水平/圆形样式