Made calibration more ergonomic. Crashes because loading empty 'empty_card' directory

This commit is contained in:
Lukas Wölfer
2020-06-12 17:31:12 +02:00
parent 6565792030
commit f4ac445f61
5 changed files with 79 additions and 29 deletions

View File

@@ -3,7 +3,7 @@ import enum
import itertools import itertools
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Set, Tuple, Union from typing import Dict, List, Optional, Set, Tuple, Union
import json
class SpecialCard(enum.Enum): class SpecialCard(enum.Enum):
"""Different types of special cards""" """Different types of special cards"""
@@ -50,6 +50,27 @@ class Position(enum.Enum):
Bunker = enum.auto() Bunker = enum.auto()
Goal = enum.auto() Goal = enum.auto()
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}
class Board: class Board:
"""Solitaire board""" """Solitaire board"""
@@ -187,3 +208,12 @@ class Board:
if count != 4: if count != 4:
return False return False
return True return True
def to_json(self) -> str:
mystruct = {
"field": [[_field_card_to_str(card) for card in row] for row in self.field],
"hua_set": self.flower_gone,
"bunker": [_bunker_card_to_str(card) for card in self.bunker],
"goal": [_goal_card_to_str(card) for card in self.goal],
}
return json.dumps(mystruct)

View File

