Mirror an image using Python - 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')

Related

How to trim out a green screen and crop an image as fast as possible?

So I've been trying to remove the green screen, and crop, and I've had success, however, it is pretty slow, especially when I'm trying to use it on hundreds of pictures. I am not very familiar with image processing or PIL libraries, so I would love advice on how to make my code faster.
How it works: Basically it loops through each pixel, recording the pixel it looped over, and does it until it hits a non green like color, at which point it records the number of pixels away from the edge. I went with four loops, because i wanted to minimize the number of pixels i had to traverse (I can do the same thing with one loop but it would traverse across every pixel). The visitedPixel set prevents dealing with the same pixel. After the loops were done, it got a set of pixels that can be used to trim out the green screen edges, and thus cropping the image.
def trim_greenscreen_and_crop(image_name, output_name):
img = Image.open(image_name)
pixels = img.load()
width = img.size[0]
height = img.size[1]
visitedPixel = set()
box = [0, 0, 0, 0]
# left edge
break_flag = False
for x in range(width):
for y in range(height):
coords = (x, y)
r, g, b = pixels[x, y]
if not (g > r and g > b and g > 200) and coords not in visitedPixel:
box[0] = x - 1
break_flag = True
break
visitedPixel.add(coords)
if break_flag:
break
# top edge
break_flag = False
for y in range(height):
for x in range(width):
coords = (x, y)
r, g, b = pixels[x, y]
if not (g > r and g > b and g > 200) and coords not in visitedPixel:
box[1] = y-1
break_flag = True
break
visitedPixel.add(coords)
if break_flag:
break
# right edge
break_flag = False
for x in range(width - 1, -1, -1):
for y in range(height):
coords = (x, y)
r, g, b = pixels[x, y]
if not (g > r and g > b and g > 200) and coords not in visitedPixel:
box[2] = x + 1
break_flag = True
break
visitedPixel.add(coords)
if break_flag:
break
# bottom edge
break_flag = False
for y in range(height - 1, -1, -1):
for x in range(width):
coords = (x, y)
r, g, b = pixels[x, y]
if not (g > r and g > b and g > 200) and coords not in visitedPixel:
box[3] = y + 1
break_flag = True
break
visitedPixel.add(coords)
if break_flag:
break
cropped_img = img.crop(box)
if cropped_img.size == (0, 0):
return img.size
# cropped_img.save(output_name)
return cropped_img.size
Before:
After:
So i figured using numpy, and got this much faster solution which involves finding the variance of the rows and columns, thanks to MarkSetchell's idea.
draft:
def trim_greenscreen_and_crop(image_name, output_name):
# use numpy to read the image
img = Image.open(image_name)
np_img = np.array(Image.open(image_name))
# use numpy to get the variance across the rows and columns
row_var = np.var(np_img, axis=0)
col_var = np.var(np_img, axis=1)
# select the rows and columns with some variance (basically not all green)
no_variance_row = np.where(row_var > 5)
no_variance_col = np.where(col_var > 5)
# checks if the entire image is green, then dont trim
if len(no_variance_row[0]) == 0 or len(no_variance_col[0]) == 0:
return img.size
else:
# crops the image using the distance from the edges to the first non-green pixel
cropped_img = img.crop((no_variance_row[0][0], no_variance_col[0][0], no_variance_row[0][-1], no_variance_col[0][-1]))
cropped_img.save(output_name)
return cropped_img.size
You could speed up edge detection if you consider image itself is 'big enough' and loop not every pixel of the source image but go by diagonal, incrementing in one go both x and y until reach non-green color. You can repeat this process from all four corners. I hope you got the idea.
Edit:
you can also speed up by checking not every pixel, but check pixels on some 'grid', i.e. increment x and y by some big enough step. This also will work if your image is big enough

Why is my code only working on part of my image?

