SHARE
TWEET

Untitled

a guest Apr 28th, 2017 118 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. '''
  2.  
  3. USAGE: $python this_script.py ipod.jpg background.jpg
  4.  
  5. '''
  6. import random
  7. from PIL import Image
  8. import cv2
  9. import numpy as np
  10.  
  11.  
  12. class BBox(object):
  13.     '''
  14.    Given a set of cornerpoints (list of 4 2D position vectors), computes a tight bounding box around them
  15.    '''
  16.  
  17.     def __init__(self, cornerpoints):
  18.         self.left = np.min(cornerpoints[:, 0])
  19.         self.right = np.max(cornerpoints[:, 0])
  20.         self.upper = np.max(cornerpoints[:, 1])
  21.         self.lower = np.min(cornerpoints[:, 1])
  22.  
  23.         self.width = self.right - self.left
  24.         self.height = self.lower - self.upper
  25.  
  26.         self.coords = np.array([[self.left, self.upper],
  27.                                 [self.right, self.upper],
  28.                                 [self.right, self.lower],
  29.                                 [self.left, self.lower]])
  30.  
  31.        
  32. class PhotoSynthesizer(object):
  33.     '''
  34.    Class for synthesizing "photos" of rectangles. Simulates perspective distortion, scale, rotation and translation.
  35.  
  36.    Convention for box/cornerpoints ordering:
  37.  
  38.            [top-left, top-right, bottom-right, bottom-left]
  39.  
  40.    i.e.    clockwise from top-left.
  41.    '''
  42.     def __init__(self, canvas_size,
  43.                  scale_amount=(0.3, 0.7), rotation_amount=15, distortion_amount=50):
  44.  
  45.         self.canvas_size = canvas_size
  46.  
  47.         self.scale_amount = scale_amount
  48.         self.rotation_amount = rotation_amount
  49.         self.distortion_amount = distortion_amount
  50.  
  51.     def _to_homogenous(self, nparr):
  52.         homogenous = np.c_[nparr, np.ones(4)]
  53.         return homogenous
  54.  
  55.     def _from_homogenous(self, nparr):
  56.         outarr = np.zeros((4, 2))
  57.         outarr[:, 0] = nparr[:, 0] / nparr[:, 2]
  58.         outarr[:, 1] = nparr[:, 1] / nparr[:, 2]
  59.         return outarr
  60.  
  61.     def _transform_cornerpoints(self, matrix, coordinate_list):
  62.         # turn the coords array into homogeneous coordinates, to do matrix operations on them
  63.         coords_homogeneous = self._to_homogenous(coordinate_list)
  64.         # transpose the cornerpoints arrays so that they can be multiplied by matrices
  65.         coords_homogeneous = np.transpose(coords_homogeneous)
  66.  
  67.         # perform dot
  68.         coords_homogeneous = np.dot(matrix, coords_homogeneous)
  69.  
  70.         # un-transpose
  71.         coords_homogeneous = np.transpose(coords_homogeneous)
  72.         # convert from homogeneous to 2D
  73.         return self._from_homogenous(coords_homogeneous)
  74.  
  75.     def _random_homography(self):
  76.  
  77.         # assume that the ID card images have been rescaled to the fit the canvas
  78.         w_original = self.canvas_size[0]
  79.         h_original = self.canvas_size[1]
  80.  
  81.         cornerpoints_original = np.array([[0, 0],
  82.                                           [w_original, 0],
  83.                                           [w_original, h_original],
  84.                                           [0, h_original]], dtype=np.float32)
  85.  
  86.         # flip the y-axis
  87.         cornerpoints = np.copy(cornerpoints_original)
  88.         cornerpoints[:, 1] *= -1
  89.  
  90.         # translate the cornerpoints such that the centre of the card is at the origin
  91.         translate_vector = np.array([w_original / 2, h_original / -2])
  92.         cornerpoints = cornerpoints - translate_vector
  93.  
  94.         # perspective distort the cornerpoints
  95.         jitter = np.random.uniform(-self.distortion_amount, self.distortion_amount, (4, 2))
  96.         cornerpoints = (cornerpoints + jitter).astype(np.float32)
  97.  
  98.         # perform a random rotation on the card cornerpoints
  99.         theta = np.pi / 180 * np.random.uniform(-self.rotation_amount, self.rotation_amount)
  100.         rotation_matrix = np.array([[np.cos(theta), -np.sin(theta), 0],
  101.                                     [np.sin(theta), np.cos(theta),  0],
  102.                                     [0,                         0,  1]])
  103.         cornerpoints = self._transform_cornerpoints(rotation_matrix, cornerpoints).astype(np.float32)
  104.  
  105.         # apply a random rescale (to produce image samples with IDs of varying sizes)
  106.         # that preserves aspect ratio
  107.         scale_factor = random.uniform(self.scale_amount[0], self.scale_amount[1])
  108.         zoom_matrix = np.array([[scale_factor, 0., 0.],
  109.                                 [0., scale_factor, 0.],
  110.                                 [0., 0., 1.]])
  111.         cornerpoints = self._transform_cornerpoints(zoom_matrix, cornerpoints)
  112.  
  113.         # work out the tightly enclosing bounding box around cornerpoints
  114.         bbox = BBox(cornerpoints)
  115.         # print('tight bounding box: ')
  116.         # print(bbox.upper, bbox.left, bbox.lower, bbox.right)
  117.  
  118.         # work out the maximum amounts that the card can travel horizontally and vertically, while remaining
  119.         # relatively non-truncated
  120.         upperbound = (self.canvas_size[1] / 2) + bbox.height / 10.
  121.         lowerbound = -upperbound
  122.         rightbound = (self.canvas_size[0] / 2) + bbox.width / 10.
  123.         leftbound = -rightbound
  124.  
  125.         # print('upper, left, lower, right boundaries:')
  126.         # print(upperbound, leftbound, lowerbound, rightbound)
  127.  
  128.         # these are the maximum amounts the card can travel in either direction before becoming *too* truncated
  129.         max_travel_right = rightbound - bbox.right
  130.         max_travel_left = bbox.left - leftbound
  131.         max_travel_up = upperbound - bbox.upper
  132.         max_travel_down = bbox.lower - lowerbound
  133.  
  134.         # print('max travel upper, left, lower, right')
  135.         # print(max_travel_up, max_travel_left, max_travel_down, max_travel_right)
  136.  
  137.         random_translate = np.array([random.uniform(-max_travel_left, max_travel_right),
  138.                                      random.uniform(-max_travel_down, max_travel_up)])
  139.  
  140.         # print('random translation vector:')
  141.         # print(random_translate)
  142.  
  143.         cornerpoints = cornerpoints + random_translate
  144.  
  145.         # # translate the cornerpoints centre back from the cartesian origin to the image centre
  146.         cornerpoints = (cornerpoints + translate_vector).astype(np.float32)
  147.  
  148.         # flip the y-axis back
  149.         cornerpoints[:, 1] *= -1
  150.  
  151.         H = cv2.getPerspectiveTransform(cornerpoints_original, cornerpoints)
  152.         # PIL is weird and insists on using the INVERSE homography matrix
  153.         J = np.linalg.inv(H)
  154.         # only interested in the 8 degrees of freedom, as a tuple
  155.         h = tuple(J.flatten()[:8])
  156.  
  157.         return h, cornerpoints
  158.  
  159.     def snap(self, img_rectangle, img_background):
  160.         '''
  161.        :param img_rectangle: PIL image of rectangle
  162.        :param img_background_new: PIL image of background
  163.        :return: cv2 image - composite of rectangle and background with random perspective
  164.        '''
  165.  
  166.         img_rectangle_new = img_rectangle.convert("RGBA")
  167.         img_rectangle_new = img_rectangle_new.resize(self.canvas_size)
  168.  
  169.         # compute a random projective transform homography
  170.         h, cornerpoints_new = self._random_homography()
  171.  
  172.         # apply the transform
  173.         new_image = img_rectangle_new.transform(self.canvas_size, Image.PERSPECTIVE, h, Image.BICUBIC)
  174.  
  175.         # rescale the background to fit the canvas
  176.         img_background_new = img_background.resize(self.canvas_size)
  177.  
  178.         # paste projective-transformed ID onto background
  179.         img_background_new.paste(new_image, (0, 0), new_image)
  180.  
  181.         # convert the PIL image to opencv:
  182.         img_background_new = np.array(img_background_new)
  183.         img_background_new = cv2.cvtColor(img_background_new, cv2.COLOR_RGB2BGR)
  184.  
  185.         cols = [(225, 0, 0), (0, 225, 0), (0, 0, 225), (225, 225, 0)]
  186.  
  187.         for j in range(4):
  188.             cornerpoints_new = cornerpoints_new.astype(int)
  189.             cv2.circle(img_background_new, tuple(cornerpoints_new[j]), 5, color=cols[j], thickness=-1)
  190.  
  191.         show(img_background_new)
  192.  
  193. def show(im, winname=''):
  194.     cv2.imshow(winname, im)
  195.     cv2.waitKey(0)
  196.     cv2.destroyAllWindows()
  197.  
  198.  
  199. if __name__ == '__main__':
  200.     import sys
  201.  
  202.     IMG_RECTANGLE = Image.open(sys.argv[1])
  203.     IMG_BACKGROUND = Image.open(sys.argv[2])
  204.     CANVAS_SIZE = (640, 410)
  205.     SCALE_AMOUNT = (0.4, 1.1)
  206.     DISTORTION_AMOUNT = 50
  207.  
  208.     photographer = PhotoSynthesizer(CANVAS_SIZE)
  209.  
  210.     while True:
  211.         photographer.snap(IMG_RECTANGLE, IMG_BACKGROUND)
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top