@@ -36,7 +36,6 @@ def get_field_square_iterator(
"""Return iterator for both the square, as well as the matching card border""" """Return iterator for both the square, as well as the matching card border"""
my_adj = fake_adjustment(conf.field_adjustment) my_adj = fake_adjustment(conf.field_adjustment)
my_border_adj = fake_adjustment(conf.border_adjustment) my_border_adj = fake_adjustment(conf.border_adjustment)
squares = card_finder.get_field_squares( squares = card_finder.get_field_squares(
image, my_adj, count_x=row_count, count_y=column_count image, my_adj, count_x=row_count, count_y=column_count
) )
@@ -50,7 +49,6 @@ def get_field_square_iterator(
def match_template(template: np.ndarray, search_image: np.ndarray) -> float: def match_template(template: np.ndarray, search_image: np.ndarray) -> float:
"""Return matchiness for the template on the search image""" """Return matchiness for the template on the search image"""
res = cv2.matchTemplate(search_image, template, cv2.TM_CCOEFF_NORMED) res = cv2.matchTemplate(search_image, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
assert isinstance(max_val, (int, float)) assert isinstance(max_val, (int, float))
@@ -64,7 +62,6 @@ def parse_field_square(
(match_template(template, square), name) for template, name in conf.catalogue (match_template(template, square), name) for template, name in conf.catalogue
] ]
best_val, best_name = max(square_fits, key=lambda x: x[0]) best_val, best_name = max(square_fits, key=lambda x: x[0])
best_border = max( best_border = max(
match_template(template=template, search_image=border) match_template(template=template, search_image=border)
for template in conf.card_border for template in conf.card_border
@@ -232,6 +229,14 @@ def parse_board(image: np.ndarray, conf: Configuration) -> Board:
result.goal = parse_goal(image, conf) result.goal = parse_goal(image, conf)
return result return result
def parse_start_board(image: np.ndarray, conf: Configuration) -> Board:
result = Board()
result.field = parse_field(image, conf)
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): def field_card_to_str(card: Card):
if card == SpecialCard.Hua: if card == SpecialCard.Hua:

View File

@@ -1,43 +1,53 @@
import os
import tempfile import tempfile
import time import time
from pathlib import Path from pathlib import Path
from typing import List
import cv2 import cv2
import numpy as np import numpy as np
import os
import pyautogui import pyautogui
import shenzhen_solitaire.card_detection.configuration as configuration import shenzhen_solitaire.card_detection.configuration as configuration
import shenzhen_solitaire.clicker as clicker import shenzhen_solitaire.clicker as clicker
import shenzhen_solitaire.solver.solver as solver import shenzhen_solitaire.solver.solver as solver
from shenzhen_solitaire.card_detection.board_parser import parse_board from shenzhen_solitaire.board import Board
from shenzhen_solitaire.card_detection.board_parser import parse_start_board
from shenzhen_solitaire.solver.board_actions import Action
OFFSET = (0, 0) OFFSET = (0, 0)
SIZE = (2560, 1440) # SIZE = (2560, 1440)
SIZE = (1366, 768)
NEW_BUTTON = (1900, 1100) NEW_BUTTON = (1900, 1100)
SAVE_UNSOLVED = False SAVE_UNSOLVED = False
UNSOLVED_DIR = "E:/shenzhen-solitaire/unsolved"
def solve() -> None:
with tempfile.TemporaryDirectory() as screenshot_dir: def extern_solve(board: Board) -> List[Action]:
pass
def solve(conf: configuration.Configuration) -> None:
with tempfile.TemporaryDirectory(prefix="shenzhen_solitaire") as screenshot_dir:
print("Taking screenshot") print("Taking screenshot")
screenshot_file = Path(screenshot_dir) / "screenshot.png" screenshot_file = Path(screenshot_dir) / "screenshot.png"
screenshot = pyautogui.screenshot(region=(*OFFSET, *SIZE)) screenshot = pyautogui.screenshot(region=(*OFFSET, *SIZE))
screenshot.save(screenshot_file) screenshot.save(screenshot_file)
image = cv2.imread(str(screenshot_file)) image = cv2.imread(str(screenshot_file))
input()
print("Solving") print("Solving")
conf = configuration.load("test_config.zip") board = parse_start_board(image, conf)
board = parse_board(image, conf) print(board.to_json())
assert board.check_correct() assert board.check_correct()
input()
solution_iterator = next(solver.solve(board, timeout=10, verbose=True), None) solution_iterator = next(solver.solve(board, timeout=10, verbose=True), None)
if solution_iterator is None: if solution_iterator is None:
clicker.click(NEW_BUTTON, OFFSET) clicker.click(NEW_BUTTON, OFFSET)
time.sleep(10) time.sleep(10)
if SAVE_UNSOLVED: if SAVE_UNSOLVED:
fd, outfile = tempfile.mkstemp( fd, outfile = tempfile.mkstemp(dir=UNSOLVED_DIR, suffix=".png")
dir="E:/shenzhen-solitaire/unsolved", suffix=".png"
)
sock = os.fdopen(fd, "w") sock = os.fdopen(fd, "w")
sock.close() sock.close()
cv2.imwrite(outfile, image) cv2.imwrite(outfile, image)
@@ -53,8 +63,9 @@ def solve() -> None:
def main() -> None: def main() -> None:
time.sleep(3) time.sleep(3)
conf = configuration.load("test_config.zip")
while True: while True:
solve() solve(conf)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -49,25 +49,30 @@ def main() -> None:
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
) )
print("Field borders") print("Field borders")
border_adjustment = adjustment.adjust_squares( conf.border_adjustment = adjustment.adjust_squares(
image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment) image, count_x=8, count_y=13, adjustment=copy.deepcopy(conf.field_adjustment)
) )
conf.bunker_adjustment.w = conf.field_adjustment.w for adj in (conf.bunker_adjustment, conf.goal_adjustment,conf.hua_adjustment):
conf.bunker_adjustment.h = conf.field_adjustment.h adj.w = conf.field_adjustment.w
adj.h = conf.field_adjustment.h
adj.dx = conf.field_adjustment.dx
adj.dy = conf.field_adjustment.dy
conf.bunker_adjustment.x = conf.field_adjustment.x
print("Bunker cards") print("Bunker cards")
bunker_adjustment = adjustment.adjust_squares( conf.bunker_adjustment = adjustment.adjust_squares(
image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment) image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.bunker_adjustment)
) )
conf.goal_adjustment.w = conf.field_adjustment.w
conf.goal_adjustment.h = conf.field_adjustment.h conf.goal_adjustment.x = conf.field_adjustment.x + 5 * conf.field_adjustment.dx
conf.goal_adjustment.y = conf.bunker_adjustment.y
print("Goal cards") print("Goal cards")
goal_adjustment = adjustment.adjust_squares( conf.goal_adjustment = adjustment.adjust_squares(
image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment) image, count_x=3, count_y=1, adjustment=copy.deepcopy(conf.goal_adjustment)
) )
conf.hua_adjustment.w = conf.field_adjustment.w conf.hua_adjustment.y = conf.bunker_adjustment.y
conf.hua_adjustment.h = conf.field_adjustment.h
print("Hua card") print("Hua card")
hua_adjustment = adjustment.adjust_squares( conf.hua_adjustment = adjustment.adjust_squares(
image, count_x=1, count_y=1, adjustment=copy.deepcopy(conf.hua_adjustment) image, count_x=1, count_y=1, adjustment=copy.deepcopy(conf.hua_adjustment)
) )

View File

@@ -21,7 +21,7 @@ def main() -> None:
help="Path to the screenshot", help="Path to the screenshot",
) )
parser.add_argument( parser.add_argument(
"--conf", "--config",
dest="config_path", dest="config_path",
type=str, type=str,
default="config.zip", default="config.zip",
@@ -29,9 +29,8 @@ def main() -> None:
) )
args = parser.parse_args() args = parser.parse_args()
print(args.screenshot_path)
image = cv2.imread(args.screenshot_path) image = cv2.imread(args.screenshot_path)
conf = configuration.load(args.config) conf = configuration.load(args.config_path)
squares = card_finder.get_field_squares(image, conf.field_adjustment, 5, 8) squares = card_finder.get_field_squares(image, conf.field_adjustment, 5, 8)
catalogue = card_finder.catalogue_cards(squares) catalogue = card_finder.catalogue_cards(squares)
conf.card_border.extend( conf.card_border.extend(