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

Question:

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 ???

Asked By: Victor Alhassan

||

Answers:

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:

enter image description here

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 512×512, hence we will get 4 tiles of same size:

enter image description here enter image description here enter image description here enter image description here

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.

Answered By: ZdaR

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)
Answered By: Daniel F

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)
Answered By: ClimbingTheCurve

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 
Answered By: Imran Ahmad Shahid

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:

enter image description here

Lets say you want 4 256×256 tiles for simplicity.

convert lena512.png -crop 256x256 lena512_%d.png

or by percent

convert lena512.png -crop 50x50% lena512_%d.png

enter image description here

enter image description here

enter image description here

enter image description here

Answered By: fmw42

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))
Answered By: TheArmbreaker
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.