diff --git a/16_10_conf.zip b/16_10_conf.zip new file mode 100644 index 0000000..3c454ba Binary files /dev/null and b/16_10_conf.zip differ diff --git a/shenzhen_solitaire/card_detection/adjustment.py b/shenzhen_solitaire/card_detection/adjustment.py index 0da8fac..7991cd0 100644 --- a/shenzhen_solitaire/card_detection/adjustment.py +++ b/shenzhen_solitaire/card_detection/adjustment.py @@ -6,6 +6,7 @@ from typing import Optional, Tuple import cv2 import numpy +import math @dataclass @@ -16,8 +17,8 @@ class Adjustment: y: int = 0 w: int = 0 h: int = 0 - dx: int = 0 - dy: int = 0 + dx: float = 0 + dy: float = 0 def get_square( @@ -25,10 +26,10 @@ def get_square( ) -> Tuple[int, int, int, int]: """Get one square from index and adjustment""" return ( - adjustment.x + adjustment.dx * index_x, - adjustment.y + adjustment.dy * index_y, - adjustment.x + adjustment.w + adjustment.dx * index_x, - adjustment.y + adjustment.h + adjustment.dy * index_y, + math.floor(adjustment.x + adjustment.dx * index_x), + math.floor(adjustment.y + adjustment.dy * index_y), + math.floor(adjustment.x + adjustment.w + adjustment.dx * index_x), + math.floor(adjustment.y + adjustment.h + adjustment.dy * index_y), ) @@ -41,9 +42,10 @@ def adjust_squares( if not adjustment: adjustment = Adjustment(w=10, h=10) - high_speed = False + speed_mod = "n" + speed_mods = ["n", "s", "h"] - def _adjustment_step(keycode: int, high_speed: bool) -> None: + def _adjustment_step(keycode: int, speed_mod: str) -> None: assert adjustment is not None x_keys = {104: -1, 115: +1} y_keys = {116: -1, 110: +1} @@ -51,8 +53,8 @@ def adjust_squares( h_keys = {111: -1, 101: +1} dx_keys = {59: -1, 112: +1} dy_keys = {44: -1, 46: +1} - high_speed_fac = 10 - cur_high_speed_fac = high_speed_fac if high_speed else 1 + speed_facs = {"n": 1, "s": 8, "h": 64} + cur_high_speed_fac = speed_facs[speed_mod] if keycode in x_keys: adjustment.x += x_keys[keycode] * cur_high_speed_fac elif keycode in y_keys: @@ -62,9 +64,10 @@ def adjust_squares( elif keycode in h_keys: adjustment.h += h_keys[keycode] * cur_high_speed_fac elif keycode in dx_keys: - adjustment.dx += dx_keys[keycode] * cur_high_speed_fac + adjustment.dx += dx_keys[keycode] * cur_high_speed_fac * 1 / 8 elif keycode in dy_keys: - adjustment.dy += dy_keys[keycode] * cur_high_speed_fac + adjustment.dy += dy_keys[keycode] * cur_high_speed_fac * 1 / 8 + cv2.namedWindow("Window", flags=cv2.WINDOW_NORMAL) while True: @@ -72,7 +75,10 @@ def adjust_squares( for index_x, index_y in itertools.product(range(count_x), range(count_y)): square = get_square(adjustment, index_x, index_y) cv2.rectangle( - working_image, (square[0], square[1]), (square[2], square[3]), (0, 0, 0) + working_image, + (math.floor(square[0]), math.floor(square[1])), + (math.floor(square[2]), math.floor(square[3])), + (0, 0, 0), ) cv2.imshow("Window", working_image) keycode = cv2.waitKey(0) @@ -80,29 +86,9 @@ def adjust_squares( if keycode == 27: break if keycode == 229: - high_speed = not high_speed + speed_mod = speed_mods[(speed_mods.index(speed_mod) + 1) % len(speed_mods)] continue - _adjustment_step(keycode, high_speed) + _adjustment_step(keycode, speed_mod) cv2.destroyWindow("Window") return adjustment - - -def adjust_field(image: numpy.ndarray) -> Adjustment: - """Open configuration grid for the field""" - return adjust_squares(image, 8, 13, Adjustment(42, 226, 15, 15, 119, 24)) - - -def adjust_bunker(image: numpy.ndarray) -> Adjustment: - """Open configuration grid for the bunker""" - return adjust_squares(image, 3, 1) - - -def adjust_hua(image: numpy.ndarray) -> Adjustment: - """Open configuration grid for the flower card""" - return adjust_squares(image, 1, 1) - - -def adjust_goal(image: numpy.ndarray) -> Adjustment: - """Open configuration grid for the goal""" - return adjust_squares(image, 3, 1) diff --git a/shenzhen_solitaire/card_detection/board_parser.py b/shenzhen_solitaire/card_detection/board_parser.py index 6efe58f..c92374d 100644 --- a/shenzhen_solitaire/card_detection/board_parser.py +++ b/shenzhen_solitaire/card_detection/board_parser.py @@ -235,42 +235,4 @@ def parse_start_board(image: np.ndarray, conf: Configuration) -> Board: result.flower_gone = parse_hua(image, conf) result.bunker = [None] * 3 result.goal = parse_goal(image, conf) - return result - - -def field_card_to_str(card: Card): - if card == SpecialCard.Hua: - return "Hua" - if isinstance(card, SpecialCard): - return {"Special": card.name} - elif isinstance(card, NumberCard): - return {"Number": {"value": card.number, "suit": card.suit.name}} - - -def bunker_card_to_str(card: Union[Tuple[SpecialCard, int], Optional[Card]]): - if card is None: - return "Empty" - if isinstance(card, tuple): - return {"Blocked": card[0].name} - return {"Stashed": field_card_to_str(card)} - - -def goal_card_to_str(card: Optional[NumberCard]): - if card is None: - return None - return {"value": card.number, "suit": card.suit.name} - - -def parse_to_json(image: np.ndarray, conf: Configuration) -> str: - field = parse_field(image, conf) - flower_gone = parse_hua(image, conf) - bunker = parse_bunker(image, conf) - goal = parse_goal(image, conf) - - mystruct = { - "field": [[field_card_to_str(card) for card in row] for row in field], - "hua_set": flower_gone, - "bunker": [bunker_card_to_str(card) for card in bunker], - "goal": [goal_card_to_str(card) for card in goal], - } - return json.dumps(mystruct) + return result \ No newline at end of file diff --git a/shenzhen_solitaire/card_detection/card_finder.py b/shenzhen_solitaire/card_detection/card_finder.py index 99b9fb3..e705577 100644 --- a/shenzhen_solitaire/card_detection/card_finder.py +++ b/shenzhen_solitaire/card_detection/card_finder.py @@ -32,7 +32,6 @@ def get_field_squares( def catalogue_cards(squares: List[np.ndarray]) -> List[Tuple[np.ndarray, Card]]: """Run manual cataloging for given squares""" cv2.namedWindow("Catalogue", cv2.WINDOW_NORMAL) - cv2.waitKey(1) result: List[Tuple[np.ndarray, Card]] = [] print("Card ID is [B]ai, [Z]hong, [F]a, [H]ua, [R]ed, [G]reen, [B]lack") print("Numbercard e.g. R3") @@ -51,7 +50,7 @@ def catalogue_cards(squares: List[np.ndarray]) -> List[Tuple[np.ndarray, Card]]: for square in squares: while True: cv2.imshow("Catalogue", cv2.resize(square, (500, 500))) - cv2.waitKey(1) + cv2.waitKey(100) card_id = input("Card ID:").lower() card_type: Optional[Card] = None if len(card_id) == 1: diff --git a/tools/feature_extraction.py b/tools/feature_extraction.py index 224dcf0..d7e9654 100644 --- a/tools/feature_extraction.py +++ b/tools/feature_extraction.py @@ -26,9 +26,7 @@ def prepare_image(image: np.array) -> np.array: def get_contour(image: np.array) -> np.array: gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - ret, edge_image = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY_INV) - kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)) - edge_image = cv2.morphologyEx(edge_image, cv2.MORPH_CLOSE, kernel) + ret, edge_image = cv2.threshold(gray_image, 140, 255, cv2.THRESH_BINARY_INV) border_image(edge_image, size=1) contours, hierarchy = cv2.findContours( edge_image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE @@ -123,7 +121,7 @@ def main() -> None: pc = configuration.load("test_config.zip") laptop = configuration.load("laptop_conf.zip") bla = [(i, t) for i, t in pc.catalogue if t == SpecialCard.Hua] - bla = pc.catalogue + # bla = pc.catalogue for pc_image, pc_card_type in bla: debug_match(pc_image, pc_card_type, laptop.catalogue) diff --git a/tools/generate/all_borders.py b/tools/generate/all_borders.py index f9d7fe7..d711356 100644 --- a/tools/generate/all_borders.py +++ b/tools/generate/all_borders.py @@ -2,6 +2,7 @@ import argparse import copy import dataclasses import json +import math import os import cv2 @@ -48,28 +49,31 @@ def main() -> None: conf.field_adjustment = adjustment.adjust_squares( image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) ) - print("Field borders") - conf.border_adjustment = adjustment.adjust_squares( - image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) - ) - for adj in (conf.bunker_adjustment, conf.goal_adjustment,conf.hua_adjustment): + for adj in ( + conf.bunker_adjustment, + conf.goal_adjustment, + conf.hua_adjustment, + conf.border_adjustment, + ): adj.w = conf.field_adjustment.w adj.h = conf.field_adjustment.h adj.dx = conf.field_adjustment.dx adj.dy = conf.field_adjustment.dy + print("Field borders") + conf.border_adjustment = adjustment.adjust_squares( + image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.border_adjustment) + ) conf.bunker_adjustment.x = conf.field_adjustment.x - print("Bunker cards") + print("Bunker and goal cards") conf.bunker_adjustment = adjustment.adjust_squares( - image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment) + image, count_x=8, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment) + ) + conf.goal_adjustment = copy.deepcopy(conf.bunker_adjustment) + conf.goal_adjustment.x = math.floor( + conf.bunker_adjustment.x + conf.bunker_adjustment.dx * 5 ) - conf.goal_adjustment.x = conf.field_adjustment.x + 5 * conf.field_adjustment.dx - conf.goal_adjustment.y = conf.bunker_adjustment.y - print("Goal cards") - conf.goal_adjustment = adjustment.adjust_squares( - image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment) - ) conf.hua_adjustment.y = conf.bunker_adjustment.y print("Hua card") conf.hua_adjustment = adjustment.adjust_squares( @@ -77,7 +81,10 @@ def main() -> None: ) print("Special button") conf.special_button_adjustment = adjustment.adjust_squares( - image, count_x=1, count_y=3, adjustment=copy.deepcopy(conf.special_button_adjustment) + image, + count_x=1, + count_y=3, + adjustment=copy.deepcopy(conf.special_button_adjustment), ) configuration.save(conf, args.config) diff --git a/tools/generate/catalogue.py b/tools/generate/catalogue.py index b93079e..74c5d97 100644 --- a/tools/generate/catalogue.py +++ b/tools/generate/catalogue.py @@ -1,9 +1,12 @@ import argparse +import copy +import math import cv2 import numpy as np -import copy -from shenzhen_solitaire.card_detection import configuration, adjustment, card_finder + +from shenzhen_solitaire.card_detection import (adjustment, card_finder, + configuration) from shenzhen_solitaire.card_detection.configuration import Configuration @@ -41,7 +44,7 @@ def main() -> None: ) empty_adjust = copy.deepcopy(conf.border_adjustment) - empty_adjust.y = empty_adjust.y + 4 * empty_adjust.dy + empty_adjust.y = math.floor(empty_adjust.y + 4 * empty_adjust.dy) conf.empty_card.extend(card_finder.get_field_squares(image, empty_adjust, 1, 1)) conf.green_card.extend( diff --git a/tools/to_json.py b/tools/to_json.py index 97c786d..fc49608 100644 --- a/tools/to_json.py +++ b/tools/to_json.py @@ -4,7 +4,7 @@ import sys import cv2 import shenzhen_solitaire.card_detection.configuration as configuration -from shenzhen_solitaire.card_detection.board_parser import parse_to_json +from shenzhen_solitaire.card_detection.board_parser import parse_board, parse_start_board def main() -> None: @@ -13,13 +13,17 @@ def main() -> None: parser.add_argument( "--config", dest="config_path", type=str, help="Config path", ) + parser.add_argument("--simple", action="store_true", help="Parse a start board, use when config is not complete") args = parser.parse_args() image = cv2.imread(args.board_path) - conf = configuration.load("test_config.zip") + conf = configuration.load(args.config_path) - print(parse_to_json(image, conf)) + if args.simple: + print(parse_start_board(image, conf).to_json()) + else: + print(parse_board(image, conf).to_json()) if __name__ == "__main__":