Lota18-
Lota18-

Reputation: 113

Python - How to crop an image according to its values?

I have an image like this: all zero pixels; a square with some non-zero values. I would like to crop the image in order to create a new image with only the non-zero values. I've tried things like image = np.extract(image != 0, image) or image = image[image != 0] but those return an array and no more a matrix. How can I solve? Thanks

example of image

Upvotes: 1

Views: 2794

Answers (3)

Gustavo Kaneto
Gustavo Kaneto

Reputation: 683

You can use cv.boundingRect if you don't want to use numpy np.nonzero.

Also, cv.boundingRect is faster than numpy (probably because of C++ binding?).

image = np.array([[0,0,0,0,0], [0,0,1,2,0], [0,0,3,3,0], [0,0,0,0,0], [0,0,0,0,0]])

# the line below is usually not necessary when dealing with
# gray scale images opened with imread(), but you need it if 
# you're working with the array created above, to get uint8 values
image = cv.convertScaleAbs(image)

x, y, w, h = cv.boundingRect(image)
newImg = image[y:y+h, x:x+w]

Timing

In the example above, with an 5x5 array, cv.boundingRect is 2x faster:

%timeit x, y = np.nonzero(image)
1.4 µs ± 219 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit x, y, w, h = cv.boundingRect(image)
722 ns ± 30.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

With an 1000x1500 image, cv.boundingRect is very much faster (40x to more than 2000x, depending on the contents of image):

# blank (all zero) image 
image = np.zeros((1500,1000), dtype=np.uint8)

%timeit x, y = np.nonzero(image)
6.67 ms ± 40 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit x, y, w, h = cv.boundingRect(image)
159 µs ± 1.14 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

# only non-zero pixels
image = np.ones((1500,1000), dtype=np.uint8)

%timeit x, y = np.nonzero(image)
17.2 ms ± 155 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit x, y, w, h = cv.boundingRect(image)
7.48 µs ± 46.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Numpy is still fast enough if you are dealing with just one image. But things get different when processing live video frames, for example.

Upvotes: 0

FBruzzesi
FBruzzesi

Reputation: 6475

As an alternative to @yatu solution, you can use numpy.ix_ which allows to index the cross product of the passed arrays:

import numpy as np
image = np.array([[0,0,0,0,0], [0,0,1,2,0], [0,0,3,3,0], [0,0,0,0,0], [0,0,0,0,0]])

x, y = np.nonzero(image)
image[np.ix_(np.unique(x),np.unique(y))]
array([[1, 2],
       [3, 3]])

where

np.ix_(np.unique(x),np.unique(y))
(array([[1],
        [2]], dtype=int64), array([[2, 3]], dtype=int64))

Upvotes: 0

yatu
yatu

Reputation: 88226

One way is using the np.nonzero and ndarray.reshape:

x, y = np.nonzero(image)
xl,xr = x.min(),x.max()
yl,yr = y.min(),y.max()
image[xl:xr+1, yl:yr+1]

Using a sample array:

image = np.array([[0,0,0,0,0], [0,0,1,2,0], [0,0,3,3,0], [0,0,0,0,0], [0,0,0,0,0]])

print(image)

array([[0, 0, 0, 0, 0],
       [0, 0, 1, 2, 0],
       [0, 0, 3, 3, 0],
       [0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0]])

x, y = np.nonzero(image)
xl,xr = x.min(),x.max()
yl,yr = y.min(),y.max()
image[xl:xr+1, yl:yr+1]

array([[1, 2],
       [3, 3]])

Upvotes: 2

Related Questions