How to better crop and paste an image in PIL - python

I am trying to crop an avatar and place it in a given location in another image using python pil.
Here is the output of what I have so far:
And here is the code:
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import textwrap
text = "Creating Twitter Cards dynamically with Python"
background_image = "data/obi-pvc.png" # this is the background
avatar = Image.open("data/avatar.png")
font = "data/fonts/AllertaStencil-Regular.ttf"
background = Image.open(background_image)
background_width, background_height = background.size
avatar.convert('RGBA')
## DO NOT change below this line!!
save_name = f"{text.lower().replace(' ', '_')}.png"
#textwrapped = textwrap.wrap(text, width=text_wrap_width)
# crop avatar
width, height = avatar.size
x = (width - height)//2
avatar_cropped = avatar.crop((x, 0, x+height, height))
width_cr, height_cr = avatar_cropped.size
# create grayscale image with white circle (255) on black background (0)
mask = Image.new('L', avatar_cropped.size)
mask_draw = ImageDraw.Draw(mask)
width, height = avatar_cropped.size
mask_draw.ellipse((0, 0, width, height), fill=255)
# add mask as alpha channel
avatar_cropped.putalpha(mask)
draw = ImageDraw.Draw(background)
font = ImageFont.truetype(font, font_size)
draw.text((offset,margin), '\n'.join(textwrapped), font=font, fill=color)
x, y = avatar_cropped.size
margin = 40
# left top
position_tl = (0 + margin, 0 + margin)
position_tr = (x - margin - width_cr, 0 + margin)
position_bl = (0 + margin, y - margin - height_cr)
position_br = (x - margin - width_cr, y - margin - height_cr)
background.paste(avatar_cropped, position)
background.save(f"data/output/{save_name}")
display(background)
The avatar should fit within the circle. I can't seem to really figure out how to apply the positioning. Thanks
Here is the avatar:

Related

How do I translate x, y coordinates with matchTemplate once I've cropped the template area?

