Zoom Into Image With OpenCV - python

I have the following picture as an example:
529 x 550 px (100 %)
As a target I would like to have the image zoomed to about
150 %, but it should still be
529 x 550 px:
I was able to write the code using PIL, but I want to have it with Cv2. Can someone help me please?
from PIL import Image
import cv2 as cv
def zoom_at(img, x, y, zoom):
w, h = img.size
zoom2 = zoom * 2
img = img.crop((x - w / zoom2, y - h / zoom2,
x + w / zoom2, y + h / zoom2))
return img.resize((w, h), Image.LANCZOS)
img = Image.open("image.png")
img = zoom_at(img, 264.5, 275, 1.5)
img = img.save('image_zoomed.png')
#Ofer Sadan
import cv2 as cv
def zoom(img, zoom_factor=1.5):
return cv.resize(img, None, fx=zoom_factor, fy=zoom_factor)
img = cv.imread('original.png')
# Original: 529 × 550
height, width = img.shape[:2]
zoomed = zoom(img, 1.5)
# Zoomed: 794 × 825
cropped = zoomed[0:550, 0:529] # Wrong area
# Now I want to crop the middle of the new image as variable.
cv.imwrite('zoomed.png', zoomed)
cv.imwrite('cropped.png', cropped)

There you go:
cv:
import cv2 as cv
def zoom_at(img, zoom=1, angle=0, coord=None):
cy, cx = [ i/2 for i in img.shape[:-1] ] if coord is None else coord[::-1]
rot_mat = cv2.getRotationMatrix2D((cx,cy), angle, zoom)
result = cv2.warpAffine(img, rot_mat, img.shape[1::-1], flags=cv2.INTER_LINEAR)
return result
Laymans manual:
import cv2 as cv
def zoom_at(img, zoom, coord=None):
"""
Simple image zooming without boundary checking.
Centered at "coord", if given, else the image center.
img: numpy.ndarray of shape (h,w,:)
zoom: float
coord: (float, float)
"""
# Translate to zoomed coordinates
h, w, _ = [ zoom * i for i in img.shape ]
if coord is None: cx, cy = w/2, h/2
else: cx, cy = [ zoom*c for c in coord ]
img = cv.resize( img, (0, 0), fx=zoom, fy=zoom)
img = img[ int(round(cy - h/zoom * .5)) : int(round(cy + h/zoom * .5)),
int(round(cx - w/zoom * .5)) : int(round(cx + w/zoom * .5)),
: ]
return img
img = cv.imread('x3Lkg.png')
cv.imwrite('x3Lkg_zoomed.png', zoom_at(img, 1.5, coord=(264.5, 275)) )

I have a little snippet I used a while ago that I can't currently test so let me know if it actually works or not
import cv2 as cv
def zoom(img, zoom_factor=2):
return cv.resize(img, None, fx=zoom_factor, fy=zoom_factor)
And you can crop before the zoom or after it as you wish:
img = cv.imread(img_path)
cropped = img[200:300, 150:250]
zoomed = zoom(img, 3)
zoomed_and_cropped = zoom(cropped, 3)

For anyone who does not want to the math manually this works for me.
import cv2
def zoom_center(img, zoom_factor=1.5):
y_size = img.shape[0]
x_size = img.shape[1]
# define new boundaries
x1 = int(0.5*x_size*(1-1/zoom_factor))
x2 = int(x_size-0.5*x_size*(1-1/zoom_factor))
y1 = int(0.5*y_size*(1-1/zoom_factor))
y2 = int(y_size-0.5*y_size*(1-1/zoom_factor))
# first crop image then scale
img_cropped = img[y1:y2,x1:x2]
return cv2.resize(img_cropped, None, fx=zoom_factor, fy=zoom_factor)
# read original
img = cv2.imread('original.png')
# call our function
img_zoomed_and_cropped = zoom_center(img)
# write zoomed and cropped version
cv.imwrite('zoomed_and_cropped.png', img_zoomed_and_cropped)
Notice that I first cropped and then rescaled. It is more efficient and you will notice it when dealing with a live video feed.

