How to Split Image Into Multiple Pieces in Python - python

I'm trying to split a photo into multiple pieces using PIL.
def crop(Path,input,height,width,i,k,x,y,page):
im = Image.open(input)
imgwidth = im.size[0]
imgheight = im.size[1]
for i in range(0,imgheight-height/2,height-2):
print i
for j in range(0,imgwidth-width/2,width-2):
print j
box = (j, i, j+width, i+height)
a = im.crop(box)
a.save(os.path.join(Path,"PNG","%s" % page,"IMG-%s.png" % k))
k +=1
but it doesn't seem to be working. It splits the photo but not in an exact way (you can try it).

Splitting image to tiles of MxN pixels (assuming im is numpy.ndarray):
tiles = [im[x:x+M,y:y+N] for x in range(0,im.shape[0],M) for y in range(0,im.shape[1],N)]
In the case you want to split the image to four pieces:
M = im.shape[0]//2
N = im.shape[1]//2
tiles[0] holds the upper left tile

Edit: I believe this answer missed the intent to cut an image into rectangles in columns and rows. This answer cuts only into rows. It looks like other answers cut in columns and rows.
Simpler than all these is to use a wheel someone else invented :) It may be more involved to set up, but then it's a snap to use.
These instructions are for Windows 7; they may need to be adapted for other OSs.
Get and install pip from here.
Download the install archive, and extract it to your root Python installation directory. Open a console and type (if I recall correctly):
python get-pip.py install
Then get and install the image_slicer module via pip, by entering the following command at the console:
python -m pip install image_slicer
Copy the image you want to slice into the Python root directory, open a python shell (not the "command line"), and enter these commands:
import image_slicer
image_slicer.slice('huge_test_image.png', 14)
The beauty of this module is that it
Is installed in python
Can invoke an image split with two lines of code
Accepts any even number as an image slice parameter (e.g. 14 in this example)
Takes that parameter and automagically splits the given image into so many slices, and auto-saves the resultant numbered tiles in the same directory, and finally
Has a function to stitch the image tiles back together (which I haven't yet tested); files apparently must be named after the convention which you will see in the split files after testing the image_slicer.slice function.

from PIL import Image
def crop(path, input, height, width, k, page, area):
im = Image.open(input)
imgwidth, imgheight = im.size
for i in range(0,imgheight,height):
for j in range(0,imgwidth,width):
box = (j, i, j+width, i+height)
a = im.crop(box)
try:
o = a.crop(area)
o.save(os.path.join(path,"PNG","%s" % page,"IMG-%s.png" % k))
except:
pass
k +=1

As an alternative solution, we will construct the tiles by generating a grid of coordinates using itertools.product. We will ignore partial tiles on the edges, only iterating through the cartesian product between the two intervals, i.e. range(0, h-h%d, d) X range(0, w-w%d, d).
Given filename: the image file name, d: the tile size, dir_in: the path to the directory containing the image, and dir_out: the directory where tiles will be outputted:
from PIL import Image
from itertools import product
def tile(filename, dir_in, dir_out, d):
name, ext = os.path.splitext(filename)
img = Image.open(os.path.join(dir_in, filename))
w, h = img.size
grid = product(range(0, h-h%d, d), range(0, w-w%d, d))
for i, j in grid:
box = (j, i, j+d, i+d)
out = os.path.join(dir_out, f'{name}_{i}_{j}{ext}')
img.crop(box).save(out)

crop would be a more reusable
function if you separate the
cropping code from the
image saving
code. It would also make the call
signature simpler.
im.crop returns a
Image._ImageCrop instance. Such
instances do not have a save method.
Instead, you must paste the
Image._ImageCrop instance onto a
new Image.Image
Your ranges do not have the right
step sizes. (Why height-2 and not
height? for example. Why stop at
imgheight-(height/2)?).
So, you might try instead something like this:
import Image
import os
def crop(infile,height,width):
im = Image.open(infile)
imgwidth, imgheight = im.size
for i in range(imgheight//height):
for j in range(imgwidth//width):
box = (j*width, i*height, (j+1)*width, (i+1)*height)
yield im.crop(box)
if __name__=='__main__':
infile=...
height=...
width=...
start_num=...
for k,piece in enumerate(crop(infile,height,width),start_num):
img=Image.new('RGB', (height,width), 255)
img.paste(piece)
path=os.path.join('/tmp',"IMG-%s.png" % k)
img.save(path)

Here is a concise, pure-python solution that works in both python 3 and 2:
from PIL import Image
infile = '20190206-135938.1273.Easy8thRunnersHopefully.jpg'
chopsize = 300
img = Image.open(infile)
width, height = img.size
# Save Chops of original image
for x0 in range(0, width, chopsize):
for y0 in range(0, height, chopsize):
box = (x0, y0,
x0+chopsize if x0+chopsize < width else width - 1,
y0+chopsize if y0+chopsize < height else height - 1)
print('%s %s' % (infile, box))
img.crop(box).save('zchop.%s.x%03d.y%03d.jpg' % (infile.replace('.jpg',''), x0, y0))
Notes:
The crops that go over the right and bottom of the original image are adjusted to the original image limit and contain only the original pixels.
It's easy to choose a different chopsize for w and h by using two chopsize vars and replacing chopsize as appropriate in the code above.

Not sure if this is the most efficient answer, but it works for me:
import os
import glob
from PIL import Image
Image.MAX_IMAGE_PIXELS = None # to avoid image size warning
imgdir = "/path/to/image/folder"
# if you want file of a specific extension (.png):
filelist = [f for f in glob.glob(imgdir + "**/*.png", recursive=True)]
savedir = "/path/to/image/folder/output"
start_pos = start_x, start_y = (0, 0)
cropped_image_size = w, h = (500, 500)
for file in filelist:
img = Image.open(file)
width, height = img.size
frame_num = 1
for col_i in range(0, width, w):
for row_i in range(0, height, h):
crop = img.crop((col_i, row_i, col_i + w, row_i + h))
name = os.path.basename(file)
name = os.path.splitext(name)[0]
save_to= os.path.join(savedir, name+"_{:03}.png")
crop.save(save_to.format(frame_num))
frame_num += 1
This is mostly based on DataScienceGuy answer here

Here is a late answer that works with Python 3
from PIL import Image
import os
def imgcrop(input, xPieces, yPieces):
filename, file_extension = os.path.splitext(input)
im = Image.open(input)
imgwidth, imgheight = im.size
height = imgheight // yPieces
width = imgwidth // xPieces
for i in range(0, yPieces):
for j in range(0, xPieces):
box = (j * width, i * height, (j + 1) * width, (i + 1) * height)
a = im.crop(box)
try:
a.save("images/" + filename + "-" + str(i) + "-" + str(j) + file_extension)
except:
pass
Usage:
imgcrop("images/testing.jpg", 5, 5)
Then the images will be cropped into pieces according to the specified X and Y pieces, in my case 5 x 5 = 25 pieces

Here is another solution, just using NumPy built-in np.array_split :
def divide_img_blocks(img, n_blocks=(5, 5)):
horizontal = np.array_split(img, n_blocks[0])
splitted_img = [np.array_split(block, n_blocks[1], axis=1) for block in horizontal]
return np.asarray(splitted_img, dtype=np.ndarray).reshape(n_blocks)
It returns a NumPy array with the dimension passed as n_blocks.
Each element of the array is a block, so to access each block and save it as an image you should write something like the following:
result = divide_img_blocks(my_image)
for i in range(result.shape[0]):
for j in range(result.shape[1]):
cv2.imwrite(f"my_block_{i}_{j}.jpg", result[i,j])
This answer is very fast, faster than #Nir answer, which among the posted ones was the cleanest. Additionally is almost three orders of magnitude faster than the suggested package (i.e. image_slicer).
Time taken by divide_img_blocks: 0.0009832382202148438
Time taken by Nir answer: 0.002960681915283203
Time taken by image_slicer.slice: 0.4419238567352295
Hope it can still be useful.

I find it easier to skimage.util.view_as_windows or `skimage.util.view_as_blocks which also allows you to configure the step
http://scikit-image.org/docs/dev/api/skimage.util.html?highlight=view_as_windows#skimage.util.view_as_windows

import os
import sys
from PIL import Image
savedir = r"E:\new_mission _data\test"
filename = r"E:\new_mission _data\test\testing1.png"
img = Image.open(filename)
width, height = img.size
start_pos = start_x, start_y = (0, 0)
cropped_image_size = w, h = (1024,1024)
frame_num = 1
for col_i in range(0, width, w):
for row_i in range(0, height, h):
crop = img.crop((col_i, row_i, col_i + w, row_i + h))
save_to= os.path.join(savedir, "testing_{:02}.png")
crop.save(save_to.format(frame_num))
frame_num += 1

For anyone looking for a simple approach to this, here is a simple working function for splitting an image into NxN sections.
def slice_image(filename, N):
i = Image.open(filename)
width = i.width
height = i.height
for x in range(N):
for y in range(N):
index = (x * pieces) + 1 + y
img = i.crop((x * width/N, y * height/N,
x * width/N+ width/N, y * height/N+ height/N))
img.save(f"{filename}_sliced_{index}.jpeg")

Thanks #Ivan for teaching me something about itertools and grids. Came here to split up tomographic 3D image data (tif-files) into smaller regions for evaluation. I adapted the script to 3D-TIF files (using the tiffile library) and added a "centered" approach. So the tiles don't start in the upper-left corner but are centered and crop too small tiles at the borders at each direction. Maybe this also help other people.
from itertools import product
import tifffile as tif
import numpy as np
path = 'PATH'
filename= 'FILENAME.tif'
img = tif.imread(path+filename)
depth, height, width = img.shape
tilesize = 100
grid = product(range(int((depth%tilesize)/2), int(depth-(depth%tilesize)/2), tilesize),
range(int((width%tilesize)/2), int(width-((width%tilesize)/2)), tilesize),
range(int((height%tilesize)/2), int(height-(height%tilesize)/2), tilesize))
for z,y,x in grid:
crop = img[z:z+tilesize, y:y+tilesize, x:x+tilesize]
tif.imwrite(path+filename+f'{z:04d}z_{y:04d}y_{x:04d}x.tif', crop, dtype = np.uint8)

This is my script tools, it is very sample to splite css-sprit image into icons:
Usage: split_icons.py img dst_path width height
Example: python split_icons.py icon-48.png gtliu 48 48
Save code into split_icons.py :
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import sys
import glob
from PIL import Image
def Usage():
print '%s img dst_path width height' % (sys.argv[0])
sys.exit(1)
if len(sys.argv) != 5:
Usage()
src_img = sys.argv[1]
dst_path = sys.argv[2]
if not os.path.exists(sys.argv[2]) or not os.path.isfile(sys.argv[1]):
print 'Not exists', sys.argv[2], sys.argv[1]
sys.exit(1)
w, h = int(sys.argv[3]), int(sys.argv[4])
im = Image.open(src_img)
im_w, im_h = im.size
print 'Image width:%d height:%d will split into (%d %d) ' % (im_w, im_h, w, h)
w_num, h_num = int(im_w/w), int(im_h/h)
for wi in range(0, w_num):
for hi in range(0, h_num):
box = (wi*w, hi*h, (wi+1)*w, (hi+1)*h)
piece = im.crop(box)
tmp_img = Image.new('L', (w, h), 255)
tmp_img.paste(piece)
img_path = os.path.join(dst_path, "%d_%d.png" % (wi, hi))
tmp_img.save(img_path)

I tried the solutions above, but sometimes you just gotta do it yourself.
Might be off by a pixel in some cases but works fine in general.
import matplotlib.pyplot as plt
import numpy as np
def image_to_tiles(im, number_of_tiles = 4, plot=False):
"""
Function that splits SINGLE channel images into tiles
:param im: image: single channel image (NxN matrix)
:param number_of_tiles: squared number
:param plot:
:return tiles:
"""
n_slices = np.sqrt(number_of_tiles)
assert int(n_slices + 0.5) ** 2 == number_of_tiles, "Number of tiles is not a perfect square"
n_slices = n_slices.astype(np.int)
[w, h] = cropped_npy.shape
r = np.linspace(0, w, n_slices+1)
r_tuples = [(np.int(r[i]), np.int(r[i+1])) for i in range(0, len(r)-1)]
q = np.linspace(0, h, n_slices+1)
q_tuples = [(np.int(q[i]), np.int(q[i+1])) for i in range(0, len(q)-1)]
tiles = []
for row in range(n_slices):
for column in range(n_slices):
[x1, y1, x2, y2] = *r_tuples[row], *q_tuples[column]
tiles.append(im[x1:y1, x2:y2])
if plot:
fig, axes = plt.subplots(n_slices, n_slices, figsize=(10,10))
c = 0
for row in range(n_slices):
for column in range(n_slices):
axes[row,column].imshow(tiles[c])
axes[row,column].axis('off')
c+=1
return tiles
Hope it helps.

I would suggest to use multiprocessing instead of a regular for loop as follows:
from PIL import Image
import os
def crop(infile,height,width):
im = Image.open(infile)
imgwidth, imgheight = im.size
for i in range(imgheight//height):
for j in range(imgwidth//width):
box = (j*width, i*height, (j+1)*width, (i+1)*height)
yield im.crop(box)
def til_image(infile):
infile=...
height=...
width=...
start_num=...
for k,piece in enumerate(crop(infile,height,width),start_num):
img=Image.new('RGB', (height,width), 255)
img.paste(piece)
path=os.path.join('/tmp',"IMG-%s.png" % k)
img.save(path)
from multiprocessing import Pool, cpu_count
try:
pool = Pool(cpu_count())
pool.imap_unordered(tile_image, os.listdir(root), chunksize=4)
finally:
pool.close()

the easiest way:
import image_slicer
image_slicer.slice('/Address of image for exp/A1.png',16)
this command splits the image into 16 slices and saves them in the directory that the input image is there.
you should first install image_slicer:
pip install image_slicer

Splitting an image into squares of a specific size
I adapted a solution so that it accepts a specific tile size instead of an amount of tiles because I needed to cut the image up into a grid of 32px squares.
The parameters are the image_path and the size of the tiles in pixels.
I tried to make the code as readable as possible.
# Imports
from PIL import Image
import os
import random
# Function
def image_to_tiles(im, tile_size = 32):
"""
Function that splits an image into tiles
:param im: image: image path
:param tile_size: width in pixels of a tile
:return tiles:
"""
image = Image.open(im)
w = image.width
h = image.height
row_count = np.int64((h-h%tile_size)/tile_size)
col_count = np.int64((w-w%tile_size)/tile_size)
n_slices = np.int64(row_count*col_count)
# Image info
print(f'Image: {im}')
print(f'Dimensions: w:{w} h:{h}')
print(f'Tile count: {n_slices}')
r = np.linspace(0, w, row_count+1)
r_tuples = [(np.int64(r[i]), np.int64(r[i])+tile_size) for i in range(0, len(r)-1)]
q = np.linspace(0, h, col_count+1)
q_tuples = [(np.int64(q[i]), np.int64(q[i])+tile_size) for i in range(0, len(q)-1)]
#print(f'r_tuples:{r_tuples}\n\nq_tuples:{q_tuples}\n')
tiles = []
for row in range(row_count):
for column in range(col_count):
[y1, y2, x1, x2] = *r_tuples[row], *q_tuples[column]
x2 = x1+tile_size
y2 = y1+tile_size
tile_image = image.crop((x1,y1,x2,y2))
tile_coords = {'x1':x1,'y1':y1,'x2':x2,'y2':y2}
tiles.append({'image':tile_image,'coords':tile_coords})
return tiles
# Testing:
img_path ='/home/user/path/to/image.jpg'
tiles = image_to_tiles(img_path)
for i in range(20):
tile = random.choice(tiles)
tile['image'].show()

you can use numpy stride tricks to achive this, but be careful, as this function has to be used with extreme care (doc)
import numpy as np
from numpy.lib.stride_tricks import as_strided
def img_pieces(img, piece_size):
height, width, chanels = img.shape
n_bytes = img.strides[-1]
return np.reshape(
as_strided(
img,
(
height // piece_size,
width // piece_size,
piece_size,
piece_size,
chanels
),
(
n_bytes * chanels * width * piece_size,
n_bytes * chanels * piece_size,
n_bytes * chanels * width,
n_bytes * chanels,
n_bytes
)
),
(
-1,
piece_size,
piece_size,
chanels
)
)

Here's my attempt on a grayscale image with only numpy based on the solution from here, with some minor tweaks (adding channels) it might suit your needs:
import numpy as np
# Seperate grayscale images to w * h tiles, add padding with zeros if image not scaled
def to_tiles(arr: np.ndarray, tilesize: tuple[int, int]) -> np.ndarray:
def f(x: tuple[int, int]) -> tuple[int, int]:
tmp = list(x)
if tmp[1] > 0:
tmp[0] = tmp[0] + 1
return tuple(tmp)
# # Stride Implementation
# bytelength = np.int8(np.divide(arr.nbytes, arr.size))
assert arr.ndim == 2, "array must be 2d (grayscale) image"
a_h, a_w = arr.shape
h, w = tilesize
assert a_h > h, "tile height is larger than arr height"
assert a_w > w, "tile width is larger than arr width"
row, row_r = f(np.divmod(a_h, h))
col, col_r = f(np.divmod(a_w, w))
arr = np.pad(
arr,
[
(
np.int8(np.ceil(np.divide(h-row_r, 2))) if row_r != 0 else 0,
np.int8(np.floor(np.divide(h-row_r, 2))) if row_r != 0 else 0,
),
(
np.int8(np.ceil(np.divide(w-col_r, 2))) if col_r != 0 else 0,
np.int8(np.floor(np.divide(w-col_r, 2))) if col_r != 0 else 0,
),
],
"constant",
constant_values=(0),
)
# # Stride Implementation
# arr = np.lib.stride_tricks.as_strided(
# arr, shape=(row, col, h, w), strides=(h*a_w*bytelength, w*bytelength, a_w*bytelength, bytelength)
# )
arr = arr.reshape(row, h, col, w).swapaxes(1, 2)
arr = arr.reshape(-1, h, w)
return arr
Here's an example of the result. Image from FUNSD dataset.

def split(img,nbxsplit,nbysplit):
xdemi=int(img.shape[0]/nbxsplit)
ydemi=int(img.shape[1]/nbxsplit)
arr=[]
for i in range(0,img.shape[0]-xdemi+1,xdemi):
for j in range(0,img.shape[1]-ydemi+1,ydemi):
arr.append(img[i:i+xdemi][j:j+ydemi])
a=np.reshape(a,(img.shape[0]-xdemi,img.shape[1]-xdemi))
return a

Not sure if it's still relevant, but my attempt is following:
(I am assuming the image is a numpy array. I am not using Pil or anything, since i didn't want to have any dependencies other than numpy.)
def cut_image_grid(image:np.ndarray, grid_size:int=4):
height, width = image.shape[0], image.shape[1]
piece_height, piece_width = height//grid_size, width//grid_size
pieces = []
for i in range(grid_size):
for j in range(grid_size):
y = i * piece_height
x = j * piece_width
h = (i+1) * piece_height if i < grid_size else None
w = (j+1) * piece_width if j < grid_size else None
piece = image[y:h, x:w]
pieces.append(piece)
return np.array(pieces)
As input the function is receiving a numpy image and an integer (which you could also turn into tuples, but i wanted to have evenly spaced grid cells always with same amount of cells row and column wise).
At first, the code calculates the width and height of the cells based on the given grid_size. After that the code iterates over all rows and columns and generates x, y Coordinates, as well as x0 and y0 (y+height, x+width) for defining the cells.
Every cell is saved as a list into pieces, which is then transformed into a numpy array and returned.

import cv2
def crop_image(image_path, output_path):
im = cv2.imread(os.listdir()[2])
imgheight=im.shape[0]
imgwidth=im.shape[1]
y1 = 0
M = 2000
N = 2000
for y in range(0,imgheight,M):
for x in range(0, imgwidth, N):
y1 = y + M
x1 = x + N
tiles = im[y:y+M,x:x+N]
if tiles.shape[0] < 100 or tiles.shape[1]<100:
continue
cv2.rectangle(im, (x, y), (x1, y1), (0, 255, 0))
cv2.imwrite(output_path + str(x) + '_' + str(y)+"{}.png".format(image_path),tiles)
crop_image(os.listdir()[2], './cutted/')

Related

Why my photo collage output using numpy has strange color profile?

After a long time of researching and asking questions, I have made my prototype code that makes a collage of a list of photos given as a list of strs.
It resizes the images according to the positions of the images in the list, then randomly rotates the images and randomly arrange them in a minimum bounding area.
It uses cv2, numpy, PIL and rpack, to be honest I have absolutely no idea how these libraries work, why my code is working, I just know how to make them work, I only know how to put them together.
So here is my code:
import cv2
import numpy as np
import random
import rpack
from fractions import Fraction
from math import prod
from pathlib import Path
from PIL import Image
from typing import Tuple
folder = 'D:/test/'
images = [
'Mass Effect.jpg',
'Dragon Age Origins.jpg',
'Life Is Strange.jpg',
'Star Wars KOTOR.jpg',
'Dragon Age 2.jpg',
'Choice of Robots.jpg',
'Perfect Match.png',
'Jade Empire.jpg',
"Serafina's Saga.jpg",
'Rising Angels Reborn.jpg',
'Across The Void.png',
"Heart's Blight.png",
'The Gray Wolf And The Little Lamb.jpg',
'Night of the Lesbian Vampires.png',
'Tethered.png',
'Contract Demon.jpg',
"Yuki's 4P.png"
]
def resize_guide(image_size: Tuple[int, int], unit_shape: Tuple[int, int], target_ratio: float) -> Tuple[int, int]:
aspect_ratio = Fraction(*image_size).limit_denominator()
horizontal = aspect_ratio.numerator
vertical = aspect_ratio.denominator
target_area = prod(unit_shape) * target_ratio
unit_length = (target_area/(horizontal*vertical))**.5
return (int(horizontal*unit_length), int(vertical*unit_length))
images = [cv2.imread(folder+name) for name in images]
size_hint = [i**.75 for i in range(1, len(images)+1)][::-1]
resized_images = []
for image, hint in zip(images, size_hint):
height, width = image.shape[:2]
guide = resize_guide((width, height), (640,360), hint)
resized = cv2.resize(image, guide, interpolation = cv2.INTER_AREA)
resized_images.append(resized)
def make_border(image, value, border=16):
return cv2.copyMakeBorder(
image,
top=border,
bottom=border,
left=border,
right=border,
borderType=cv2.BORDER_CONSTANT,
value=value
)
def rotate_image(image, angle):
h, w = image.shape[:2]
cX, cY = (w // 2, h // 2)
M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
cos = np.abs(M[0, 0])
sin = np.abs(M[0, 1])
nW = int((h * sin) + (w * cos))
nH = int((h * cos) + (w * sin))
M[0, 2] += (nW / 2) - cX
M[1, 2] += (nH / 2) - cY
return cv2.warpAffine(image, M, (nW, nH))
rotated_images = []
sizes = []
for image in resized_images:
image = make_border(image, (255, 255, 255))
rotated = rotate_image(image, random.randrange(-15, 16))
image = make_border(image, (0,0,0))
rotated_images.append(rotated)
height, width = rotated.shape[:2]
sizes.append((width, height))
shapes = [(x, y, w, h) for (x, y), (w, h) in zip(rpack.pack(sizes), sizes)]
rightmost = sorted(shapes, key=lambda x: -x[0] - x[2])[0]
bound_width = rightmost[0] + rightmost[2]
downmost = sorted(shapes, key=lambda x: -x[1] - x[3])[0]
bound_height = downmost[1] + downmost[3]
collage = np.zeros([bound_height, bound_width, 3],dtype=np.uint8)
for image, (x, y, w, h) in zip(rotated_images, shapes):
collage[y:y+h, x:x+w] = image
collage = Image.fromarray(collage, 'RGB')
collage.save('D:/collages/' + random.randbytes(4).hex() + '.png')
Because the output is way too large (over 20 MiB) it can't fit here, I have uploaded it to Google Drive: https://drive.google.com/file/d/16w4wsC_od4dh4QI7BYj8MM2gMngbSLV1/view?usp=sharing
So far the results seem promising, the only complaint I have is that the colors look very strange, I swear the original images have normal colors.
Can someone please tell me what I did wrong?
OK so while executing the code, the interpreter complained a lot about:
libpng warning: iCCP: known incorrect sRGB profile
I used cracked Adobe Photoshop CS6 to edit the images, is this the source of the problem or is it something else?
You are doing everything just fine. The only mistake you are making is while storing the image. Just remove the last two lines and add the following line.
cv2.imwrite('D:/collages/' + random.randbytes(4).hex() + '.png', collage)

aligning face image and merge in python cv2

I have a bunch of face image dataset (taken from http://vision.ucsd.edu/content/yale-face-database ) that I basically want to turn into a gif of the scramble suit from the movie scanner darkly ( http://2.bp.blogspot.com/-tRLWSOqh84Y/VSb_cF7sOoI/AAAAAAAAAWI/3XqT6d_exso/s1600/scramble%2Bsuit%2B2.gif ).
So far, I am able to take the images and cut them into face "pieces" in python in bulk.
The next step I am unable to do is to "align" these faces so that all the pieces form a face when they are merged or put back together.
Im also unsure how to merge or put them back together.
Once i have a bunch of images of randomly pieced together images, i am able to create the gif myself.
here is the code i have so far of taking the images, converting them to jpg and cutting them into necessary pieces (which was taken from here https://leslietj.github.io/2020/06/30/Automatic-Face-Crop-Using-Dlib/ ):
import sys
import dlib
from skimage import io
import numpy as np
import cv2
import matplotlib.pylab as plt
import math
from PIL import Image
import os
def arc_points(point1, point2, num_of_points):
points = []
center_x = (point1[0] + point2[0])/2
center_y = (point1[1] + point2[1])/2
radius = abs((point1[0] - point2[0])/2)
for i in range(num_of_points):
if i == 0:
continue
point = []
x = center_x + radius * math.cos(math.pi + i * math.pi / num_of_points)
y = center_y + radius * math.sin(math.pi + i * math.pi / num_of_points)
point.append(x)
point.append(y)
points.append(point)
return points
def get_landmarks(img,mode=1):
dets = detector(img, 1)
landmarks = np.zeros((34, 2))
for k, d in enumerate(dets):
shape = predictor(img, d)
#quarter face (#1)
if mode == 1:
landmarks[0]= (shape.part(0).x, shape.part(0).y)
landmarks[1] = (shape.part(1).x, shape.part(1).y)
landmarks[2] = (shape.part(2).x, shape.part(2).y)
landmarks[3] = (shape.part(30).x, shape.part(30).y)
landmarks[4] = (shape.part(29).x, shape.part(29).y)
landmarks[5] = (shape.part(28).x, shape.part(28).y)
point1 = [shape.part(0).x, shape.part(0).y]
point2 = [shape.part(28).x, shape.part(28).y]
points = arc_points(point1, point2, 29)
for i in range(len(points)):
landmarks[33 - i] = (points[i][0], points[i][1])
#half face (#2)
if mode == 2:
landmarks[0] = (shape.part(0).x, shape.part(0).y)
landmarks[1] = (shape.part(1).x, shape.part(1).y)
landmarks[2] = (shape.part(2).x, shape.part(2).y)
landmarks[3] = (shape.part(14).x, shape.part(14).y)
landmarks[4] = (shape.part(15).x, shape.part(15).y)
landmarks[5] = (shape.part(16).x, shape.part(16).y)
point1 = [shape.part(0).x, shape.part(0).y]
point2 = [shape.part(16).x, shape.part(16).y]
points = arc_points(point1, point2, 29)
#print(points)
for i in range(len(points)):
#print(33-i)
landmarks[33 - i] = (points[i][0], points[i][1])
if mode == 3:
#3/4 face (#3)
for i in range(9):
landmarks[i] = (shape.part(i).x, shape.part(i).y)
landmarks[9] = (shape.part(31).x, shape.part(31).y)
landmarks[10] = (shape.part(14).x, shape.part(14).y)
landmarks[11] = (shape.part(15).x, shape.part(15).y)
landmarks[12] = (shape.part(16).x, shape.part(16).y)
point1 = [shape.part(0).x, shape.part(0).y]
point2 = [shape.part(16).x, shape.part(16).y]
points = arc_points(point1, point2, 22)
for i in range(len(points)):
landmarks[33 - i] = (points[i][0], points[i][1])
#full face (#4)
if mode == 4:
for i in range(17):
landmarks[i] = (shape.part(i).x, shape.part(i).y)
point1 = [shape.part(0).x, shape.part(0).y]
point2 = [shape.part(16).x, shape.part(16).y]
points = arc_points(point1, point2, 18)
for i in range(len(points)):
landmarks[33 - i] = (points[i][0], points[i][1])
return landmarks
def inside(X,Y,Region):
j=len(Region)-1
flag=False
for i in range(len(Region)):
if (Region[i][1]<Y and Region[j][1]>=Y or Region[j][1]<Y and Region[i][1]>=Y):
if (Region[i][0] + (Y - Region[i][1]) / (Region[j][1] - Region[i][1]) * (Region[j][0] - Region[i][0]) < X):
flag =not flag
j=i
return flag
count=0
files = os.listdir('yalefaces')
for filename in files:
if filename.endswith('glasses') or filename.endswith('happy') or filename.endswith('noglasses') or filename.endswith('normal'):
path = os.path.join('yalefaces',filename)
# importing the image
im = Image.open(path)
# converting to jpg
rgb_im = im.convert("RGB")
# exporting the image
rgb_im.save('temp.jpg')
count+=1
path = 'temp.jpg'
for im in range(1,5):
#path = 'subject01.jpg'
detector = dlib.get_frontal_face_detector()
# the .dat file can be downloaded following this link:
# https://sourceforge.net/projects/dclib/files/dlib/v18.10/shape_predictor_68_face_landmarks.dat.bz2/download
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
img = io.imread(path)
region = get_landmarks(img,mode=im)
shape = list(img.shape)
cropped_img = img.copy()
for i in range(shape[0]):
for j in range(shape[1]):
if not inside(j, i, region):
#print(img[0])
cropped_img[i, j] = (img[0,0][0], img[0,0][1], img[0,0][2]) # the RGB values of the background
cropped_img = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2GRAY)
cv2.imwrite(str(count).zfill(3)+'-'+str(im).zfill(2)+'.jpg', cropped_img)
since this is several steps of pre-processing:
align faces using this script https://pyimagesearch.com/2017/05/22/face-alignment-with-opencv-and-python/
cut up the faces and put them together. as mentioned before, i was doing a cv2.add which is not what i want. i actually want the layers to stack up and if there is anything underneath, ignore it. cv2.add doesnt do this so i had to make my own. so if i had two layers and i want to stack them up so that the first layer on top is the one that takes priority and the bottom layer is ignored if there is anything above it.
def reduction(layer1,layer2):
for i in range(0,layer1.shape[0]):
for j in range(0,layer1.shape[1]):
pixel1 = layer1.item(i, j)
pixel2 = layer2.item(i, j)
if layer2[i,j] != 255:
layer1[i,j]=255
return layer1
layer2 = reduction(layer2,layer1)
for i in range(0,layer1.shape[0]):
for j in range(0,layer1.shape[1]):
pixel = layer2[i,j]
if pixel != 255:
layer1[i,j]=layer2[i,j]
and thats it. i didnt realize images are just numpy arrays so i can just manipulate the arrays directly.

Make objects of an image the closest to each other

I don't have much experience with PIL and I've got these images edited from a stack of microscopy image cells, each one is in a mask of an image size 30x30. I've been struggling to put these cells in a black background as closest as possible to each other without overlapping.
My code is the following:
def spread_circles(circles, rad, iterations,step):
radsqr = rad**2
for i in range(iterations):
for ix,c in enumerate(circles):
vecs = c-circles
dists = np.sum((vecs)**2,axis=1)
if len(dists)>0:
push = (vecs[dists<radsqr,:].T*dists[dists<radsqr]).T
push = np.sum(push,axis=0)
pushmag = np.sum(push*push)**0.5
if pushmag>0:
push = push/pushmag*step
circles[ix]+=push
return circles
def gen_image(sample,n_iter, height=850, width = 850, max_shape=30, num_circles=150):
circles = np.random.uniform(low=max_shape,high=height-max_shape,size=(num_circles,2))
circles = spread_circles(circles, max_shape, n_iter, 1).astype(int)
img = Image.new(mode='F',size=(height,width),color=0).convert('RGBA')
final1 = Image.new("RGBA", size=(height,width))
final1.paste(img, (0,0), img)
for n,c in enumerate(circles):
foreground = sample[n]
final1.paste(foreground, (c[0],c[1]), foreground)
return final1
But it's hard to avoid overlapping if I do few iterations, and if I Increase they'd be too much sparsed, like this:
What I want it's something similar like inside the red circles that I drew :
I need them closer as they can get, almost like tiles. How can I do that?
I have started thinking about this and have got a couple of strategies implemented. Anyone else fancying some fun is more than welcome to borrow, steal, appropriate or hack any chunks of my code that they can use! I'll probably play some more tomorrow.
#!/usr/bin/env python3
from PIL import Image, ImageOps
import numpy as np
from glob import glob
import math
def checkCoverage(im):
"""Determines percentage of image that is cells rather than background"""
N = np.count_nonzero(im)
return N * 100 / im.size
def loadImages():
"""Load all cell images in current directory into list of trimmed Numpy arrays"""
images = []
for filename in glob('*.png'):
# Open and convert to greyscale
im = Image.open(filename).convert('L')
# Trim to bounding box
im = im.crop(im.getbbox())
images.append(np.array(im))
return images
def Strategy1():
"""Get largest image and pad all images to that size - at least it will tesselate perfectly"""
images = loadImages()
N = len(images)
# Find height of tallest image and width of widest image
maxh = max(im.shape[0] for im in images)
maxw = max(im.shape[1] for im in images)
# Determine how many images we will pack across and down the output image - could be improved
Nx = int(math.sqrt(N))+1
Ny = int(N/Nx)+1
print(f'Padding {N} images each to height:{maxh} x width:{maxw}')
# Create output image
res = Image.new('L', (Nx*maxw,Ny*maxh), color=0)
# Pack all images from list onto regular grid
x, y = 0, 0
for im in images:
this = Image.fromarray(im)
h, w = im.shape
# Pack this image into top-left of its grid-cell, unless
# a) in first row, in which case pack to bottom
# b) in first col, in which case pack to right
thisx = x*maxw
thisy = y*maxh
if y==0:
thisy += maxh - h
if x==0:
thisx += maxw - w
res.paste(this, (thisx,thisy))
x += 1
if x==Nx:
x = 0
y += 1
# Trim extraneous black edges
res = res.crop(res.getbbox())
# Save as JPEG so we don't find it as a PNG in next strategy
res.save('strategy1.jpg')
cov = checkCoverage(np.array(res))
print(f'Strategy1 coverage: {cov}')
def Strategy2():
"""Rotate all images to portrait (tall rather than wide) and order by height so we tend to stack equal height images side-by-side"""
tmp = loadImages()
# Recreate list with all images in portrait format, i.e. tall
portrait = []
for im in tmp:
if im.shape[0] >= im.shape[1]:
# Already portrait, add as-is
portrait.append(im)
else:
# Landscape, so rotate
portrait.append(np.rot90(im))
images = sorted(portrait, key=lambda x: x.shape[0], reverse=True)
N = len(images)
maxh, maxw = 31, 31
# Determine how many images we will pack across and down the output image
Nx = int(math.sqrt(N))+1
Ny = int(N/Nx)+1
print(f'Packing images by height')
# Create output image
resw, resh = Nx*maxw, Ny*maxh
res = Image.new('L', (resw,resh), color=0)
# Pack all from list
xpos, ypos = 0, 0
# Pack first row L->R, second row R->L and alternate
packToRight = True
for im in images:
thisw, thish = im.shape
this = Image.fromarray(im)
if packToRight:
if xpos+thisw < resw:
# If it fits to the right, pack it there
res.paste(this,(xpos,ypos))
xpos += thisw
else:
# Else start a new row, pack at right end and continue packing to left
packToRight = False
res.paste(this,(resw-thisw,ypos))
ypos = res.getbbox()[3]
else:
if xpos>thisw:
# If it fits to the left, pack it there
res.paste(this,(xpos-thisw,ypos))
xpos -= thisw
else:
# Else start a new row, pack at left end and continue packing to right
ypos = res.getbbox()[3]
packToRight = True
res.paste(this,(0,ypos))
# Trim any black edges
res = res.crop(res.getbbox())
# Save as JPEG so we don't find it as a PNG in next strategy
res.save('strategy2.jpg')
cov = checkCoverage(np.array(res))
print(f'Strategy2 coverage: {cov}')
Strategy1()
Strategy2()
Strategy1 gives this at 42% coverage:
Strategy2 gives this at 64% coverage:

Creating image tiles (m*n) of original image using Python and Numpy

I am using numpy to create tiles of (224*224) from my 16-bit tiff image (13777*16004). I was able to crop/slice into equal tiles of 224*224 along the rows and columns. I ran into problems while trying to create new tiles shifting by half of the tile size... For instance: A rough algorithm of what i am trying to achieve
(1:224, 1:224)
(1:224, 112:336)
( , 224:448)
The goal is to retain tile size (224*224) while shifting by half of tile size to obtain more image tiles...
Snippet of code written to perform task
row_x = img.shape[0]
column_y = img.shape[1]
tile_size_x = 224
tile_size_y = 224
range_x = mpz(ceil(row_x/tile_size_x))
range_y = mpz(ceil(column_y/tile_size_y))
for x in range(range_x, row_x):
for y in range(range_y, column_y):
x0 = x * tile_size_x
x1 = int(x0/2) + tile_size_x
y0 = y * tile_size_y
y1 = int(y0/2) + tile_size_y
z = img[x0:x1, y0:y1]
print (z.shape,z.dtype)
I keep getting wrong results, can anyone help ???
You went a little off while calculating the range of your for loop. The number of slices to be made, must be calculated using the offset between two slices, which is x0/2 in your case, I have simplified your code and defined some meaningful variables which you can configure to get desired tiles from a given image:
import cv2
import math
img = cv2.imread("/path/to/lena.png") # 512x512
img_shape = img.shape
tile_size = (256, 256)
offset = (256, 256)
for i in xrange(int(math.ceil(img_shape[0]/(offset[1] * 1.0)))):
for j in xrange(int(math.ceil(img_shape[1]/(offset[0] * 1.0)))):
cropped_img = img[offset[1]*i:min(offset[1]*i+tile_size[1], img_shape[0]), offset[0]*j:min(offset[0]*j+tile_size[0], img_shape[1])]
# Debugging the tiles
cv2.imwrite("debug_" + str(i) + "_" + str(j) + ".png", cropped_img)
As current offset if exact multiple of image dimensions, which is 512x512, hence we will get 4 tiles of same size:
Changing the value of offset, would get you tiles of irregular size, if the offset if not exact multiple of the image dimensions, you may later filter those tiles if not required by changing the math.ceil to math.floor in the for loop.
You can use as_strided for this pretty efficiently I think.
def window_nd(a, window, steps = None):
ashp = np.array(a.shape)
wshp = np.array(window).reshape(-1)
if steps:
stp = np.array(steps).reshape(-1)
else:
stp = np.ones_like(ashp)
astr = np.array(a.strides)
assert np.all(np.r_[ashp.size == wshp.size, wshp.size == stp.size, wshp <= ashp])
shape = tuple((ashp - wshp) // stp + 1) + tuple(wshp)
strides = tuple(astr * stp) + tuple(astr)
as_strided = np.lib.stride_tricks.as_strided
aview = as_strided(a, shape = shape, strides = strides)
return aview
EDIT: Generalizing the striding method as much as I can.
For your specific question:
aview = window_nd(a, (288, 288), (144, 144))
z = aview.copy().reshape(-1, wx, wy) #to match expected output
print(z.shape, z.dtype) # z.shape should be (num_patches, 288, 288)
I think you can use this
def TileImage(image,rows,cols):
imagename = image
im = Image.open(imagename)
width, height = im.size
indexrow = 0
indexcolum = 0
left = 0
top = 0
right = width/col
buttom = 0
while(right<=width):
buttom = height/rows
top = 0
indexrow=0
while(top<height):
print(f"h : {height}, w : {width}, left : {left},top : {top},right : {right}, buttom : {buttom}")
cropimg= im.crop((left, top, right, buttom))
cropimg.save(imagename + str(indexrow) + str(indexcolum) +".jpg")
top = buttom
indexrow += 1
buttom += height/rows
indexcolum+=1
left = right
right += width/col
If you do not mind using ImageMagick, then it is trivial using the -crop command. See https://imagemagick.org/Usage/crop/#crop_tile
You can call imagemagick using Python subprocess call.
Input:
Lets say you want 4 256x256 tiles for simplicity.
convert lena512.png -crop 256x256 lena512_%d.png
or by percent
convert lena512.png -crop 50x50% lena512_%d.png
I prefer to calculate the number of tiles beforehand and then use a simple reshape. For example
tile = 512
img_height = img.shape[1]
img_width = img.shape[0]
number_of_vertical_tiles = img_height // tile
number_of_horizontal_tiles = img_width // tile
cropped_img = img[:tile*number_of_vertical_tiles, :tile*number_of_horizontal_tiles,:]
tiled_img = img.reshape(-1, tile, tile, 3)
I was not allowed to comment under the top answer from #ZdaR due to my lag of reputation. However, the code was so on point for my use case, I wanted to provide necessary changes for Python3 and Color-Channels from cv2. Thank you, #ZdaR.
This is his code adapted for Python3 with list(range()) instead of xrange() and cv2.COLOR_BGR2RGB when reading and writing the picture. Somehow cv2 uses the channels the other way around.
img = cv2.cvtColor(cv2.imread("path/to/lena.png"),cv2.COLOR_BGR2RGB)
img_shape = img.shape
tile_size = (640, 640)
offset = (640, 640)
for i in list(range(int(math.ceil(img_shape[0]/(offset[1] * 1.0))))):
for j in list(range(int(math.ceil(img_shape[1]/(offset[0] * 1.0))))):
cropped_img = img[offset[1]*i:min(offset[1]*i+tile_size[1], img_shape[0]), offset[0]*j:min(offset[0]*j+tile_size[0], img_shape[1])]
# Debugging the tiles
cv2.imwrite("debug_" + str(i) + "_" + str(j) + ".png", cv2.cvtColor(cropped_img,cv2.COLOR_BGR2RGB))

Mirror an image using Python

I need to flip a picture horizontally, without using the reverse function, I thought I had it right but the returned image is just the bottom right corner of the picture and it is not flipped.
The code I have is
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X = img.getWidth()
Y = img.getHeight()
for y in range(Y):
for x in range(X):
A = img.getPixel(x,y)
r = A[0]
g = A[1]
b = A[2]
color = graphics.color_rgb(r,g,b)
img.setPixel(X-x,y,color)
img = graphics.Image(graphics.Point(0,0), image2)
win = graphics.GraphWin(image2, img.getWidth(), img.getHeight())
img.draw(win)
Where did I go wrong?
Here some things that I think could be improved:
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X = img.getWidth()
Y = img.getHeight()
for y in range(Y):
for x in range(X):
A = img.getPixel(x,y)
r = A[0]
g = A[1]
b = A[2]
color = graphics.color_rgb(r,g,b)
This assignment could be more pythonic:
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r,g,b)
img.setPixel(X-x,y,color)
img now has the image half-flipped. This happens because you are writing the content on the same image source, losing the old content anytime until you reach the middle. (Notice that X-x will increase the image size by 1 pixel. If the image width is 100, in the first iteration X-x = 100 - 0 = 100 and because it starts from 0, the image is made wider 1 pixel.) Then, you start copying back. Also, you do not use that content because:
img = graphics.Image(graphics.Point(0,0), image2)
Here is the problem: you just overwrote the content of img without giving it any use. Later:
win = graphics.GraphWin(image2, img.getWidth(), img.getHeight())
img.draw(win)
This seems unrelated with the purpose of the function (flip an image). What I would do is:
import graphics
import sys
def Flip(image_filename):
img_src = graphics.Image(graphics.Point(0, 0), image_filename)
img_dst = img_src.clone()
X, Y = img_src.getWidth(), img_src.getHeight()
for x in range(X):
for y in range(Y):
r, g, b = img_src.getPixel(x, y)
color = graphics.color_rgb(r, g, b)
img_dst.setPixel(X-x-1, y, color)
return img_dst
if __name__ == '__main__':
input = sys.argv[1] or 'my_image.ppm'
output = 'mirror-%s' % input
img = Flip (input)
img.save(output)
Notices that the function Flip only take care of flipping the image, outside the function you can do whatever you need the image, as you can see in 'main' program.
If you want to use only one image, it is possible and more efficient. For that, you can use the same principle for swapping values between variables:
def Flip(image_filename):
img = graphics.Image(graphics.Point(0, 0), image_filename)
X, Y = img.getWidth(), img.getHeight()
for x in range(X/2):
for y in range(Y):
r_1, g_1, b_1 = img.getPixel(x, y)
color_1 = graphics.color_rgb(r_1, g_1, b_1)
r_2, g_2, b_2 = img.getPixel(X-x-1, y)
color_2 = graphics.color_rgb(r_2, g_2, b_2)
img.setPixel(X-x-1, y, color_1)
img.setPixel(x, y, color_2)
return img
I know it has been a long time but you can try this!
from PIL import Image
Image.open('img.png')
img = Image.open('img.png')
Mirror_Image=img.transpose(Image.FLIP_LEFT_RIGHT)
Mirror_Image.save(r'imgoutput.png')
Image.open('imgoutput.png')

Categories