I'm using python 3.9.6 and OpenCV 4.5.1
I'm trying to detect an objects on a template. My template is a real-time feed of my monitor and my objects are jpg's.
The issue: When I crop my template to speed up detection my mouse starts clicking in the wrong location.
This only happens after I've cropped my template. I think it's because I'm cropping my template at the wrong time in my script. My full monitor is (0 , 0, 1920, 1080) but I only want to capture [220:900, 270:1590]
I've followed the OpenCV documentation and a few online tutorials so far but I'm now stuck.
How do I click on img (third code block) rather than an incorrect off-set caused by cropping my template incorrectly?
I'm using win32gui to grab my template:
import numpy as np
import win32gui, win32ui, win32con
class WindowCapture:
# properties
w = 0
h = 0
hwnd = None
cropped_x = 0
cropped_y = 0
offset_x = 0
offset_y = 0
# constructor
def __init__(self, window_name=None):
# find the handle for the window we want to capture.
# if no window name is given, capture the entire screen
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise Exception('Window not found: {}'.format(window_name))
# get the window size
window_rect = win32gui.GetWindowRect(self.hwnd)
self.w = window_rect[2] - window_rect[0]
self.h = window_rect[3] - window_rect[1]
# account for the window border and titlebar and cut them off
border_pixels = 0
titlebar_pixels = 0
self.w = self.w - (border_pixels * 2)
self.h = self.h - titlebar_pixels - border_pixels
self.cropped_x = border_pixels
self.cropped_y = titlebar_pixels
# set the cropped coordinates offset so we can translate screenshot
# images into actual screen positions
self.offset_x = window_rect[0] + self.cropped_x
self.offset_y = window_rect[1] + self.cropped_y
def get_screenshot(self):
# get the window image data
wDC = win32gui.GetWindowDC(self.hwnd)
dcObj = win32ui.CreateDCFromHandle(wDC)
cDC = dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY)
# convert the raw data into a format opencv can read
# dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
signedIntsArray = dataBitMap.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (self.h, self.w, 4)
# free resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
img = img[...,:3]
img = np.ascontiguousarray(img)
return img
#staticmethod
def list_window_names():
def winEnumHandler(hwnd, ctx):
if win32gui.IsWindowVisible(hwnd):
print(hex(hwnd), win32gui.GetWindowText(hwnd))
win32gui.EnumWindows(winEnumHandler, None)
And OpenCV and numpy for my object detection:
import cv2 as cv
import numpy as np
class Vision:
# properties
needle_img = None
needle_w = 0
needle_h = 0
method = None
# constructor
def __init__(self, needle_img_path, method=cv.TM_CCORR_NORMED):
self.needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED)
# Save the dimensions of the needle image
self.needle_w = self.needle_img.shape[1]
self.needle_h = self.needle_img.shape[0]
# There are 6 methods to choose from:
# TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED
self.method = method
def find(self, haystack_img, threshold=0.5, debug_mode=None):
# run the OpenCV algorithm
result = cv.matchTemplate(haystack_img, self.needle_img, self.method)
# Get the all the positions from the match result that exceed our threshold
locations = np.where(result >= threshold)
locations = list(zip(*locations[::-1]))
rectangles = []
for loc in locations:
rect = [int(loc[0]), int(loc[1]), self.needle_w, self.needle_h]
# Add every box to the list twice in order to retain single (non-overlapping) boxes
rectangles.append(rect)
rectangles.append(rect)
# Apply group rectangles
rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5)
points = []
if len(rectangles):
line_color = (0, 255, 0)
line_type = cv.LINE_4
marker_color = (255, 0, 255)
marker_type = cv.MARKER_CROSS
# Loop over all the rectangles
for (x, y, w, h) in rectangles:
# Determine the center position
center_x = x + int(w/2)
center_y = y + int(h/2)
# Save the points
points.append((center_x, center_y))
if debug_mode == 'rectangles':
# Determine the box position
top_left = (x, y)
bottom_right = (x + w, y + h)
# Draw the box
cv.rectangle(haystack_img, top_left, bottom_right, color=line_color,
lineType=line_type, thickness=2)
elif debug_mode == 'points':
# Draw the center point
cv.drawMarker(haystack_img, (center_x, center_y),
color=marker_color, markerType=marker_type,
markerSize=40, thickness=2)
############ DISPLAYS MATCHES #############
if debug_mode:
cv.imshow('Matches', haystack_img)
return points
And then passing in both variables in a separate script here:
import cv2 as cv
import pyautogui as py
from windowcapture import WindowCapture
from vision import Vision
# initialize the WindowCapture class
# leave blank to capture the whole screen
haystack = WindowCapture()
# initialize the Vision class
needle = Vision('needle.jpg')
while(True):
# get an updated image of the game
screenshot = template.get_screenshot()
screenshotCropped = screenshot[220:900, 270:1590]
img = needle.find(screenshotCropped, 0.85, 'points')
if img:
py.moveTo(img[0])
The line causing the issue is: screenshotCropped = screenshot[220:900, 270:1590] If it's removed I click on the object correctly.
I also tried adding border_pixels and titlebar_pixels to allow me to crop directly from WindowCapture but I run into the same issue detailed above.
If I understand your code correctly, when you crop the image, you're not (yet) accounting for the X/Y offset introduced through that crop.
If I understand your example correctly, your code
screenshotCropped = screenshot[220:900, 270:1590]
is cropping from 220-900 along the y-axis (height) and 270-1590 along the x-axis (width), yes? If so, try
x_0, x_1 = 270,1590
y_0, y_1 = 220,900
screenshotCropped = screenshot[y_0:y_1, x_0:x_1]
...
if img:
x_coord = img[0][0] + x_0
y_coord = img[0][1] + y_0
py.moveTo(x_coord,y_coord)
If your cropping region changes, update your (x_0, x_1, y_0, y_1) values accordingly (in both the crop operation and the py.moveTo operation)?

How to move the circular image to the exact center position of the bigger image?

