From 37e29838c53f44d83a8b48da7b37b3fb855dfb9c Mon Sep 17 00:00:00 2001 From: LmeSzinc <37934724+LmeSzinc@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:29:22 +0800 Subject: [PATCH] Refactor: Get in-game language from plane name --- assets/share/base/main_page/OCR_MAP_NAME.png | Bin 0 -> 18036 bytes module/ocr/keyword.py | 16 +- module/ocr/ocr.py | 197 +++++++++++++------ module/ui/draggable_list.py | 2 +- tasks/base/assets/assets_base_main_page.py | 15 ++ tasks/base/main_page.py | 80 ++++++++ tasks/base/ui.py | 5 +- tasks/forgotten_hall/ui.py | 18 +- 8 files changed, 257 insertions(+), 76 deletions(-) create mode 100644 assets/share/base/main_page/OCR_MAP_NAME.png create mode 100644 tasks/base/assets/assets_base_main_page.py create mode 100644 tasks/base/main_page.py diff --git a/assets/share/base/main_page/OCR_MAP_NAME.png b/assets/share/base/main_page/OCR_MAP_NAME.png new file mode 100644 index 0000000000000000000000000000000000000000..da91953cf93cb960f332b8e52ff2e6e9f36f65e9 GIT binary patch literal 18036 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 literal 0 HcmV?d00001 diff --git a/module/ocr/keyword.py b/module/ocr/keyword.py index f77a8f612..3f106ebee 100644 --- a/module/ocr/keyword.py +++ b/module/ocr/keyword.py @@ -61,9 +61,12 @@ class Keyword: def __bool__(self): return True - def _keywords_to_find(self, in_current_server=False, ignore_punctuation=True): - if in_current_server: - match server.lang: + def _keywords_to_find(self, lang: str = None, ignore_punctuation=True): + if lang is None: + lang = server.lang + + if lang in server.VALID_LANG: + match lang: case 'cn': if ignore_punctuation: return [self.cn_parsed] @@ -122,11 +125,12 @@ class Keyword: return name == keyword @classmethod - def find(cls, name, in_current_server=False, ignore_punctuation=True): + def find(cls, name, lang: str = None, ignore_punctuation=True): """ Args: name: Name in any server or instance id. - in_current_server: True to search the names from current server only. + lang: Lang to find from + None to search the names from current server only. ignore_punctuation: True to remove punctuations and turn into lowercase before searching. Returns: @@ -157,7 +161,7 @@ class Keyword: instance: Keyword for instance in cls.instances.values(): for keyword in instance._keywords_to_find( - in_current_server=in_current_server, ignore_punctuation=ignore_punctuation): + lang=lang, ignore_punctuation=ignore_punctuation): if cls._compare(name, keyword): return instance diff --git a/module/ocr/ocr.py b/module/ocr/ocr.py index 485ad6317..7f065b0f6 100644 --- a/module/ocr/ocr.py +++ b/module/ocr/ocr.py @@ -1,8 +1,9 @@ import re import time from datetime import timedelta +from typing import Optional -import cv2 +import numpy as np from pponnxcr.predict_system import BoxedResult import module.config.server as server @@ -11,70 +12,33 @@ from module.base.decorator import cached_property from module.base.utils import area_pad, corner2area, crop, float2str from module.exception import ScriptError from module.logger import logger +from module.ocr.keyword import Keyword from module.ocr.models import OCR_MODEL, TextSystem from module.ocr.utils import merge_buttons -def enlarge_canvas(image): - """ - Enlarge image into a square fill with black background. In the structure of PaddleOCR, - image with w:h=1:1 is the best while 3:1 rectangles takes three times as long. - Also enlarge into the integer multiple of 32 cause PaddleOCR will downscale images to 1/32. - - No longer needed, already included in pponnxcr. - """ - height, width = image.shape[:2] - length = int(max(width, height) // 32 * 32 + 32) - border = (0, length - height, 0, length - width) - if sum(border) > 0: - image = cv2.copyMakeBorder(image, *border, borderType=cv2.BORDER_CONSTANT, value=(0, 0, 0)) - return image - - class OcrResultButton: - def __init__(self, boxed_result: BoxedResult, keyword_classes: list): + def __init__(self, boxed_result: BoxedResult, matched_keyword: Optional[Keyword]): """ Args: boxed_result: BoxedResult from ppocr-onnx - keyword_classes: List of Keyword classes + matched_keyword: Keyword object or None """ self.area = boxed_result.box self.search = area_pad(self.area, pad=-20) # self.color = self.button = boxed_result.box - try: - self.matched_keyword = self.match_keyword(boxed_result.ocr_text, keyword_classes) - self.name = str(self.matched_keyword) - except ScriptError: + if matched_keyword is not None: + self.matched_keyword = matched_keyword + self.name = str(matched_keyword) + else: self.matched_keyword = None self.name = boxed_result.ocr_text self.text = boxed_result.ocr_text self.score = boxed_result.score - @staticmethod - def match_keyword(ocr_text, keyword_classes): - """ - Args: - ocr_text (str): - keyword_classes: List of Keyword classes - - Returns: - Keyword: - - Raises: - ScriptError: If no keywords matched - """ - for keyword_class in keyword_classes: - try: - matched = keyword_class.find(ocr_text, in_current_server=True, ignore_punctuation=True) - return matched - except ScriptError: - continue - - raise ScriptError - def __str__(self): return self.name @@ -89,6 +53,10 @@ class OcrResultButton: def __bool__(self): return True + @property + def is_keyword_matched(self) -> bool: + return self.matched_keyword is not None + class Ocr: # Merge results with box distance <= thres @@ -201,6 +169,127 @@ class Ocr: text=str([result.ocr_text for result in results])) return results + def _match_result( + self, + result: str, + keyword_classes, + lang: str = None, + ignore_punctuation=True, + ignore_digit=True): + """ + Args: + result (str): + keyword_classes: A list of `Keyword` class or classes inherited `Keyword` + + Returns: + If matched, return `Keyword` object or objects inherited `Keyword` + If not match, return None + """ + if not isinstance(keyword_classes, list): + keyword_classes = [keyword_classes] + + # Digits will be considered as the index of keyword + if ignore_digit: + if result.isdigit(): + return None + + # Try in current lang + for keyword_class in keyword_classes: + try: + matched = keyword_class.find( + result, + lang=lang, + ignore_punctuation=ignore_punctuation + ) + return matched + except ScriptError: + continue + + return None + + def matched_single_line( + self, + image, + keyword_classes, + lang: str = None, + ignore_punctuation=True + ) -> OcrResultButton: + """ + Args: + image: Image to detect + keyword_classes: `Keyword` class or classes inherited `Keyword`, or a list of them. + lang: + ignore_punctuation: + + Returns: + OcrResultButton: Or None if it didn't matched known keywords. + """ + result = self.ocr_single_line(image) + + result = self._match_result( + result, + keyword_classes=keyword_classes, + lang=lang, + ignore_punctuation=ignore_punctuation, + ) + + logger.attr(name=f'{self.name} matched', + text=result) + return result + + def matched_multi_lines( + self, + image_list, + keyword_classes, + lang: str = None, + ignore_punctuation=True + ) -> list[OcrResultButton]: + """ + Args: + image_list: + keyword_classes: `Keyword` class or classes inherited `Keyword`, or a list of them. + lang: + ignore_punctuation: + + Returns: + List of matched OcrResultButton. + OCR result which didn't matched known keywords will be dropped. + """ + results = self.ocr_multi_lines(image_list) + + results = [self._match_result( + result, + keyword_classes=keyword_classes, + lang=lang, + ignore_punctuation=ignore_punctuation, + ) for result in results] + results = [result for result in results if result.is_keyword_matched] + + logger.attr(name=f'{self.name} matched', + text=results) + return results + + def _product_button( + self, + boxed_result: BoxedResult, + keyword_classes, + lang: str = None, + ignore_punctuation=True, + ignore_digit=True + ) -> OcrResultButton: + if not isinstance(keyword_classes, list): + keyword_classes = [keyword_classes] + + matched_keyword = self._match_result( + boxed_result.ocr_text, + keyword_classes=keyword_classes, + lang=lang, + ignore_punctuation=ignore_punctuation, + ignore_digit=ignore_digit, + ) + button = OcrResultButton(boxed_result, matched_keyword) + return button + def matched_ocr(self, image, keyword_classes, direct_ocr=False) -> list[OcrResultButton]: """ Args: @@ -212,21 +301,11 @@ class Ocr: List of matched OcrResultButton. OCR result which didn't matched known keywords will be dropped. """ - if not isinstance(keyword_classes, list): - keyword_classes = [keyword_classes] - - def is_valid(keyword): - # Digits will be considered as the index of keyword - if keyword.isdigit(): - return False - return True - results = self.detect_and_ocr(image, direct_ocr=direct_ocr) - results = [ - OcrResultButton(result, keyword_classes) - for result in results if is_valid(result.ocr_text) - ] - results = [result for result in results if result.matched_keyword is not None] + + results = [self._product_button(result, keyword_classes) for result in results] + results = [result for result in results if result.is_keyword_matched] + logger.attr(name=f'{self.name} matched', text=results) return results diff --git a/module/ui/draggable_list.py b/module/ui/draggable_list.py index 9b378afb3..7f8f46c92 100644 --- a/module/ui/draggable_list.py +++ b/module/ui/draggable_list.py @@ -182,7 +182,7 @@ class DraggableList: main.wait_until_stable(self.search_button, timer=Timer( 0, count=0), timeout=Timer(1.5, count=5)) skip_first_screenshot = True - if last_buttons == set(self.cur_buttons): + if self.cur_buttons and last_buttons == set(self.cur_buttons): logger.warning(f'No more rows in {self}') return False last_buttons = set(self.cur_buttons) diff --git a/tasks/base/assets/assets_base_main_page.py b/tasks/base/assets/assets_base_main_page.py new file mode 100644 index 000000000..586dac75b --- /dev/null +++ b/tasks/base/assets/assets_base_main_page.py @@ -0,0 +1,15 @@ +from module.base.button import Button, ButtonWrapper + +# This file was auto-generated, do not modify it manually. To generate: +# ``` python -m dev_tools.button_extract ``` + +OCR_MAP_NAME = ButtonWrapper( + name='OCR_MAP_NAME', + share=Button( + file='./assets/share/base/main_page/OCR_MAP_NAME.png', + area=(48, 15, 373, 32), + search=(28, 0, 393, 52), + color=(69, 72, 78), + button=(48, 15, 373, 32), + ), +) diff --git a/tasks/base/main_page.py b/tasks/base/main_page.py new file mode 100644 index 000000000..5c928b3e5 --- /dev/null +++ b/tasks/base/main_page.py @@ -0,0 +1,80 @@ +import re +from typing import Optional + +import module.config.server as server +from module.base.base import ModuleBase +from module.config.server import VALID_LANG +from module.exception import RequestHumanTakeover, ScriptError +from module.logger import logger +from module.ocr.ocr import Ocr +from tasks.base.assets.assets_base_main_page import OCR_MAP_NAME +from tasks.base.page import Page, page_main +from tasks.map.keywords import KEYWORDS_MAP_PLANE, MapPlane + + +class OcrPlaneName(Ocr): + def after_process(self, result): + # RobotSettlement1 + result = re.sub(r'\d+$', '', result) + + return super().after_process(result) + + +class MainPage(ModuleBase): + # Same as BigmapPlane class + # Current plane + plane: MapPlane = KEYWORDS_MAP_PLANE.Herta_ParlorCar + + _lang_checked = False + + def check_lang_from_map_plane(self) -> Optional[str]: + logger.info('check_lang_from_map_plane') + lang_unknown = self.config.Emulator_GameLanguage == 'auto' + + if lang_unknown: + lang_list = VALID_LANG + else: + # Try current lang first + lang_list = [server.lang] + [lang for lang in VALID_LANG if lang != server.lang] + + for lang in lang_list: + logger.info(f'Try ocr in lang {lang}') + ocr = OcrPlaneName(OCR_MAP_NAME, lang=lang) + result = ocr.ocr_single_line(self.device.image) + keyword = ocr._match_result(result, keyword_classes=MapPlane, lang=lang) + if keyword is not None: + self.plane = keyword + logger.attr('CurrentPlane', self.plane) + logger.info(f'check_lang_from_map_plane matched lang: {lang}') + if lang_unknown or lang != server.lang: + self.config.Emulator_GameLanguage = lang + return lang + + if lang_unknown: + logger.critical('Cannot detect in-game text language, please set it to 简体中文 or English') + raise RequestHumanTakeover + else: + logger.warning(f'Cannot detect in-game text language, assume current lang={server.lang} is correct') + return server.lang + + def handle_lang_check(self, page: Page): + if MainPage._lang_checked: + return + if page != page_main: + return + + self.check_lang_from_map_plane() + MainPage._lang_checked = True + + def acquire_lang_checked(self): + if MainPage._lang_checked: + return + + logger.info('acquire_lang_checked') + try: + self.ui_goto(page_main) + except AttributeError: + logger.critical('Method ui_goto() not found, class MainPage must be inherited by class UI') + raise ScriptError + + self.handle_lang_check(page=page_main) diff --git a/tasks/base/ui.py b/tasks/base/ui.py index 7704bb615..2d8fae753 100644 --- a/tasks/base/ui.py +++ b/tasks/base/ui.py @@ -5,13 +5,14 @@ from module.exception import GameNotRunningError, GamePageUnknownError from module.logger import logger from module.ocr.ocr import Ocr from tasks.base.assets.assets_base_page import CLOSE +from tasks.base.main_page import MainPage from tasks.base.page import Page, page_main from tasks.base.popup import PopupHandler from tasks.combat.assets.assets_combat_finish import COMBAT_EXIT from tasks.combat.assets.assets_combat_prepare import COMBAT_PREPARE -class UI(PopupHandler): +class UI(PopupHandler, MainPage): ui_current: Page ui_main_confirm_timer = Timer(0.2, count=0) @@ -124,6 +125,7 @@ class UI(PopupHandler): continue if self.appear(page.check_button, interval=5): logger.info(f'Page switch: {page} -> {page.parent}') + self.handle_lang_check(page) if self.ui_page_confirm(page): logger.info(f'Page arrive confirm {page}') button = page.links[page.parent] @@ -151,6 +153,7 @@ class UI(PopupHandler): bool: If UI switched. """ logger.hr("UI ensure") + self.acquire_lang_checked() self.ui_get_current_page(skip_first_screenshot=skip_first_screenshot) if self.ui_current == destination: logger.info("Already at %s" % destination) diff --git a/tasks/forgotten_hall/ui.py b/tasks/forgotten_hall/ui.py index 715221322..93be99f96 100644 --- a/tasks/forgotten_hall/ui.py +++ b/tasks/forgotten_hall/ui.py @@ -22,7 +22,7 @@ class ForgottenHallStageOcr(Ocr): raw = image.copy() area = OCR_STAGE.area image = crop(raw, area) - yellow = color_similarity_2d(image, color=(250, 201, 111)) + yellow = color_similarity_2d(image, color=(255, 200, 112)) gray = color_similarity_2d(image, color=(100, 109, 134)) image = np.maximum(yellow, gray) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) @@ -36,7 +36,7 @@ class ForgottenHallStageOcr(Ocr): for cont in contours: rect = cv2.boundingRect(cv2.convexHull(cont).astype(np.float32)) # Filter with rectangle width, usually to be 62~64 - if not 62 - 10 < rect[2] < 62 + 10: + if not 62 - 10 < rect[2] < 65 + 10: continue rect = (rect[0], rect[1], rect[0] + rect[2], rect[1] + rect[3]) rect = area_offset(rect, offset=area[:2]) @@ -52,16 +52,16 @@ class ForgottenHallStageOcr(Ocr): boxes = self._find_number(image) image_list = [crop(image, area) for area in boxes] results = self.ocr_multi_lines(image_list) - boxed_results = [ + results = [ BoxedResult(area_offset(boxes[index], (-50, 0)), image_list[index], text, score) for index, (text, score) in enumerate(results) ] - results_buttons = [ - OcrResultButton(result, keyword_classes) - for result in boxed_results - ] - logger.attr(name=f'{self.name} matched', text=results_buttons) - return results_buttons + + results = [self._product_button(result, keyword_classes, ignore_digit=False) for result in results] + results = [result for result in results if result.is_keyword_matched] + + logger.attr(name=f'{self.name} matched', text=results) + return results class DraggableStageList(DraggableList):