423

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.

12 Answers 12

856

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)
Sign up to request clarification or add additional context in comments.

12 Comments

Hmm... But how i can save crop image into variable?
remember that x and y are flipped. I missed this.
Alternatively, if you have defined a crop margin, you can do crop_img = img[margin:-margin, margin:-margin]
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()
@javadba numpy implementation detail. Numpy uses row, col notation instead of col, row
|
181

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.

:)

4 Comments

Hi, Shouldn't it be ` roi = im[y1:y2+1, x1:x2+1]` under you circumstances? Because numpy uses excluded region to slice.
@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 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 to create code blocks and can share your jupytor notebook on stackoverflow python chat room to get someones help.
@samkhan13 actually I have a weird issue that I posted on Github Opencv Issues: github.com/opencv/opencv/issues/15406 I'll check out chat as well. Thanks!
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) 

2 Comments

@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?
Kudos for using variable names that can actually be understood.
25

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.

3 Comments

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.
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
9

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')

Comments

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

2 Comments

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 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
8

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

1 Comment

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.
5

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()

1 Comment

or (startY : endY, Start X : end of X) in absolute pixels
2

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

Comments

1

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
)

Comments

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

Comments

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()

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.