I want to move the circular image to the exact center position of the bigger image. How to do accomplish that task accurately?
from IPython.display import display
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
def show_saved_image(str):
img = Image.open(str)
display(img)
im1 = Image.open('rocket.jpg')
im2 = Image.open('lena.jpg')
#height, width, channels = im1.shape
im1_width, im1_height = im1.size
im1_centreX, im1_centreY = int(im1_width/2), int(im1_height/2)
im2_width, im2_height = im2.size
im2_centreX, im2_centreY = int(im2_width/2), int(im2_height/2)
print(im1_width, im1_height)
print(im2_width, im2_height)
radius = int(min(im2_width/2, im2_height/2))
ulX, ulY = im2_centreX-radius, im2_centreY-radius
lrX, lrY = im2_centreX+radius, im2_centreY+radius
desired_pointX, desired_pointY = im1_centreX-radius, im1_centreY-radius
# ![rocket_pillow_paste_out](data/dst/rocket_pillow_paste_out.jpg)
mask_im = Image.new("L", im2.size, 0)
draw = ImageDraw.Draw(mask_im)
draw.ellipse((ulX, ulY, lrX, lrY), fill=255)
# mask_im_blur = mask_im.filter(ImageFilter.GaussianBlur(10))
# mask_im_blur.save('mask_circle_blur.jpg', quality=95)
back_im = im1.copy()
back_im.paste(im2, (desired_pointX, desired_pointY), mask_im)
#back_im.paste(im2, (desired_pointX, desired_pointY), mask_im_blur)
back_im.save('output.jpg', quality=95)
im = Image.open('output.jpg')
draw = ImageDraw.Draw(im)
draw.ellipse((im1_centreX-4, im1_centreY-4, im1_centreX+4, im1_centreY+4 ), fill=(0, 255, 0), outline=(0, 0, 0))
draw.ellipse((desired_pointX-4, desired_pointY-4, desired_pointX+4, desired_pointY+4 ), fill=(255, 0, 0), outline=(0, 0, 0))
im.save('imagedraw.jpg', quality=95)
show_saved_image("imagedraw.jpg")
Images:
rocket.jpg
lena.jpg
If there is another way, then please help me with that, too.
You just need to modify this line
desired_pointX, desired_pointY = im1_centreX - radius, im1_centreY - radius
to
desired_pointX, desired_pointY = im1_centreX - int(im2_width/2), im1_centreY - int(im2_height/2)
Your mask_im has shape im2.size, so you need to adapt to that, not just the radius of the circle. Since radius is int(im2_height/2), the vertical alignment is fine, but radius is smaller than int(im2_width/2), that's why the insufficient shift leftwards.

Remove white background from image with opencv python