I created code to equalize the luminosity values of pixels in an image so that when the image is further edited I do not have dark or light spots in my final image. However, the code seems to stop short and only equalize part of my image. Any ideas as to why the code is stopping early?
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('EXP_0159-2_8b.tif')
imgOut = img.copy()
for i in range(0, len(img[0, :])):
imgLine1 = (img[:, i] < 165) * img[:, i]
p = imgLine1.nonzero()
if len(p[0]) < 1:
imgOut[:, i] == 0
else:
imgLine2 = imgLine1[p[0]]
def curvefitting(lineFunction):
x = np.arange(0, len(lineFunction))
y = lineFunction
curve = np.polyfit(x, y, deg = 2)
a = curve[0]
b = curve[1]
c = curve[2]
curveEquation = (a*(x**2)) + (b*(x**1)) + (c)
curveCorrected = lineFunction - curveEquation + 200
return curveCorrected
imgLine1[p[0]] = curvefitting(imgLine2)
imgOut[:, i] = imgLine1
plt.imshow(imgOut, cmap = 'gray')
The for loop takes the individual columns of pixels in my image and restricts the endpoints of that column to (0, 165), so that pixels outside of that range are turned into zero and ignored by the nonzero() function. The if condition just finalizes the conversion of values outside (0, 165) to zero. Additionally, I converted the image to gray so I would not have to deal with colors and could focus only on luminosity.
This is my corrected image. The program works to average the luminosity values across the entire surface. However, you can see that it stops before reaching the end. The initial image was darker on the sides and lighter in the middle, but the file is too large to upload.
Any help is greatly appreciated.
If you are not interested in color you can convert input image to grayscale. That would simplified the matrix multiplications. The simplified version would be
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])
def curvefitting(lineFunction):
x = np.arange(0, len(lineFunction))
y = lineFunction
curve = np.polyfit(x, y, deg = 2)
a = curve[0]
b = curve[1]
c = curve[2]
curveEquation = [(a*(x_**2)) + (b*(x_**1)) + (c) for x_ in x]
curveCorrected = lineFunction - curveEquation + 200
return curveCorrected
img = mpimg.imread('EXP_0159-2_8b.tif')
img = rgb2gray(img)
imgOut = img.copy()
for i in range(0, len(img[0, :])):
imgLine1 = (img[:, i] < 165) * img[:, i]
p = imgLine1.nonzero()
if len(p) < 1:
imgOut[:, i] == 0
else:
imgLine2 = imgLine1[p]
imgLine1[p] = curvefitting(imgLine2)
imgOut[:, i] = imgLine1
plt.imshow(imgOut, cmap = 'gray')
plt.show()

Anti-Aliasing with Pixel-by-Pixel filter

