|
@@ -1,370 +0,0 @@
|
|
|
-import argparse
|
|
|
-import cv2
|
|
|
-import json
|
|
|
-import datetime
|
|
|
-import time
|
|
|
-import numpy as np
|
|
|
-from shutil import copyfile
|
|
|
-from os.path import exists
|
|
|
-from os import makedirs
|
|
|
-from math import sqrt
|
|
|
-
|
|
|
-
|
|
|
-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)
|
|
|
- food_limits = FoodLimits(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 == "5":
|
|
|
- state = 3
|
|
|
- food_limits.clear()
|
|
|
- food_limits.help()
|
|
|
- cv2.setMouseCallback(window_name, food_limits.on_mouse)
|
|
|
-
|
|
|
-
|
|
|
- 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(), **food_limits.toJSON()},
|
|
|
- file, indent=4, sort_keys=True)
|
|
|
-
|
|
|
- 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()
|
|
|
-
|
|
|
- elif state == 3:
|
|
|
- food_limits.draw()
|
|
|
- if food_limits.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 '5' to setup food limits")
|
|
|
- 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 FoodLimits:
|
|
|
-
|
|
|
- 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
|
|
|
- self.min_dist = 5
|
|
|
-
|
|
|
- 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):
|
|
|
- # TODO Improve with warp perspective...
|
|
|
- self.min_dist = self.img.shape[0]
|
|
|
- for i, (x,y) in enumerate(self.limits):
|
|
|
- dist = sqrt((x - self.limits[i-1][0])**2 + (y - self.limits[i-1][1])**2)
|
|
|
- self.min_dist = dist if dist < self.min_dist else self.min_dist
|
|
|
- return self.min_dist
|
|
|
-
|
|
|
- def toJSON(self):
|
|
|
- return {'Min Food Size': self.min_dist}
|
|
|
-
|
|
|
- def help(self):
|
|
|
- print("--- Food Setup: Click on the {} corners of the tiniest food".format(self.max_limits))
|
|
|
-
|
|
|
- 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("--- Food 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("--- Food Setup: " + str(self.compute()))
|
|
|
- self.done = True
|
|
|
- else:
|
|
|
- self.clear()
|
|
|
- self.help()
|
|
|
-
|
|
|
-
|
|
|
-class FoodColor:
|
|
|
-
|
|
|
- def __init__(self, img, scale, window_name):
|
|
|
- self.colors = []
|
|
|
- self.orig = img
|
|
|
- self.img = img.copy()
|
|
|
- self.scale = scale
|
|
|
- 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])
|
|
|
- self.show_selected()
|
|
|
-
|
|
|
- def show_selected(self):
|
|
|
- if len(self.colors) >= 2:
|
|
|
- low, high = self.compute()
|
|
|
- mask = cv2.inRange(self.img, np.array(low, dtype=np.uint8), np.array(high, dtype=np.uint8))
|
|
|
- maskrgb = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
|
|
|
-
|
|
|
- selected = np.zeros(self.img.shape, dtype=np.uint8)
|
|
|
- selected[:, :, 2] = mask
|
|
|
-
|
|
|
- self.img = cv2.add(cv2.subtract(self.img, maskrgb), selected)
|
|
|
-
|
|
|
- def draw(self):
|
|
|
- cv2.imshow(self.window_name, cv2.resize(self.img, (0, 0), fx=self.scale, fy=self.scale))
|
|
|
- self.confirm()
|
|
|
-
|
|
|
- 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):
|
|
|
- print("--- Color Setup: Click several times on foods to setup food color and then press enter.")
|
|
|
-
|
|
|
- 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:
|
|
|
- self.add(x, y)
|
|
|
-
|
|
|
- def confirm(self):
|
|
|
- key = cv2.waitKey(10)
|
|
|
- if key == 13: # Enter
|
|
|
- print("--- Color Setup: " + str(self.compute()))
|
|
|
- self.done = True
|
|
|
- elif key == 0 and len(self.colors) > 0: # Delete
|
|
|
- del self.colors[len(self.colors)-1]
|
|
|
- self.img = self.orig.copy()
|
|
|
- self.show_selected()
|
|
|
- print("Last color removed. {} remaining(s).".format(len(self.colors)))
|
|
|
-
|
|
|
-
|
|
|
-if __name__ == "__main__":
|
|
|
- main()
|