3

Below I have attached two images. I want the first image to be cropped in a heart shape according to the mask image (2nd image).

I searched for solutions but I was not able to get the simple and easier way to do this. Kindly help me with the solution.

2 images:

  1. Image to be cropped: enter image description here

  2. Mask image:

enter image description here

Pavan
  • 381
  • 1
  • 4
  • 19
  • 1
    Please do not use "cropping". Cropping implies that you are cutting out a rectangular region in your image. You in fact mean *masking*. – rayryeng Jul 09 '20 at 20:48

2 Answers2

9

Let's start by loading the temple image from sklearn:

from sklearn.datasets import load_sample_images

dataset = load_sample_images()     
temple = dataset.images[0]
plt.imshow(temple)

enter image description here

Since, we need to use the second image as mask, we must do a binary thresholding operation. This will create a black and white masked image, which we can then use to mask the former image.

from matplotlib.pyplot import imread

heart = imread(r'path_to_im\heart.jpg', cv2.IMREAD_GRAYSCALE)
_, mask = cv2.threshold(heart, thresh=180, maxval=255, type=cv2.THRESH_BINARY)

We can now trim the image so its dimensions are compatible with the temple image:

temple_x, temple_y, _ = temple.shape
heart_x, heart_y = mask.shape

x_heart = min(temple_x, heart_x)
x_half_heart = mask.shape[0]//2

heart_mask = mask[x_half_heart-x_heart//2 : x_half_heart+x_heart//2+1, :temple_y]
plt.imshow(heart_mask, cmap='Greys_r')

enter image description here

Now we have to slice the image that we want to mask, to fit the dimensions of the actual mask. Another shape would have been to resize the mask, which is doable, but we'd then end up with a distorted heart image. To apply the mask, we have cv2.bitwise_and:

temple_width_half = temple.shape[1]//2
temple_to_mask = temple[:,temple_width_half-x_half_heart:temple_width_half+x_half_heart]
masked = cv2.bitwise_and(temple_to_mask,temple_to_mask,mask = heart_mask)
plt.imshow(masked)

enter image description here

If you want to instead make the masked (black) region transparent:

tmp = cv2.cvtColor(masked, cv2.COLOR_BGR2GRAY)
_,alpha = cv2.threshold(tmp,0,255,cv2.THRESH_BINARY)
b, g, r = cv2.split(masked)
rgba = [b,g,r, alpha]
masked_tr = cv2.merge(rgba,4)

plt.axis('off')
plt.imshow(dst)

enter image description here

yatu
  • 86,083
  • 12
  • 84
  • 139
  • Excellent work!, just wondering is it possible to remove the black region (crop it) rather than making the background white? – Iamnotperfect Jan 14 '22 at 18:59
  • @Iamnotperfect The background is not white, but transparent. If you actually mean "no pixels there": that's impossible. Images can only be cropped to rectangular areas. – Mew Mar 18 '22 at 22:52
2

Since I am on a remote server, cv2.imshow doesnt work for me. I imported plt.

This code does what you are looking for:

import cv2
import matplotlib.pyplot as plt

img_org = cv2.imread('~/temple.jpg')
img_mask = cv2.imread('~/heart.jpg')
##Resizing images
img_org = cv2.resize(img_org, (400,400), interpolation = cv2.INTER_AREA)
img_mask = cv2.resize(img_mask, (400,400), interpolation = cv2.INTER_AREA)

for h in range(len(img_mask)):
    for w in range(len(img_mask)):
        if img_mask[h][w][0] == 0:
            for i in range(3):
                img_org[h][w][i] = 0
        else:
            continue
            
plt.imshow(img_org)
vagitus
  • 264
  • 2
  • 10
  • 1
    You can replace the `for` loop with `cv2.bitwise_and(img_mask, img_org)`. Iterating over each pixel is expensive. – rayryeng Jul 09 '20 at 20:47