|
@@ -1,260 +1,283 @@
|
|
|
-import argparse
|
|
|
-import cv2
|
|
|
-import json
|
|
|
-
|
|
|
-
|
|
|
-def main():
|
|
|
-
|
|
|
- ap = argparse.ArgumentParser()
|
|
|
- ap.add_argument("-i", "--input", default='data/_MG_0207.JPG', help="input image")
|
|
|
- ap.add_argument("-s", "--scale", type=float, default=0.25, help="based on the image shapes, scale the results by this factor")
|
|
|
- ap.add_argument("-f", "--file", type=str, default="setup.json", help="name file to save setup")
|
|
|
-
|
|
|
- args = vars(ap.parse_args())
|
|
|
-
|
|
|
- img = cv2.imread(args["input"])
|
|
|
- height, width, _ = img.shape
|
|
|
- scale = args["scale"]
|
|
|
-
|
|
|
- window_name = "Setup"
|
|
|
- cv2.namedWindow(window_name)
|
|
|
- show_menu()
|
|
|
-
|
|
|
- food_color = FoodColor(img, scale, window_name)
|
|
|
- board_setup = BoardLimits(img, scale, window_name)
|
|
|
-
|
|
|
- done = False
|
|
|
- state = 0
|
|
|
- while not done:
|
|
|
- key = cv2.waitKey(10)
|
|
|
-
|
|
|
- if state == 0:
|
|
|
- if key == ord("q"):
|
|
|
- done = True
|
|
|
-
|
|
|
- elif key == ord("1"):
|
|
|
- state = 1
|
|
|
- board_setup.clear()
|
|
|
- board_setup.help()
|
|
|
- cv2.setMouseCallback(window_name, board_setup.on_mouse)
|
|
|
-
|
|
|
- elif key == ord("2"):
|
|
|
- state = 2
|
|
|
- food_color.clear()
|
|
|
- food_color.help()
|
|
|
- cv2.setMouseCallback(window_name, food_color.on_mouse)
|
|
|
-
|
|
|
- elif key == ord("s"):
|
|
|
- with open(args["file"], "w") as file:
|
|
|
- json.dump({**board_setup.toJSON(), **food_color.toJSON()}, file)
|
|
|
- done = True
|
|
|
-
|
|
|
- if state == 1:
|
|
|
- board_setup.draw()
|
|
|
- if board_setup.done:
|
|
|
- state = 0
|
|
|
- show_menu()
|
|
|
-
|
|
|
- elif state == 2:
|
|
|
- food_color.draw()
|
|
|
- if food_color.done:
|
|
|
- state = 0
|
|
|
- show_menu()
|
|
|
-
|
|
|
- else:
|
|
|
- cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
|
|
|
- cv2.setMouseCallback(window_name, null_callback)
|
|
|
-
|
|
|
-
|
|
|
-def show_menu():
|
|
|
- print("\nCommands : ")
|
|
|
- print("\tPress '1' to setup board limits")
|
|
|
- print("\tPress '2' to setup food color")
|
|
|
- print("\tPress 's' to save & quit")
|
|
|
- print("\tPress 'q' to quit without saving")
|
|
|
-
|
|
|
-
|
|
|
-def null_callback(event, x, y, flags, param):
|
|
|
- return
|
|
|
-
|
|
|
-
|
|
|
-class BoardLimits:
|
|
|
-
|
|
|
- def __init__(self, img, scale, window_name):
|
|
|
- self.limits = []
|
|
|
- self.max_limits = 4
|
|
|
- self.middle = []
|
|
|
- self.max_middle = 2
|
|
|
- self.adjusted_middle = []
|
|
|
- self.orig = img
|
|
|
- self.img = img.copy()
|
|
|
- self.scale = scale
|
|
|
- self.window_name = window_name
|
|
|
- self.done = False
|
|
|
- self.limits_drawn = False
|
|
|
- self.middle_drawn = False
|
|
|
-
|
|
|
- def add_limit(self, x, y):
|
|
|
- x_img = int(x / self.scale)
|
|
|
- y_img = int(y / self.scale)
|
|
|
- self.limits.append((x_img, y_img))
|
|
|
- cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
|
|
|
-
|
|
|
- def add_middle(self, x, y):
|
|
|
- x_img = int(x / self.scale)
|
|
|
- y_img = int(y / self.scale)
|
|
|
- self.middle.append((x_img, y_img))
|
|
|
- cv2.drawMarker(self.img, (x_img, y_img), (0, 255, 0), thickness=5)
|
|
|
-
|
|
|
- def draw(self):
|
|
|
- if len(self.limits) == self.max_limits and not self.limits_drawn:
|
|
|
- for i, limit in enumerate(self.limits):
|
|
|
- cv2.line(self.img, self.limits[i-1], limit, (0, 0, 255), thickness=3)
|
|
|
- self.limits_drawn = True
|
|
|
-
|
|
|
- if len(self.middle) == self.max_middle and not self.middle_drawn:
|
|
|
- cv2.line(self.img, self.middle[0], self.middle[1], (0, 255, 0), thickness=3)
|
|
|
-
|
|
|
- self.adjusted_middle.append(self.project_point(self.limits[0], self.limits[-1], self.middle[0]))
|
|
|
- self.adjusted_middle.append(self.project_point(self.limits[1], self.limits[2], self.middle[1]))
|
|
|
- cv2.drawMarker(self.img, self.adjusted_middle[0], (255, 0, 0), thickness=5)
|
|
|
- cv2.drawMarker(self.img, self.adjusted_middle[1], (255, 0, 0), thickness=5)
|
|
|
- cv2.line(self.img, self.adjusted_middle[0], self.adjusted_middle[1], (255, 0, 0), thickness=3)
|
|
|
-
|
|
|
- self.middle_drawn = True
|
|
|
-
|
|
|
- cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
|
|
|
-
|
|
|
- if self.enough_data():
|
|
|
- self.confirm()
|
|
|
-
|
|
|
- def enough_data(self):
|
|
|
- return len(self.limits) == self.max_limits and len(self.middle) == self.max_middle
|
|
|
-
|
|
|
- def compute(self):
|
|
|
- return self.limits, self.middle
|
|
|
-
|
|
|
- def toJSON(self):
|
|
|
- return {'Limits': self.limits, 'Middle': self.middle, 'Adjusted': self.adjusted_middle}
|
|
|
-
|
|
|
- def project_point(self, v1, v2, p):
|
|
|
- e1 = (v2[0] - v1[0], v2[1] - v1[1])
|
|
|
- e2 = (p[0] - v1[0], p[1] - v1[1])
|
|
|
- dp = e1[0] * e2[0] + e1[1] * e2[1]
|
|
|
- dist = e1[0]**2 + e1[1]**2
|
|
|
- px = int(v1[0] + (dp * e1[0] / dist))
|
|
|
- py = int(v1[1] + (dp * e1[1] / dist))
|
|
|
- return px, py
|
|
|
-
|
|
|
- def help(self):
|
|
|
- print("--- Board Setup: Click on the {} corners of the board and "
|
|
|
- "then on each side of the half board separation".format(self.max_limits))
|
|
|
- print("--- Board Setup: Please start from left corner and do it in the right order ")
|
|
|
-
|
|
|
- def clear(self):
|
|
|
- self.limits = []
|
|
|
- self.limits_drawn = False
|
|
|
- self.middle = []
|
|
|
- self.middle_drawn = False
|
|
|
- self.adjusted_middle = []
|
|
|
- self.img = self.orig.copy()
|
|
|
- self.done = False
|
|
|
-
|
|
|
- def on_mouse(self, event, x, y, flags, param):
|
|
|
- if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
|
|
|
- if len(self.limits) < self.max_limits:
|
|
|
- self.add_limit(x, y)
|
|
|
- else:
|
|
|
- self.add_middle(x, y)
|
|
|
-
|
|
|
- def confirm(self):
|
|
|
- print("--- Board Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
|
|
|
- key = cv2.waitKey(0)
|
|
|
- if key == 13: # Enter
|
|
|
- print("--- Board Setup: " + str(self.compute()))
|
|
|
- self.done = True
|
|
|
- else:
|
|
|
- self.clear()
|
|
|
- self.help()
|
|
|
-
|
|
|
-
|
|
|
-class FoodColor:
|
|
|
-
|
|
|
- def __init__(self, img, scale, window_name, max_qt=0):
|
|
|
- self.colors = []
|
|
|
- self.orig = img
|
|
|
- self.img = img.copy()
|
|
|
- self.scale = scale
|
|
|
- self.max_qt = max_qt
|
|
|
- self.window_name = window_name
|
|
|
- self.done = False
|
|
|
-
|
|
|
- def add(self, x, y):
|
|
|
- x_img = int(x / self.scale)
|
|
|
- y_img = int(y / self.scale)
|
|
|
- self.colors.append(self.orig[y_img, x_img])
|
|
|
- cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
|
|
|
-
|
|
|
- def draw(self):
|
|
|
- cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
|
|
|
- if self.enough_data():
|
|
|
- self.confirm()
|
|
|
-
|
|
|
- def enough_data(self):
|
|
|
- if self.max_qt == 0:
|
|
|
- key = cv2.waitKey(10)
|
|
|
- return key == 13 # Enter
|
|
|
- else:
|
|
|
- return len(self.colors) == self.max_qt
|
|
|
-
|
|
|
- def compute(self):
|
|
|
- low_color = [255, 255, 255]
|
|
|
- high_color = [0, 0, 0]
|
|
|
-
|
|
|
- if len(self.colors) == 0:
|
|
|
- return tuple(high_color), tuple(low_color)
|
|
|
-
|
|
|
- for color in self.colors:
|
|
|
- for i, c in enumerate(color):
|
|
|
- if c < low_color[i]:
|
|
|
- low_color[i] = c
|
|
|
-
|
|
|
- if c > high_color[i]:
|
|
|
- high_color[i] = c
|
|
|
-
|
|
|
- return tuple(low_color), tuple(high_color)
|
|
|
-
|
|
|
- def toJSON(self):
|
|
|
- l, h = self.compute()
|
|
|
- l = tuple([int(x) for x in l])
|
|
|
- h = tuple([int(x) for x in h])
|
|
|
- return {'Low Food Color': l, 'High Food Color': h}
|
|
|
-
|
|
|
- def help(self):
|
|
|
- if self.max_qt == 0:
|
|
|
- print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
|
|
|
- else:
|
|
|
- print("--- Color Setup: Click {} times on food to setup food color".format(self.max_qt))
|
|
|
-
|
|
|
- def clear(self):
|
|
|
- self.colors = []
|
|
|
- self.img = self.orig.copy()
|
|
|
- self.done = False
|
|
|
-
|
|
|
- def on_mouse(self, event, x, y, flags, param):
|
|
|
- if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
|
|
|
- self.add(x, y)
|
|
|
-
|
|
|
- def confirm(self):
|
|
|
- print("--- Color Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
|
|
|
- key = cv2.waitKey(0)
|
|
|
- if key == 13: # Enter
|
|
|
- print("--- Color Setup: " + str(self.compute()))
|
|
|
- self.done = True
|
|
|
- else:
|
|
|
- self.clear()
|
|
|
- self.help()
|
|
|
-
|
|
|
-
|
|
|
-if __name__ == "__main__":
|
|
|
+import argparse
|
|
|
+import cv2
|
|
|
+import json
|
|
|
+import datetime
|
|
|
+import time
|
|
|
+from shutil import copyfile
|
|
|
+from os.path import exists
|
|
|
+from os import makedirs
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+
|
|
|
+ ap = argparse.ArgumentParser()
|
|
|
+ ap.add_argument("-i", "--input", required=True, help="input image")
|
|
|
+ ap.add_argument("-s", "--scale", type=float, default=0.25, help="scales input image by this factor (default: x0.25)")
|
|
|
+ ap.add_argument("-c", "--config", type=str, default="config.json",
|
|
|
+ help="name file to save config (default: config.json)")
|
|
|
+
|
|
|
+ args = vars(ap.parse_args())
|
|
|
+
|
|
|
+ img = cv2.imread(args["input"])
|
|
|
+ height, width, _ = img.shape
|
|
|
+ scale = args["scale"]
|
|
|
+
|
|
|
+ window_name = "Setup"
|
|
|
+ cv2.namedWindow(window_name)
|
|
|
+ cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
|
|
|
+ cv2.setMouseCallback(window_name, null_callback)
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+ food_color = FoodColor(img, scale, window_name)
|
|
|
+ board_setup = BoardLimits(img, scale, window_name)
|
|
|
+
|
|
|
+ done = False
|
|
|
+ state = 0
|
|
|
+ setup_vars = {'Aspect Ratio': 1.0, 'Discrete Height': 100, 'Discrete Width': 100}
|
|
|
+ while not done:
|
|
|
+ cv2.waitKey(10)
|
|
|
+
|
|
|
+ if state == 0:
|
|
|
+ cv2.imshow(window_name, cv2.resize(img, (0, 0), fx=scale, fy=scale))
|
|
|
+ cv2.setMouseCallback(window_name, null_callback)
|
|
|
+
|
|
|
+ key = input("Enter command: ")
|
|
|
+
|
|
|
+ if key == "q":
|
|
|
+ done = True
|
|
|
+
|
|
|
+ elif key == "1":
|
|
|
+ state = 1
|
|
|
+ board_setup.clear()
|
|
|
+ board_setup.help()
|
|
|
+ cv2.setMouseCallback(window_name, board_setup.on_mouse)
|
|
|
+
|
|
|
+ elif key == "2":
|
|
|
+ state = 2
|
|
|
+ food_color.clear()
|
|
|
+ food_color.help()
|
|
|
+ cv2.setMouseCallback(window_name, food_color.on_mouse)
|
|
|
+
|
|
|
+ elif key == "3":
|
|
|
+ setup_vars['Aspect Ratio'] = -1
|
|
|
+ while setup_vars['Aspect Ratio'] <= 0:
|
|
|
+ try:
|
|
|
+ setup_vars['Aspect Ratio'] = float(input("Insert image height by width ratio here: "))
|
|
|
+ except ValueError:
|
|
|
+ setup_vars['Aspect Ratio'] = -1
|
|
|
+
|
|
|
+ if setup_vars['Aspect Ratio'] <= 0:
|
|
|
+ print("Insert only a floating number with dot as separator.")
|
|
|
+
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+ elif key == "4":
|
|
|
+ setup_vars['Discrete Height'] = -1
|
|
|
+ while setup_vars['Discrete Height'] <= 0:
|
|
|
+ try:
|
|
|
+ setup_vars['Discrete Height'] = int(input("Insert height discrete resolution: "))
|
|
|
+ except ValueError:
|
|
|
+ setup_vars['Discrete Height'] = -1
|
|
|
+
|
|
|
+ if setup_vars['Discrete Height'] <= 0:
|
|
|
+ print("Insert only round numbers.")
|
|
|
+
|
|
|
+ setup_vars['Discrete Width'] = -1
|
|
|
+ while setup_vars['Discrete Width'] <= 0:
|
|
|
+ try:
|
|
|
+ setup_vars['Discrete Width'] = int(input("Insert width discrete resolution: "))
|
|
|
+ except ValueError:
|
|
|
+ setup_vars['Discrete Width'] = -1
|
|
|
+
|
|
|
+ if setup_vars['Discrete Width'] <= 0:
|
|
|
+ print("Insert only round numbers.")
|
|
|
+
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+ elif key == "s":
|
|
|
+ ts = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H.%M.%S-')
|
|
|
+ if exists(args["config"]):
|
|
|
+ if not exists("bkp/"):
|
|
|
+ makedirs("bkp")
|
|
|
+ copyfile(args["config"], "bkp/" + ts + args["config"])
|
|
|
+
|
|
|
+ with open(args["config"], "w") as file:
|
|
|
+ json.dump({**setup_vars, **board_setup.toJSON(), **food_color.toJSON()}, file)
|
|
|
+
|
|
|
+ done = True
|
|
|
+
|
|
|
+ else:
|
|
|
+ print("Error: Unrecognised Command.")
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+ elif state == 1:
|
|
|
+ board_setup.draw()
|
|
|
+ if board_setup.done:
|
|
|
+ state = 0
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+ elif state == 2:
|
|
|
+ food_color.draw()
|
|
|
+ if food_color.done:
|
|
|
+ state = 0
|
|
|
+ show_menu()
|
|
|
+
|
|
|
+
|
|
|
+def show_menu():
|
|
|
+ print("\nCommands: ")
|
|
|
+ print("\tEnter '1' to setup board limits")
|
|
|
+ print("\tEnter '2' to setup food color")
|
|
|
+ print("\tEnter '3' to insert image aspect ratio")
|
|
|
+ print("\tEnter '4' to insert discrete image height and width")
|
|
|
+ print("\tEnter 's' to save & quit")
|
|
|
+ print("\tEnter 'q' to quit without saving")
|
|
|
+
|
|
|
+
|
|
|
+def null_callback(event, x, y, flags, param):
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+class BoardLimits:
|
|
|
+
|
|
|
+ def __init__(self, img, scale, window_name):
|
|
|
+ self.limits = []
|
|
|
+ self.max_limits = 4
|
|
|
+ self.orig = img
|
|
|
+ self.img = img.copy()
|
|
|
+ self.scale = scale
|
|
|
+ self.window_name = window_name
|
|
|
+ self.done = False
|
|
|
+ self.limits_drawn = False
|
|
|
+
|
|
|
+ def add_limit(self, x, y):
|
|
|
+ x_img = int(x / self.scale)
|
|
|
+ y_img = int(y / self.scale)
|
|
|
+ self.limits.append((x_img, y_img))
|
|
|
+ cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
|
|
|
+
|
|
|
+ def draw(self):
|
|
|
+ if len(self.limits) == self.max_limits and not self.limits_drawn:
|
|
|
+ for i, limit in enumerate(self.limits):
|
|
|
+ cv2.line(self.img, self.limits[i-1], limit, (0, 0, 255), thickness=3)
|
|
|
+ self.limits_drawn = True
|
|
|
+
|
|
|
+ cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
|
|
|
+
|
|
|
+ if self.enough_data():
|
|
|
+ self.confirm()
|
|
|
+
|
|
|
+ def enough_data(self):
|
|
|
+ return len(self.limits) == self.max_limits
|
|
|
+
|
|
|
+ def compute(self):
|
|
|
+ return self.limits
|
|
|
+
|
|
|
+ def toJSON(self):
|
|
|
+ return {'Limits': self.limits}
|
|
|
+
|
|
|
+ def help(self):
|
|
|
+ print("--- Board Setup: Click on the {} corners of the board".format(self.max_limits))
|
|
|
+ print("--- Board Setup: Please start from left corner and do it in the right order")
|
|
|
+
|
|
|
+ def clear(self):
|
|
|
+ self.limits = []
|
|
|
+ self.limits_drawn = False
|
|
|
+ self.img = self.orig.copy()
|
|
|
+ self.done = False
|
|
|
+
|
|
|
+ def on_mouse(self, event, x, y, flags, param):
|
|
|
+ if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
|
|
|
+ if len(self.limits) < self.max_limits:
|
|
|
+ self.add_limit(x, y)
|
|
|
+
|
|
|
+ def confirm(self):
|
|
|
+ print("--- Board Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
|
|
|
+ key = cv2.waitKey(0)
|
|
|
+ if key == 13: # Enter
|
|
|
+ print("--- Board Setup: " + str(self.compute()))
|
|
|
+ self.done = True
|
|
|
+ else:
|
|
|
+ self.clear()
|
|
|
+ self.help()
|
|
|
+
|
|
|
+
|
|
|
+class FoodColor:
|
|
|
+
|
|
|
+ def __init__(self, img, scale, window_name, max_qt=0):
|
|
|
+ self.colors = []
|
|
|
+ self.orig = img
|
|
|
+ self.img = img.copy()
|
|
|
+ self.scale = scale
|
|
|
+ self.max_qt = max_qt
|
|
|
+ self.window_name = window_name
|
|
|
+ self.done = False
|
|
|
+
|
|
|
+ def add(self, x, y):
|
|
|
+ x_img = int(x / self.scale)
|
|
|
+ y_img = int(y / self.scale)
|
|
|
+ self.colors.append(self.orig[y_img, x_img])
|
|
|
+ cv2.drawMarker(self.img, (x_img, y_img), (0, 0, 255), thickness=5)
|
|
|
+
|
|
|
+ def draw(self):
|
|
|
+ cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
|
|
|
+ if self.enough_data():
|
|
|
+ self.confirm()
|
|
|
+
|
|
|
+ def enough_data(self):
|
|
|
+ if self.max_qt == 0:
|
|
|
+ key = cv2.waitKey(10)
|
|
|
+ return key == 13 # Enter
|
|
|
+ else:
|
|
|
+ return len(self.colors) == self.max_qt
|
|
|
+
|
|
|
+ def compute(self):
|
|
|
+ low_color = [255, 255, 255]
|
|
|
+ high_color = [0, 0, 0]
|
|
|
+
|
|
|
+ if len(self.colors) == 0:
|
|
|
+ return tuple(high_color), tuple(low_color)
|
|
|
+
|
|
|
+ for color in self.colors:
|
|
|
+ for i, c in enumerate(color):
|
|
|
+ if c < low_color[i]:
|
|
|
+ low_color[i] = c
|
|
|
+
|
|
|
+ if c > high_color[i]:
|
|
|
+ high_color[i] = c
|
|
|
+
|
|
|
+ return tuple(low_color), tuple(high_color)
|
|
|
+
|
|
|
+ def toJSON(self):
|
|
|
+ l, h = self.compute()
|
|
|
+ l = tuple([int(x) for x in l])
|
|
|
+ h = tuple([int(x) for x in h])
|
|
|
+ return {'Low Food Color': l, 'High Food Color': h}
|
|
|
+
|
|
|
+ def help(self):
|
|
|
+ if self.max_qt == 0:
|
|
|
+ print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
|
|
|
+ else:
|
|
|
+ print("--- Color Setup: Click {} times on food to setup food color".format(self.max_qt))
|
|
|
+
|
|
|
+ def clear(self):
|
|
|
+ self.colors = []
|
|
|
+ self.img = self.orig.copy()
|
|
|
+ self.done = False
|
|
|
+
|
|
|
+ def on_mouse(self, event, x, y, flags, param):
|
|
|
+ if event == cv2.EVENT_LBUTTONUP and not self.enough_data() :
|
|
|
+ self.add(x, y)
|
|
|
+
|
|
|
+ def confirm(self):
|
|
|
+ print("--- Color Setup: Press enter if you're ok with data or any other key if you want to restart setup...")
|
|
|
+ key = cv2.waitKey(0)
|
|
|
+ if key == 13: # Enter
|
|
|
+ print("--- Color Setup: " + str(self.compute()))
|
|
|
+ self.done = True
|
|
|
+ else:
|
|
|
+ self.clear()
|
|
|
+ self.help()
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
main()
|