cropper.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. from collections import namedtuple
  2. import cv2 as cv
  3. from .blender import Blender
  4. from .stitching_error import StitchingError
  5. class Rectangle(namedtuple('Rectangle', 'x y width height')):
  6. __slots__ = ()
  7. @property
  8. def area(self):
  9. return self.width * self.height
  10. @property
  11. def corner(self):
  12. return (self.x, self.y)
  13. @property
  14. def size(self):
  15. return (self.width, self.height)
  16. @property
  17. def x2(self):
  18. return self.x + self.width
  19. @property
  20. def y2(self):
  21. return self.y + self.height
  22. def times(self, x):
  23. return Rectangle(*(int(round(i*x)) for i in self))
  24. def draw_on(self, img, color=(0, 0, 255), size=1):
  25. if len(img.shape) == 2:
  26. img = cv.cvtColor(img, cv.COLOR_GRAY2RGB)
  27. start_point = (self.x, self.y)
  28. end_point = (self.x2-1, self.y2-1)
  29. cv.rectangle(img, start_point, end_point, color, size)
  30. return img
  31. class Cropper:
  32. DEFAULT_CROP = False
  33. def __init__(self, crop=DEFAULT_CROP):
  34. self.do_crop = crop
  35. self.overlapping_rectangles = []
  36. self.cropping_rectangles = []
  37. def prepare(self, imgs, masks, corners, sizes):
  38. if self.do_crop:
  39. mask = self.estimate_panorama_mask(imgs, masks, corners, sizes)
  40. self.compile_numba_functionality()
  41. lir = self.estimate_largest_interior_rectangle(mask)
  42. corners = self.get_zero_center_corners(corners)
  43. rectangles = self.get_rectangles(corners, sizes)
  44. self.overlapping_rectangles = self.get_overlaps(
  45. rectangles, lir)
  46. self.intersection_rectangles = self.get_intersections(
  47. rectangles, self.overlapping_rectangles)
  48. def crop_images(self, imgs, aspect=1):
  49. for idx, img in enumerate(imgs):
  50. yield self.crop_img(img, idx, aspect)
  51. def crop_img(self, img, idx, aspect=1):
  52. if self.do_crop:
  53. intersection_rect = self.intersection_rectangles[idx]
  54. scaled_intersection_rect = intersection_rect.times(aspect)
  55. cropped_img = self.crop_rectangle(img, scaled_intersection_rect)
  56. return cropped_img
  57. return img
  58. def crop_rois(self, corners, sizes, aspect=1):
  59. if self.do_crop:
  60. scaled_overlaps = \
  61. [r.times(aspect) for r in self.overlapping_rectangles]
  62. cropped_corners = [r.corner for r in scaled_overlaps]
  63. cropped_corners = self.get_zero_center_corners(cropped_corners)
  64. cropped_sizes = [r.size for r in scaled_overlaps]
  65. return cropped_corners, cropped_sizes
  66. return corners, sizes
  67. @staticmethod
  68. def estimate_panorama_mask(imgs, masks, corners, sizes):
  69. _, mask = Blender.create_panorama(imgs, masks, corners, sizes)
  70. return mask
  71. def compile_numba_functionality(self):
  72. # numba functionality is only imported if cropping
  73. # is explicitely desired
  74. try:
  75. import numba
  76. except ModuleNotFoundError:
  77. raise StitchingError("Numba is needed for cropping but not installed")
  78. from .largest_interior_rectangle import largest_interior_rectangle
  79. self.largest_interior_rectangle = largest_interior_rectangle
  80. def estimate_largest_interior_rectangle(self, mask):
  81. lir = self.largest_interior_rectangle(mask)
  82. lir = Rectangle(*lir)
  83. return lir
  84. @staticmethod
  85. def get_zero_center_corners(corners):
  86. min_corner_x = min([corner[0] for corner in corners])
  87. min_corner_y = min([corner[1] for corner in corners])
  88. return [(x - min_corner_x, y - min_corner_y) for x, y in corners]
  89. @staticmethod
  90. def get_rectangles(corners, sizes):
  91. rectangles = []
  92. for corner, size in zip(corners, sizes):
  93. rectangle = Rectangle(*corner, *size)
  94. rectangles.append(rectangle)
  95. return rectangles
  96. @staticmethod
  97. def get_overlaps(rectangles, lir):
  98. return [Cropper.get_overlap(r, lir) for r in rectangles]
  99. @staticmethod
  100. def get_overlap(rectangle1, rectangle2):
  101. x1 = max(rectangle1.x, rectangle2.x)
  102. y1 = max(rectangle1.y, rectangle2.y)
  103. x2 = min(rectangle1.x2, rectangle2.x2)
  104. y2 = min(rectangle1.y2, rectangle2.y2)
  105. if x2 < x1 or y2 < y1:
  106. raise StitchingError("Rectangles do not overlap!")
  107. return Rectangle(x1, y1, x2-x1, y2-y1)
  108. @staticmethod
  109. def get_intersections(rectangles, overlapping_rectangles):
  110. return [Cropper.get_intersection(r, overlap_r) for r, overlap_r
  111. in zip(rectangles, overlapping_rectangles)]
  112. @staticmethod
  113. def get_intersection(rectangle, overlapping_rectangle):
  114. x = abs(overlapping_rectangle.x - rectangle.x)
  115. y = abs(overlapping_rectangle.y - rectangle.y)
  116. width = overlapping_rectangle.width
  117. height = overlapping_rectangle.height
  118. return Rectangle(x, y, width, height)
  119. @staticmethod
  120. def crop_rectangle(img, rectangle):
  121. return img[rectangle.y:rectangle.y2, rectangle.x:rectangle.x2]