# crop center portion of a numpy image

3k Views

Let's say I have a numpy image of some width x and height y. I have to crop the center portion of the image to width cropx and height cropy. Let's assume that cropx and cropy are positive non zero integers and less than the respective image size. What's the best way to apply the slicing for the output image?

Something along these lines -

``````def crop_center(img,cropx,cropy):
y,x = img.shape
startx = x//2-(cropx//2)
starty = y//2-(cropy//2)
return img[starty:starty+cropy,startx:startx+cropx]
``````

Sample run -

``````In [45]: img
Out[45]:
array([[88, 93, 42, 25, 36, 14, 59, 46, 77, 13, 52, 58],
[43, 47, 40, 48, 23, 74, 12, 33, 58, 93, 87, 87],
[54, 75, 79, 21, 15, 44, 51, 68, 28, 94, 78, 48],
[57, 46, 14, 98, 43, 76, 86, 56, 86, 88, 96, 49],
[52, 83, 13, 18, 40, 33, 11, 87, 38, 74, 23, 88],
[81, 28, 86, 89, 16, 28, 66, 67, 80, 23, 95, 98],
[46, 30, 18, 31, 73, 15, 90, 77, 71, 57, 61, 78],
[33, 58, 20, 11, 80, 25, 96, 80, 27, 40, 66, 92],
[13, 59, 77, 53, 91, 16, 47, 79, 33, 78, 25, 66],
[22, 80, 40, 24, 17, 85, 20, 70, 81, 68, 50, 80]])

In [46]: crop_center(img,4,6)
Out[46]:
array([[15, 44, 51, 68],
[43, 76, 86, 56],
[40, 33, 11, 87],
[16, 28, 66, 67],
[73, 15, 90, 77],
[80, 25, 96, 80]])
``````

A more general solution based on @Divakar 's answer:

``````def cropND(img, bounding):
start = tuple(map(lambda a, da: a//2-da//2, img.shape, bounding))
slices = tuple(map(slice, start, end))
return img[slices]
``````

and if we have an array `a`

``````>>> a = np.arange(100).reshape((10,10))

array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])
``````

We can clip it with `cropND(a, (5,5))`, you will get:

``````>>> cropND(a, (5,5))

array([[33, 34, 35, 36, 37],
[43, 44, 45, 46, 47],
[53, 54, 55, 56, 57],
[63, 64, 65, 66, 67],
[73, 74, 75, 76, 77]])
``````

It not only works with 2D image but also 3D image.

Have a nice day.

• 2
• why is this not more upvoted? Images can quite often have multiple channels (3D)
• 1
• Hi, I have 3D image with the shape of (WxHxD) (281, 389, 104), but once I am trying to run `cropND`, I receive an error: `Traceback (most recent call last): File "", line 1, in cropND(img,(256,256)) File "", line 2, in cropND start = tuple(map(lambda a, da: a//2-da//2, img.shape, bounding)) File "", line 2, in start = tuple(map(lambda a, da: a//2-da//2, img.shape, bounding)) TypeError: unsupported operand type(s) for //: 'NoneType' and 'int'`
• 2
• @S.EB Try something like `cropND(img, (256, 256, 104))`

Thanks, Divakar.

Your answer got me going the right direction. I came up with this using negative slice offsets to count 'from the end':

``````def cropimread(crop, xcrop, ycrop, fn):
"Function to crop center of an image file"
if crop:
ysize, xsize, chan = img_pre.shape
xoff = (xsize - xcrop) // 2
yoff = (ysize - ycrop) // 2
img= img_pre[yoff:-yoff,xoff:-xoff]
else:
img= img_pre
return img
``````
• You might want to use `//` instead of `/` to avoid making them floating pt numbers as they might not work for indexing, at least I guess with Python 3.x versions.
• 1
• I would remove the questions from this "answer"
• 2
• This does not work for odd numbers. E.g. cropping a 101 wide image to 50 will result in a 51 wide image!

A simple modification from @Divakar 's answer that preserves the image channel:

``````    def crop_center(self, img, cropx, cropy):
_, y, x = img.shape
startx = x // 2 - (cropx // 2)
starty = y // 2 - (cropy // 2)
return img[:, starty:starty + cropy, startx:startx + cropx]
``````