45

I have a set of points that make a shape (closed polyline). Now I want to copy/crop all pixels from some image inside this shape, leaving the rest black/transparent. How do I do this?

For example, I have this:

enter image description here

and I want to get this:

enter image description here

Saullo G. P. Castro
  • 56,802
  • 26
  • 179
  • 234
ffriend
  • 27,562
  • 13
  • 91
  • 132
  • 1
    I believe you will want to work with an irregular ROI (region of interest). You might start here: http://stackoverflow.com/questions/10632195/define-image-roi-with-opencv-in-c – w-m Mar 11 '13 at 15:24
  • Just in case: this question is not duplicate, since referred one describes C API and not Python (that question is still helpful, though). – ffriend Mar 11 '13 at 21:23

2 Answers2

68

*edit - updated to work with images that have an alpha channel.

This worked for me:

  • Make a mask with all black (all masked)
  • Fill a polygon with white in the shape of your ROI
  • combine the mask and your image to get the ROI with black everywhere else

You probably just want to keep the image and mask separate for functions that accept masks. However, I believe this does what you specifically asked for:

import cv2
import numpy as np

# original image
# -1 loads as-is so if it will be 3 or 4 channel as the original
image = cv2.imread('image.png', -1)
# mask defaulting to black for 3-channel and transparent for 4-channel
# (of course replace corners with yours)
mask = np.zeros(image.shape, dtype=np.uint8)
roi_corners = np.array([[(10,10), (300,300), (10,300)]], dtype=np.int32)
# fill the ROI so it doesn't get wiped out when the mask is applied
channel_count = image.shape[2]  # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,)*channel_count
cv2.fillPoly(mask, roi_corners, ignore_mask_color)
# from Masterfool: use cv2.fillConvexPoly if you know it's convex

# apply the mask
masked_image = cv2.bitwise_and(image, mask)

# save the result
cv2.imwrite('image_masked.png', masked_image)
KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • @kobejohn to create mask to make transparent background with this crop? – kju Dec 07 '15 at 04:36
  • @kju do you mean you want transparent instead of black outside the ROI? – KobeJohn Dec 07 '15 at 13:54
  • @KobeJohn yes, it is. – kju Dec 09 '15 at 02:58
  • 3
    @kju This seems limited enough that I just updated the answer instead of making a new question. I guess many people doing this probably also want a transparent mask. – KobeJohn Dec 12 '15 at 14:03
  • 2
    You should use `cv2.fillConvexPoly` if your shape is a convex polygon. [The documentation](http://docs.opencv.org/3.0-beta/modules/imgproc/doc/drawing_functions.html#fillconvexpoly) states that this method is "much faster" than `cv2.fillPoly`. – Masterfool Mar 20 '17 at 23:22
  • @Masterfool Good point! Since I am unaware of a built-in test for convexness, I will leave it with the general fill and add a comment for when to use `cv2.fillConvexPoly`. For anyone that is working with very large images, it would be worth the cost to implement a convex test. See [this SO question](http://stackoverflow.com/questions/471962/how-do-determine-if-a-polygon-is-complex-convex-nonconvex). – KobeJohn Mar 22 '17 at 15:26
  • 1
    For my fillPoly didn't work but fillConvexPoly did. Thanks @Masterfool – Michał Gacka May 17 '17 at 08:25
  • This way is very time consuming. I would search for another approach. Does it exists something similar to slicing but for irregular shapes? – Cătălina Sîrbu May 17 '20 at 11:16
7

The following code would be helpful for cropping the images and get them in a white background.

import cv2
import numpy as np

# load the image
image_path = 'input image path'
image = cv2.imread(image_path)

# create a mask with white pixels
mask = np.ones(image.shape, dtype=np.uint8)
mask.fill(255)

# points to be cropped
roi_corners = np.array([[(0, 300), (1880, 300), (1880, 400), (0, 400)]], dtype=np.int32)
# fill the ROI into the mask
cv2.fillPoly(mask, roi_corners, 0)

# The mask image
cv2.imwrite('image_masked.png', mask)

# applying th mask to original image
masked_image = cv2.bitwise_or(image, mask)

# The resultant image
cv2.imwrite('new_masked_image.png', masked_image)

Input Image: input image

Mask Image: mask image

Resultant output image: enter image description here

Kanish Mathew
  • 825
  • 10
  • 6