mower-ng/mower/utils/recognize/__init__.py
zhbaor aaac1deaeb
Some checks failed
ci/woodpecker/push/check_format Pipeline failed
首页识别改进
2025-05-01 20:42:30 +08:00

874 lines
34 KiB
Python

import time
from typing import Optional, Tuple
import cv2
from mower import __rootdir__ as __rootdir__
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.csleep import MowerExit
from mower.utils.image import bytes2img, cmatch, cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.matcher import Matcher
from mower.utils.number import NumberRecognizer
from mower.utils.scene import Scene, SceneComment
from mower.utils.vector import clamp_scope, va
from .data import color, template_matching, template_matching_score
class Recognizer:
def __init__(self, image: Optional[tp.Image] = None) -> None:
self.w = 1920
self.h = 1080
self.num = NumberRecognizer()
self.clear()
self.loading_time = 0
self.LOADING_TIME_LIMIT = 5
if image is not None:
self._img = image
def clear(self):
self._img = None
self._gray = None
self._hsv = None
self._matcher = None
self.scene = Scene.UNDEFINED
self.animation_stop = False
@property
def img(self):
if self._img is None:
self.start()
return self._img
@property
def gray(self):
if self._gray is None:
self._gray = cv2.cvtColor(self.img, cv2.COLOR_RGB2GRAY)
return self._gray
@property
def hsv(self):
if self._hsv is None:
self._hsv = cv2.cvtColor(self.img, cv2.COLOR_RGB2HSV)
return self._hsv
@property
def matcher(self):
if self._matcher is None:
self._matcher = Matcher(self.gray)
return self._matcher
def start(self, screencap: Optional[bytes] = None) -> None:
"""init with screencap"""
retry_times = 5
while retry_times > 0:
try:
if screencap is not None:
self._img = bytes2img(screencap)
else:
self._img = config.device.screencap()
return
except cv2.error as e:
logger.warning(e)
retry_times -= 1
time.sleep(1)
continue
raise RuntimeError("init Recognizer failed")
def update(self) -> None:
if config.stop_mower.is_set():
raise MowerExit
self.clear()
def color(self, x: int, y: int) -> tp.Pixel:
"""get the color of the pixel"""
return self.img[y][x]
def detect_index_scene(self) -> bool:
res = loadres("index_nav", True)
img = cropimg(self.gray, ((25, 17), (456, 96)))
img = thres2(img, 240)
result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED)
result = result[0][0]
if result > 0.85:
logger.debug(result)
return True
return False
def detect_recruit_agent_scene(self) -> bool:
for p in [
"PIONEER",
"SPECIAL",
"SNIPER",
"CASTER",
"WARRIOR",
"TANK",
"MEDIC",
"SUPPORT",
]:
if self.find(f"recruit/profession/{p}", scope=((800, 745), (1140, 850))):
return True
return False
def check_current_focus(self):
if config.device.check_current_focus():
self.update()
def check_loading_time(self):
if self.scene == Scene.CONNECTING:
self.loading_time += 1
if self.loading_time > 1:
logger.debug(f"检测到连续等待{self.loading_time}")
else:
self.loading_time = 0
if self.loading_time > self.LOADING_TIME_LIMIT:
logger.info(f"检测到连续等待{self.loading_time}")
config.device.exit()
time.sleep(3)
self.check_current_focus()
def get_scene(self) -> int:
"""get the current scene in the game"""
if self.scene != Scene.UNDEFINED:
return self.scene
# 连接中,优先级最高
if self.find("connecting"):
self.scene = Scene.CONNECTING
# 平均色匹配
elif self.find("nav_bar"):
self.scene = Scene.NAVIGATION_BAR
elif self.find("sanity_charge"):
self.scene = Scene.SANITY_CHARGE
elif self.find("sanity_charge_dialog"):
self.scene = Scene.SANITY_CHARGE_DIALOG
elif self.find("confirm"):
self.scene = Scene.CONFIRM
elif self.find("order_label"):
self.scene = Scene.ORDER_LIST
elif self.find("drone"):
self.scene = Scene.DRONE_ACCELERATE
elif self.find("factory_collect"):
self.scene = Scene.FACTORY_ROOMS
elif self.find("mail/banner"):
self.scene = Scene.MAIL
elif self.find("navigation/record_restoration"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("choose_agent/support_status") or self.find(
"choose_agent/support_agent"
):
self.scene = Scene.OPERATOR_SUPPORT
elif self.find("fight/give_up"):
self.scene = Scene.OPERATOR_GIVEUP
elif self.find("fight/collection") or self.find("fight/collection_on"):
self.scene = Scene.OPERATOR_AGENT_SELECT
elif self.find("choose_agent/fast_select") or self.find(
"choose_agent/ope_start"
):
if self.find("rogue/action"):
self.scene = Scene.ROGUE_SELECT
else:
self.scene = Scene.OPERATOR_SELECT
elif self.find("fight/ban"):
self.scene = Scene.OPERATOR_BAN
elif self.find("ope_eliminate"):
self.scene = Scene.OPERATOR_ELIMINATE
elif self.find("ope_elimi_agency_panel"):
self.scene = Scene.OPERATOR_ELIMINATE_AGENCY
elif self.find("riic/report_title"):
self.scene = Scene.RIIC_REPORT
elif self.find("control_central_assistants"):
self.scene = Scene.CTRLCENTER_ASSISTANT
elif self.find("build_mode"):
self.scene = Scene.INFRA_MAIN
elif self.find("infra_todo"):
self.scene = Scene.INFRA_TODOLIST
elif self.find("infra_overview_rest") or self.find("infra_overview_work"):
self.scene = Scene.INFRA_ARRANGE
elif self.find("arrange_confirm"):
self.scene = Scene.INFRA_ARRANGE_CONFIRM
elif self.find("open_recruitment"):
self.scene = Scene.RECRUIT_MAIN
elif self.find("recruiting_instructions"):
self.scene = Scene.RECRUIT_TAGS
elif self.find("shop/trade_token_dialog"):
self.scene = Scene.SHOP_TRADE_TOKEN
elif self.find("shop/credit"):
self.scene = Scene.SHOP_CREDIT
elif self.find("shop/token"):
self.scene = Scene.SHOP_TOKEN
elif self.find("shop/recommend"):
self.scene = Scene.SHOP_OTHERS
elif self.find("shop/recommend_off"):
self.scene = Scene.SHOP_OTHERS
elif self.find("shop/cart"):
self.scene = Scene.SHOP_CREDIT_CONFIRM
elif self.find("shop/assist.jpg"):
self.scene = Scene.SHOP_ASSIST
elif self.find("I_know"):
self.scene = Scene.LOGIN_ANNOUNCE_NEW
elif self.find("login_logo") and (
self.find("hypergryph") or self.find("age_notice")
):
if self.find("login_awake"):
self.scene = Scene.LOGIN_QUICKLY
elif self.find("login_account"):
self.scene = Scene.LOGIN_MAIN
else:
self.scene = Scene.LOGIN_MAIN_NOENTRY
elif self.find("12cadpa"):
self.scene = Scene.LOGIN_START
elif self.find("login_bilibili"):
self.scene = Scene.LOGIN_BILIBILI
elif self.find("skip"):
self.scene = Scene.SKIP
elif self.find("login_connecting"):
self.scene = Scene.LOGIN_LOADING
elif self.find("choose_agent/riic/work_state") or self.find(
"choose_agent/riic/work_state_blue"
):
self.scene = Scene.RIIC_OPERATOR_SELECT
elif self.find("ope_recover_potion_on"):
self.scene = Scene.OPERATOR_RECOVER_POTION
elif self.find("ope_recover_originite_on", scope=((1530, 120), (1850, 190))):
self.scene = Scene.OPERATOR_RECOVER_ORIGINITE
elif self.find("double_confirm/main"):
if self.find("double_confirm/exit"):
self.scene = Scene.EXIT_GAME
elif self.find("double_confirm/friend"):
self.scene = Scene.BACK_TO_FRIEND_LIST
elif self.find("double_confirm/give_up"):
self.scene = Scene.OPERATOR_FAULT_CONFIRM
elif self.find("double_confirm/infrastructure"):
self.scene = Scene.LEAVE_INFRASTRUCTURE
elif self.find("double_confirm/recruit"):
self.scene = Scene.REFRESH_TAGS
elif self.find("double_confirm/network"):
self.scene = Scene.NETWORK_CHECK
elif self.find("double_confirm/voice"):
self.scene = Scene.DOWNLOAD_VOICE_RESOURCES
elif self.find("double_confirm/sss"):
self.scene = Scene.SSS_EXIT_CONFIRM
elif self.find("double_confirm/sss_abandon_drop"):
self.scene = Scene.SSS_ABANDON_DROP_IN_FIGHT
elif self.find("double_confirm/product_plan"):
self.scene = Scene.PRODUCT_SWITCHING_CONFIRM
elif self.find("double_confirm/rogue_recruit"):
self.scene = Scene.ROGUE_ABANDON_RECRUIT
elif self.find("double_confirm/explore"):
self.scene = Scene.ROGUE_ABANDON
elif self.find("double_confirm/refresh_shop"):
self.scene = Scene.ROGUE_REFRESH_SHOP
elif self.find("double_confirm/headhunting"):
self.scene = Scene.HEADHUNTING_FREE_CONFIRM
elif self.find("double_confirm/record.jpg"):
self.scene = Scene.BATTLE_RECORD
else:
self.scene = Scene.DOUBLE_CONFIRM
elif self.find("mission_trainee_on"):
self.scene = Scene.MISSION_TRAINEE
elif self.find("shop/spent_credit"):
self.scene = Scene.SHOP_UNLOCK_SCHEDULE
elif self.find("loading7"):
self.scene = Scene.LOADING
elif self.find("clue/daily"):
self.scene = Scene.CLUE_DAILY
elif self.find("clue/receive"):
self.scene = Scene.CLUE_RECEIVE
elif self.find("clue/give_away"):
self.scene = Scene.CLUE_GIVE_AWAY
elif self.find("clue/summary"):
self.scene = Scene.CLUE_SUMMARY
elif self.find("clue/filter_all"):
self.scene = Scene.CLUE_PLACE
elif self.find("clue/message_board_banner"):
self.scene = Scene.CLUE_MESSAGE_BOARD
elif self.find("clue/message_board_friend"):
self.scene = Scene.CLUE_MESSAGE_BOARD_FRIEND
elif self.find("clue/access_records.jpg"):
self.scene = Scene.CLUE_ACCESS_RECORD
elif self.find("upgrade"):
self.scene = Scene.UPGRADE
elif self.find("restore_all_sanity"):
self.scene = Scene.RESTORE_ALL_SANITY
elif self.find("operator/trust"):
self.scene = Scene.OPERATOR_DETAILS
elif self.find("depot/all") or self.find("depot/all_in"):
self.scene = Scene.DEPOT
elif self.find("rhodes") and (
self.find("operator/filter_hide") or self.find("operator/filter_show")
):
self.scene = Scene.OPERATOR_MANAGEMENT
elif self.find("pull_once"):
self.scene = Scene.HEADHUNTING
elif self.find("read_and_agree") or self.find("next_step"):
self.scene = Scene.AGREEMENT_UPDATE
elif self.find("notice"):
self.scene = Scene.NOTICE
elif self.find("sss/main"):
self.scene = Scene.SSS_MAIN
elif self.find("sss/start") or self.find("sss/start_ex"):
self.scene = Scene.SSS_START
elif self.find("sss/check_ex"):
self.scene = Scene.SSS_EC_EX
elif self.find("sss/ec"):
self.scene = Scene.SSS_EC
elif self.find("sss/choose_ec"):
self.scene = Scene.SSS_CHOOSE_EC
elif self.find("sss/device"):
self.scene = Scene.SSS_DEVICE
elif self.find("sss/squad"):
self.scene = Scene.SSS_SQUAD
elif self.find("sss/drop"):
self.scene = Scene.SSS_DROP_AGENT_BEFORE_FIGHT
elif self.find("sss/deploy") or self.find("sss/deploy_ex"):
self.scene = Scene.SSS_DEPLOY
elif self.find("sss/loading") or self.find("sss/loading_ex"):
self.scene = Scene.LOADING
elif self.find("sss/redeploy") or self.find("sss/redeploy_ex"):
self.scene = Scene.SSS_REDEPLOY
elif self.find("sss/terminated"):
self.scene = Scene.SSS_TERMINATED
elif self.find("sss/action") or self.find("sss/action_ex"):
self.scene = Scene.SSS_ACTION
elif self.find("sss/accomplished"):
self.scene = Scene.SSS_ACCOMPLISHED
elif self.find("sss/ope/use"):
self.scene = Scene.SSS_ELIMI_AGENCY
elif self.find("rogue/double_confirm/main"):
if self.find("rogue/double_confirm/this_node"):
self.scene = Scene.ROGUE_REFRESH_CONFIRM
elif self.find("rogue/double_confirm/no_more_tips"):
self.scene = Scene.ROGUE_CONFUSION_WARNING
else:
self.scene = Scene.ROGUE_DOUBLE_CONFIRM
elif self.find("rogue/Sarkaz_load/mental_load"):
self.scene = Scene.ROGUE_SARKAZ_LOAD
elif self.find("rogue/Sarkaz_load/lose"):
self.scene = Scene.ROGUE_SARKAZ_LIGHTEN_LOAD
elif self.find("rogue/close"):
self.scene = Scene.ROGUE_NOTICE
elif self.find("rogue/back"):
if self.find("rogue/select_team"):
self.scene = Scene.ROGUE_TEAM_SELECT
elif self.find("rogue/choose_support"):
self.scene = Scene.ROGUE_AWARD_BEFORE_EXPLORE
elif self.find("rogue/init_recruit"):
self.scene = Scene.ROGUE_INIT_RECRUIT
elif self.find("rogue/select_recruit_combination"):
self.scene = Scene.ROGUE_RECRUIT_SELECT
elif self.find("rogue/recruit/check_recruit"):
self.scene = Scene.ROGUE_RECRUIT_AGENT_SELECT
elif self.find("rogue/Sarkaz_dart"):
self.scene = Scene.ROGUE_NODE_LOADING
elif self.find("rogue/team"):
if self.find("rogue/go"):
self.scene = Scene.ROGUE_NODE_BEFORE_GO
elif self.find("rogue/hide_dart"):
self.scene = Scene.ROGUE_NODE_OPTION_SELECT
elif self.find("rogue/playing_tips"):
if self.find("rogue/shop/eccentric_businessman"):
if self.find("rogue/shop/invest_system"):
if self.find("rogue/shop/investment_portal"):
self.scene = Scene.ROGUE_INVESTMENT_PORTAL
else:
self.scene = Scene.ROGUE_INVESTMENT
else:
self.scene = Scene.ROGUE_SHOP
else:
self.scene = Scene.ROGUE_MAIN
else:
self.scene = Scene.UNKNOWN_ROGUE
else:
self.scene = Scene.UNKNOWN_ROGUE
elif self.find("rogue/view_details") and self.find("rogue/fold_up"):
self.scene = Scene.ROGUE_AGENT_DETAIL
elif self.find("navigation/score/previous"):
self.scene = Scene.TERMINAL_PREVIOUS
# elif self.find("sign_in/moon_festival/banner"):
# self.scene = Scene.MOON_FESTIVAL
elif self.find("sign_in/task/banner"):
self.scene = Scene.ACTIVITY_TASK
elif self.find("sign_in/shop/price_black"):
self.scene = Scene.ACTIVITY_SHOP_BUY
elif self.find("sign_in/shop/price_white"):
self.scene = Scene.ACTIVITY_SHOP_BUY
elif self.find("sign_in/shop/banner"):
self.scene = Scene.ACTIVITY_SHOP
elif self.find("sign_in/special_access/banner"):
self.scene = Scene.ACTIVITY_SPECIAL_ACCESS
elif self.find("rogue/theme_select"):
self.scene = Scene.ROGUE_THEME_SELECT
elif (
self.find("rogue/regular1")
or self.find("rogue/regular2")
or self.find("rogue/regular3")
or self.find("rogue/regular4")
):
self.scene = Scene.ROGUE_INDEX
elif self.find("rogue/view_data_back"):
self.scene = Scene.ROGUE_SETTLEMENT
elif self.find("rogue/level_up_check"):
self.scene = Scene.ROGUE_LEVEL_UP
elif self.find("rogue/monthly_commision"):
self.scene = Scene.ROGUE_MONTHLY_COMMISION
elif self.find("rogue/layer_loading/1") or self.find("rogue/layer_loading/2"):
self.scene = Scene.ROGUE_LAYER_LOADING
elif self.find("rogue/select_difficulty"):
self.scene = Scene.ROGUE_DIFFICULTY_SELECT
elif self.find("contract"):
self.scene = Scene.HEADHUNTING_RESULT
elif self.find("start_story"):
self.scene = Scene.STORY_STAGE
elif self.find("sf/exit_button"):
if self.find("sf/continue"):
self.scene = Scene.SF_CONTINUE
elif self.find("sf/properties"):
if self.find("sf/select"):
self.scene = Scene.SF_SELECT
elif self.find("sf/return"):
self.scene = Scene.SF_ACTIONS
elif self.find("sf/success"):
self.scene = Scene.SF_RESULT
elif self.find("sf/failure"):
self.scene = Scene.SF_RESULT
else:
self.scene = Scene.UNKNOWN
elif self.find("sf/inheritance"):
self.scene = Scene.SF_SELECT_TEAM
elif self.find("sf/continue_event"):
self.scene = Scene.SF_EVENT
elif self.find("sf/click_anywhere"):
self.scene = Scene.SF_CLICK_ANYWHERE
elif self.find("sf/team_pass"):
self.scene = Scene.SF_TEAM_PASS
elif self.find("sf/end"):
self.scene = Scene.SF_END
else:
self.scene = Scene.UNKNOWN
elif self.find("sf/exit"):
self.scene = Scene.SF_EXIT
elif self.find("guess/main"):
self.scene = Scene.GUESS_MAIN
elif self.find("guess/back"):
self.scene = Scene.GUESS_CHOOSE
elif self.find("guess/confirm"):
self.scene = Scene.GUESS_EXIT
elif self.find("guess/exit"):
self.scene = Scene.GUESS_FIGHT
elif self.find("guess/continue"):
self.scene = Scene.GUESS_SETTLEMENT
# 模板匹配
elif self.detect_index_scene():
if self.match3d("originite")[0] >= 0.9:
self.scene = Scene.INDEX_ORIGINITE
elif self.match3d("sanity")[0] >= 0.8:
self.scene = Scene.INDEX_SANITY
else:
self.scene = Scene.INDEX
elif self.find("materiel_ico"):
self.scene = Scene.MATERIEL
elif self.find("loading"):
self.scene = Scene.LOADING
elif self.find("loading2"):
self.scene = Scene.LOADING
elif self.find("loading3"):
self.scene = Scene.LOADING
elif self.find("loading4"):
self.scene = Scene.LOADING
elif self.find("ope_plan"):
self.scene = Scene.OPERATOR_BEFORE
elif self.find("navigation/activity/banner_normal"):
self.scene = Scene.ACTIVITY_NORMAL_CHOOSE_LEVEL
elif self.find("navigation/activity/banner_ex"):
self.scene = Scene.ACTIVITY_EX_CHOOSE_LEVEL
elif self.find("navigation/episode"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/AP-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/LS-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/CA-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/CE-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/SK-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/PR-A-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/PR-B-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/PR-C-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("navigation/collection/PR-D-1"):
self.scene = Scene.OPERATOR_CHOOSE_LEVEL
elif self.find("ope_agency_going"):
self.scene = Scene.OPERATOR_ONGOING
elif self.find("sss/abandon"):
self.scene = Scene.SSS_DROP_IN_FIGHT
elif self.find("story_skip_confirm_dialog"):
self.scene = Scene.STORY_SKIP
elif self.find("story_skip"):
self.scene = Scene.STORY
elif (
self.find("fight/gear")
or self.find("fight/pause_sign")
or self.find("fight/tower")
):
self.scene = Scene.OPERATOR_FIGHT
elif self.find("ope_finish"):
self.scene = Scene.OPERATOR_FINISH
elif self.find("fight/use"):
self.scene = Scene.OPERATOR_SUPPORT_AGENT
elif self.find("business_card"):
self.scene = Scene.BUSINESS_CARD
elif self.find("friend_list"):
self.scene = Scene.FRIEND_LIST
elif self.find("credit_visiting"):
self.scene = Scene.FRIEND_VISITING
elif (
self.find("arrange_check_in")
or self.find("arrange_check_in_on")
or self.find("meeting_arrange_check_in")
or self.find("meeting_arrange_check_in_on")
):
self.scene = Scene.INFRA_DETAILS
elif self.find("user") or self.find("user_on"):
self.scene = Scene.ACTIVITY_ROOM_DETAILS
elif self.find("ope_failed"):
self.scene = Scene.OPERATOR_FAILED
elif self.find("mission_daily_on"):
self.scene = Scene.MISSION_DAILY
elif self.find("mission_weekly_on"):
self.scene = Scene.MISSION_WEEKLY
elif self.detect_recruit_agent_scene():
if self.find("recruit/agent_token") or self.find(
"recruit/agent_token_first"
):
self.scene = Scene.RECRUIT_AGENT
else:
self.scene = Scene.ROGUE_AGENT
elif self.find("terminal/main"):
self.scene = Scene.TERMINAL_MAIN
elif self.find("terminal/score"):
self.scene = Scene.TERMINAL_SCORE
elif self.find("terminal/collection"):
self.scene = Scene.TERMINAL_COLLECTION
elif self.find("terminal/regular"):
self.scene = Scene.TERMINAL_REGULAR
elif self.find("terminal/longterm"):
self.scene = Scene.TERMINAL_LONGTERM
elif self.find("terminal/periodic"):
self.scene = Scene.TERMINAL_PERIODIC
elif self.find("sign_in/shop/entry"):
self.scene = Scene.ACTIVITY_MAIN
elif self.find("announcement_close"):
self.scene = Scene.ANNOUNCEMENT
elif self.find("choose_product_options"):
self.scene = Scene.CHOOSE_PRODUCT
elif self.find("order_switching_notice"):
self.scene = Scene.SWITCH_ORDER
elif self.find("clue"):
self.scene = Scene.INFRA_CONFIDENTIAL
elif self.find("sss/drop_EC"):
self.scene = Scene.SSS_DROP_EC_BEFORE_FIGHT
elif self.find("sss/operation_complete"):
self.scene = Scene.SSS_OPERATION_COMPLETE
elif self.find("rogue/recruit/use_support"):
self.scene = Scene.ROGUE_USE_SUPPORT
elif self.find("login_captcha"):
self.scene = Scene.LOGIN_CAPTCHA
# 没弄完的
# elif self.find("ope_elimi_finished"):
# self.scene = Scene.OPERATOR_ELIMINATE_FINISH
# 兜底
elif self.find("nav_button"):
self.scene = Scene.UNKNOWN_WITH_NAVBAR
else:
self.scene = Scene.UNKNOWN
self.check_current_focus()
logger.debug(f"Scene {self.scene}: {SceneComment[self.scene]}")
return self.scene
def find_ra_battle_exit(self) -> bool:
im = cv2.cvtColor(self.img, cv2.COLOR_RGB2HSV)
im = cv2.inRange(im, (29, 0, 0), (31, 255, 255))
score, scope = self.template_match(
"ra/battle_exit", ((75, 47), (165, 126)), cv2.TM_CCOEFF_NORMED
)
return scope if score > 0.8 else None
def detect_ra_adventure(self) -> bool:
img = cropimg(self.gray, ((385, 365), (475, 465)))
img = thres2(img, 250)
res = loadres("ra/adventure", True)
result = cv2.matchTemplate(img, res, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
logger.debug(f"{max_val=} {max_loc=}")
return max_val >= 0.9
def get_ra_scene(self) -> int:
"""
生息演算场景识别
"""
# 场景缓存
if self.scene != Scene.UNDEFINED:
return self.scene
# 连接中,优先级最高
if self.find("connecting"):
self.scene = Scene.CONNECTING
elif self.find("loading"):
self.scene = Scene.UNKNOWN
elif self.find("loading4"):
self.scene = Scene.UNKNOWN
# 奇遇
elif self.detect_ra_adventure():
self.scene = Scene.RA_ADVENTURE
# 快速跳过剧情对话
elif self.find("ra/guide_dialog"):
self.scene = Scene.RA_GUIDE_DIALOG
# 快速退出作战
elif self.find_ra_battle_exit():
self.scene = Scene.RA_BATTLE
elif self.find("ra/battle_exit_dialog"):
self.scene = Scene.RA_BATTLE_EXIT_CONFIRM
# 作战与分队
elif self.find("ra/squad_edit"):
self.scene = Scene.RA_SQUAD_EDIT
elif self.find("ra/start_action"):
if self.find("ra/action_points"):
self.scene = Scene.RA_BATTLE_ENTRANCE
else:
self.scene = Scene.RA_GUIDE_BATTLE_ENTRANCE
elif self.find("ra/get_item"):
self.scene = Scene.RA_GET_ITEM
elif self.find("ra/return_from_kitchen"):
self.scene = Scene.RA_KITCHEN
elif self.find("ra/squad_edit_confirm_dialog"):
self.scene = Scene.RA_SQUAD_EDIT_DIALOG
elif self.find("ra/enter_battle_confirm_dialog"):
self.scene = Scene.RA_SQUAD_ABNORMAL
elif self.find("ra/battle_complete"):
self.scene = Scene.RA_BATTLE_COMPLETE
# 结算界面
elif self.find("ra/day_complete"):
self.scene = Scene.RA_DAY_COMPLETE
elif self.find("ra/period_complete") and self.find("ra/click_anywhere"):
self.scene = Scene.RA_PERIOD_COMPLETE
# 森蚺图耶对话
elif self.find("ra/guide_entrance"):
self.scene = Scene.RA_GUIDE_ENTRANCE
# 存档操作
elif self.find("ra/delete_save_confirm_dialog"):
self.scene = Scene.RA_DELETE_SAVE_DIALOG
# 地图识别
elif self.find("ra/waste_time_button"):
self.scene = Scene.RA_DAY_DETAIL
elif self.find("ra/waste_time_dialog"):
self.scene = Scene.RA_WASTE_TIME_DIALOG
elif self.find("ra/map_back", thres=200) and self.color(1817, 333)[0] > 250:
self.scene = Scene.RA_MAP
# 一张便条
elif self.find("ra/notice"):
self.scene = Scene.RA_NOTICE
# 一张便条
elif self.find("ra/no_enough_drink"):
self.scene = Scene.RA_INSUFFICIENT_DRINK
# 从首页选择终端进入生息演算主页
elif self.find("terminal_longterm"):
self.scene = Scene.TERMINAL_LONGTERM
elif self.find("ra/main_title"):
self.scene = Scene.RA_MAIN
elif self.detect_index_scene():
self.scene = Scene.INDEX
elif self.find("terminal_main"):
self.scene = Scene.TERMINAL_MAIN
else:
self.scene = Scene.UNKNOWN
self.check_current_focus()
logger.debug(f"Scene: {self.scene}: {SceneComment[self.scene]}")
self.check_loading_time()
return self.scene
def get_train_scene(self) -> int:
"""
训练室场景识别
"""
# 场景缓存
if self.scene != Scene.UNDEFINED:
return self.scene
# 连接中,优先级最高
if self.find("connecting"):
self.scene = Scene.CONNECTING
elif self.find("infra_overview"):
self.scene = Scene.INFRA_MAIN
elif self.find("train_main"):
self.scene = Scene.TRAIN_MAIN
elif self.find("skill_collect_confirm", scope=((1142, 831), (1282, 932))):
self.scene = Scene.TRAIN_FINISH
elif self.find("training_support"):
self.scene = Scene.TRAIN_SKILL_SELECT
elif self.find("upgrade_failure"):
self.scene = Scene.TRAIN_SKILL_UPGRADE_ERROR
elif self.find("skill_confirm"):
self.scene = Scene.TRAIN_SKILL_UPGRADE
else:
self.scene = Scene.UNKNOWN
self.check_current_focus()
logger.debug(f"Scene: {self.scene}: {SceneComment[self.scene]}")
self.check_loading_time()
return self.scene
def find(
self,
res: tp.Res,
draw: bool = False,
scope: tp.Scope | None = None,
threshold: float = 0.9,
) -> tp.Scope | None:
"""找图
Args:
res (tp.Res): 元素名
draw (bool, optional): 画图,用于调试. Defaults to False.
scope (tp.Scope | None, optional): 匹配区域,只对特征匹配和预设为None的模板匹配有用. Defaults to None.
threshold (float, optional): 匹配阈值,只对特征匹配有用. Defaults to 0.9.
Returns:
tp.Scope | None: 匹配成功时返回元素所在的区域,失败时返回None
"""
if scope:
scope = clamp_scope(scope)
if res in color:
res_img = loadres(res)
h, w, _ = res_img.shape
pos_list = color[res]
if not isinstance(pos_list[0], tuple):
pos_list = [color[res]]
for pos in pos_list:
scope = pos, va(pos, (w, h))
img = cropimg(self.img, scope)
if cmatch(img, res_img, draw=draw):
score = cv2.matchTemplate(img, res_img, cv2.TM_CCOEFF_NORMED)[0][0]
threshold = (
template_matching_score[res]
if res in template_matching_score
else 0.9
)
if score >= threshold:
logger.debug(f"cmatch: {res=} {score=} {scope=}")
return scope
return None
if res in template_matching:
threshold = 0.9
if res in template_matching_score:
threshold = template_matching_score[res]
res_img = loadres(res, True)
h, w = res_img.shape
pos = template_matching[res] or scope
if isinstance(pos[0], (tuple, list)):
scope = pos
else:
scope = pos, va(pos, (w, h))
img = cropimg(self.gray, scope)
result = cv2.matchTemplate(img, res_img, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if draw:
logger.debug(f"{max_val=}, pos={va(max_loc, scope[0])}")
if max_val >= threshold:
top_left = va(max_loc, scope[0])
scope = top_left, va(top_left, (w, h))
logger.debug(f"template matching: {res=} {max_val=} {scope=}")
return scope
return None
res_img = loadres(res, True)
score, scope = self.matcher.match(res_img, draw, scope)
return scope if score >= threshold else None
def template_match(
self,
res: str,
scope: Optional[tp.Scope] = None,
method: int = cv2.TM_CCOEFF_NORMED,
) -> Tuple[float, tp.Scope]:
logger.debug(f"{res=}")
template = loadres(res, True)
w, h = template.shape[::-1]
if scope:
x, y = scope[0]
img = cropimg(self.gray, scope)
else:
x, y = (0, 0)
img = self.gray
result = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
score = min_val
else:
top_left = max_loc
score = max_val
p1 = (top_left[0] + x, top_left[1] + y)
p2 = (p1[0] + w, p1[1] + h)
scope = p1, p2
logger.debug(f"{score=} {scope=}")
return score, scope
def match(
self, res: tp.Res, draw: bool = False, scope: tp.Scope | None = None
) -> tuple[float, tp.Scope | None]:
logger.debug(f"{res=}")
return self.matcher.match(loadres(res, True), draw, scope)
def match2d(
self, res: tp.Res, draw: bool = False, scope: tp.Scope | None = None
) -> tuple[float, tp.Scope | None]:
logger.debug(f"{res=}")
return self.matcher.match2d(loadres(res, True), draw, scope)
def match3d(
self, res: tp.Res, draw: bool = False, scope: tp.Scope | None = None
) -> tuple[float, tp.Scope | None]:
logger.debug(f"{res=}")
return self.matcher.match3d(loadres(res, True), draw, scope)