How to Split Image Into Multiple Pieces in Python

152,014

Solution 1

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

Solution 2

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

Solution 3

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

  1. Is installed in python
  2. Can invoke an image split with two lines of code
  3. Accepts any even number as an image slice parameter (e.g. 14 in this example)
  4. 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
  5. 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.

Solution 4

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)

enter image description here

Solution 5

  1. 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.
  2. 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
  3. 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)
Share:
152,014
Elteroooo
Author by

Elteroooo

Updated on July 09, 2022

Comments

  • Elteroooo
    Elteroooo almost 2 years

    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).

  • Elteroooo
    Elteroooo about 13 years
    thanks for your solution but it doesn't work with me, the picture has been not cropped good, i see red color, i think the problem maybe here : img.paste(piece)
  • Dean Liu
    Dean Liu over 7 years
    This is an especially nice solution if you have memory constraints. Large images may fail on machines with low memory when using image_slicer.
  • Rodrigo Laguna
    Rodrigo Laguna almost 7 years
    it looks good, but its documentation is poor. It also gives good control over tiles once they are created but it is not easy to see how the image will be sliced. I was expecting a kind of tuple to set number of rows and columns
  • Giacomo Alzetta
    Giacomo Alzetta over 5 years
    Why is k a parameter of this function? Shouldn't it always be 0 when calling the function? Also what is area? Why do you crop the image twice?
  • NeStack
    NeStack over 5 years
    For my case the easiest solution, also isn't calculation expensive
  • Alex Hall
    Alex Hall over 5 years
    Per comments on other answers, no this is probably not an option on memory-constrained systems.
  • TankorSmash
    TankorSmash about 4 years
    only works for pngs and doesn't work for files in directories, but this was very helpful, thank you!
  • Suraj shah
    Suraj shah almost 4 years
    this code works for me as you can change cropped_image_size at your convinence
  • Suraj shah
    Suraj shah almost 4 years
    this code can crop a large image into number of small images
  • Sam
    Sam almost 4 years
    What do all your arguments mean?
  • Kubra Altun
    Kubra Altun about 3 years
    Very practical solution, thanks. It might be useful to send 'fp' as an argument to the function because 'filename' and 'fp' variables can be confusing.
  • Ivan
    Ivan almost 3 years
    Thank you @Kubra. I was using fp in my source code it seems, I have changed it to the correct argument naming.
  • reticivis
    reticivis almost 3 years
    The behavior that @Elteroooo is getting is because the code has an error, on line 18: img=Image.new('RGB', (height,width), 255), width and height should have been switched. I suggested an edit but it was rejected ¯_(ツ)_/¯
  • Imo
    Imo over 2 years
    Worked a treat for me with jpg's. Thanks.
  • pookie
    pookie over 2 years
    Does this assume the image is square? It would be nice if you could update your answer with a test image -- one you know works. Thanks!
  • Filippo M. Libardi
    Filippo M. Libardi over 2 years
    Hi @pookie, the method will take any image size and any number of "blocks". Here is a way of testing it.
  • gildniy
    gildniy over 2 years
    Really needed this solution
  • Mario
    Mario over 2 years
    NameError: name 'cropped_npy' is not defined
  • Baabda
    Baabda over 2 years
    yeah sorry, should be [w, h] = im.shape
  • Nir
    Nir over 2 years
    @DeepPatel you are wrong. The resulting tiles do not overshoot the image size for any values of N and M, since slicing the image beyond its borders ignores the redundant parts. The following statement is correct for any 0<=x<=im.shape[0]: assert im[im.shape[0]-x:im.shape[0]+x:,:].shape[0] == x
  • Deep Patel
    Deep Patel over 2 years
    My bad, I was using this for something else and may have made some mistake when modifying it. You are right.
  • user7082181
    user7082181 about 2 years
    \get-pip.py': [Errno 2] No such file or directory
  • user7082181
    user7082181 about 2 years
    arguments are quiet obvious if you read the code and k is the offset
  • Alex Hall
    Alex Hall almost 2 years
    @user7082181 try another method of installing pip as documented (at this writing) here: pip.pypa.io/en/latest/installation