setup_detection.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import argparse
  2. import cv2
  3. import json
  4. def main():
  5. ap = argparse.ArgumentParser()
  6. ap.add_argument("-i", "--input", default='data/_MG_0207.JPG', help="input image")
  7. ap.add_argument("-s", "--scale", type=float, default=0.25, help="based on the image shapes, scale the results by this factor")
  8. ap.add_argument("-f", "--file", type=str, default="setup.json", help="name file to save setup")
  9. args = vars(ap.parse_args())
  10. img = cv2.imread(args["input"])
  11. height, width, _ = img.shape
  12. scale = args["scale"]
  13. window_name = "Setup"
  14. cv2.namedWindow(window_name)
  15. show_menu()
  16. food_color = FoodColor(img, scale, window_name)
  17. board_setup = BoardLimits(img, scale, window_name)
  18. done = False
  19. state = 0
  20. while not done:
  21. key = cv2.waitKey(10)
  22. if state == 0:
  23. if key == ord("q"):
  24. done = True
  25. elif key == ord("1"):
  26. state = 1
  27. board_setup.clear()
  28. board_setup.help()
  29. cv2.setMouseCallback(window_name, board_setup.on_mouse)
  30. elif key == ord("2"):
  31. state = 2
  32. food_color.clear()
  33. food_color.help()
  34. cv2.setMouseCallback(window_name, food_color.on_mouse)
  35. elif key == ord("s"):
  36. with open(args["file"], "w") as file:
  37. json.dump({**board_setup.toJSON(), **food_color.toJSON()}, file)
  38. done = True
  39. if state == 1:
  40. board_setup.draw()
  41. if board_setup.done:
  42. state = 0
  43. show_menu()
  44. elif state == 2:
  45. food_color.draw()
  46. if food_color.done:
  47. state = 0
  48. show_menu()
  49. else:
  50. cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
  51. cv2.setMouseCallback(window_name, null_callback)
  52. def show_menu():
  53. print("\nCommands : ")
  54. print("\tPress '1' to setup board limits")
  55. print("\tPress '2' to setup food color")
  56. print("\tPress 's' to save & quit")
  57. print("\tPress 'q' to quit without saving")
  58. def null_callback(event, x, y, flags, param):
  59. return
  60. class BoardLimits:
  61. def __init__(self, img, scale, window_name):
  62. self.limits = []
  63. self.max_limits = 4
  64. self.middle = []
  65. self.max_middle = 2
  66. self.adjusted_middle = []
  67. self.orig = img
  68. self.img = img.copy()
  69. self.scale = scale
  70. self.window_name = window_name
  71. self.done = False
  72. self.limits_drawn = False
  73. self.middle_drawn = False
  74. def add_limit(self, x, y):
  75. x_img = int(x / self.scale)
  76. y_img = int(y / self.scale)
  77. self.limits.append((x_img, y_img))
  78. cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
  79. def add_middle(self, x, y):
  80. x_img = int(x / self.scale)
  81. y_img = int(y / self.scale)
  82. self.middle.append((x_img, y_img))
  83. cv2.drawMarker(self.img, (x_img, y_img), (0, 255, 0), thickness=5)
  84. def draw(self):
  85. if len(self.limits) == self.max_limits and not self.limits_drawn:
  86. for i, limit in enumerate(self.limits):
  87. cv2.line(self.img, self.limits[i-1], limit, (0, 0, 255), thickness=3)
  88. self.limits_drawn = True
  89. if len(self.middle) == self.max_middle and not self.middle_drawn:
  90. cv2.line(self.img, self.middle[0], self.middle[1], (0, 255, 0), thickness=3)
  91. self.adjusted_middle.append(self.project_point(self.limits[0], self.limits[-1], self.middle[0]))
  92. self.adjusted_middle.append(self.project_point(self.limits[1], self.limits[2], self.middle[1]))
  93. cv2.drawMarker(self.img, self.adjusted_middle[0], (255, 0, 0), thickness=5)
  94. cv2.drawMarker(self.img, self.adjusted_middle[1], (255, 0, 0), thickness=5)
  95. cv2.line(self.img, self.adjusted_middle[0], self.adjusted_middle[1], (255, 0, 0), thickness=3)
  96. self.middle_drawn = True
  97. cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
  98. if self.enough_data():
  99. self.confirm()
  100. def enough_data(self):
  101. return len(self.limits) == self.max_limits and len(self.middle) == self.max_middle
  102. def compute(self):
  103. return self.limits, self.middle
  104. def toJSON(self):
  105. return {'Limits': self.limits, 'Middle': self.middle, 'Adjusted': self.adjusted_middle}
  106. def project_point(self, v1, v2, p):
  107. e1 = (v2[0] - v1[0], v2[1] - v1[1])
  108. e2 = (p[0] - v1[0], p[1] - v1[1])
  109. dp = e1[0] * e2[0] + e1[1] * e2[1]
  110. dist = e1[0]**2 + e1[1]**2
  111. px = int(v1[0] + (dp * e1[0] / dist))
  112. py = int(v1[1] + (dp * e1[1] / dist))
  113. return px, py
  114. def help(self):
  115. print("--- Board Setup: Click on the {} corners of the board and "
  116. "then on each side of the half board separation".format(self.max_limits))
  117. print("--- Board Setup: Please start from left corner and do it in the right order ")
  118. def clear(self):
  119. self.limits = []
  120. self.limits_drawn = False
  121. self.middle = []
  122. self.middle_drawn = False
  123. self.adjusted_middle = []
  124. self.img = self.orig.copy()
  125. self.done = False
  126. def on_mouse(self, event, x, y, flags, param):
  127. if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
  128. if len(self.limits) < self.max_limits:
  129. self.add_limit(x, y)
  130. else:
  131. self.add_middle(x, y)
  132. def confirm(self):
  133. print("--- Board Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
  134. key = cv2.waitKey(0)
  135. if key == 13: # Enter
  136. print("--- Board Setup: " + str(self.compute()))
  137. self.done = True
  138. else:
  139. self.clear()
  140. self.help()
  141. class FoodColor:
  142. def __init__(self, img, scale, window_name, max_qt=0):
  143. self.colors = []
  144. self.orig = img
  145. self.img = img.copy()
  146. self.scale = scale
  147. self.max_qt = max_qt
  148. self.window_name = window_name
  149. self.done = False
  150. def add(self, x, y):
  151. x_img = int(x / self.scale)
  152. y_img = int(y / self.scale)
  153. self.colors.append(self.orig[y_img, x_img])
  154. cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
  155. def draw(self):
  156. cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
  157. if self.enough_data():
  158. self.confirm()
  159. def enough_data(self):
  160. if self.max_qt == 0:
  161. key = cv2.waitKey(10)
  162. return key == 13 # Enter
  163. else:
  164. return len(self.colors) == self.max_qt
  165. def compute(self):
  166. low_color = [255, 255, 255]
  167. high_color = [0, 0, 0]
  168. if len(self.colors) == 0:
  169. return tuple(high_color), tuple(low_color)
  170. for color in self.colors:
  171. for i, c in enumerate(color):
  172. if c < low_color[i]:
  173. low_color[i] = c
  174. if c > high_color[i]:
  175. high_color[i] = c
  176. return tuple(low_color), tuple(high_color)
  177. def toJSON(self):
  178. l, h = self.compute()
  179. l = tuple([int(x) for x in l])
  180. h = tuple([int(x) for x in h])
  181. return {'Low Food Color': l, 'High Food Color': h}
  182. def help(self):
  183. if self.max_qt == 0:
  184. print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
  185. else:
  186. print("--- Color Setup: Click {} times on food to setup food color".format(self.max_qt))
  187. def clear(self):
  188. self.colors = []
  189. self.img = self.orig.copy()
  190. self.done = False
  191. def on_mouse(self, event, x, y, flags, param):
  192. if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
  193. self.add(x, y)
  194. def confirm(self):
  195. print("--- Color Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
  196. key = cv2.waitKey(0)
  197. if key == 13: # Enter
  198. print("--- Color Setup: " + str(self.compute()))
  199. self.done = True
  200. else:
  201. self.clear()
  202. self.help()
  203. if __name__ == "__main__":
  204. main()