403

How can I crop images, like I've done before in PIL, using OpenCV.

Working example on PIL

im = Image.open('0.png').convert('L')
im = im.crop((1, 1, 98, 33))
im.save('_0.png')

But how I can do it on OpenCV?

This is what I tried:

im = cv.imread('0.png', cv.CV_LOAD_IMAGE_GRAYSCALE)
(thresh, im_bw) = cv.threshold(im, 128, 255, cv.THRESH_OTSU)
im = cv.getRectSubPix(im_bw, (98, 33), (1, 1))
cv.imshow('Img', im)
cv.waitKey(0)

But it doesn't work.

I think I incorrectly used getRectSubPix. If this is the case, please explain how I can correctly use this function.

Seanny123
  • 8,776
  • 13
  • 68
  • 124
Nolik
  • 4,337
  • 5
  • 18
  • 14

12 Answers12

817

It's very simple. Use numpy slicing.

import cv2
img = cv2.imread("lenna.png")
crop_img = img[y:y+h, x:x+w]
cv2.imshow("cropped", crop_img)
cv2.waitKey(0)
opyate
  • 5,388
  • 1
  • 37
  • 64
Froyo
  • 17,947
  • 8
  • 45
  • 73
  • 10
    Hmm... But how i can save crop image into variable? – Nolik Mar 23 '13 at 18:27
  • 126
    **remember** that x and y are flipped. I missed this. – markroxor Aug 26 '18 at 09:31
  • 25
    Alternatively, if you have defined a crop margin, you can do `crop_img = img[margin:-margin, margin:-margin]` – Rufus Aug 28 '18 at 02:35
  • 74
    This is great, just be aware that changing crop_img will change img. Otherwise, you should crop_img = img[y:y+h, x:x+w].copy() – user1270710 Oct 27 '18 at 00:46
  • @markroxor " x,y flipped" thx for pointing that out ! why are they flipped? – WestCoastProjects Jun 06 '19 at 19:05
  • 2
    @javadba numpy implementation detail. Numpy uses row, col notation instead of col, row – Froyo Jun 11 '19 at 18:46
  • 2
    Using x, y to explain this is a little confusing, actually it is better to explain this using `axis`. In the above answer, y actually represents the first axis and x represents the second axis referring [The Basics](https://docs.scipy.org/doc/numpy/user/quickstart.html#the-basics). Then we can understand that like accessing the first axis and then the second. This is the same with 2D array access in Python, Java or C/C++. – Ynjxsjmh Jul 07 '19 at 12:16
  • I think this should be: crop_img = img[y:y+h-1, x:x+w-1] – Kees van Zon Dec 05 '19 at 22:15
  • @Froyo i'm really in need of help with this question, can you help me, please? https://stackoverflow.com/questions/61216402/how-to-improve-image-segmentation-using-the-watershed – Carlos Diego Apr 28 '20 at 17:01
  • x and y are undefined..? – Cfomodz Apr 16 '22 at 03:35
  • @Cfomodz define your x and y first. – Froyo Apr 21 '22 at 13:50
  • Lol thanks @Froyo.... /s Obviously they need set first, I am saying with the given code snippet they are not, nor do they point to what they should be set to. – Cfomodz Apr 25 '22 at 17:47
174

i had this question and found another answer here: copy region of interest

If we consider (0,0) as top left corner of image called im with left-to-right as x direction and top-to-bottom as y direction. and we have (x1,y1) as the top-left vertex and (x2,y2) as the bottom-right vertex of a rectangle region within that image, then:

roi = im[y1:y2, x1:x2]

here is a comprehensive resource on numpy array indexing and slicing which can tell you more about things like cropping a part of an image. images would be stored as a numpy array in opencv2.

:)

Hugo
  • 27,885
  • 8
  • 82
  • 98