I have been trying to remove background from a logo and use it as a watermark on product images.I tried to remove background from logo with masking but it also removing black fonts from logo. i need help in removing background without changing the logo. images are attached below. output image is the image i get as output with this code.
logo image: https://drive.google.com/file/d/1yMG6cDuPt8q3EqOJ4Amzp_czWq5hrGS5/view?usp=sharing
product image: https://drive.google.com/file/d/13SmkTgBtWD3yIJq-qGI0aZJ-hjaLbyuB/view?usp=sharing
output image: https://drive.google.com/file/d/1k-fQ9tPUEJKQXPdmdB2ajtliAAG4irNs/view?usp=sharing
this is my code
import cv2
import numpy as np
img = cv2.imread('images/1L2Z3A443AAMC.jpg')
logo = cv2.imread('water2.png')
logo = cv2.resize(logo,(int(img.shape[0]/1.2),int(img.shape[1]/2)))
logo_gray = cv2.cvtColor(logo,cv2.COLOR_BGR2GRAY)
ret,mask= cv2.threshold(logo_gray,245,255,cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
logo_final = cv2.bitwise_and(logo,logo, mask=mask_inv)
h_img,w_img,_ = img.shape
h_logo,w_logo,_ = logo.shape
center_y = int(h_img/2)
center_x = int(w_img/2)
top_y = center_y - (int(h_logo/2))
left_x = center_x - (int(w_logo/2))
bottom_y = top_y + h_logo
right_x = left_x + w_logo
roi = img[top_y:bottom_y,left_x:right_x]
result = cv2.addWeighted(roi,1,logo_final,1,0)
img[top_y:bottom_y,left_x:right_x] = result
cv2.imwrite('sample.jpg',img)
In order to remove the background from a logo. We need to used masking. A masking for the foreground and another masking for the background.
logo = cv2.imread("water2.png", -1)
logo_mask = logo[:,:,3]
logo_mask_inv = cv2.bitwise_not(logo_mask)
logo = logo[:,:,0:3]
logo_background = cv2.bitwise_and(roi, roi, mask=logo_mask_inv)
logo_foreground = cv2.bitwise_and(logo, logo, mask=logo_mask)
final_logo = cv2.add(logo_background, logo_foreground)

Python transparent watermark is not transparent

I have this issue, where whatever watermark I put, the background, which is transparent, appears as non-transparent.
Here is the code:
from PIL import Image
import glob
def watermark_with_transparency(input_image_path, output_image_path, watermark_image_path):
base_image = Image.open(input_image_path) # open base image
watermark = Image.open(watermark_image_path) # open water mark
width, height = base_image.size # getting size of image
width_of_watermark, height_of_watermark = watermark.size
position = [width / 2 - width_of_watermark / 2, height / 2 - height_of_watermark / 2]
position[0] = int(position[0])
position[1] = int(position[1])
water = watermark.copy()
water.convert('RGBA')
water.putalpha(70)
water.save('solid.png')
transparent = Image.new('RGBA', (width, height), (0, 0, 0, 0))
transparent.paste(base_image, (0, 0))
transparent.paste(water, position, mask=water)
transparent.show()
transparent.convert('RGB').save(output_image_path)
print('Image Done..!')
for inputImage in glob.glob('images/*.jpg'):
output = inputImage.replace('images\\', '')
outputImage = 'watermark images\\' + str(output)
watermark_with_transparency(inputImage, outputImage, 'watermark images/watermark.png')
Here you may see what the result is:

Right-align text with PIL?

I've an Arabic text and want to move the text to the upper right corner in the image.
I did try using align='right' in the code also tried direction=rtl but it shows an empty area of the image on the right top corner.
If you want to try the code make sure to Install libraqm & raqm
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import textwrap
#configuration
font_size=36
width=3840
height=390
back_ground_color=(255,255,255)
font_size=80
font_color=(0,0,0)
text = " وأيضا هذا النصح محبوب جدا جدا لذلك انا أفضلههذا النص مكتوب باللغةمكتوب باللغةمكتوب باللغةمكتوب باللغةمكتوب باللغة مكتوب باللغة العربية وهو واضح بشكل جميل"
wrapped = textwrap.fill(text, 100)
im = Image.new ( "RGB", (width,height), back_ground_color )
draw = ImageDraw.Draw ( im )
unicode_font = ImageFont.truetype("Sahel.ttf", font_size)
draw.text ( (0,0), wrapped, font=unicode_font, fill=font_color,spacing=30,direction='rtl',align='right',features='rtla')
im.save('text.png')
Summary
The align parameter can be used for the smaller lines (usually last one) in order to align them according to the text box
In order to right align the text box according to the image you need to use the draw.textsize function to get the text box size and calculate its position according to your image size.
Working example with some comments
from PIL import Image, ImageDraw, ImageFont
import textwrap
# configuration
font_size = 36
width = 3840
height = 390
back_ground_color = (255, 255, 255)
font_size = 80
font_color = (0, 0, 0)
text = " وأيضا هذا النصح محبوب جدا جدا لذلك انا أفضلههذا النص مكتوب باللغةمكتوب باللغةمكتوب باللغةمكتوب باللغةمكتوب باللغة مكتوب باللغة العربية وهو واضح بشكل جميل"
wrapped = textwrap.fill(text, 100)
im = Image.new("RGB", (width, height), back_ground_color)
draw = ImageDraw.Draw(im)
unicode_font = ImageFont.truetype("Sahel Regular.ttf", font_size)
# get text box size
text_w, text_h = draw.textsize(wrapped, font=unicode_font, direction="rtl", language="ar")
draw.text(
# (0, 0), # top left corner
# (width - text_w, 0), # top right corner
# (width - text_w, height - text_h), # bottom right corner
# (0, height - text_h), # bottom left corner
# ((width - text_w) // 2, (height - text_h) // 2), # center
(width - text_w, (height - text_h) // 2), # center vertical + start from right
wrapped,
font=unicode_font,
fill=font_color,
direction='rtl',
language='ar',
align='left', # affect the second line of the text, set to right to see the last line aligned to the right
)
im.show()
Result

Categories