Reputation: 83
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 ???
Upvotes: 7
Views: 21108
Reputation: 47
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))
Upvotes: 0
Reputation: 53081
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
Upvotes: 1
Reputation: 822
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
Upvotes: 1
Reputation: 333
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)
Upvotes: 0
Reputation: 14399
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)
Upvotes: 5
Reputation: 22954
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.
Upvotes: 10