In [1]:
#%matplotlib inline
In [2]:
from skimage.io import imread
from skimage import color, data, restoration
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image, ImageDraw
import numpy as np
from scipy.misc import imresize
from scipy import ndimage as nd
In [3]:
'''
image uint8 normalize function
'''
def norm(img):
return np.real((img - np.min(img)) * 256 / (np.max(img) - np.min(img)))
In [4]:
RGB = plt.imread('./img/agbar.png')
scale_factor=2
RGB = imresize(RGB, ( RGB.shape[0]/scale_factor, RGB.shape[1]/scale_factor,3),interp='bilinear').astype('float')
img = color.rgb2gray(RGB)
#Visualization
fig = plt.figure(1)
plt.subplot(1,2,1)
plt.imshow(RGB.astype('uint8'))
plt.title('Original')
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(norm(img), cmap="gray")
plt.title('GrayScale')
plt.axis('off')
plt.gcf().set_size_inches((10,10))
fig.tight_layout()
plt.show()
In [5]:
# * * * * * * * * * * * * * * *
# * Gradient Magnitude Matrix *
# * * * * * * * * * * * * * * *
def gradient(img):
'''
compute image gradient magnitude matrix.
'''
gx, gy = np.gradient(img)
grad = np.sqrt(gx*gx + gy*gy)
return grad
# * * * * * * * * *
# * Energy Matrix *
# * * * * * * * * *
def compute_energy(im):
'''
This implements the dynamic programming seam-find algorithm. For an m*n picture, this algorithm
takes O(m*n) time
'''
im_width, im_height = im.shape
cost = np.zeros(im.shape)
cost[0] = im[0] #first row for energy matrix as the original gradient magnitude
for x in xrange(1, im_width):
for y in xrange(im_height):
if y == 0:
min_val = min( cost[x-1, y], cost[x-1, y+1] )
elif y < im_height - 2:
min_val = min( cost[x-1, y], cost[x-1, y+1] )
min_val = min( min_val, cost[x-1, y-1] )
else:
min_val = min( cost[x-1, y], cost[x-1, y-1] )
cost[x,y] = im[x,y] + min_val
return cost
In [6]:
grad = gradient(img)
energy = compute_energy(grad)
#Visualization
fig = plt.figure(2)
plt.subplot(1,2,1)
plt.imshow(norm(grad), cmap="gray")
plt.title('Gradient Magnitude')
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(norm(energy), cmap="gray" )
plt.title('Energy')
plt.axis('off')
plt.gcf().set_size_inches((10,10))
fig.tight_layout()
plt.show()
In [7]:
def find_vertical_seam(im, mask):
'''
Takes a grayscale img and returns the lowest energy vertical seam as a list of pixels (2-tuples).
This implements the dynamic programming seam-find algorithm. For an m*n picture, this algorithm
takes O(m*n) time
@im: a grayscale image
'''
assert len(im.shape) == 2
im_width, im_height = im.shape
grad = apply_mask(gradient(im), mask)
cost = compute_energy(grad)
min_val = 1e1000
path = []
for y in xrange(im_height):
if cost[im_width-1,y] < min_val:
min_val = cost[im_width-1,y]
min_ptr = y
# if there is still a negative value in the last row of energy mat -> the selected area deletion is not finished
if(min_val >= 0):
return path, True
pos = (im_width-1, min_ptr)
path.append(pos)
while pos[0] != 0:
val = cost[pos] - grad[pos]
x,y = pos
if y == 0:
if val == cost[x-1,y+1]:
pos = (x-1,y+1)
else:
pos = (x-1,y)
elif y <= im_height - 2:
if val == cost[x-1,y+1]:
pos = (x-1,y+1)
elif val == cost[x-1,y]:
pos = (x-1,y)
else:
pos = (x-1,y-1)
else:
if val == cost[x-1,y]:
pos = (x-1,y)
else:
pos = (x-1,y-1)
path.append(pos)
#print "Reconstruction Complete."
return path, False
def mark_seam(img, path, mark_as='red'):
'''
Marks a seam for easy visual checking
@img: an input img
@path: the seam
'''
assert mark_as in ['red','green','blue','black','white']
assert len(img.shape) == 3
#print "Marking seam..."
for pixel in path:
if mark_as == 'red':
img[pixel] = (255,0,0)
elif mark_as == 'green':
img[pixel] = (0,255,0)
elif mark_as == 'blue':
img[pixel] = (0,0,255)
elif mark_as == 'white':
img[pixel] = (255,255,255)
elif mark_as == 'black':
img[pixel] = (0,0,0)
#print "Marking Complete."
return img;
def delete_vertical_seam (img, path):
'''
Deletes the pixels in a vertical path from img
@img: an input img
@path: pixels to delete in a vertical path
'''
#print "Deleting Vertical Seam..."
img_height, img_width = img.shape[:2]
if(len(img.shape) == 3):
i = np.zeros((img_height, img_width-1, img.shape[2]))
else:
i = np.zeros((img_height, img_width-1))
path_set = set(path)
seen_set = set()
for x in xrange(img_height):
for y in xrange(img_width):
if (x,y) not in path_set and x not in seen_set:
i[x,y] = img[x,y]
elif (x,y) in path_set:
seen_set.add(x)
else:
i[x,y-1] = img[x,y]
#print "Deletion Complete."
return i;
In [8]:
class get_mouse_click():
"""
Mouse interaction interface for radial distortion removal.
"""
def __init__(self, img):
height, width = img.shape[:2]
self.figure = plt.imshow(img, extent=(0, width, height, 0))
plt.gray()
plt.title('select the object to remove')
plt.xlabel('Select sets of points with left mouse button,\n'
'click right button to close the polygon.')
plt.connect('button_press_event', self.button_press)
plt.connect('motion_notify_event', self.mouse_move)
self.img = np.atleast_3d(img)
self.points = []
self.centre = np.array([(width - 1)/2., (height - 1)/2.])
self.height = height
self.width = width
self.make_cursorline()
self.figure.axes.set_autoscale_on(False)
plt.show()
plt.close()
def make_cursorline(self):
self.cursorline, = plt.plot([0],[0],'r:+',
linewidth=2,markersize=15,markeredgecolor='b')
def button_press(self,event):
"""
Register mouse clicks.
"""
if (event.button == 1 and event.xdata and event.ydata):
self.points.append((event.xdata,event.ydata))
print "Coordinate entered: (%f,%f)" % (event.xdata, event.ydata)
#if len(self.points) % 2 == 0:
plt.gca().lines.append(self.cursorline)
self.make_cursorline()
if (event.button != 1):
#print "pepito: " ,self.points
self.points.append((self.points[0][0],self.points[0][1]))
plt.close()
return self.points
def mouse_move(self,event):
"""
Handle cursor drawing.
"""
pts_last_set=len(self.points)
pts = np.zeros((pts_last_set+1,2))
if pts_last_set > 0:
# Line follows up to 3 clicked points:
pts[:pts_last_set] = self.points[-pts_last_set:]
# The last point of the line follows the mouse cursor
pts[pts_last_set:] = [event.xdata,event.ydata]
self.cursorline.set_data(pts[:,0], pts[:,1])
plt.draw()
def compute_mask(width,height,polygon):
img = Image.new('L', (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
mask = np.array(img)
mask100 = 100*np.ones([mask.shape[0],mask.shape[1]])
mask=mask*101
mask = 1. - mask; # switch 0s and 1s
return mask
In [9]:
# * * * * * * * * * * * * *
# * Area Mask application *
# * * * * * * * * * * * * *
def apply_mask(img, mask):
masked = img.copy()
for x in xrange(mask.shape[0]):
for y in xrange(mask.shape[1]):
if mask[x,y] == -100:
masked[x,y] = mask[x,y]
return masked
# * * * * * * * * * * * * *
# * Seam Carving Deletion *
# * * * * * * * * * * * * *
def seam_carving_deletion(RGB, mask, verbose=False):
seams = []
rgb = RGB.copy();
i = 1
while(True):
gray = color.rgb2gray(rgb)
path, end = find_vertical_seam(gray, mask)
if (end):
return rgb, seams;
rgb = mark_seam(rgb, path, 'blue')
seams.append([plt.imshow(rgb.astype('uint8'))])
rgb = delete_vertical_seam(rgb, path)
mask = delete_vertical_seam(mask, path)
if verbose:
print "iteration "+str(i),"\tseam carved...", rgb.shape
i += 1
return rgb, seams;
In [15]:
width, height = img.shape
# GET AREA TO DELETE FROM IMAGE
rdi = get_mouse_click(RGB.astype('uint8'))
polygon = rdi.points
# COMPUTE MASK FROM POLYGON
mask = compute_mask(height, width, polygon)
In [16]:
fig = plt.figure(1)
result, seams = seam_carving_deletion(RGB, mask, True)
In [20]:
#run animation
ani = animation.ArtistAnimation(fig, seams, interval=100, blit=True, repeat_delay=1000);
plt.show();
In [18]:
#Visualization
fig = plt.figure(4)
plt.subplot(1,2,1)
plt.imshow(RGB.astype('uint8'))
plt.title('Original')
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(result.astype('uint8'))
plt.title('Seam Carving Deletion')
plt.axis('off')
plt.gcf().set_size_inches((10,10))
fig.tight_layout()
plt.show();
In [ ]: