import cv2 import numpy as np from PIL import ImageGrab class Scanner: def __init__(self, config): self.config = config self.template = cv2.imread(config['template_path']) if self.template is None: raise FileNotFoundError(f"Template not found: {config['template_path']}") self.th, self.tw = self.template.shape[:2] self.last_score = 0.0 self.last_box = None def find(self, screen): if self.config.get('search_mode') == 'purple_frame': return self.find_purple_frame(screen) screen_h, screen_w = screen.shape[:2] if self.tw > screen_w or self.th > screen_h: self.last_score = 0.0 self.last_box = None return None res = cv2.matchTemplate(screen, self.template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(res) self.last_score = max_val if max_val >= self.config['confidence']: left, top = max_loc self.last_box = (left, top, self.tw, self.th) cx = left + self.tw // 2 cy = top + self.th // 2 return (cx, cy) self.last_box = None return None def find_purple_frame(self, screen): """Find the rectangular purple highlight, ignoring everything inside it.""" hsv = cv2.cvtColor(screen, cv2.COLOR_BGR2HSV) lower = np.array( self.config.get('frame_hsv_lower', [125, 60, 120]), dtype=np.uint8, ) upper = np.array( self.config.get('frame_hsv_upper', [150, 255, 255]), dtype=np.uint8, ) mask = cv2.inRange(hsv, lower, upper) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours( mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE, ) min_size = int(self.config.get('frame_min_size', 40)) min_rectangularity = float( self.config.get('frame_min_rectangularity', 0.80) ) candidates = [] for contour in contours: x, y, w, h = cv2.boundingRect(contour) if w < min_size or h < min_size: continue rectangularity = cv2.contourArea(contour) / float(w * h) if rectangularity < min_rectangularity: continue candidates.append((w * h, rectangularity, x, y, w, h)) if not candidates: self.last_score = 0.0 self.last_box = None return None _, rectangularity, x, y, w, h = max(candidates) self.last_score = rectangularity self.last_box = (x, y, w, h) return (x + w // 2, y + h // 2) def grab_screen(self): img = ImageGrab.grab() return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)