Files
my-botty/src/template_finder.py
T
2021-12-08 08:59:24 +01:00

203 lines
8.7 KiB
Python

import cv2
from screen import Screen
from typing import Tuple, Union, List
from dataclasses import dataclass
import numpy as np
from logger import Logger
import time
import os
from config import Config
from utils.misc import load_template, list_files_in_folder, alpha_to_mask
@dataclass
class TemplateMatch:
name: str = None
score: float = -1.0
position: Tuple[float, float] = None
valid: bool = False
class TemplateFinder:
"""
Loads images from assets/templates and assets/npc and provides search functions
to find these assets within another image
"""
def __init__(self, screen: Screen):
self._screen = screen
self._config = Config()
self.last_res = None
# load templates with their filename as key in the dict
template_path = "assets\\templates"
npc_path = "assets\\npc"
pathes = list_files_in_folder(npc_path) + list_files_in_folder(template_path)
self._templates = {}
for file_path in pathes:
file_name: str = os.path.basename(file_path)
if file_name.endswith('.png'):
key = file_name[:-4].upper()
template_img = load_template(file_path, 1.0, True)
mask = alpha_to_mask(template_img)
self._templates[key] = [
cv2.cvtColor(template_img,cv2.COLOR_BGRA2BGR),
cv2.cvtColor(template_img,cv2.COLOR_BGRA2GRAY),
1.0,
mask
]
def get_template(self, key):
return cv2.cvtColor(self._templates[key][0], cv2.COLOR_BGRA2BGR)
def search(
self,
ref: Union[str, np.ndarray, List[str]],
inp_img: np.ndarray,
threshold: float = 0.68,
roi: List[float] = None,
normalize_monitor: bool = False,
best_match: bool = False,
use_grayscale: bool = False,
) -> TemplateMatch:
"""
Search for a template in an image
:param ref: Either key of a already loaded template, list of such keys, or a image which is used as template
:param inp_img: Image in which the template will be searched
:param threshold: Threshold which determines if a template is found or not
:param roi: Region of Interest of the inp_img to restrict search area. Format [left, top, width, height]
:param normalize_monitor: If True will return positions in monitor coordinates. Otherwise in coordinates of the input image.
:param best_match: If list input, will search for list of templates by best match. Default behavior is first match.
:param use_grayscale: Use grayscale template matching for speed up
:return: Returns a TempalteMatch object with a valid flag
"""
if roi is None:
# if no roi is provided roi = full inp_img
roi = [0, 0, inp_img.shape[1], inp_img.shape[0]]
rx, ry, rw, rh = roi
inp_img = inp_img[ry:ry + rh, rx:rx + rw]
if type(ref) == str:
templates = [self._templates[ref][0]]
templates_gray = [self._templates[ref][1]]
scales = [self._templates[ref][2]]
masks = [self._templates[ref][3]]
names = [ref]
best_match = False
elif type(ref) == list:
templates = [self._templates[i][0] for i in ref]
templates_gray = [self._templates[i][1] for i in ref]
scales = [self._templates[i][2] for i in ref]
masks = [self._templates[i][3] for i in ref]
names = ref
else:
templates = [ref]
templates_gray = [cv2.cvtColor(ref, cv2.COLOR_BGRA2GRAY)]
scales = [1.0]
masks = [None]
best_match = False
scores = [0] * len(ref)
ref_points = [(0, 0)] * len(ref)
for count, template in enumerate(templates):
template_match = TemplateMatch()
scale = scales[count]
mask = masks[count]
img: np.ndarray = cv2.resize(inp_img, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
rx *= scale
ry *= scale
rw *= scale
rh *= scale
if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]:
if use_grayscale:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
template = templates_gray[count]
self.last_res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED, mask=mask)
np.nan_to_num(self.last_res, copy=False, nan=0.0, posinf=0.0, neginf=0.0)
_, max_val, _, max_pos = cv2.minMaxLoc(self.last_res)
if max_val > threshold:
ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry)
ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale)))
if normalize_monitor:
ref_point = self._screen.convert_screen_to_monitor(ref_point)
if best_match:
scores[count]=max_val
ref_points[count]=ref_point
else:
try: template_match.name = names[count]
except: pass
template_match.position = ref_point
template_match.score = max_val
template_match.valid = True
return template_match
if max(scores) > 0:
idx=scores.index(max(scores))
try: template_match.name = names[idx]
except: pass
template_match.position = ref_points[idx]
template_match.score = scores[idx]
template_match.valid = True
return template_match
def search_and_wait(
self,
ref: Union[str, List[str]],
roi: List[float] = None,
time_out: float = None,
threshold: float = 0.68,
best_match: bool = False,
take_ss: bool = True,
use_grayscale: bool = False
) -> TemplateMatch:
"""
Helper function that will loop and keep searching for a template
:param time_out: After this amount of time the search will stop and it will return [False, None]
:param take_ss: Bool value to take screenshot on timeout or not (flag must still be set in params!)
Other params are the same as for TemplateFinder.search()
"""
if type(ref) is str:
ref = [ref]
Logger.debug(f"Waiting for Template {ref}")
start = time.time()
while 1:
img = self._screen.grab()
template_match = self.search(ref, img, roi=roi, threshold=threshold, best_match=best_match, use_grayscale=use_grayscale)
is_loading_black_roi = np.average(img[:, 0:self._config.ui_roi["loading_left_black"][2]]) < 1.0
if not is_loading_black_roi or "LOADING" in ref:
if template_match.valid:
Logger.debug(f"Found Match: {template_match.name} ({template_match.score*100:.1f}% confidence)")
return template_match
if time_out is not None and (time.time() - start) > time_out:
if self._config.general["info_screenshots"] and take_ss:
cv2.imwrite(f"./info_screenshots/info_wait_for_{ref}_time_out_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
Logger.debug(f"Could not find any of the above templates")
return template_match
# Testing: Have whatever you want to find on the screen
if __name__ == "__main__":
from screen import Screen
from config import Config
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
search_templates = ["A3_TOWN_1"]
while 1:
# img = cv2.imread("")
img = screen.grab()
display_img = img.copy()
start = time.time()
for key in search_templates:
template_match = template_finder.search(key, img, best_match=True, threshold=0.35, use_grayscale=True)
if template_match.valid:
cv2.putText(display_img, str(template_match.name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
cv2.circle(display_img, template_match.position, 7, (255, 0, 0), thickness=5)
print(f"Name: {template_match.name} Pos: {template_match.position}, Score: {template_match.score}")
# print(time.time() - start)
display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST)
cv2.imshow('test', display_img)
key = cv2.waitKey(1)