samkhan13
  • 3,315
  • 2
  • 33
  • 54
  • 1
    Hi, Shouldn't it be ` roi = im[y1:y2+1, x1:x2+1]` under you circumstances? Because numpy uses excluded region to slice. – Scott Yang May 10 '19 at 18:36
  • @samkhan13, when I crop using this formula, all of my crops have shape (0, width, channels). Ie. I am not getting a y dimension at all – mLstudent33 Aug 28 '19 at 06:13
  • @mLstudent33 it is likely that the image `im` has not been read correctly and is empty. try using an IDE with breakpoints to diagnose your code step by step. you can use [google colab](https://colab.research.google.com/) to create code blocks and can share your jupytor notebook on [stackoverflow python chat room](https://chat.stackoverflow.com/rooms/6/python) to get someones help. – samkhan13 Aug 28 '19 at 16:37
  • @samkhan13 actually I have a weird issue that I posted on Github Opencv Issues: https://github.com/opencv/opencv/issues/15406 I'll check out chat as well. Thanks! – mLstudent33 Aug 28 '19 at 16:43
41

This code crops an image from x=0,y=0 to h=100,w=200.

import numpy as np
import cv2

image = cv2.imread('download.jpg')
y=0
x=0
h=100
w=200
crop = image[y:y+h, x:x+w]
cv2.imshow('Image', crop)
cv2.waitKey(0) 
Community
  • 1
  • 1
m.hatami
  • 603
  • 1
  • 7
  • 21
  • @hatami, so height is 100 pixels "below" y = 0 right? It is the 101st row of the numpy array? And width is 200 pixels to the right of x =0 correct? – mLstudent33 Aug 28 '19 at 06:18
  • 2
    Kudos for using variable names that can actually be understood. – Spectric Mar 25 '21 at 00:53
24

Note that, image slicing is not creating a copy of the cropped image but creating a pointer to the roi. If you are loading so many images, cropping the relevant parts of the images with slicing, then appending into a list, this might be a huge memory waste.

Suppose you load N images each is >1MP and you need only 100x100 region from the upper left corner.

Slicing:

X = []
for i in range(N):
    im = imread('image_i')
    X.append(im[0:100,0:100]) # This will keep all N images in the memory. 
                              # Because they are still used.

Alternatively, you can copy the relevant part by .copy(), so garbage collector will remove im.

X = []
for i in range(N):
    im = imread('image_i')
    X.append(im[0:100,0:100].copy()) # This will keep only the crops in the memory. 
                                     # im's will be deleted by gc.

After finding out this, I realized one of the comments by user1270710 mentioned that but it took me quite some time to find out (i.e., debugging etc). So, I think it worths mentioning.

smttsp
  • 4,011
  • 3
  • 33
  • 62
  • take a look at this: https://stackoverflow.com/q/60359398/7644562 – Abdul Rehman Feb 23 '20 at 11:58
  • In mean of memory space occupied, I understand that copying the region of interest is the best thing to do, but what about time consuming ? If i do `copy()` the ROI, compared to slicing, what would be the result ?. Also, If I have a variable `tmp` in which I store each picture I load from my computer, the slicing shouldn't have a bad impact on my memory, right? _The problem you describe is only related to what happens when you load all images and then you store again them ROI, having both the originals and the ROI_ . **Please let me know if I did understand right.** – Cătălina Sîrbu May 17 '20 at 09:48
  • Copying will be negligible time in the case I said. Unless you copy large images so many times, you won't have a time difference. In my code, the effect will be like less than 1ms per cropping. The problem is you either store the large image and a pointer(ROI which is only few bytes) or you store a small image in memory(in my case). If you do this a few times, it is fine. However if you do this thousands of times, memory usage will be crazy with slicing. Like you fill the entire memory after a couple if thousand image loading if you do slicing. Whereas my code will still be on the order if MBs – smttsp May 17 '20 at 13:15
8

Robust crop with opencv copy border function:

def imcrop(img, bbox):
   x1, y1, x2, y2 = bbox
   if x1 < 0 or y1 < 0 or x2 > img.shape[1] or y2 > img.shape[0]:
        img, x1, x2, y1, y2 = pad_img_to_fit_bbox(img, x1, x2, y1, y2)
   return img[y1:y2, x1:x2, :]

def pad_img_to_fit_bbox(img, x1, x2, y1, y2):
    img = cv2.copyMakeBorder(img, - min(0, y1), max(y2 - img.shape[0], 0),
                            -min(0, x1), max(x2 - img.shape[1], 0),cv2.BORDER_REPLICATE)
   y2 += -min(0, y1)
   y1 += -min(0, y1)
   x2 += -min(0, x1)
   x1 += -min(0, x1)
   return img, x1, x2, y1, y2
belgraviton
  • 146
  • 1
  • 3
  • 1
    Can you please explain what is bbox here and what are we supposed to give in its value because whatever value I'm trying to pass, it is giving me error on `x1,y1,x2,y2 = bbox` while saying: `TypeError: 'int' object is not iterable` – Sabah Mar 23 '19 at 06:13
  • @sabah It is supposed to be a tuple or list with 4 values. If you define bbox as a single integer you will get an error – Yonatan Simson Oct 15 '20 at 19:45
7

here is some code for more robust imcrop ( a bit like in matlab )

def imcrop(img, bbox): 
    x1,y1,x2,y2 = bbox
    if x1 < 0 or y1 < 0 or x2 > img.shape[1] or y2 > img.shape[0]:
        img, x1, x2, y1, y2 = pad_img_to_fit_bbox(img, x1, x2, y1, y2)
    return img[y1:y2, x1:x2, :]

def pad_img_to_fit_bbox(img, x1, x2, y1, y2):
    img = np.pad(img, ((np.abs(np.minimum(0, y1)), np.maximum(y2 - img.shape[0], 0)),
               (np.abs(np.minimum(0, x1)), np.maximum(x2 - img.shape[1], 0)), (0,0)), mode="constant")
    y1 += np.abs(np.minimum(0, y1))
    y2 += np.abs(np.minimum(0, y1))
    x1 += np.abs(np.minimum(0, x1))
    x2 += np.abs(np.minimum(0, x1))
    return img, x1, x2, y1, y2
Ioannis Nasios
  • 8,292
  • 4
  • 33
  • 55
Dan Erez
  • 1,364
  • 15
  • 16
  • There's a bug in this in that you need to do the += on y2 before y1 and x2 before x1. Alternatively calculate the padY = np.abs(np.minimum(0, y1) and just have y1 += padY and y2 += padY. – Peter Oct 06 '22 at 07:02
7

Below is the way to crop an image.

image_path: The path to the image to edit

coords: A tuple of x/y coordinates (x1, y1, x2, y2)[open the image in mspaint and check the "ruler" in view tab to see the coordinates]

saved_location: Path to save the cropped image

from PIL import Image
    def crop(image_path, coords, saved_location:
        image_obj = Image.open("Path of the image to be cropped")
            cropped_image = image_obj.crop(coords)
            cropped_image.save(saved_location)
            cropped_image.show()


if __name__ == '__main__':
    image = "image.jpg"
    crop(image, (100, 210, 710,380 ), 'cropped.jpg')
Sanyal
  • 864
  • 10
  • 22
2

to make it easier for you here is the code that i use :

    top=514
    right=430
    height= 40
    width=100
    croped_image = image[top : (top + height) , right: (right + width)]
    plt.imshow(croped_image, cmap="gray")
    plt.show()
Bruno Ribeiro
  • 1,280
  • 16
  • 21
1

to crop or region of interest(ROI) for face use below code

import cv2 
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
image=cv2.imread("ronaldo.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
     cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),2) 
     roi_image = gray[y:y+h, x:x+w]
cv2.imshow("crop/region of interset image",roi_image) 
cv2.waitKey(0)
cv2.destroyAllWindows()

check for reference

0

Alternatively, you could use tensorflow for the cropping and openCV for making an array from the image.

import cv2
img = cv2.imread('YOURIMAGE.png')

Now img is a (imageheight, imagewidth, 3) shape array. Crop the array with tensorflow:

import tensorflow as tf
offset_height=0
offset_width=0
target_height=500
target_width=500
x = tf.image.crop_to_bounding_box(
    img, offset_height, offset_width, target_height, target_width
)

Reassemble the image with tf.keras, so we can look at it if it worked:

tf.keras.preprocessing.image.array_to_img(
    x, data_format=None, scale=True, dtype=None
)

This prints out the pic in a notebook (tested in Google Colab).


The whole code together:

import cv2
img = cv2.imread('YOURIMAGE.png')

import tensorflow as tf
offset_height=0
offset_width=0
target_height=500
target_width=500
x = tf.image.crop_to_bounding_box(
    img, offset_height, offset_width, target_height, target_width
)

tf.keras.preprocessing.image.array_to_img(
    x, data_format=None, scale=True, dtype=None
)
zabop
  • 6,750
  • 3
  • 39
  • 84
0

By using this function you can easily crop image

def cropImage(Image, XY: tuple, WH: tuple, returnGrayscale=False):
    # Extract the x,y and w,h values
    (x, y) = XY
    (w, h) = WH
    # Crop Image with numpy splitting
    crop = Image[y:y + h, x:x + w]
    # Check if returnGrayscale Var is true if is then convert image to grayscale
    if returnGrayscale:
        crop = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
    # Return cropped image
    return crop

HOPE THIS HELPS

0
# Import packages
import cv2

import numpy as np
img = cv2.imread('skewness.png')
print(img.shape) # Print image shape

cv2.imshow("original", img)

# Cropping an image
cropped_image = img[80:280, 150:330]
 
# Display cropped image
cv2.imshow("cropped", cropped_image)

# Save the cropped image
cv2.imwrite("Cropped Image.jpg", cropped_image)

#The function waitKey waits for a key event infinitely (when \f$\texttt{delay}\leq 0\f$ ) or for delay milliseconds, when it is positive
cv2.waitKey(0)

#The function destroyAllWindows destroys all of the opened HighGUI windows.
cv2.destroyAllWindows()
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103