如何为 numpy 数组创建圆形掩码? [英] How can I create a circular mask for a numpy array?

查看:83
本文介绍了如何为 numpy 数组创建圆形掩码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试用 Python 圆形遮罩图像.我在网上找到了一些示例代码,但我不确定如何更改数学以将我的圆圈放在正确的位置.

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

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

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

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

解决方案

您上网的算法有部分错误,至少就您的目的而言是这样.如果我们有以下图像,我们希望它像这样被屏蔽:

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

现在,要找出圆圈内的像素,我们需要图像中每个像素的索引.函数

所以当我们将它与 radius 进行比较时,它与对这个梯度图像进行阈值处理是相同的.

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

h, w = img.shape[:2]掩码 = 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))掩码 = create_circular_mask(h, w, center=center)

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

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

radius = h/4掩码 = create_circular_mask(h, w, radius=radius)

给我们一个半径不精确延伸到最小维度的中心圆:

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

center = (int(w/4), int(h/4))半径 = h/2掩码 = create_circular_mask(h, w, center=center, radius=radius)

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

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

I am trying to circular mask an image in Python. I found some example code on the web, but I'm not sure how to change the maths to get my circle in the correct place.

I have an image image_data of type numpy.ndarray with shape (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)

I see that this code applies euclidean distance to calculate dist_from_center, but I don't understand the X - total_rows and Y - total_cols part. This produces a mask that is a quarter of a circle, centered on the top-left of the image.

What role are X and Y playing on the circle? And how can I modify this code to produce a mask that is centered somewhere else in the image instead?

解决方案

The algorithm you got online is partly wrong, at least for your purposes. If we have the following image, we want it masked like so:

The easiest way to create a mask like this is how your algorithm goes about it, but it's not presented in the way that you want, nor does it give you the ability to modify it in an easy way. What we need to do is look at the coordinates for each pixel in the image, and get a true/false value for whether or not that pixel is within the radius. For example, here's a zoomed in picture showing the circle radius and the pixels that were strictly within that radius:

Now, to figure out which pixels lie inside the circle, we'll need the indices of each pixel in the image. The function np.ogrid() gives two vectors, each containing the pixel locations (or indices): there's a column vector for the column indices and a row vector for the row indices:

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

This format is useful for broadcasting so that if we use them in certain functions, it will actually create a grid of all the indices instead of just those two vectors. We can thus use np.ogrid() to create the indices (or pixel coordinates) of the image, and then check each pixel coordinate to see if it's inside or outside the circle. In order to tell whether it's inside the center, we can simply find the Euclidean distance from the center to every pixel location, and then if that distance is less than the circle radius, we'll mark that as included in the mask, and if it's greater than that, we'll exclude it from the mask.

Now we've got everything we need to make a function that creates this mask. Furthermore we'll add a little bit of nice functionality to it; we can send in the center and the radius, or have it automatically calculate them.

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

In this case, dist_from_center is a matrix the same height and width that is specified. It broadcasts the column and row index vectors into a matrix, where the value at each location is the distance from the center. If we were to visualize this matrix as an image (scaling it into the proper range), then it would be a gradient radiating from the center we specify:

So when we compare it to radius, it's identical to thresholding this gradient image.

Note that the final mask is a matrix of booleans; True if that location is within the radius from the specified center, False otherwise. So we can then use this mask as an indicator for a region of pixels we care about, or we can take the opposite of that boolean (~ in numpy) to select the pixels outside that region. So using this function to color pixels outside the circle black, like I did up at the top of this post, is as simple as:

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

But if we wanted to create a circular mask at a different point than the center, we could specify it (note that the function is expecting the center coordinates in x, y order, not the indexing row, col = y, x order):

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

Which, since we're not giving a radius, would give us the largest radius so that the circle would still fit in the image bounds:

Or we could let it calculate the center but use a specified radius:

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

Giving us a centered circle with a radius that doesn't extend exactly to the smallest dimension:

And finally, we could specify any radius and center we wanted, including a radius that extends outside the image bounds (and the center can even be outside the image bounds!):

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

What the algorithm you found online does is equivalent to setting the center to (0, 0) and setting the radius to h:

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

这篇关于如何为 numpy 数组创建圆形掩码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