I have a code that split large images into 1024X1024 small images with 10% overlap. After this process I am processing each 1024X1024 small image. Finally, I want to combine these small images I have processed in the original image size. How can I do the combine process? Can you share some sample code? Thanks...
import cv2
path_to_img = "demo.png"
img = cv2.imread(path_to_img)
img_h, img_w, _ = img.shape
split_width = 1024
split_height = 1024
def start_points(size, split_size, overlap=0):
points = [0]
stride = int(split_size * (1-overlap))
counter = 1
while True:
pt = stride * counter
if pt + split_size >= size:
points.append(size - split_size)
break
else:
points.append(pt)
counter += 1
return points
X_points = start_points(img_w, split_width, 0.1)
Y_points = start_points(img_h, split_height, 0.1)
splitted_images = []
for i in Y_points:
for j in X_points:
split = img[i:i+split_height, j:j+split_width]
splitted_images.append(split)
To reconstruct the original image is almost the same principle. Simply use the horizontal and vertical coordinates you created and reverse the operation. You will of course need to use an external counter that will help you iterate through your list of patches. The only other intricacy you need is to declare a container that will house the final image. You can do that by declaring an array of the same type as the input image and setting it to all zeroes:
import numpy as np
final_image = np.zeros_like(img)
index = 0
for i in Y_points:
for j in X_points:
final_image[i:i+split_height, j:j+split_width] = splitted_images[index]
index += 1
final_image will now contain the reconstructed image using the patches. Take note that I have simply overwritten any values that are overlapping with the most recent patch that overlaps any area of interest that would have overlapping values last.
Related
I need to split an image into multiple images, based on the white borders between them.
for example:
output:
using Python, I don't know how to start this mission.
Here is a solution for the "easy" case where we know the grid configuration. I provide this solution even though I doubt this is what you were asked to do.
In your example image of the cat, if we are given the grid configuration, 2x2, we can do:
from PIL import Image
def subdivide(file, nx, ny):
im = Image.open(file)
wid, hgt = im.size # Size of input image
w = int(wid/nx) # Width of each subimage
h = int(hgt/ny) # Height of each subimage
for i in range(nx):
x1 = i*w # Horicontal extent...
x2 = x1+w # of subimate
for j in range(ny):
y1 = j*h # Certical extent...
y2 = y1+h # of subimate
subim = im.crop((x1, y1, x2, y2))
subim.save(f'{i}x{j}.png')
subdivide("cat.png", 2, 2)
The above will create these images:
My previous answer depended on knowing the grid configuration of the input image. This solution does not.
The main challenge is to detect where the borders are and, thus, where the rectangles that contain the images are located.
To detect the borders, we'll look for (vertical and horizontal) image lines where all pixels are "white". Since the borders in the image are not really pure white, we'll use a value less than 255 as the whiteness threshold (WHITE_THRESH in the code.)
The gist of the algorithm is in the following lines of code:
whitespace = [np.all(gray[:,i] > WHITE_THRESH) for i in range(gray.shape[1])]
Here "whitespace" is a list of Booleans that looks like
TTTTTFFFFF...FFFFFFFFTTTTTTTFFFFFFFF...FFFFTTTTT
where "T" indicates the corresponding horizontal location is part of the border (white).
We are interested in the x-locations where there are transitions between T and F. The call to the function slices(whitespace) returns a list of tuples of indices
[(x1, x2), (x1, x2), ...]
where each (x1, x2) pair indicates the xmin and xmax location of images in the x-axis direction.
The slices function finds the "edges" where there are transitions between True and False using the exclusive-or operator and then returns the locations of the transitions as a list of tuples (pairs of indices).
Similar code is used to detect the vertical location of borders and images.
The complete runnable code below takes as input the OP's image "cat.png" and:
Extracts the sub-images into 4 PNG files "fragment-0-0.png", "fragment-0-1.png", "fragment-1-0.png" and "fragment-1-1.png".
Creates a (borderless) version of the original image by pasting together the above fragments.
The runnable code and resulting images follow. The program runs in about 0.25 seconds.
from PIL import Image
import numpy as np
def slices(lst):
""" Finds the indices where lst changes value and returns them in pairs
lst is a list of booleans
"""
edges = [lst[i-1] ^ lst[i] for i in range(len(lst))]
indices = [i for i,v in enumerate(edges) if v]
pairs = [(indices[i], indices[i+1]) for i in range(0, len(indices), 2)]
return pairs
def extract(xx_locs, yy_locs, image, prefix="image"):
""" Locate and save the subimages """
data = np.asarray(image)
for i in range(len(xx_locs)):
x1,x2 = xx_locs[i]
for j in range(len(yy_locs)):
y1,y2 = yy_locs[j]
arr = data[y1:y2, x1:x2, :]
Image.fromarray(arr).save(f'{prefix}-{i}-{j}.png')
def assemble(xx_locs, yy_locs, prefix="image", result='composite'):
""" Paste the subimages into a single image and save """
wid = sum([p[1]-p[0] for p in xx_locs])
hgt = sum([p[1]-p[0] for p in yy_locs])
dst = Image.new('RGB', (wid, hgt))
x = y = 0
for i in range(len(xx_locs)):
for j in range(len(yy_locs)):
img = Image.open(f'{prefix}-{i}-{j}.png')
dst.paste(img, (x,y))
y += img.height
x += img.width
y = 0
dst.save(f'{result}.png')
WHITE_THRESH = 110 # The original image borders are not actually white
image_file = 'cat.png'
image = Image.open(image_file)
# To detect the (almost) white borders, we make a grayscale version of the image
gray = np.asarray(image.convert('L'))
# Detect location of images along the x axis
whitespace = [np.all(gray[:,i] > WHITE_THRESH) for i in range(gray.shape[1])]
xx_locs = slices(whitespace)
# Detect location of images along the y axis
whitespace = [np.all(gray[i,:] > WHITE_THRESH) for i in range(gray.shape[0])]
yy_locs = slices(whitespace)
extract(xx_locs, yy_locs, image, prefix='fragment')
assemble(xx_locs, yy_locs, prefix='fragment', result='composite')
Individual fragments:
The composite image:
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:
I'm working in Python on image stacks with up to 1000 images in tiff format. Each image is a 16bit image, 2048x2048 pixels, full size of 8MB, thus the full stack is several GB large. I have three color channels which I merge. The operations I do on the merged stacks (like converting to HSV space, applying filters...) I do on each individual image of the stack.
But when I want to load the full stacks in the beginning, I obviously run out of memory.
Is there a way to handle these large stacks? I had the idea to only load the single images from the stack, do the operations on that individual image, and concatenate it to a stack again. Unfortunately, I couldn't find a way to do that. Or is there a better solution?
Any help is very much appreciated.
Here is my code so far (where I run out of memory when loading the images, but it works with smaller test stacks):
import numpy as np
from skimage import io
import matplotlib
b = io.imread('blue_channel.tif')
g = io.imread('green_channel.tif')
r = io.imread('red_channel.tif')
Npic = np.shape(b)[0] #get number of images
sizex = np.shape(b)[1] #get size in x
sizey = np.shape(b)[2] #get size in y
Ncol = 3
merged_channels = np.zeros([Npic, sizex, sizey, Ncol])
merged_channels[:,:,:,0] = r
merged_channels[:,:,:,1] = g
merged_channels[:,:,:,2] = b
merged_channels = merged_channels.astype(np.float32) #Cast Image data type
merged_channels /= 255 #Scale value to float32 range 0-1
merged_channels_hsv = np.zeros([Npic, sizex, sizey, Ncol])
for i in tqdm(range(Npic)):
merged_channels_hsv[i, :, :, :] = matplotlib.colors.rgb_to_hsv(merged_channels[i, :, :, :])
merged_channels_hsv *= 255
merged_channels_hsv = merged_channels_hsv.astype(np.uint8)
imsave('merged_channels_hsv.tif', merged_channels_hsv[:,:,:,:])
Here are sample images:
Merged RGB image:
Converted to HSV space, only H-channel:
EDIT:
I found a way to define the image numbers I want to load from a tiff-file using
skimage.external.tifffile.imread(files, **kwargs):
import numpy as np
from tifffile import imread
def sequence(start, end):
res = []
diff = 1
x = start
while x <= end:
res.append(x)
x += diff
return res
seq = sequence(1, 100) #load first 100 images from stack
merged_channels = imread('filename.tif', key = seq)
I have an 8bit binary image that shows me the outline of a circle. The outline is only 1 pixel wide. Using the function view_as_windows lets me generate smaller arrays or windows of an input array like this picture, with adjacent overlapping windows. The size of this image is 250×250.
from skimage.io import imread
from skimage.util import view_as_windows
fname = "C:\\Users\\Username\\Desktop\\Circle.tif"
array = imread(fname)
window_shape = (50, 50)
step = 20
new_array = view_as_windows(array, window_shape, step=step)
This gives me 11×11 overplapping windows. However, I want to extract only windows along the line of the circle so that I can reassemble this object at a later time. The line of a each window should be positioned centrally or in a way so that I have access to the information right under the circle.
This is what I have tried so far:
First I replaced the values (0) and (255) with (1) and (0), respectively. This way, math is a bit easier.
array[array==0] = 1
array[array==255] = 0
Then I iterated over the windows in new_array. In this case over the first two dimensions. new_array.shape is (11, 11, 50, 50)
for j in range(new_array.shape[0]):
for i in range(new_array.shape[1]):
Window = new_array[j, i]
SliceOfWindow = Slice[20:30, 20:30]
sumAxis0 = np.sum(Slice, axis=0)
sumSlice = np.sum(sumAxis0)
if sumSlice >= SliceOfWindow.shape[0]
imsave(...)
I created a smaller slice of the shape = (10, 10) within each window, placed in the center. If the sum of each slice >= the length of a slice I have saved that array as an image.
Can this be done in a more precise way? Is there a way to yield better results (better windows!)?
For a convex curve, you could use polar coordinates and sort the edge pixels by their angle through numpy.argsort and numpy.arctan2.
Demo
from skimage import io
import matplotlib.pyplot as plt
import numpy as np
img = io.imread('https://i.stack.imgur.com/r3D6I.png')
# Arbitrary point inside the curve
row_cen, col_cen = 125, 125
# Coordinates of the edge pixels
row, col = np.nonzero(img == 0)
# Put the origin on the lower left corner
x = col - col_cen
y = -(row - row_cen)
# Indices of the centers of the windows
step = 60
idx = np.argsort(np.arctan2(y, x))[::step]
windows = np.zeros_like(img)
size = 15
for _, n in enumerate(idx):
windows[row[n] - size:row[n] + size, col[n] - size:col[n] + size] = 255
plt.imshow(windows, cmap='gray')
for i, n in enumerate(idx):
plt.text(col[n], row[n], i, fontsize='14',
horizontalalignment='center',
verticalalignment='center')
plt.show()
I have been working on a piece of code to create a disparity map.
I don't want to use OpenCV for more than loading / saving the images converting them to grayscale.
So far, I've managed to implement the algorithm explained in this website. I'm using the version of the algorithm that uses the Sum of Absolute Differences (SAD). To test my implementation, I'm using the stereo images from this dataset.
Here's my code:
import cv2
import numpy as np
# Load the stereo images
img = cv2.imread('bow-view1.png')
img2 = cv2.imread('bow-view5.png')
# convert stereo images to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
# get the size of the images
# l -> lines
# c -> columns
# v -> channel (RGB)
l,c,v = img.shape
# initialize arrays
minSAD = np.ones((l,c)) * 1000
sad = np.ones((l,c))
winsad = np.ones((l,c))
disp = np.zeros((l,c))
max_shift = 30
# set size of the SAD window
w_l = 2
w_c = 2
for shift in range(max_shift):
print("New Shift: %d"%(shift))
for u in range(0,l):
for v in range(0,c):
# calculate SAD
if(u+shift < l):
sad[u,v] = np.abs((int(gray[u,v]) - int(gray2[u+shift,v])))
sum_sad = 0
for d in range(w_l):
for e in range(w_c):
if(u+d < l and v+e < c):
sum_sad += sad[u+d,v+e]
winsad[u,v] = sum_sad
# Save disparity
if(sad[u,v] < minSAD[u,v]):
minSAD[u,v] = winsad[u,v]
disp[u,v] = shift
print("Process Complete")
# write disparity map to image
cv2.imwrite('outputHT/disparity/sad.png',disp)
print("Disparity Map Generated")
This is the output generated by that code:
I should get an output similar (or very close to) this:
I've tried several window sizes (in the SAD step), but I keep getting results like this one or images that are all black.
Any answer that helps me figure out the problem or that at least points me in the right direction will be very appreciated!
One thing you are missing here is that all the values in the disp array will be between 0 and 30 which correspond to black pixel, so in order to map these values between 0 and 255 you have to multiply the shift by 8.