3

I am learning OpenCV and tried to paste a small image over a large image. But, it showed an error as both the images should have an equal size. I have also tried to follow the suggestion provided (How to paste an image onto a larger image using Pillow?) and (How do you composite an image onto another image with PIL in Python?)

   import cv2 as cv
   from scipy import ndimage

   img1 = cv.imread('Please put your file name')

   top_left_x = min([x1,x2,x3,x4])
   top_left_y = min([y1,y2,y3,y4])
   bot_right_x = max([x1,x2,x3,x4])
   bot_right_y = max([y1,y2,y3,y4])

   y_right =bot_right_y + 1
   x_right =bot_right_x + 1

  cropped = img[top_left_y: y_right, top_left_x: x_right]

  rotate = ndimage.rotate(cropped, ang)

The final output image should be at centre.

Rajkamal Mishra
  • 121
  • 1
  • 9
  • Possible duplicate of [How do you composite an image onto another image with PIL in Python?](https://stackoverflow.com/questions/2563822/how-do-you-composite-an-image-onto-another-image-with-pil-in-python) – Rajkamal Mishra Jul 04 '19 at 06:48

2 Answers2

6

This is a Pure PIL solution:-

from PIL import Image

img1 = Image.open(r"Source_Image_path")

# The values used to crop the original image (will form a bbox)
x1, y1, x2, y2 = 10, 10, 400, 400

# The angle at which the cropped Image must be rotated
angle = 50

# cropping the original image 
img = img1.crop((x1, y1, x2, y2))

# Firstly converting the Image mode to RGBA, and then rotating it
img = img.convert("RGBA").rotate(angle, resample=Image.BICUBIC)

# calibrating the bbox for the beginning and end position of the cropped image in the original image 
# i.e the cropped image should lie in the center of the original image
x1 = int(.5 * img1.size[0]) - int(.5 * img.size[0])
y1 = int(.5 * img1.size[1]) - int(.5 * img.size[1])
x2 = int(.5 * img1.size[0]) + int(.5 * img.size[0])
y2 = int(.5 * img1.size[1]) + int(.5 * img.size[1])

# pasting the cropped image over the original image, guided by the transparency mask of cropped image
img1.paste(img, box=(x1, y1, x2, y2), mask=img)

# converting the final image into its original color mode, and then saving it
img1.convert(img1.mode).save("Destination_path")

INPUT IMAGE:-

enter image description here

OUTPUT IMAGE:-

enter image description here

The code itself is self explanatory, but you might be wondering why are we converting the cropped image back and forth to RGBA. The reason for that is, because if we rotate a non alpha image in PIL, we end up with black bars/edges on the image where the pixel values no longer exists (read this question for more). But if we do the same for an alpha image, i.e passing an alpha image via rotate() then the empty pixel values end up being fully transparent (or alpha = 0).

Vasu Deo.S
  • 1,820
  • 1
  • 11
  • 23
  • Nice, though I would use `rotate(angle, resample=Image.BICUBIC, expand=True)`. You'll get much better quality output (rotate defaults to nearest neighbour, which is ugly on edges), and you won't trim the corners off the rotated patch. You still get an ugly edge from the `paste` sadly, I'm not sure how to fix that. – jcupitt Jul 04 '19 at 07:48
  • @jcupitt Resampling is definitely a good option. Expand is only viable if the OP wants to stretch the rotated image to its full length, I mean the rotated image should fit in the output frame. I am not quite sure what you mean by ugly edge? The black bars where the image data does not exists? – Vasu Deo.S Jul 04 '19 at 07:51
  • Try adding `expand` to your code and running it again, you'll find you get the full square of pixels, not your chopped-down octagon. It won't stretch it, it'll just not clip it. Save as a PNG and try zooming in on an angled edge of the paste. You'll see there is no antialiasing and you have a jagged staircase. – jcupitt Jul 04 '19 at 09:34
  • @jcupitt I think you missed my first comment, I clearly stated what `expand` would do, and why resampling is a good option (agreeing on both points that you stated). The only doubt I had was because you said *You still get an ugly edge from the paste sadly, I'm not sure how to fix that* and I wasn't able to understand what **Ugly Edge** are you mentioning? – Vasu Deo.S Jul 04 '19 at 09:42
  • The ugly edge is the lack of antialising when you `paste` -- you'll get staircasing along the edges of the pasted image. I don't know how to fix that in PIL, sadly. – jcupitt Jul 04 '19 at 10:02
0

Although, none of the solution available worked for OpenCV and not even sorted my issue. But, after several modifications, I am able to solve this issue in OpenCV.

import cv2
img = cv2.imread('Please put your file name')

# Setting the parameters
ang = 47
top_left_x = min([12,42,53,11])
top_left_y = min([41,56,75,20])
bot_right_x = max([12,42,53,11])
bot_right_y = max([41,56,75,20])

y_right =bot_right_y + 1
x_right =bot_right_x + 1

# Cropping the image
cropped_img = img[top_left_y: y_right, top_left_x: x_right]

###########  Rotating the image ##########
# First setting the centre and roatation angle parameter. 
# To rotate sub-image from the centre of the original image
rows,cols = img.shape
M = cv2.getRotationMatrix2D((cols/2,rows/2),ang,1)

# Setting the size with original image to resolve size issue
rotated_img = cv2.warpAffine(cropped_img,M,(img.shape[1],img.shape[0]))

# Pasting the rotated image on original image
# The original image will be in the background with transparency 0.3
# The sub-image will be pasted above the original image with transparency 0.7
img = cv2.addWeighted(img, 0.3, rotated_img, 0.7, 0)

# Showing the image
cv2.namedWindow('img', cv2.WINDOW_NORMAL)
cv2.imshow('output_image.jpg', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

This would not be the most appropriate answer but can serve the purpose for many of us.

Rajkamal Mishra
  • 121
  • 1
  • 9