For putting a specific point in the input image at a specific point in the output image, with a precise scale factor, you would want to use cv.warpAffine.
This function requires you to build a transformation matrix. That is easy.
def translate(tx=0, ty=0):
T = np.eye(3)
T[0:2,2] = [tx, ty]
return T
def scale(s=1, sx=1, sy=1):
T = np.diag([s*sx, s*sy, 1])
return T
def rotate(degrees):
T = np.eye(3)
# just involves some sin() and cos()
T[0:2] = cv.getRotationMatrix2D(center=(0,0), angle=-degrees, scale=1.0)
return T
im = cv.imread("x3Lkg.png")
(ih,iw) = im.shape[:2] # input height, input width
# parameters
scale_factor = 10
angle_degrees = 15
(ow, oh) = (529, 550) # output size
(icx, icy) = (459, 352) # zoom onto that pixel in input
(ocx, ocy) = ((ow-1)/2, (oh-1)/2) # put there in output (it's the exact center)
# the transformation, read from right to left
H = translate(+ocx, +ocy) # rotate(degrees=angle_degrees) # scale(scale_factor) # translate(-icx, -icy)
# assume that H is affine, not a full homography
assert np.allclose(H[2], [0,0,1])
M = H[0:2]
# produce the picture
# use INTER_LINEAR, INTER_CUBIC, INTER_LANCZOS4 for smooth interpolation
# use INTER_AREA for scale factors much below 1
out = cv.warpAffine(im, dsize=(ow,oh), M=M, flags=cv.INTER_NEAREST)
# imshow(out)

Related

Detect drops of water using OpenCV

