I was trying to add a logo to my video using OpenCv by modifying a code that I found online (that was made for merging two pictures). The process worked somehow but the added logo was displayed only like a quarter of it, not full.
So my questions are :
How do I change the size and the cropping of the added logo ?
Is it possible also to change it's position on the video (like in the bottom)?
Any help or documentation (for beginner) would be appreciated.
The code I used:
import cv2
import numpy as np
foreground = cv2.imread('logo.jpg')
cap = cv2.VideoCapture("video.mp4")
alpha = 0.4
while True:
ret, background = cap.read()
background = cv2.flip(background,1)
added_image = cv2.addWeighted(background[150:250,150:250,:],alpha,foreground[0:100,0:100,:],1-alpha,0)
background[150:250,150:250] = added_image
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(background,'alpha:{}'.format(alpha),(10,30), font, 1,(255,255,255),2,cv2.LINE_AA)
cv2.imshow('a',background)
k = cv2.waitKey(10)
if k == ord('q'):
break
if k == ord('a'):
alpha +=0.1
if alpha >=1.0:
alpha = 1.0
elif k== ord('d'):
alpha -= 0.1
if alpha <=0.0:
alpha = 0.0
cap.release()
cv2.destroyAllWindows()
I added comments and some ease-of-use stuff to your code to help make it easier to understand what each part is supposed to be doing. The overall structure is the same, but hopefully this helps you figure out why the overlay looks the way it does.
import cv2
import numpy as np
# load image and start video
foreground = cv2.imread('logo.jpg')
cap = cv2.VideoCapture("video.mp4")
# init font
font = cv2.FONT_HERSHEY_SIMPLEX
# position of logo on video
top_left = [0, 0] # the top-left corner of your logo goes here
tx = top_left[0] # less typing later
ty = top_left[1]
# crop of logo
left = 0
right = 100
top = 0 # y = 0 is the top of the image
bottom = 100
# calculate width and height of logo crop
width = right - left
height = bottom - top
# main loop
alpha = 0.4
while True:
# read image
ret, background = cap.read()
# horizontal flip to create mirror image
background = cv2.flip(background,1)
# quick primer on 2d image slicing:
# images are [y,x]
# crop = image[0:100, 300:500] will be a 200x100 image (width x height)
# the logo slice should give you a decent idea of what's going on
# WARNING: there are no out-of-bounds checks here, make sure that
# tx + width is less than the width of the background
# ty + height is less than the height of the background
# right is less than the width of the foreground
# bottom is less than the height of the foreground
# get crops of background and logo
background_slice = background[ty:ty+height, tx:tx+width];
logo_slice = foreground[top:bottom, left:right];
# since we don't change our crop, we could do the logo slice outside the loop
# but I'll keep it here for clarity
# add slice of logo onto slice of background
added_image = cv2.addWeighted(background_slice,alpha,logo_slice,1-alpha,0)
background[ty:ty+height, tx:tx+width] = added_image
# draw alpha value
cv2.putText(background,'alpha:{}'.format(alpha),(10,30), font, 1,(255,255,255),2,cv2.LINE_AA)
# show image
cv2.imshow('a',background)
k = cv2.waitKey(10)
# process keypresses
if k == ord('q'):
break
if k == ord('a'):
alpha +=0.1
if alpha >=1.0:
alpha = 1.0
elif k== ord('d'):
alpha -= 0.1
if alpha <=0.0:
alpha = 0.0
# close
cap.release()
cv2.destroyAllWindows()
Related
I am currently working on a school project where I am trying to simulate an LSD trip using a webcam and video processing effects. I am using python and opencv to accomplish this, but I am having trouble figuring out how to create/apply a certain effect that acts like a "drift/morph/melt/flow" to the webcam footage.
I have attached an example of the effect I am trying to achieve. It looks like the image is slowly melting and distorting, almost as if it is being pulled in multiple directions at once.
Example 1
Example 2
Example 3
Example 4
I have looked into various image processing techniques such as warping, affine transformations, and image blending, but I am not sure which method would be best for creating this specific effect. Below are some of the lines of code I have tried (I am very new to coding so I have just been playing around with stuff I have already found made on the internet):
import cv2
import numpy as np
# Capture video from webcam
cap = cv2.VideoCapture(0)
while True:
# Read frame from webcam
ret, frame = cap.read()
# Apply swirling effect
rows, cols = frame.shape[:2]
for i in range(rows):
for j in range(cols):
dx = i - rows // 2
dy = j - cols // 2
distance = np.sqrt(dx**2 + dy**2)
angle = np.arctan2(dy, dx) + distance * 0.1
x = int(rows // 2 + distance * np.cos(angle))
y = int(cols // 2 + distance * np.sin(angle))
if x >= 0 and x < rows and y >= 0 and y < cols:
frame[i, j] = frame[x, y]
# Display the resulting frame
cv2.imshow('Video', frame)
# Break the loop if the user hits 'q'
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Release the capture and destroy the window
cap.release()
cv2.destroyAllWindows()
import cv2
from skimage.transform import swirl
# Create a VideoCapture object to access the webcam
cap = cv2.VideoCapture(0)
while True:
# Read a frame from the webcam
_, frame = cap.read()
# Convert the frame to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Apply the swirl effect to the frame
swirled = swirl(gray, rotation=0, strength=10, radius=120)
# Display the swirled frame in a window
cv2.imshow('Swirled', swirled)
# Wait for the user to press a key
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
# Release the VideoCapture object and destroy all windows
cap.release()
cv2.destroyAllWindows()
I have also found a link with someone achieved a similar effect to what I am looking for using a program called TouchDesigner
Any advice or guidance on how to accomplish this using python and opencv and any other libraries I might need would be greatly appreciated.
Here is one way to create an animated GIF from one single image (or video frame) in Python/OpenCV/PIL.
Read the input image
Set parameters
Create X and Y ramps
Loop over the input creating sinusoids and incrementing the phase
Use remap to warp the input according to the sinusoids in X and Y
Convert the image to PIL format and save the frames in a list
When loop is finished, save the frames from the list to an animated GIF using PIL
Input:
import numpy as np
import cv2
from PIL import Image
img = cv2.imread("bluecar_sm.jpg")
# get dimensions
h, w = img.shape[:2]
# set wavelength
wave_x = 2*w
wave_y = h
# set amount, number of frames and delay
amount_x = 10
amount_y = 5
num_frames = 100
delay = 50
border_color = (128,128,128)
# create X and Y ramps
x = np.arange(w, dtype=np.float32)
y = np.arange(h, dtype=np.float32)
frames = []
# loop and change phase
for i in range(0,num_frames):
# compute phase to increment over 360 degree for number of frames specified so makes full cycle
phase_x = i*360/num_frames
phase_y = phase_x
# create sinusoids in X and Y, add to ramps and tile out to fill to size of image
x_sin = amount_x * np.sin(2 * np.pi * (x/wave_x + phase_x/360)) + x
map_x = np.tile(x_sin, (h,1))
y_sin = amount_y * np.sin(2 * np.pi * (y/wave_y + phase_y/360)) + y
map_y = np.tile(y_sin, (w,1)).transpose()
# do the warping using remap
result = cv2.remap(img.copy(), map_x, map_y, cv2.INTER_CUBIC, borderMode = cv2.BORDER_CONSTANT, borderValue=border_color)
# show result
cv2.imshow('result', result)
cv2.waitKey(delay)
# convert to PIL format and save frames
result = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
pil_result = Image.fromarray(result)
frames.append(pil_result)
# write animated gif from frames using PIL
frames[0].save('bluecar_sm_animation.gif',save_all=True, append_images=frames[1:], optimize=False, duration=delay, loop=0)
Full Sized Animated GIF
Here is a reduced version that has a small enough file size so that it can be displayed directly.
I've bellow function:
def alphaMerge(small_foreground, background, top, left):
result = background.copy()
fg_b, fg_g, fg_r, fg_a = cv.split(small_foreground)
print(fg_b, fg_g, fg_r, fg_a)
fg_a = fg_a / 255.0
label_rgb = cv.merge([fg_b * fg_a, fg_g * fg_a, fg_r * fg_a])
height, width = small_foreground.shape[0], small_foreground.shape[1]
part_of_bg = result[top:top + height, left:left + width, :]
bg_b, bg_g, bg_r = cv.split(part_of_bg)
part_of_bg = cv.merge([bg_b * (1 - fg_a), bg_g * (1 - fg_a), bg_r * (1 - fg_a)])
cv.add(label_rgb, part_of_bg, part_of_bg)
result[top:top + height, left:left + width, :] = part_of_bg
return result
if __name__ == '__main__':
folder_dir = r"C:\photo_datasets\products_small"
logo = cv.imread(r"C:\Users\PiotrSnella\photo_datasets\discount.png", cv.IMREAD_UNCHANGED)
for images in os.listdir(folder_dir):
input_path = os.path.join(folder_dir, images)
image_size = os.stat(input_path).st_size
if image_size < 8388608:
img = cv.imread(input_path, cv.IMREAD_UNCHANGED)
height, width, channels = img.shape
if height > 500 and width > 500:
result = alphaMerge(logo, img, 0, 0)
cv.imwrite(r'C:\photo_datasets\products_small_output_cv\{}.png'.format(images), result)
I want to combine two pictures, one with the logo which I would like to apply on full dataset from folder products_small. I'm getting a error part_of_bg = cv.merge([bg_b * (1 - fg_a), bg_g * (1 - fg_a), bg_r * (1 - fg_a)]) ValueError: operands could not be broadcast together with shapes (720,540) (766,827)
I tried other combining options and still get the error about problem with shapes, the photo could be a problem or something with the code?
Thank you for your help guys :)
Here is one way to do that in Python/OpenCV. I will place a 20% resized logo onto the pants image at coordinates 660,660 on the right side pocket.
Read the background image (pants)
Read the foreground image (logo) unchanged to preserve the alpha channel
Resize the foreground (logo) to 20%
Create a transparent image the size of the background image
Insert the resized foreground (logo) into the transparent image at the desired location
Extract the alpha channel from the inserted, resized foreground image
Extract the base BGR channels from the inserted, resized foreground image
Blend the background image and the base BGR image using the alpha channel as a mask using np.where(). Note all images must be the same dimensions and 3 channels
Save the result
Background Image:
Foreground Image:
import cv2
import numpy as np
# read background image
bimg = cv2.imread('pants.jpg')
hh, ww = bimg.shape[:2]
# read foreground image
fimg = cv2.imread('flashsale.png', cv2.IMREAD_UNCHANGED)
# resize foreground image
fimg_small = cv2.resize(fimg, (0,0), fx=0.2, fy=0.2)
ht, wd = fimg_small.shape[:2]
# create transparent image
fimg_new = np.full((hh,ww,4), (0,0,0,0), dtype=np.uint8)
# insert resized image into transparent image at desired coordinates
fimg_new[660:660+ht, 660:660+wd] = fimg_small
# extract alpha channel from foreground image as mask and make 3 channels
alpha = fimg_new[:,:,3]
alpha = cv2.merge([alpha,alpha,alpha])
# extract bgr channels from foreground image
base = fimg_new[:,:,0:3]
# blend the two images using the alpha channel as controlling mask
result = np.where(alpha==(0,0,0), bimg, base)
# save result
cv2.imwrite("pants_flashsale.png", result)
# show result
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Result:
This just requires some multiplication and subtraction.
Your overlay has an actual alpha channel, not just a boolean mask. You should use it. It makes edges look better than just a hard boolean mask.
I see one issue with your overlay: it doesn't have any "shadow" to give the white text contrast against a potentially white background.
When you resize RGBA data, it's not trivial. You'd better export the graphic from your vector graphics program in the desired resolution in the first place. Resizing after the fact requires operations to make sure partially transparent pixels (neither 100% opaque nor 100% transparent) are calculated properly so undefined "background" from the fully transparent areas of the overlay image is not mixed into those partially transparent pixels.
base = cv.imread("U3hRd.jpg")
overlay = cv.imread("OBxGQ.png", cv.IMREAD_UNCHANGED)
(bheight, bwidth) = base.shape[:2]
(oheight, owidth) = overlay.shape[:2]
print("base:", bheight, bwidth)
print("overlay:", oheight, owidth)
# place overlay in center
#ox = (bwidth - owidth) // 2
#oy = (bheight - oheight) // 2
# place overlay in top left
ox = 0
oy = 0
overlay_color = overlay[:,:,:3]
overlay_alpha = overlay[:,:,3] * np.float32(1/255)
# "unsqueeze" (insert 1-sized color dimension) so numpy broadcasting works
overlay_alpha = np.expand_dims(overlay_alpha, axis=2)
composite = base.copy()
base_roi = base[oy:oy+oheight, ox:ox+owidth]
composite_roi = composite[oy:oy+oheight, ox:ox+owidth]
composite_roi[:,:] = overlay_color * overlay_alpha + base_roi * (1 - overlay_alpha)
This is what you wanted on top left corner. Noticed, the logo on white foreground doesn't work on background on pant.jpg.
Just 17 lines of codes compared to
import cv2
import numpy as np
img1 = cv2.imread('pant.jpg')
overlay_img1 = np.ones(img1.shape,np.uint8)*255
img2 = cv2.imread('logo3.png')
rows,cols,channels = img2.shape
overlay_img1[0:rows, 0:cols ] = img2
img2gray = cv2.cvtColor(overlay_img1,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray,220,255,cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
temp1 = cv2.bitwise_and(img1,img1,mask = mask_inv)
temp2 = cv2.bitwise_and(overlay_img1,overlay_img1, mask = mask)
result = cv2.add(temp1,temp2)
cv2.imshow("Result",result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Logo resize(320x296):
I am doing a project in python.I want to detect rectangle shape object by opening the webcam using python.I had tried it but I didn't get accurate Answer.I show the object in front of webcam If any finger touched our object it doesn't recognize our object.please anyone can help me.Thanks in Advance:)
Here is my code:
py:
import math
import numpy as np
import cv2
#dictionary of all contours
contours = {}
#array of edges of polygon
approx = []
#scale of the text
scale = 2
#camera
cap = cv2.VideoCapture(0)
print("press q to exit")
# Define the codec and create VideoWriter object
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))
#calculate angle
def angle(pt1,pt2,pt0):
dx1 = pt1[0][0] - pt0[0][0]
dy1 = pt1[0][1] - pt0[0][1]
dx2 = pt2[0][0] - pt0[0][0]
dy2 = pt2[0][1] - pt0[0][1]
return float((dx1*dx2 + dy1*dy2))/math.sqrt(float((dx1*dx1 + dy1*dy1))*(dx2*dx2 + dy2*dy2) + 1e-10)
while(cap.isOpened()):
#Capture frame-by-frame
ret, frame = cap.read()
if ret==True:
#grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#Canny
canny = cv2.Canny(frame,80,240,3)
#contours
canny2, contours, hierarchy = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i in range(0,len(contours)):
#approximate the contour with accuracy proportional to
#the contour perimeter
approx = cv2.approxPolyDP(contours[i],cv2.arcLength(contours[i],True)*0.02,True)
#Skip small or non-convex objects
if(abs(cv2.contourArea(contours[i]))<100 or not(cv2.isContourConvex(approx))):
continue
x,y,w,h = cv2.boundingRect(contours[i])
vtc = len(approx)
if(vtc==4):
cv2.putText(frame,'RECTANGLE',(x,y),cv2.FONT_HERSHEY_SIMPLEX,scale,(255,255,255),2,cv2.LINE_AA)
#Display the resulting frame
out.write(frame)
cv2.imshow('frame',frame)
cv2.imshow('canny',canny)
if cv2.waitKey(1) == 1048689: #if q is pressed
break
#When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
Here is my output:
Non Working output
Working output
You currently have this part in your code:
#Skip small or non-convex objects
if(abs(cv2.contourArea(contours[i]))<100 or not(cv2.isContourConvex(approx))):
continue
x,y,w,h = cv2.boundingRect(contours[i])
vtc = len(approx)
if(vtc==4):
cv2.putText(frame,'RECTANGLE',(x,y),cv2.FONT_HERSHEY_SIMPLEX,scale,(255,255,255),2,cv2.LINE_AA)
here it is possible to create a rectangle around the contour and compare the areas. For that it is possible to use boundingRect, however, your phone can be slightly angled, so minAreaRect is better for this. It will return ((x,y), (w,h), angle) you care is the (w,h) part since the area is w*h. You already know hot to get the actual area of the contour, since it is in your code.
At the end the code should look like this:
#Skip small or non-convex objects
if(abs(cv2.contourArea(contours[i]))<100 or not(cv2.isContourConvex(approx))):
continue
x,y,w,h = cv2.boundingRect(contours[i])
vtc = len(approx)
rect = cv2.minAreaRect(contours[i])
rectArea = rect[1][0] * rect[1][1]
contourArea = cv2.contourArea(contours[i])
# now it will check if the difference is less than 10% of the rect area
if vtc==4 or abs(rectArea - contourArea) < rectArea * .10:
cv2.putText(frame,'RECTANGLE',(x,y),cv2.FONT_HERSHEY_SIMPLEX,scale,(255,255,255),2,cv2.LINE_AA)
Probably this will work, but you may need to adjust the threshold (I used 10% of rectArea). Even the 4 point check can be omitted, If it is a rectangle it will have a perfect fit (rectarea-contourarea) = 0.
I hope this helps, however this is a simple way. More possible answers are also valid for this. You can even think it with machine learning algorithms, or rectangle fitting algorithms.
So the popular approach to lane detection using computer vision is to perform these 5 steps:
Convert the image to grayscale, smooth the image by gaussian
function
Use canny edge function to detect edges (obviously right? )
Mark the region of interest ROI
Use hough transform fucntion to detect straight lines and have line function to draw them.
That's what my approach.
But the point here is we usually need to manually select the ROI. In most case when apply to dash camera on a car, it's ok since the view does not change much.
But my situation is different, I want to detect road lanes based on traffic surveillance cameras, and of course there are many of them. Each camera has its own view, so I think that there must be a way to automatically separate the road and non-road areas.
My question is how to detect the ROI automatically?
My idea here is that the road area will have lots of pixel movements and the non-road area will not. From that idea we could automatically detect the ROI.
I have manage to use opencv to extract the background (background subtracted) from this video (https://youtu.be/bv3NEzjb5sU) using openCV and subtractBackgroundMOG2 function.
The code about canny edge and hough transform is basically ok after we have the ROI. This below is the code to train and extract the background. I though we could modify it to give the region mask or something that can use as ROI for later steps.
Thank you.
import cv2
from pathlib import Path
def bg_train(video_source, number_of_run, number_of_frames):
cap = cv2.VideoCapture(video_source)
fgbg = cv2.createBackgroundSubtractorMOG2(detectShadows=True)
frame_number = -1
default_background = Path("default_background.png")
# the number of loop that will run to create a better background
i = 0
# check the file if it's already exist
if default_background.is_file():
i = 1
default_background = "default_background.png"
else:
default_background = None
i = 0
while i < number_of_run:
# Capture next frame and show in window
ret, frame = cap.read()
if not ret:
print("frame capture failed or not :)")
break
cv2.imshow("training_original", frame)
# subtract foreground and show in new window
background_read = cv2.imread(default_background)
fg = fgbg.apply(frame, background_read, -1)
fg_mask = filter_mask(fg)
cv2.imshow('Training_FG', fg_mask)
# subtract background and show in new window
bg = fgbg.getBackgroundImage(fg_mask)
cv2.imshow('Training_background', bg)
print(i, frame_number, bg.shape[0], bg.shape[1], bg.shape[2])
# Counting frame and save the final background after training
frame_number += 1
if frame_number == number_of_frames and i < number_of_run - 1:
i += 1
cv2.imwrite("default_background.png", bg)
cap.release()
cv2.destroyAllWindows()
cap = cv2.VideoCapture(video_source)
frame_number = -1
elif frame_number == number_of_frames and i == number_of_run - 1:
i += 1
cv2.imwrite("background_final.png", bg)
cv2.imshow("final background", bg)
return 1, bg
k = cv2.waitKey(1) & 0xff
if k == 27:
print("exit by user...")
return 0, None
cap.release()
cv2.destroyAllWindows()
def main():
video_source = "highway-30s.mp4"
check, background_after = bg_train(video_source, 2, 500)
if check == 0:
return 0
elif check == 1:
cv2.imshow("the background, press ESC to close window", background_after)
c = cv2.waitKey(0)
if c == 27:
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
You could train the algorithm to pick the ROI, over an initial amount of time, by tracking movement in the viewport over a series of frames.
Filter out movements and create lines/vectors representing their direction. Once you have enough of these samples you can figure out the best ROI by using a bounding box which encapsulates these vectors.
I have a large number of images of a fixed size (say 500*500). I want to write a python script which will resize them to a fixed size (say 800*800) but will keep the original image at the center and fill the excess area with a fixed color (say black).
I am using PIL. I can resize the image using the resize function now, but that changes the aspect ratio. Is there any way to do this?
You can create a new image with the desired new size, and paste the old image in the center, then saving it. If you want, you can overwrite the original image (are you sure? ;o)
import Image
old_im = Image.open('someimage.jpg')
old_size = old_im.size
new_size = (800, 800)
new_im = Image.new("RGB", new_size) ## luckily, this is already black!
box = tuple((n - o) // 2 for n, o in zip(new_size, old_size))
new_im.paste(old_im, box)
new_im.show()
# new_im.save('someimage.jpg')
You can also set the color of the new border with a third argument of Image.new() (for example: Image.new("RGB", new_size, "White"))
Yes, there is.
Make something like this:
from PIL import Image, ImageOps
ImageOps.expand(Image.open('original-image.png'),border=300,fill='black').save('imaged-with-border.png')
You can write the same at several lines:
from PIL import Image, ImageOps
img = Image.open('original-image.png')
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('imaged-with-border.png')
And you say that you have a list of images. Then you must use a cycle to process all of them:
from PIL import Image, ImageOps
for i in list-of-images:
img = Image.open(i)
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('bordered-%s' % i)
Alternatively, if you are using OpenCV, they have a function called copyMakeBorder that allows you to add padding to any of the sides of an image. Beyond solid colors, they've also got some cool options for fancy borders like reflecting or extending the image.
import cv2
img = cv2.imread('image.jpg')
color = [101, 52, 152] # 'cause purple!
# border widths; I set them all to 150
top, bottom, left, right = [150]*4
img_with_border = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
Sources: OpenCV border tutorial and
OpenCV 3.1.0 Docs for copyMakeBorder
PIL's crop method can actually handle this for you by using numbers that are outside the bounding box of the original image, though it's not explicitly stated in the documentation. Negative numbers for left and top will add black pixels to those edges, while numbers greater than the original width and height for right and bottom will add black pixels to those edges.
This code accounts for odd pixel sizes:
from PIL import Image
with Image.open('/path/to/image.gif') as im:
old_size = im.size
new_size = (800, 800)
if new_size > old_size:
# Set number of pixels to expand to the left, top, right,
# and bottom, making sure to account for even or odd numbers
if old_size[0] % 2 == 0:
add_left = add_right = (new_size[0] - old_size[0]) // 2
else:
add_left = (new_size[0] - old_size[0]) // 2
add_right = ((new_size[0] - old_size[0]) // 2) + 1
if old_size[1] % 2 == 0:
add_top = add_bottom = (new_size[1] - old_size[1]) // 2
else:
add_top = (new_size[1] - old_size[1]) // 2
add_bottom = ((new_size[1] - old_size[1]) // 2) + 1
left = 0 - add_left
top = 0 - add_top
right = old_size[0] + add_right
bottom = old_size[1] + add_bottom
# By default, the added pixels are black
im = im.crop((left, top, right, bottom))
Instead of the 4-tuple, you could instead use a 2-tuple to add the same number of pixels on the left/right and top/bottom, or a 1-tuple to add the same number of pixels to all sides.
It is important to consider old dimension, new dimension and their difference here. If the difference is odd (not even), you will need to specify slightly different values for left, top, right and bottom borders.
Assume the old dimension is ow,oh and new one is nw,nh.
So, this would be the answer:
import Image, ImageOps
img = Image.open('original-image.png')
deltaw=nw-ow
deltah=nh-oh
ltrb_border=(deltaw/2,deltah/2,deltaw-(deltaw/2),deltah-(deltah/2))
img_with_border = ImageOps.expand(img,border=ltrb_border,fill='black')
img_with_border.save('imaged-with-border.png')
You can load the image with scipy.misc.imread as a numpy array. Then create an array with the desired background with numpy.zeros((height, width, channels)) and paste the image at the desired location:
import numpy as np
import scipy.misc
im = scipy.misc.imread('foo.jpg', mode='RGB')
height, width, channels = im.shape
# make canvas
im_bg = np.zeros((height, width, channels))
im_bg = (im_bg + 1) * 255 # e.g., make it white
# Your work: Compute where it should be
pad_left = ...
pad_top = ...
im_bg[pad_top:pad_top + height,
pad_left:pad_left + width,
:] = im
# im_bg is now the image with the background.
ximg = Image.open(qpath)
xwid,xhgt = func_ResizeImage(ximg)
qpanel_3 = tk.Frame(Body,width=xwid+10,height=xhgt+10,bg='white',bd=5)
ximg = ximg.resize((xwid,xhgt),Image.ANTIALIAS)
ximg = ImageTk.PhotoImage(ximg)
panel = tk.Label(qpanel_3,image=ximg)
panel.image = ximg
panel.grid(row = 2)
from PIL import Image
from PIL import ImageOps
img = Image.open("dem.jpg").convert("RGB")
This part will add black borders at the sides (10% of width)
img_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),0,int(0.1*img.size[0]),0), fill=(0,0,0))
img_side.save("sunset-sides.jpg")
This part will add black borders at the bottom & top (10% of height)
img_updown = ImageOps.expand(img, border=(0,int(0.1*img.size[1]),0,int(0.1*img.size[1])), fill=(0,0,0))
img_updown.save("sunset-top_bottom.jpg")
This part will add black borders at the bottom,top & sides (10% of height-width)
img_updown_side = ImageOps.expand(img, border=(int(0.1*img.size[0]),int(0.1*img.size[1]),int(0.1*img.size[0]),int(0.1*img.size[1])), fill=(0,0,0))
img_updown_side.save("sunset-all_sides.jpg")
img.close()
img_side.close()
img_updown.close()
img_updown_side.close()