如何为 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_rows
和 Y - total_cols
部分。这会生成一个四分之一圆的蒙版,以图像的左上角为中心。
X
和Y
在圈子里扮演什么角色?以及如何修改此代码以生成以图像其他位置为中心的蒙版?
【问题讨论】:
这实际上不是欧几里得距离。应该是那个总和的平方根。是的,我认为你是对的,应该是center_row
和 center_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+1
和if 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 数组创建圆形掩码?的主要内容,如果未能解决你的问题,请参考以下文章
iOS UIImageView如何为圆形ImageView添加填充