I am trying to detect drops inside the water, where at first I will detect the edges, but there are light spots in the image, which are also detected as drops.
Noting that the drops are white surrounded by a dark layer.
My code :
import cv2
import numpy as np
def unsharp_mask(img, blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
gaussian = cv2.GaussianBlur(img, (5,5), 0)
return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)
def clahe(img, clip_limit = 2.0):
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(5,5))
return clahe.apply(img)
def get_sobel(img, size = -1):
sobelx64f = cv2.Sobel(img,cv2.CV_64F,2,0,size)
abs_sobel64f = np.absolute(sobelx64f)
return np.uint8(abs_sobel64f)
img = cv2.imread("img_brightened.jpg")
# save color copy for visualizing
imgc = img.copy()
# resize image to make the analytics easier (a form of filtering)
resize_times = 1.5
img = cv2.resize(img, None, img, fx = 1 / resize_times, fy = 1 / resize_times)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("Input", img)
# use sobel operator to evaluate high frequencies
sobel = get_sobel(img)
# experimentally calculated function - needs refining
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557
# don't apply clahe if there is enough high freq to find blobs
if(clip_limit < 1.0):
clip_limit = 0.1
# limit clahe if there's not enough details - needs more tests
if(clip_limit > 8.0):
clip_limit = 8
# apply clahe and unsharp mask to improve high frequencies as much as possible
img = clahe(img, clip_limit)
img = unsharp_mask(img)
# filter the image to ensure edge continuity and perform Canny
img_blurred = (cv2.GaussianBlur(img, (2*2+1,2*2+1), 0))
canny = cv2.Canny(img_blurred, 100, 255)
cv2.imshow("Output", canny)
cv2.waitKey(0)
Result
I used codes from https://github.com/kavyamusty/Shading-removal-of-images/blob/master/Article%20submission.ipynb, which works for removing shadows first, then the cv2.HoughCircles to find the circles.
The codes as below :
import cv2
import numpy as np
import matplotlib.pyplot as plt
def max_filtering(N, I_temp):
wall = np.full((I_temp.shape[0]+(N//2)*2, I_temp.shape[1]+(N//2)*2), -1)
wall[(N//2):wall.shape[0]-(N//2), (N//2):wall.shape[1]-(N//2)] = I_temp.copy()
temp = np.full((I_temp.shape[0]+(N//2)*2, I_temp.shape[1]+(N//2)*2), -1)
for y in range(0,wall.shape[0]):
for x in range(0,wall.shape[1]):
if wall[y,x]!=-1:
window = wall[y-(N//2):y+(N//2)+1,x-(N//2):x+(N//2)+1]
num = np.amax(window)
temp[y,x] = num
A = temp[(N//2):wall.shape[0]-(N//2), (N//2):wall.shape[1]-(N//2)].copy()
return A
def min_filtering(N, A):
wall_min = np.full((A.shape[0]+(N//2)*2, A.shape[1]+(N//2)*2), 300)
wall_min[(N//2):wall_min.shape[0]-(N//2), (N//2):wall_min.shape[1]-(N//2)] = A.copy()
temp_min = np.full((A.shape[0]+(N//2)*2, A.shape[1]+(N//2)*2), 300)
for y in range(0,wall_min.shape[0]):
for x in range(0,wall_min.shape[1]):
if wall_min[y,x]!=300:
window_min = wall_min[y-(N//2):y+(N//2)+1,x-(N//2):x+(N//2)+1]
num_min = np.amin(window_min)
temp_min[y,x] = num_min
B = temp_min[(N//2):wall_min.shape[0]-(N//2), (N//2):wall_min.shape[1]-(N//2)].copy()
return B
def background_subtraction(I, B):
O = I - B
norm_img = cv2.normalize(O, None, 0,255, norm_type=cv2.NORM_MINMAX)
return norm_img
def min_max_filtering(M, N, I):
if M == 0:
#max_filtering
A = max_filtering(N, I)
#min_filtering
B = min_filtering(N, A)
#subtraction
normalised_img = background_subtraction(I, B)
elif M == 1:
#min_filtering
A = min_filtering(N, I)
#max_filtering
B = max_filtering(N, A)
#subtraction
normalised_img = background_subtraction(I, B)
return normalised_img
# Read Image
img = cv2.imread(r"D:/Image.jpg")
# Copy origin image
cimg = img.copy()
# Initialization array of uint8
img_remove_shadow = np.zeros(np.shape(img), dtype="uint8")
for i in range(np.shape(img)[2]):
img_remove_shadow[:, :, i] = np.array(min_max_filtering(M = 0, N = 20, I = img[:, :, i]))
# Using median blur
img = cv2.medianBlur(img_remove_shadow,5)
# Change to gray image
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("Removing Shadow", img)
# Find circles
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 5, np.array([]), 40, 23, 5,20)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
The result is as below:
PS: The codes take 11.74s running time, I would appreciate it if someone could optimize the code.

Reading images from files simultaneously

I have multiple different folders with the images have same naming like a.png etc. I want to modify the above code to read this same named files in different directories and give their opencv output using yolo at the same time. To be more specific I have 10 files which contains images transported with different categories like one folder contains rgb files and the other contains gray files etc. To compare their output, I want to show the images with same naming but in different folders. I know it should not be that hard but I am pretty confused. Thanks in advance!
import cv2
import numpy as np
import os
import matplotlib.pyplot as plt
import tkinter
from tkinter import filedialog
def cal_alpB(minMax):
minD = minMax[0]
maxD = minMax[1]
alpha = 255/(maxD-minD)
beta = -alpha*minD
return [alpha, beta]
def getMinMax(path):
with open(path+'/config') as f:
minMax = f.read().splitlines()
minMax = minMax[0].split(',')
minMax = [eval(x) for x in minMax]
return minMax
def normalizeData(minMax, img):
alpB = cal_alpB(minMax)
img[img>minMax[1]] = minMax[1]
img[img<0] = 0
return alpB
def boxDrawing(layerOutput, frameWidth, frameHeight, class_ids, confidences, boxes, img):
for output in layerOutput:
for detection in output:
score = detection[5:]
class_id = np.argmax(score)
confidence = score[class_id]
if confidence > 0.5:
center_x = int(detection[0] * frameWidth)
center_y = int(detection[1] * frameHeight)
width = int(detection[2] * frameWidth)
height = int(detection[3] * frameHeight)
left = int(center_x - width / 2)
top = int(center_y - height / 2)
class_ids.append(class_id)
confidences.append(float(confidence))
boxes.append([left, top, width, height])
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.8, 0.7)
font = cv2.FONT_HERSHEY_PLAIN
colors = np.random.uniform(0, 255, size = (len(boxes),3))
for i in range(len(boxes)):
if i in indexes:
x,y,w,h = boxes[i]
label = str(classes[class_ids[i]])
confi = str(round(confidences[i],2))
color = colors[i]
cv2.rectangle(img, (x,y), (x+w,y+h), color,1)
cv2.putText(img, label+" "+ confi, (x,y+20), font, 1, (255,255,255),1)
def algorythmYolo():
tkinter.Tk().withdraw()
folder = filedialog.askdirectory()
minMax = getMinMax(folder)
for filename in sorted(os.listdir(folder)):
img = cv2.imread(os.path.join(folder,filename),-1)
if img is not None:
alpB = normalizeData(minMax,img)
img = cv2.convertScaleAbs(img, alpha=alpB[0], beta= alpB[1])
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
frameHeight, frameWidth, channels = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255, (frameWidth,frameHeight), (0,0,0), swapRB = True, crop = False)
yolo.setInput(blob)
layerOutput = yolo.forward(outputLayers)
boxes = []
confidences = []
class_ids = []
boxDrawing(layerOutput,frameWidth, frameHeight,class_ids,confidences,boxes,img)
cv2.imshow("window", img)
cv2.setWindowTitle('window', folder)
cv2.waitKey(1)
else:
break
cv2.destroyAllWindows()
yolo = cv2.dnn.readNet("./yolov3.weights","./yolov3.cfg")
with open("./coco.names","r") as f:
classes = f.read().splitlines()
layers_names = yolo.getLayerNames()
outputLayers = [layers_names[i-1] for i in yolo.getUnconnectedOutLayers()]
cv2.namedWindow("window", cv2.WINDOW_NORMAL)
algorythmYolo()

Why the interpolation of resize in opencv is worse than that of PIL?

I tried resize functions with openCV and PIL respectively. And I found that the performance in PIL is way better. But according to the instruction, the method of interpolation is similar (e.g. bicubic).
Could someone provide any insight?
My complete code is below. I just want to resize the original image and padding it by pasting (for YOLO input).
import numpy as np
from PIL import Image
def letterbox_image_np(image, size):
iw, ih = image.shape[1], image.shape[0]
w, h = size
scale = min(w / iw, h / ih)
nw = int(iw * scale)
nh = int(ih * scale)
# resize image according to (416,416) & orig size
image = cv.resize(imgNP, dsize=(nw, nh), interpolation=cv.INTER_CUBIC)
# plt.imshow(image), plt.show()
new_image = 128 * np.ones((h, w, 3), dtype=np.uint8)
# plt.imshow(new_image), plt.show()
n = np.array(new_image)
offset_h = (h - nh) // 2
offset_w = (w - nw) // 2
new_image[offset_h:offset_h + nh, offset_w:offset_w + nw] = image
return new_image
def letterbox_image_pil(image, size):
iw, ih = image.size # as "Image" object
w, h = size
scale = min(w / iw, h / ih)
nw = int(iw * scale)
nh = int(ih * scale)
# resize image according to (416,416) & orig size
image = image.resize((nw, nh), Image.BICUBIC)
new_image = Image.new('RGB', size, (128, 128, 128))
# plt.imshow(new_image), plt.show()
new_image.paste(image, ((w - nw) // 2, (h - nh) // 2))
return new_image
if __name__ == '__main__':
# np_frame
imgNP = cv.imread(r"C:\ProgamData\global_dataset\img_vid\down.jpg")
# pil_frame
imgPIL = cv.cvtColor(imgNP, cv.COLOR_BGR2RGB)
imgPIL = Image.fromarray(np.uint8(imgPIL))
size = (416, 416)
# methodNP
newImg1 = letterbox_image_np(imgNP, size)
newImg1 = cv.cvtColor(newImg1, cv.COLOR_BGR2RGB)
# methodPIL
newImg2 = letterbox_image_pil(imgPIL, size)
plt.imshow(newImg1), plt.show()
plt.imshow(newImg2), plt.show()
pass
Example by by openCV
]
Example by by PIL
]

ModuleNotFoundError: No module named 'preprocessors'

I'm getting module not found error while executing my program.
import numpy as np
import cv2
from preprocessors import x_cord_contour, makeSquare, resize_to_pixel
I am quite sure that you are following along the the Deep Learning course by Rajeev D. And I further guess that you haven't downloaded the VM image as suggested in the video.
The functions x_cord_contour, makeSquare and resize_to_pixel are defined in a custom module. If you are following the course without the VM just copy and paste the functions below into your code and remove the import statement.
import numpy as np
import cv2
def x_cord_contour(contour):
# This function take a contour from findContours
# it then outputs the x centroid coordinates
M = cv2.moments(contour)
return (int(M['m10']/M['m00']))
def makeSquare(not_square):
# This function takes an image and makes the dimenions square
# It adds black pixels as the padding where needed
BLACK = [0,0,0]
img_dim = not_square.shape
height = img_dim[0]
width = img_dim[1]
#print("Height = ", height, "Width = ", width)
if (height == width):
square = not_square
return square
else:
doublesize = cv2.resize(not_square,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)
height = height * 2
width = width * 2
#print("New Height = ", height, "New Width = ", width)
if (height > width):
pad = int((height - width)/2)
#print("Padding = ", pad)
doublesize_square = cv2.copyMakeBorder(doublesize,0,0,pad,pad,cv2.BORDER_CONSTANT,value=BLACK)
else:
pad = (width - height)/2
#print("Padding = ", pad)
doublesize_square = cv2.copyMakeBorder(doublesize,pad,pad,0,0,\
cv2.BORDER_CONSTANT,value=BLACK)
doublesize_square_dim = doublesize_square.shape
#print("Sq Height = ", doublesize_square_dim[0], "Sq Width = ", doublesize_square_dim[1])
return doublesize_square
def resize_to_pixel(dimensions, image):
# This function then re-sizes an image to the specificied dimenions
buffer_pix = 4
dimensions = dimensions - buffer_pix
squared = image
r = float(dimensions) / squared.shape[1]
dim = (dimensions, int(squared.shape[0] * r))
resized = cv2.resize(image, dim, interpolation = cv2.INTER_AREA)
img_dim2 = resized.shape
height_r = img_dim2[0]
width_r = img_dim2[1]
BLACK = [0,0,0]
if (height_r > width_r):
resized = cv2.copyMakeBorder(resized,0,0,0,1,cv2.BORDER_CONSTANT,value=BLACK)
if (height_r < width_r):
resized = cv2.copyMakeBorder(resized,1,0,0,0,cv2.BORDER_CONSTANT,value=BLACK)
p = 2
ReSizedImg = cv2.copyMakeBorder(resized,p,p,p,p,cv2.BORDER_CONSTANT,value=BLACK)
img_dim = ReSizedImg.shape
height = img_dim[0]
width = img_dim[1]
#print("Padded Height = ", height, "Width = ", width)
return ReSizedImg

Combine several images horizontally with Python

I am trying to horizontally combine some JPEG images in Python.
Problem
I have 3 images - each is 148 x 95 - see attached. I just made 3 copies of the same image - that is why they are the same.
My attempt
I am trying to horizontally join them using the following code:
import sys
from PIL import Image
list_im = ['Test1.jpg','Test2.jpg','Test3.jpg']
# creates a new empty image, RGB mode, and size 444 by 95
new_im = Image.new('RGB', (444,95))
for elem in list_im:
for i in xrange(0,444,95):
im=Image.open(elem)
new_im.paste(im, (i,0))
new_im.save('test.jpg')
However, this is producing the output attached as test.jpg.
Question
Is there a way to horizontally concatenate these images such that the sub-images in test.jpg do not have an extra partial image showing?
Additional Information
I am looking for a way to horizontally concatenate n images. I would like to use this code generally so I would prefer to:
not to hard-code image dimensions, if possible
specify dimensions in one line so that they can be easily changed
You can do something like this:
import sys
from PIL import Image
images = [Image.open(x) for x in ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
new_im.save('test.jpg')
Test1.jpg
Test2.jpg
Test3.jpg
test.jpg
The nested for for i in xrange(0,444,95): is pasting each image 5 times, staggered 95 pixels apart. Each outer loop iteration pasting over the previous.
for elem in list_im:
for i in xrange(0,444,95):
im=Image.open(elem)
new_im.paste(im, (i,0))
new_im.save('new_' + elem + '.jpg')
I would try this:
import numpy as np
import PIL
from PIL import Image
list_im = ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']
imgs = [ Image.open(i) for i in list_im ]
# pick the image which is the smallest, and resize the others to match it (can be arbitrary image shape here)
min_shape = sorted( [(np.sum(i.size), i.size ) for i in imgs])[0][1]
imgs_comb = np.hstack([i.resize(min_shape) for i in imgs])
# save that beautiful picture
imgs_comb = Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta.jpg' )
# for a vertical stacking it is simple: use vstack
imgs_comb = np.vstack([i.resize(min_shape) for i in imgs])
imgs_comb = Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta_vertical.jpg' )
It should work as long as all images are of the same variety (all RGB, all RGBA, or all grayscale). It shouldn't be difficult to ensure this is the case with a few more lines of code. Here are my example images, and the result:
Test1.jpg
Test2.jpg
Test3.jpg
Trifecta.jpg:
Trifecta_vertical.jpg
Edit: DTing's answer is more applicable to your question since it uses PIL, but I'll leave this up in case you want to know how to do it in numpy.
Here is a numpy/matplotlib solution that should work for N images (only color images) of any size/shape.
import numpy as np
import matplotlib.pyplot as plt
def concat_images(imga, imgb):
"""
Combines two color image ndarrays side-by-side.
"""
ha,wa = imga.shape[:2]
hb,wb = imgb.shape[:2]
max_height = np.max([ha, hb])
total_width = wa+wb
new_img = np.zeros(shape=(max_height, total_width, 3))
new_img[:ha,:wa]=imga
new_img[:hb,wa:wa+wb]=imgb
return new_img
def concat_n_images(image_path_list):
"""
Combines N color images from a list of image paths.
"""
output = None
for i, img_path in enumerate(image_path_list):
img = plt.imread(img_path)[:,:,:3]
if i==0:
output = img
else:
output = concat_images(output, img)
return output
Here is example use:
>>> images = ["ronda.jpeg", "rhod.jpeg", "ronda.jpeg", "rhod.jpeg"]
>>> output = concat_n_images(images)
>>> import matplotlib.pyplot as plt
>>> plt.imshow(output)
>>> plt.show()
Here is a function generalizing previous approaches, creating a grid of images in PIL:
from PIL import Image
import numpy as np
def pil_grid(images, max_horiz=np.iinfo(int).max):
n_images = len(images)
n_horiz = min(n_images, max_horiz)
h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz)
for i, im in enumerate(images):
h, v = i % n_horiz, i // n_horiz
h_sizes[h] = max(h_sizes[h], im.size[0])
v_sizes[v] = max(v_sizes[v], im.size[1])
h_sizes, v_sizes = np.cumsum([0] + h_sizes), np.cumsum([0] + v_sizes)
im_grid = Image.new('RGB', (h_sizes[-1], v_sizes[-1]), color='white')
for i, im in enumerate(images):
im_grid.paste(im, (h_sizes[i % n_horiz], v_sizes[i // n_horiz]))
return im_grid
It will shrink each row and columns of the grid to the minimum. You can have only a row by using pil_grid(images), or only a column by using pil_grid(images, 1).
One benefit of using PIL over numpy-array based solutions is that you can deal with images structured differently (like grayscale or palette-based images).
Example outputs
def dummy(w, h):
"Produces a dummy PIL image of given dimensions"
from PIL import ImageDraw
im = Image.new('RGB', (w, h), color=tuple((np.random.rand(3) * 255).astype(np.uint8)))
draw = ImageDraw.Draw(im)
points = [(i, j) for i in (0, im.size[0]) for j in (0, im.size[1])]
for i in range(len(points) - 1):
for j in range(i+1, len(points)):
draw.line(points[i] + points[j], fill='black', width=2)
return im
dummy_images = [dummy(20 + np.random.randint(30), 20 + np.random.randint(30)) for _ in range(10)]
pil_grid(dummy_images):
pil_grid(dummy_images, 3):
pil_grid(dummy_images, 1):
Based on DTing's answer I created a function that is easier to use:
from PIL import Image
def append_images(images, direction='horizontal',
bg_color=(255,255,255), aligment='center'):
"""
Appends images in horizontal/vertical direction.
Args:
images: List of PIL images
direction: direction of concatenation, 'horizontal' or 'vertical'
bg_color: Background color (default: white)
aligment: alignment mode if images need padding;
'left', 'right', 'top', 'bottom', or 'center'
Returns:
Concatenated image as a new PIL image object.
"""
widths, heights = zip(*(i.size for i in images))
if direction=='horizontal':
new_width = sum(widths)
new_height = max(heights)
else:
new_width = max(widths)
new_height = sum(heights)
new_im = Image.new('RGB', (new_width, new_height), color=bg_color)
offset = 0
for im in images:
if direction=='horizontal':
y = 0
if aligment == 'center':
y = int((new_height - im.size[1])/2)
elif aligment == 'bottom':
y = new_height - im.size[1]
new_im.paste(im, (offset, y))
offset += im.size[0]
else:
x = 0
if aligment == 'center':
x = int((new_width - im.size[0])/2)
elif aligment == 'right':
x = new_width - im.size[0]
new_im.paste(im, (x, offset))
offset += im.size[1]
return new_im
It allows choosing a background color and image alignment. It's also easy to do recursion:
images = map(Image.open, ['hummingbird.jpg', 'tiger.jpg', 'monarch.png'])
combo_1 = append_images(images, direction='horizontal')
combo_2 = append_images(images, direction='horizontal', aligment='top',
bg_color=(220, 140, 60))
combo_3 = append_images([combo_1, combo_2], direction='vertical')
combo_3.save('combo_3.png')
If all image's heights are same,
import numpy as np
imgs = ['a.jpg', 'b.jp', 'c.jpg']
concatenated = Image.fromarray(
np.concatenate(
[np.array(Image.open(x)) for x in imgs],
axis=1
)
)
Maybe you can resize images before the concatenation like this,
import numpy as np
imgs = ['a.jpg', 'b.jpg', 'c.jpg']
concatenated = Image.fromarray(
np.concatenate(
[np.array(Image.open(x).resize((640,480)) for x in imgs],
axis=1
)
)
Here's my solution:
from PIL import Image
def join_images(*rows, bg_color=(0, 0, 0, 0), alignment=(0.5, 0.5)):
rows = [
[image.convert('RGBA') for image in row]
for row
in rows
]
heights = [
max(image.height for image in row)
for row
in rows
]
widths = [
max(image.width for image in column)
for column
in zip(*rows)
]
tmp = Image.new(
'RGBA',
size=(sum(widths), sum(heights)),
color=bg_color
)
for i, row in enumerate(rows):
for j, image in enumerate(row):
y = sum(heights[:i]) + int((heights[i] - image.height) * alignment[1])
x = sum(widths[:j]) + int((widths[j] - image.width) * alignment[0])
tmp.paste(image, (x, y))
return tmp
def join_images_horizontally(*row, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
return join_images(
row,
bg_color=bg_color,
alignment=alignment
)
def join_images_vertically(*column, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
return join_images(
*[[image] for image in column],
bg_color=bg_color,
alignment=alignment
)
For these images:
images = [
[Image.open('banana.png'), Image.open('apple.png')],
[Image.open('lime.png'), Image.open('lemon.png')],
]
Results will look like:
join_images(
*images,
bg_color='green',
alignment=(0.5, 0.5)
).show()
join_images(
*images,
bg_color='green',
alignment=(0, 0)
).show()
join_images(
*images,
bg_color='green',
alignment=(1, 1)
).show()
There is also skimage.util.montage to create a montage of images of the same shape:
import numpy as np
import PIL
from PIL import Image
from skimage.util import montage
list_im = ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']
imgs = [ np.array(Image.open(i)) for i in list_im ]
montage(imgs)
"""
merge_image takes three parameters first two parameters specify
the two images to be merged and third parameter i.e. vertically
is a boolean type which if True merges images vertically
and finally saves and returns the file_name
"""
def merge_image(img1, img2, vertically):
images = list(map(Image.open, [img1, img2]))
widths, heights = zip(*(i.size for i in images))
if vertically:
max_width = max(widths)
total_height = sum(heights)
new_im = Image.new('RGB', (max_width, total_height))
y_offset = 0
for im in images:
new_im.paste(im, (0, y_offset))
y_offset += im.size[1]
else:
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset, 0))
x_offset += im.size[0]
new_im.save('test.jpg')
return 'test.jpg'
from __future__ import print_function
import os
from pil import Image
files = [
'1.png',
'2.png',
'3.png',
'4.png']
result = Image.new("RGB", (800, 800))
for index, file in enumerate(files):
path = os.path.expanduser(file)
img = Image.open(path)
img.thumbnail((400, 400), Image.ANTIALIAS)
x = index // 2 * 400
y = index % 2 * 400
w, h = img.size
result.paste(img, (x, y, x + w, y + h))
result.save(os.path.expanduser('output.jpg'))
Output
Just adding to the solutions already suggested. Assumes same height, no resizing.
import sys
import glob
from PIL import Image
Image.MAX_IMAGE_PIXELS = 100000000 # For PIL Image error when handling very large images
imgs = [ Image.open(i) for i in list_im ]
widths, heights = zip(*(i.size for i in imgs))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
# Place first image
new_im.paste(imgs[0],(0,0))
# Iteratively append images in list horizontally
hoffset=0
for i in range(1,len(imgs),1):
**hoffset=imgs[i-1].size[0]+hoffset # update offset**
new_im.paste(imgs[i],**(hoffset,0)**)
new_im.save('output_horizontal_montage.jpg')
my solution would be :
import sys
import os
from PIL import Image, ImageFilter
from PIL import ImageFont
from PIL import ImageDraw
os.chdir('C:/Users/Sidik/Desktop/setup')
print(os.getcwd())
image_list= ['IMG_7292.jpg','IMG_7293.jpg','IMG_7294.jpg', 'IMG_7295.jpg' ]
image = [Image.open(x) for x in image_list] # list
im_1 = image[0].rotate(270)
im_2 = image[1].rotate(270)
im_3 = image[2].rotate(270)
#im_4 = image[3].rotate(270)
height = image[0].size[0]
width = image[0].size[1]
# Create an empty white image frame
new_im = Image.new('RGB',(height*2,width*2),(255,255,255))
new_im.paste(im_1,(0,0))
new_im.paste(im_2,(height,0))
new_im.paste(im_3,(0,width))
new_im.paste(im_4,(height,width))
draw = ImageDraw.Draw(new_im)
font = ImageFont.truetype('arial',200)
draw.text((0, 0), '(a)', fill='white', font=font)
draw.text((height, 0), '(b)', fill='white', font=font)
draw.text((0, width), '(c)', fill='white', font=font)
#draw.text((height, width), '(d)', fill='white', font=font)
new_im.show()
new_im.save('BS1319.pdf')
[![Laser spots on the edge][1]][1]
#**How to merge cropped images back to original image**
images = [Image.open(x) for x in images_list]
print("Length:: ", len(images))
widths, heights = zip(*(i.size for i in images))
print(widths, heights)
total_width = sum(widths)
max_height = sum(heights)
print(total_width,max_height)
new_im = Image.new('RGB', (5*384, 5*216))
x_offset = 0
y_offset = 0
img_size = [384,216]
def grouped(iterable, n):
return zip(*[iter(iterable)]*n)
for x,y,a,b,c in grouped(images, 5):
temp = []
temp.append([x,y,a,b,c])
print(temp[0])
print(len(temp[0]))
for lsingle_img in temp[0]:
# print(lsingle_img)
print("x_y_offset: ", (x_offset, y_offset))
new_im.paste(lsingle_img, (x_offset, y_offset))
x_offset += img_size[0]
temp = []
x_offset = 0
y_offset += img_size[1]
new_im.save('test.jpg')

Categories

Resources