Related
Here's one of the images I want to apply the outline on, and how it's supposed to look:
And here's the code that's the closest to what I want (taken from here):
import cv2
import numpy as np
from PIL import Image
import math
img = Image.open(f"frames/frame000028.png")
def stroke(origin_image, threshold, stroke_size, colors):
img = np.array(origin_image)
h, w, _ = img.shape
padding = stroke_size + 50
alpha = img[:,:,2]
rgb_img = img[:,:,0:3]
bigger_img = cv2.copyMakeBorder(rgb_img, padding, padding, padding, padding,
cv2.BORDER_CONSTANT, value=(0, 0, 0, 0))
alpha = cv2.copyMakeBorder(alpha, padding, padding, padding, padding, cv2.BORDER_CONSTANT, value=0)
bigger_img = cv2.merge((bigger_img, alpha))
h, w, _ = bigger_img.shape
_, alpha_without_shadow = cv2.threshold(alpha, threshold, 255, cv2.THRESH_BINARY) # threshold=0 in photoshop
alpha_without_shadow = 255 - alpha_without_shadow
dist = cv2.distanceTransform(alpha_without_shadow, cv2.DIST_L2, cv2.DIST_MASK_3) # dist l1 : L1 , dist l2 : l2
stroked = change_matrix(dist, stroke_size)
stroke_alpha = (stroked * 255).astype(np.uint8)
stroke_b = np.full((h, w), colors[0][2], np.uint8)
stroke_g = np.full((h, w), colors[0][1], np.uint8)
stroke_r = np.full((h, w), colors[0][0], np.uint8)
stroke = cv2.merge((stroke_b, stroke_g, stroke_r, stroke_alpha))
stroke = cv2pil(stroke)
bigger_img = cv2pil(bigger_img)
result = Image.alpha_composite(stroke, bigger_img)
return result
def change_matrix(input_mat, stroke_size):
stroke_size = stroke_size - 1
mat = np.ones(input_mat.shape)
check_size = stroke_size + 1.0
mat[input_mat > check_size] = 0
border = (input_mat > stroke_size) & (input_mat <= check_size)
mat[border] = 1.0 - (input_mat[border] - stroke_size)
return mat
def cv2pil(cv_img):
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGRA2RGBA)
pil_img = Image.fromarray(cv_img.astype("uint8"))
return pil_img
output = stroke(img, threshold=0, stroke_size=10, colors=((42,102,209),))
output.show()
I'm trying to detect the region here (circled in red) more effectively. As it currently stands, I have a few steps to get the area:
Brighten the input image
to increase contrast and likelihood to pick up edges of the image file to get this image
Crop and threshold the region of interest, and add Gaussian blur to get this image:
Use OpenCV to detect Hough Circles on the thresholded image
Select the top 10 largest circles found, then choose the one closest to the grid intersection (in the code as fv_cx and fv_cy) vertically and closest to the edge of the image horizontally.
While this works well in general
often times it misses the right circle
or it encircles an area too small
.
Using these input images, is there a better way to work on this problem?
This is my code so far:
from __future__ import print_function
import pandas as pd
from pandas.api.types import is_numeric_dtype
import os
from PIL import Image, ImageDraw, ImageFont
import math
import cv2
import matplotlib.pyplot as plt
import time
import re
import csv
from skimage import data, color, io, img_as_ubyte
from skimage.transform import hough_circle, hough_circle_peaks, hough_ellipse
from skimage.feature import canny
from skimage.draw import circle_perimeter, ellipse_perimeter
from skimage.util import img_as_ubyte
from builtins import input
import numpy as np
def append_list_as_row(file_name, list_of_elem):
with open(file_name, 'a+', newline='', encoding='utf-8') as write_obj:
csv_writer = csv.writer(write_obj, dialect='excel')
csv_writer.writerow(list_of_elem)
def round_up_to_odd(f):
return int(np.ceil(f) // 2 * 2 + 1)
# Folder path here
folder = r""
csv_file = folder + os.sep + "Measurements.csv"
csv_file2 = folder + os.sep + "Measurements2.csv"
df2 = pd.DataFrame(columns = ["filepath","od_cx","od_cy", "fv_x", "fv_y"])
for subdir, dirs, files in os.walk(folder):
for file in files:
#print os.path.join(subdir, file)
filepath = subdir + os.sep + file
if filepath.endswith(".jpeg") or filepath.endswith(".tiff") and not filepath.endswith("_OD.tiff") and not filepath.endswith("_bright.tiff") and not filepath.endswith("_FV.tiff") and not filepath.endswith("_mask.tiff"):
og_cv = cv2.imread(filepath, cv2.IMREAD_COLOR)
if "left" in str(filepath):
od = "left"
elif "right" in str(filepath):
od = "right"
OD_path = subdir + os.sep + "OD"
if not os.path.exists(str(OD_path)):
os.mkdir(str(OD_path))
OD = OD_path + os.sep + str(os.path.splitext(file)[0]) + "_OD.tiff"
fovea_path = OD_path + os.sep + str(os.path.splitext(file)[0]) + "_FV.tiff"
temp_path = subdir + os.sep + "Temp"
if not os.path.exists(str(temp_path)):
os.mkdir(str(temp_path))
bright = temp_path + os.sep + str(os.path.splitext(file)[0]) + "_bright.tiff"
thresholded_od = temp_path + os.sep + str(os.path.splitext(file)[0]) + "_thresholded_OD.tiff"
thresholded_fv = temp_path + os.sep + str(os.path.splitext(file)[0]) + "_thresholded_FV.tiff"
mask_file = temp_path + os.sep + str(os.path.splitext(file)[0]) + "_mask.tiff"
## Fovea
image = cv2.imread(filepath)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
h = image.shape[0]
w = image.shape[1]
# loop over the image
fv_cx = []
fv_cy = []
for y in range(0, h):
for x in range(0, w):
# threshold the pixel
if np.all(image[y, x] == (255, 0, 255)) and np.all(image[y, x+3] == (0, 255, 255)) and np.all(image[y, x-3] == (0, 255, 255)):
print("Found fovea")
fv_cx.append(x)
fv_cy.append(y)
# Draw them
# fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
# image = color.gray2rgb(image)
image_draw = image
if image.shape[2] == 3:
image[fv_cy, fv_cx] = (220, 20, 20)
if image.shape[2] == 4:
image[fv_cy, fv_cx] = (220, 20, 20, 0)
plt.imsave(OD, image)
print(fv_cx, fv_cy)
# else:
# fv_cx = "No fv_cx"
# fv_cy = "No fv_cy"
## Find image dimensions
source_img = Image.open(filepath)
width, height = source_img.size
x_max = int(width)
y_max = int(height)
print(x_max)
print(y_max)
#Load image
im = cv2.imread(filepath, cv2.IMREAD_COLOR)
background = Image.open(filepath).convert('RGB')
width, height = background.size
x_max = int(width)
y_max = int(height)
# Brightness adjustment - https://docs.opencv.org/3.4/d3/dc1/tutorial_basic_linear_transform.html
new_image = np.zeros(im.shape, im.dtype)
alpha = 1.0 # contrast control
beta = 0 # brightness control
new_image = cv2.convertScaleAbs(im, alpha=alpha, beta=100)
# cv2.imshow('New Image', new_image)
# cv2.waitKey(0)
cv2.imwrite(bright, new_image)
new_image = cv2.imread(bright, cv2.IMREAD_COLOR)
## OD
#Convert to HLS, so we can remove the saturated fovea
HLS = cv2.cvtColor(new_image,cv2.COLOR_BGR2HLS)
Schannel = HLS[:,:,2]
mask = cv2.inRange(Schannel, 0, 0)
# res = cv2.bitwise_and(new_image,new_image, mask= mask)
new_image = cv2.cvtColor(new_image,cv2.COLOR_BGR2GRAY)
thresh_x = round_up_to_odd((21/1033) * width)
thresh_x = 21
#### Thresholding Example Options
# img = cv2.bitwise_and(new_image,new_image, mask= mask)
img = cv2.medianBlur(new_image,5)
pil_im = Image.fromarray(mask)
# mask_width, mask_height = pil_im.size
mask_width, mask_height = (165 * (width/290)), (165 * (width/290))
print(width, height)
print(mask_width, mask_height)
margin = 10
if "_L" in filepath or "OS" in filepath:
x_center = width/2
crop_x_start = 0
crop_x_stop = int(x_center-(mask_width/2)) + margin
crop_img = img[0:height, crop_x_start:crop_x_stop]
# cv2.imshow("cropped", crop_img)
cv2.waitKey()
if "_R" in filepath or "OD" in filepath:
x_center = width/2
crop_x_start = int((x_center+(mask_width/2))) - margin
crop_x_stop = width
crop_img = img[0:height, crop_x_start:crop_x_stop]
# cv2.imshow("cropped", crop_img)
cv2.waitKey()
th2 = cv2.adaptiveThreshold(crop_img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,thresh_x,2)
th2 = cv2.GaussianBlur(th2,(21,21),0)
# cv2.imshow("cropped", th2)
cv2.waitKey()
cv2.imwrite(thresholded_od, th2)
## Hough Circle
# Load picture and detect edges
image = img_as_ubyte(th2)
edges = canny(image, sigma=3, low_threshold=10, high_threshold=50)
# Detect two radii
x=50
y=500
z=2
start = math.ceil((x/1033) * width)
stop = math.ceil((y/1033) * width)
step = math.ceil((z/1033) * width)
hough_radii = np.arange(start, stop, step)
hough_res = hough_circle(edges, hough_radii)
if fv_cy != []:
# Select the most prominent 3 circles
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii,
total_num_peaks=10)
df = pd.DataFrame(columns = ["index", "distance", "area", "cX", "cY"])
idx = (0)
# Draw them
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
# image = color.gray2rgb(image)
image = io.imread(filepath)
cx = (cx + crop_x_start)
for center_y, center_x, radius in zip(cy, cx, radii):
# d = math.sqrt(((center_x-fv_cx)**2) + ((center_y-fv_cy)**2))
p = abs(center_y - fv_cy)
q = -1 * abs(center_x - fv_cx)
d = p + q
print(d)
area = math.pi * (radius**2)
df.loc[idx, 'index'] = idx
df.loc[idx, 'area'] = int(area)
df.loc[idx, 'distance'] = int(d)
df.loc[idx, 'cX'] = int(center_x)
df.loc[idx, 'cY'] = int(center_y)
df.loc[idx, 'radius'] = int(radius)
idx += 1
df['distance'] = pd.to_numeric(df['distance'])
df['radius'] = pd.to_numeric(df['radius'])
print("DF?")
print(df)
if len(df["distance"]) > 0:
print("pass")
df_radius = df.nsmallest(3, 'distance')
print(df_radius)
if (df_radius['radius'].max()-df_radius['radius'].min()) < 3:
idx = df_radius['radius'].idxmax()
else:
idx = df['distance'].idxmin()
center_y = int(df.loc[idx, 'cY'])
center_x = int(df.loc[idx, 'cX'])
radius = int(df.loc[idx, 'radius'])
print(center_y, center_x, radius)
# Draw them
# fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
# image = color.gray2rgb(image)
image = io.imread(filepath)
image = image_draw
circy, circx = circle_perimeter(center_y, center_x, radius,
shape=image.shape)
print(image.shape)
print(image.shape[2])
if image.shape[2] == 3:
image_draw[circy, circx] = (220, 20, 20)
circy, circx = circle_perimeter(center_y, center_x, 0,
shape=image.shape)
image_draw[circy, circx] = (220, 20, 20)
if image.shape[2] == 4:
image_draw[circy, circx] = (220, 20, 20, 0)
circy, circx = circle_perimeter(center_y, center_x, 0,
shape=image.shape)
image_draw[circy, circx] = (220, 20, 20, 0)
# final = ax.imshow(image, cmap=plt.cm.gray)
# fig = plt.show()
## Need to fix saving
plt.imsave(OD, image_draw)
else:
hough_radii = np.arange(start, stop, step)
hough_res = hough_circle(edges, hough_radii)
# Select the most prominent 3 circles
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii,
total_num_peaks=1)
if cx != None:
print("Found OD")
od_cx = (re.search(r"\[([A-Za-z0-9_]+)\]",str(cx))).group(1)
od_cy = (re.search(r"\[([A-Za-z0-9_]+)\]",str(cy))).group(1)
else:
od_cx = "Not found"
od_cy = "Not found"
# Draw them
#fig, ax = plt.subplots(ncols=1, nrows=1, #figsize=(10, 4))
# image = color.gray2rgb(image)
image = io.imread(filepath)
image = image_draw
for center_y, center_x, radius in zip(cy, cx, radii):
circy, circx = circle_perimeter(center_y, center_x, radius,
shape=image.shape)
print(image.shape)
print(image.shape[2])
if image.shape[2] == 3:
image_draw[circy, circx] = (220, 20, 20)
circy, circx = circle_perimeter(center_y, center_x, 0,
shape=image.shape)
image_draw[circy, circx] = (220, 20, 20)
if image.shape[2] == 4:
image_draw[circy, circx] = (220, 20, 20, 0)
circy, circx = circle_perimeter(center_y, center_x, 0,
shape=image.shape)
image_draw[circy, circx] = (220, 20, 20, 0)
# final = ax.imshow(image, cmap=plt.cm.gray)
# fig = plt.show()
## Need to fix saving
plt.imsave(OD, image_draw)
append_list_as_row(csv_file,[filepath,center_x,center_y, fv_cx, fv_cy])
plt.close('all')
df2 = df2.append({"filepath":filepath,"od_cx":center_x, "od_cy":center_y, "fv_x":fv_cx, "fv_y":fv_cy}, ignore_index=True)
print(df2)
df2.to_csv(csv_file2)
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
]
The code below is able to detect objects without issue, however, towards the end there is the line "cv2.imshow("demo", img)"
I would expect this window to show the image with the generated bounding boxes and labels, but all I get is a blank window. I got this code originally from some examples on the internet so I'm a bit lost as to how to position that line, or why it's not generating the image.
import cv2
import numpy as np
def take_pic(output_filename):
import os
capture_img="ffmpeg -y -rtsp_transport udp -i rtsp://mycamera:apassword#172.16.66.106/live -vframes 1 " + output_filename
net = cv2.dnn.readNet("yolov3.weights", "./darknet/cfg/yolov3.cfg")
classes = []
with open("./darknet/data/coco.names", "r") as f:
classes = [line.strip() for line in f.readlines()]
layer_names = net.getLayerNames()
output_layers = [layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()]
colors = np.random.uniform(0, 255, size=(len(classes), 3))
output_filename = "/tmp/camera.jpeg"
cap = cv2.imread(output_filename)
j = 0
if j==0:
cv2.namedWindow("demo", cv2.WINDOW_AUTOSIZE)
while True:
take_pic(output_filename)
cap = cv2.imread(source)
j = j + 1
print("j= " + str(j))
img = cap
img = cv2.resize(img, None, fx=0.4, fy=0.4)
height, width, channels = img.shape
blob = cv2.dnn.blobFromImage(img, 0.00392, (416, 416), (0, 0, 0), True, crop=False)
net.setInput(blob)
outs = net.forward(output_layers)
class_ids = []
confidences = []
boxes = []
for out in outs:
for detection in out:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence > 0.5:
# Object detected
center_x = int(detection[0] * width)
center_y = int(detection[1] * height)
print(str(center_x)+" "+str(center_y))
w = int(detection[2] * width)
h = int(detection[3] * height)
# Rectangle coordinates
x = int(center_x - w / 2)
y = int(center_y - h / 2)
boxes.append([x, y, w, h])
confidences.append(float(confidence))
class_ids.append(class_id)
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)
font = cv2.FONT_HERSHEY_PLAIN
for i in range(len(boxes)):
if i in indexes:
x, y, w, h = boxes[i]
label = str(classes[class_ids[i]])
print("label :"+str(label)+"x: "+str(x)+" y: " + str(y))
color = colors[i]
cv2.rectangle(img, (x, y), (x + w, y + h), color, 2)
cv2.putText(img, label, (x, y + 30), font, 3, color, 3)
cv2.imshow("demo", img)
else:
print("camera open failed")
cv2.destroyAllWindows()
With opencv, a imshow is required to be accompanied with a waitKey method in order to display an image.
Paste something similar to this towards the end of your loop, after you call cv2.imshow:
if cv2.waitKey(0) == ord('q'):
print('exitting loop')
break
If the image shows blank during imshow method, then you might need to multiply pixels with 255. For instance, in Matlab, the images are normalized between 0 - 1.
Try:
cv2.imshow("demo", img * 255)
cv2.waitKey(0)
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')