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

新手上路,请多包涵

我正在尝试用 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 part这会生成一个四分之一圆的蒙版,以图像的左上角为中心。

XY 在圆圈上扮演什么角色?我如何修改此代码以生成一个位于图像中其他位置中心的蒙版?

原文由 user7469692 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
2 个回答

您在线获得的算法部分错误,至少对于您的目的而言。如果我们有以下图像,我们希望它像这样被屏蔽:

要遮罩的图像屏蔽图像

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

半径内的像素

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

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

这种格式对于 广播 很有用,因此如果我们在某些函数中使用它们,它实际上会创建一个包含所有索引的网格,而不仅仅是这两个向量。因此,我们可以使用 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 否则。所以我们可以使用这个掩码作为我们关心的像素区域的指示器,或者我们可以取该布尔值的相反值( ~ in 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)

四分之一圆掩模

原文由 alkasm 发布,翻译遵循 CC BY-SA 4.0 许可协议

我想提供一种不涉及 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()

由 matplotlib.pyplot 显示的 robot.jpg

要结束一个带有圆圈的 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()

我们的“原型”掩蔽圈

要将其转换为实际的二进制值掩码,我只是将低于特定值的每个像素都设为 0,将高于特定值的每个像素设为 256。“特定值”将以上面定义的相同单位确定圆的半径,所以我将其称为“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()

面具

现在我将按元素乘以图像的遮罩,然后显示结果:

 z_masked = np.multiply(z,mask)

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

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

蒙面版机器人.jpg

原文由 Justine Haupt 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题