mower-ng/mower/utils/recognize/__init__.py
2024-11-19 00:52:34 +08:00

831 lines
31 KiB
Python

import time
from typing import Optional, Tuple
import cv2
import numpy as np
from skimage.metrics import structural_similarity
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 va
from .data import color, template_matching, template_matching_score
class RecognizeError(Exception):
pass
class Recognizer:
def __init__(self, screencap: Optional[bytes] = None) -> None:
self.w = 1920
self.h = 1080
self.num = NumberRecognizer()
self.clear()
self.start(screencap)
self.loading_time = 0
self.LOADING_TIME_LIMIT = 5
def clear(self):
self._img = None
self._gray = None
self._hsv = None
self._matcher = None
self._num = None
self.scene = Scene.UNDEFINED
@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 = config.MAX_RETRYTIME
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)
h, w = res.shape
img = cropimg(self.gray, ((25, 17), (25 + w, 17 + h)))
img = thres2(img, 240)
result = cv2.matchTemplate(img, res, cv2.TM_SQDIFF_NORMED)
result = result[0][0]
if result < 0.1:
logger.debug(result)
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 check_announcement(self):
img = cropimg(self.gray, ((960, 0), (1920, 540)))
tpl = loadres("announcement_close", True)
msk = thres2(tpl, 1)
result = cv2.matchTemplate(img, tpl, cv2.TM_SQDIFF_NORMED, None, msk)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if min_val < 0.02:
return (min_loc[0] + 960 + 42, min_loc[1] + 42)
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("rogue/action"):
self.scene = Scene.ROGUE_SELECT
elif self.find("choose_agent/fast_select"):
self.scene = Scene.OPERATOR_SELECT
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("clue"):
self.scene = Scene.INFRA_CONFIDENTIAL
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"):
if config.recog.hsv[870][1530][1] > 50:
self.scene = Scene.UNKNOWN
else:
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("login_logo") and self.find("hypergryph"):
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/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/stronghold_protocol_back"):
self.scene = Scene.SP_BACK_CONFIRM
elif self.find("double_confirm/stronghold_protocol_giveup"):
self.scene = Scene.SP_GIVEUP_CONFIRM
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("upgrade"):
self.scene = Scene.UPGRADE
elif self.find("restore_all_sanity"):
self.scene = Scene.RESTORE_ALL_SANITY
elif self.find("operator/filter_hide") or self.find("operator/filter_show"):
self.scene = Scene.OPERATOR_MANAGEMENT
elif self.find("operator/trust"):
self.scene = Scene.OPERATOR_DETAILS
elif self.find("depot"):
self.scene = Scene.DEPOT
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/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/drop_EC"):
self.scene = Scene.SSS_DROP_EC_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("login_captcha"):
self.scene = Scene.LOGIN_CAPTCHA
elif self.find("sign_in/banner"):
self.scene = Scene.SIGN_IN_DAILY
# elif self.find("sign_in/moon_festival/banner"):
# self.scene = Scene.MOON_FESTIVAL
elif self.find("navigation/activity/entry"):
self.scene = Scene.ACTIVITY_MAIN
elif self.find("navigation/activity/banner"):
self.scene = Scene.ACTIVITY_CHOOSE_LEVEL
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("contract"):
self.scene = Scene.HEADHUNTING_RESULT
elif self.find("sign_in/orundum/banner"):
self.scene = Scene.SIGN_IN_ORUNDUM
elif self.find("start_story"):
self.scene = Scene.STORY_STAGE
elif self.find("stronghold_protocol/main"):
self.scene = Scene.SP_MAIN
elif self.find("stronghold_protocol/defence"):
self.scene = Scene.SP_DEFENCE
elif self.find("stronghold_protocol/auto"):
self.scene = Scene.SP_AUTO
elif self.find("stronghold_protocol/complete"):
self.scene = Scene.SP_COMPLETE
elif self.find("stronghold_protocol/animation"):
self.scene = Scene.SP_ANIMATION
elif self.is_black():
self.scene = Scene.LOADING
# 模板匹配
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/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("fight/gear") or self.find("fight/pause_sign"):
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"):
self.scene = Scene.INFRA_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.find("recruit/agent_token") or self.find("recruit/agent_token_first"):
self.scene = Scene.RECRUIT_AGENT
elif self.find("terminal_main"):
self.scene = Scene.TERMINAL_MAIN
elif self.find("main_theme"):
self.scene = Scene.TERMINAL_MAIN_THEME
elif self.find("episode"):
self.scene = Scene.TERMINAL_EPISODE
elif self.find("biography"):
self.scene = Scene.TERMINAL_BIOGRAPHY
elif self.find("collection"):
self.scene = Scene.TERMINAL_COLLECTION
elif self.find("terminal_regular"):
self.scene = Scene.TERMINAL_REGULAR
elif self.check_announcement():
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("sss/operation_complete"):
self.scene = Scene.SSS_OPERATION_COMPLETE
elif self.find("story_skip_confirm_dialog"):
self.scene = Scene.STORY_SKIP
elif self.find("story_skip"):
self.scene = Scene.STORY
elif self.find("stronghold_protocol/action"):
self.scene = Scene.SP_ACTION
# 没弄完的
# elif self.find("ope_elimi_finished"):
# self.scene = Scene.OPERATOR_ELIMINATE_FINISH
# elif self.find("shop/assist"):
# self.scene = Scene.SHOP_ASSIST
# elif self.find("login_bilibili_privacy"):
# self.scene = Scene.LOGIN_BILIBILI_PRIVACY
# 兜底
elif self.find("nav_button"):
self.scene = Scene.UNKNOWN_WITH_NAVBAR
elif self.find("rogue/back"):
self.scene = Scene.UNKNOW_ROGUE
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_sf_scene(self) -> int:
"""
隐秘战线场景识别
"""
# 场景缓存
if self.scene != Scene.UNDEFINED:
return self.scene
# 连接中,优先级最高
if self.find("connecting"):
self.scene = Scene.CONNECTING
elif self.find("notice"):
self.scene = Scene.NOTICE
elif self.find("sf/success") or self.find("sf/failure"):
self.scene = Scene.SF_RESULT
elif self.find("sf/continue"):
self.scene = Scene.SF_CONTINUE
elif self.find("sf/select"):
self.scene = Scene.SF_SELECT
elif self.find("sf/properties"):
self.scene = Scene.SF_ACTIONS
elif self.find("sf/continue_event"):
self.scene = Scene.SF_EVENT
elif self.find("sf/team_pass"):
self.scene = Scene.SF_TEAM_PASS
elif self.find("sf/inheritance", scope=((1490, 0), (1920, 100))):
self.scene = Scene.SF_SELECT_TEAM
# 从首页进入隐秘战线
elif self.detect_index_scene():
self.scene = Scene.INDEX
elif self.find("terminal_main"):
self.scene = Scene.TERMINAL_MAIN
elif self.find("main_theme"):
self.scene = Scene.TERMINAL_MAIN_THEME
elif self.find("sf/entrance"):
self.scene = Scene.SF_ENTRANCE
elif self.find("sf/click_anywhere"):
self.scene = Scene.SF_CLICK_ANYWHERE
elif self.find("sf/end"):
self.scene = Scene.SF_END
elif self.find("sf/exit"):
self.scene = Scene.SF_EXIT
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 is_black(self) -> None:
"""check if the current scene is all black"""
return np.max(self.gray[:, 105:-105]) < 16
def find(
self,
res: tp.Res,
draw: bool = False,
scope: tp.Scope | None = None,
thres: int | None = None,
judge: bool = True,
strict: bool = False,
threshold: float = 0.0,
) -> tp.Scope | None:
"""
查找元素是否出现在画面中
:param res: 待识别元素资源文件名
:param draw: 是否将识别结果输出到屏幕
:param scope: ((x0, y0), (x1, y1)),提前限定元素可能出现的范围
:param thres: 是否在匹配前对图像进行二值化处理
:param judge: 是否加入更加精确的判断
:param strict: 是否启用严格模式,未找到时报错
:param score: 是否启用分数限制,有些图片精确识别需要提高分数阈值
:return ret: 若匹配成功,则返回元素在游戏界面中出现的位置,否则返回 None
"""
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):
gray = cropimg(self.gray, scope)
res_img = cv2.cvtColor(res_img, cv2.COLOR_RGB2GRAY)
ssim = structural_similarity(gray, res_img)
if ssim >= 0.9:
logger.debug(f"cmatch+SSIM: {res=} {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 max_val >= threshold:
top_left = va(max_loc, scope[0])
scope = top_left, va(top_left, (w, h))
logger.debug(f"template matching: {res=} {scope=}")
return scope
return None
dpi_aware = res in [
"control_central",
]
if scope is None and threshold == 0.0:
if res == "training_completed":
scope = ((550, 900), (800, 1080))
threshold = 0.45
logger.debug(f"feature matching: {res=}")
res_img = loadres(res, True)
if thres is not None:
# 对图像二值化处理
res_img = thres2(res_img, thres)
matcher = Matcher(thres2(self.gray, thres))
else:
matcher = self.matcher
ret = matcher.match_old(
res_img,
draw=draw,
scope=scope,
judge=judge,
prescore=threshold,
dpi_aware=dpi_aware,
)
logger.debug(f"match_old: {res=} {ret=}")
if strict and ret is None:
raise RecognizeError(f"Can't find '{res}'")
return ret
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)