# Numpy: Get rectangle area just the size of mask

## Question:

I have an image and a mask. Both are numpy array. I get the mask through GraphSegmentation (cv2.ximgproc.segmentation), so the area isn’t rectangle, but not divided. I’d like to get a rectangle just the size of masked area, but I don’t know the efficient way.

In other words, unmasked pixels are value of 0 and masked pixels are value over 0, so I want to get a rectangle where…

• top = the smallest index of axis 0 whose value > 0
• bottom = the largest index of axis 0 whose value > 0
• left = the smallest index axis 1 whose value > 0
• right = the largest index axis 1 whose value > 0
• image = src[top : bottom, left : right]

My code is below

``````segmentation = cv2.ximgproc.segmentation.createGraphSegmentation()
segment = segmentation.processImage(src)
for i in range(np.max(segment)):
dst = np.array(src)
dst[segment != i] = 0
cv2.imwrite('output_file', dst)
``````

If you prefer pure Numpy, you can achieve this using `np.where` and `np.meshgrid`:

``````i, j = np.where(mask)
indices = np.meshgrid(np.arange(min(i), max(i) + 1),
np.arange(min(j), max(j) + 1),
indexing='ij')
sub_image = image[indices]
``````

`np.where` returns a tuple of arrays specifying, pairwise, the indices in each axis for each non-zero element of `mask`. We then create arrays of all the row and column indices we will want using `np.arange`, and use `np.meshgrid` to generate two grid-shaped arrays that index the part of the image we’re interested in. Note that we specify matrix-style indexing using `index='ij'` to avoid having to transpose the result (the default is Cartesian-style indexing).

Essentially, `meshgrid` constructs `indices` so that:

``````image[indices][a, b] == image[indices[0][a, b], indices[1][a, b]]
``````

# Example

``````>>> image = np.arange(12).reshape((4, 3))
>>> image
array([[ 0,  1,  2],
[ 3,  4,  5],
[ 6,  7,  8],
[ 9, 10, 11]])
``````

Let’s say we want to extract the `[[3,4],[6,7]]` sub-matrix, which is the bounding rectangle for the the following mask:

``````>>> mask = np.array([[0,0,0],[0,1,0],[1,0,0],[0,0,0]])
array([[0, 0, 0],
[0, 1, 0],
[1, 0, 0],
[0, 0, 0]])
``````

Then, applying the above method:

``````>>> i, j = np.where(mask)
>>> indices = np.meshgrid(np.arange(min(i), max(i) + 1), np.arange(min(j), max(j) + 1), indexing='ij')
>>> image[indices]
array([[3, 4],
[6, 7]])
``````

Here, `indices[0]` is a matrix of row indices, while `indices[1]` is the corresponding matrix of column indices:

``````>>> indices[0]
array([[1, 1],
[2, 2]])
>>> indices[1]
array([[0, 1],
[0, 1]])
``````

I think using `np.amax` and `np.amin` and cropping the image is much faster.

``````i, j = np.where(mask)
indices = np.meshgrid(np.arange(min(i), max(i) + 1),
np.arange(min(j), max(j) + 1),
indexing='ij')
sub_image = image[indices]
``````

Time taken: 50 msec

``````where = np.array(np.where(mask))

x1, y1 = np.amin(where, axis=1)
x2, y2 = np.amax(where, axis=1)
sub_image = image[x1:(x2+1), y1:(y2+1)]
``````

Time taken: 5.6 msec

I don’t get Hans’s results when running the two methods (using NumPy 1.18.5). In any case, there is a much more efficient method, where you take the arg-max along each dimension

``````i, j = np.where(mask)
y, x = np.meshgrid(
np.arange(min(i), max(i) + 1),
np.arange(min(j), max(j) + 1),
indexing="ij",
)
``````

Took 38 ms

``````where = np.array(np.where(mask))
y1, x1 = np.amin(where, axis=1)
y2, x2 = np.amax(where, axis=1) + 1
sub_image = image[y1:y2, x1:x2]
``````

Took 35 ms

``````maskx = np.any(mask, axis=0)