I've looked around on SO for a while for anti-aliasing algorithms that might work, but they all produce blurry images and/or aren't applicable to my current method.
I've created a pixel-by-pixel filter with Python and PIL that creates a palette of colors (how they're determined isn't important.) The code creates an image like the one below. As you can see, it's very aliased.
http://i.imgur.com/BlGNzmP.png
I've heard that the best way to anti-alias an image is to do it while the image is being drawn, not afterwards; however, I'm not sure if this is possible with my current method, which is to loop through each individual pixel. If a method by which I can anti-alias the image while it is being drawn is possible, please post it here.
Here's the code:
import Image, ImageDraw
from collections import Counter
def avg_color(arr):
r,g,b = 0,0,0
for item in arr:
r += item[0]
g += item[1]
b += item[2]
l = len(arr)
return (r//l,g//l,b//l)
COLOR_NUMBER = 12
PATH = "test.png"
COLORS = [
(255,255,255),
(0,0,0)
]
im = Image.open(PATH)
rgbim = im.convert('RGB')
size = rgbim.size
nim = Image.new('RGB', size)
draw = ImageDraw.Draw(nim)
for i in range(COLOR_NUMBER-2):
devs = [[765 for j in range(size[1])] for k in range(size[0])]
cols = [[765 for j in range(size[1])] for k in range(size[0])]
ind = []
for x in range(size[0]):
for y in range(size[1]):
r,g,b = rgbim.getpixel((x,y))
cols[x][y] = (r,g,b)
for c in COLORS:
diff = abs(c[0]-r)+abs(c[1]-g)+abs(c[2]-b)
devs[x][y] = min(diff,devs[x][y])
h = (0,0)
for x in range(size[0]):
for y in range(size[1]):
if devs[x][y] > devs[h[0]][h[1]]:
h = (x,y)
print "Added color",cols[h[0]][h[1]]
COLORS.append(cols[h[0]][h[1]])
print("Created palette.")
print(COLORS)
for x in range(size[0]):
for y in range(size[1]):
r,g,b = rgbim.getpixel((x,y))
devarray = []
col = COLORS[0]
for c in COLORS:
diff = abs(c[0]-r)+abs(c[1]-g)+abs(c[2]-b)
devarray.append(diff)
smallest = 0
for i in range(len(devarray)):
if devarray[i] < devarray[smallest]:
smallest = i
col = COLORS[smallest]
draw.point((x,y),fill=col)
print "Finished rough image."
nim.show()

overlay a smaller image on a larger image python OpenCv

Hi I am creating a program that replaces a face in a image with someone else's face. However, I am stuck on trying to insert the new face into the original, larger image. I have researched ROI and addWeight(needs the images to be the same size) but I haven't found a way to do this in python. Any advise is great. I am new to opencv.
I am using the following test images:
smaller_image:
larger_image:
Here is my Code so far... a mixer of other samples:
import cv2
import cv2.cv as cv
import sys
import numpy
def detect(img, cascade):
rects = cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=3, minSize=(10, 10), flags = cv.CV_HAAR_SCALE_IMAGE)
if len(rects) == 0:
return []
rects[:,2:] += rects[:,:2]
return rects
def draw_rects(img, rects, color):
for x1, y1, x2, y2 in rects:
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
if __name__ == '__main__':
if len(sys.argv) != 2: ## Check for error in usage syntax
print "Usage : python faces.py <image_file>"
else:
img = cv2.imread(sys.argv[1],cv2.CV_LOAD_IMAGE_COLOR) ## Read image file
if (img == None):
print "Could not open or find the image"
else:
cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
gray = cv2.cvtColor(img, cv.CV_BGR2GRAY)
gray = cv2.equalizeHist(gray)
rects = detect(gray, cascade)
## Extract face coordinates
x1 = rects[0][3]
y1 = rects[0][0]
x2 = rects[0][4]
y2 = rects[0][5]
y=y2-y1
x=x2-x1
## Extract face ROI
faceROI = gray[x1:x2, y1:y2]
## Show face ROI
cv2.imshow('Display face ROI', faceROI)
small = cv2.imread("average_face.png",cv2.CV_LOAD_IMAGE_COLOR)
print "here"
small=cv2.resize(small, (x, y))
cv2.namedWindow('Display image') ## create window for display
cv2.imshow('Display image', small) ## Show image in the window
print "size of image: ", img.shape ## print size of image
cv2.waitKey(1000)
A simple way to achieve what you want:
import cv2
s_img = cv2.imread("smaller_image.png")
l_img = cv2.imread("larger_image.jpg")
x_offset=y_offset=50
l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]] = s_img
Update
I suppose you want to take care of the alpha channel too. Here is a quick and dirty way of doing so:
s_img = cv2.imread("smaller_image.png", -1)
y1, y2 = y_offset, y_offset + s_img.shape[0]
x1, x2 = x_offset, x_offset + s_img.shape[1]
alpha_s = s_img[:, :, 3] / 255.0
alpha_l = 1.0 - alpha_s
for c in range(0, 3):
l_img[y1:y2, x1:x2, c] = (alpha_s * s_img[:, :, c] +
alpha_l * l_img[y1:y2, x1:x2, c])
Using #fireant's idea, I wrote up a function to handle overlays. This works well for any position argument (including negative positions).
def overlay_image_alpha(img, img_overlay, x, y, alpha_mask):
"""Overlay `img_overlay` onto `img` at (x, y) and blend using `alpha_mask`.
`alpha_mask` must have same HxW as `img_overlay` and values in range [0, 1].
"""
# Image ranges
y1, y2 = max(0, y), min(img.shape[0], y + img_overlay.shape[0])
x1, x2 = max(0, x), min(img.shape[1], x + img_overlay.shape[1])
# Overlay ranges
y1o, y2o = max(0, -y), min(img_overlay.shape[0], img.shape[0] - y)
x1o, x2o = max(0, -x), min(img_overlay.shape[1], img.shape[1] - x)
# Exit if nothing to do
if y1 >= y2 or x1 >= x2 or y1o >= y2o or x1o >= x2o:
return
# Blend overlay within the determined ranges
img_crop = img[y1:y2, x1:x2]
img_overlay_crop = img_overlay[y1o:y2o, x1o:x2o]
alpha = alpha_mask[y1o:y2o, x1o:x2o, np.newaxis]
alpha_inv = 1.0 - alpha
img_crop[:] = alpha * img_overlay_crop + alpha_inv * img_crop
Example usage:
import numpy as np
from PIL import Image
# Prepare inputs
x, y = 50, 0
img = np.array(Image.open("img_large.jpg"))
img_overlay_rgba = np.array(Image.open("img_small.png"))
# Perform blending
alpha_mask = img_overlay_rgba[:, :, 3] / 255.0
img_result = img[:, :, :3].copy()
img_overlay = img_overlay_rgba[:, :, :3]
overlay_image_alpha(img_result, img_overlay, x, y, alpha_mask)
# Save result
Image.fromarray(img_result).save("img_result.jpg")
Result:
If you encounter errors or unusual outputs, please ensure:
img should not contain an alpha channel. (e.g. If it is RGBA, convert to RGB first.)
img_overlay has the same number of channels as img.
Based on fireant's excellent answer above, here is the alpha blending but a bit more human legible. You may need to swap 1.0-alpha and alpha depending on which direction you're merging (mine is swapped from fireant's answer).
o* == s_img.*
b* == b_img.*
for c in range(0,3):
alpha = s_img[oy:oy+height, ox:ox+width, 3] / 255.0
color = s_img[oy:oy+height, ox:ox+width, c] * (1.0-alpha)
beta = l_img[by:by+height, bx:bx+width, c] * (alpha)
l_img[by:by+height, bx:bx+width, c] = color + beta
Here it is:
def put4ChannelImageOn4ChannelImage(back, fore, x, y):
rows, cols, channels = fore.shape
trans_indices = fore[...,3] != 0 # Where not transparent
overlay_copy = back[y:y+rows, x:x+cols]
overlay_copy[trans_indices] = fore[trans_indices]
back[y:y+rows, x:x+cols] = overlay_copy
#test
background = np.zeros((1000, 1000, 4), np.uint8)
background[:] = (127, 127, 127, 1)
overlay = cv2.imread('imagee.png', cv2.IMREAD_UNCHANGED)
put4ChannelImageOn4ChannelImage(background, overlay, 5, 5)
A simple function that blits an image front onto an image back and returns the result. It works with both 3 and 4-channel images and deals with the alpha channel. Overlaps are handled as well.
The output image has the same size as back, but always 4 channels.
The output alpha channel is given by (u+v)/(1+uv) where u,v are the alpha channels of the front and back image and -1 <= u,v <= 1. Where there is no overlap with front, the alpha value from back is taken.
import cv2
def merge_image(back, front, x,y):
# convert to rgba
if back.shape[2] == 3:
back = cv2.cvtColor(back, cv2.COLOR_BGR2BGRA)
if front.shape[2] == 3:
front = cv2.cvtColor(front, cv2.COLOR_BGR2BGRA)
# crop the overlay from both images
bh,bw = back.shape[:2]
fh,fw = front.shape[:2]
x1, x2 = max(x, 0), min(x+fw, bw)
y1, y2 = max(y, 0), min(y+fh, bh)
front_cropped = front[y1-y:y2-y, x1-x:x2-x]
back_cropped = back[y1:y2, x1:x2]
alpha_front = front_cropped[:,:,3:4] / 255
alpha_back = back_cropped[:,:,3:4] / 255
# replace an area in result with overlay
result = back.copy()
print(f'af: {alpha_front.shape}\nab: {alpha_back.shape}\nfront_cropped: {front_cropped.shape}\nback_cropped: {back_cropped.shape}')
result[y1:y2, x1:x2, :3] = alpha_front * front_cropped[:,:,:3] + (1-alpha_front) * back_cropped[:,:,:3]
result[y1:y2, x1:x2, 3:4] = (alpha_front + alpha_back) / (1 + alpha_front*alpha_back) * 255
return result
For just add an alpha channel to s_img I just use cv2.addWeighted before the line
l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]] = s_img
as following:
s_img=cv2.addWeighted(l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]],0.5,s_img,0.5,0)
When attempting to write to the destination image using any of these answers above and you get the following error:
ValueError: assignment destination is read-only
A quick potential fix is to set the WRITEABLE flag to true.
img.setflags(write=1)
A simple 4on4 pasting function that works-
def paste(background,foreground,pos=(0,0)):
#get position and crop pasting area if needed
x = pos[0]
y = pos[1]
bgWidth = background.shape[0]
bgHeight = background.shape[1]
frWidth = foreground.shape[0]
frHeight = foreground.shape[1]
width = bgWidth-x
height = bgHeight-y
if frWidth<width:
width = frWidth
if frHeight<height:
height = frHeight
# normalize alpha channels from 0-255 to 0-1
alpha_background = background[x:x+width,y:y+height,3] / 255.0
alpha_foreground = foreground[:width,:height,3] / 255.0
# set adjusted colors
for color in range(0, 3):
fr = alpha_foreground * foreground[:width,:height,color]
bg = alpha_background * background[x:x+width,y:y+height,color] * (1 - alpha_foreground)
background[x:x+width,y:y+height,color] = fr+bg
# set adjusted alpha and denormalize back to 0-255
background[x:x+width,y:y+height,3] = (1 - (1 - alpha_foreground) * (1 - alpha_background)) * 255
return background
I reworked #fireant's concept to allow for optional alpha masks and allow any x or y, including values outside of the bounds of the image. It will crop to the bounds.
def overlay_image_alpha(img, img_overlay, x, y, alpha_mask=None):
"""Overlay `img_overlay` onto `img` at (x, y) and blend using optional `alpha_mask`.
`alpha_mask` must have same HxW as `img_overlay` and values in range [0, 1].
"""
if y < 0 or y + img_overlay.shape[0] > img.shape[0] or x < 0 or x + img_overlay.shape[1] > img.shape[1]:
y_origin = 0 if y > 0 else -y
y_end = img_overlay.shape[0] if y < 0 else min(img.shape[0] - y, img_overlay.shape[0])
x_origin = 0 if x > 0 else -x
x_end = img_overlay.shape[1] if x < 0 else min(img.shape[1] - x, img_overlay.shape[1])
img_overlay_crop = img_overlay[y_origin:y_end, x_origin:x_end]
alpha = alpha_mask[y_origin:y_end, x_origin:x_end] if alpha_mask is not None else None
else:
img_overlay_crop = img_overlay
alpha = alpha_mask
y1 = max(y, 0)
y2 = min(img.shape[0], y1 + img_overlay_crop.shape[0])
x1 = max(x, 0)
x2 = min(img.shape[1], x1 + img_overlay_crop.shape[1])
img_crop = img[y1:y2, x1:x2]
img_crop[:] = alpha * img_overlay_crop + (1.0 - alpha) * img_crop if alpha is not None else img_overlay_crop

How to Split Image Into Multiple Pieces in 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/')

Categories