mirror of
https://github.com/LmeSzinc/AzurLaneAutoScript.git
synced 2025-01-09 05:47:30 +08:00
Refactor: Use numpy image cache instead of pillow image
This commit is contained in:
parent
6df7684a37
commit
65f166ad12
3
alas.py
3
alas.py
@ -88,6 +88,7 @@ class AzurLaneAutoScript:
|
||||
Save last 60 screenshots in ./log/error/<timestamp>
|
||||
Save logs to ./log/error/<timestamp>/log.txt
|
||||
"""
|
||||
from module.base.utils import save_image
|
||||
from module.handler.sensitive_info import handle_sensitive_image, handle_sensitive_logs
|
||||
if self.config.Error_SaveError:
|
||||
if not os.path.exists('./log/error'):
|
||||
@ -98,7 +99,7 @@ class AzurLaneAutoScript:
|
||||
for data in self.device.screenshot_deque:
|
||||
image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f')
|
||||
image = handle_sensitive_image(data['image'])
|
||||
image.save(f'{folder}/{image_time}.png')
|
||||
save_image(image, f'{folder}/{image_time}.png')
|
||||
with open(logger.log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
start = 0
|
||||
|
@ -123,16 +123,16 @@ class ModuleBase:
|
||||
logger.warning(f'wait_until_stable({button}) timeout')
|
||||
break
|
||||
|
||||
def image_area(self, button):
|
||||
def image_crop(self, button):
|
||||
"""Extract the area from image.
|
||||
|
||||
Args:
|
||||
button(Button, tuple): Button instance or area tuple.
|
||||
"""
|
||||
if isinstance(button, Button):
|
||||
return self.device.image.crop(button.area)
|
||||
return crop(self.device.image, button.area)
|
||||
else:
|
||||
return self.device.image.crop(button)
|
||||
return crop(self.device.image, button)
|
||||
|
||||
def image_color_count(self, button, color, threshold=221, count=50):
|
||||
"""
|
||||
@ -145,10 +145,7 @@ class ModuleBase:
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
if isinstance(button, Button):
|
||||
image = self.device.image.crop(button.area)
|
||||
else:
|
||||
image = self.device.image.crop(button)
|
||||
image = self.image_crop(button)
|
||||
mask = color_similarity_2d(image, color=color) > threshold
|
||||
return np.sum(mask) > count
|
||||
|
||||
@ -173,9 +170,9 @@ class ModuleBase:
|
||||
Load image from local file system and set it to self.device.image
|
||||
Test an image without taking a screenshot from emulator.
|
||||
"""
|
||||
if isinstance(value, np.ndarray):
|
||||
value = Image.fromarray(value)
|
||||
if isinstance(value, Image.Image):
|
||||
value = np.array(value)
|
||||
elif isinstance(value, str):
|
||||
value = Image.open(value).convert('RGB')
|
||||
value = load_image(value)
|
||||
|
||||
self.device.image = value
|
||||
|
@ -75,7 +75,7 @@ class Button:
|
||||
"""Check if the button appears on the image.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot.
|
||||
image (np.ndarray): Screenshot.
|
||||
threshold (int): Default to 10.
|
||||
|
||||
Returns:
|
||||
@ -98,7 +98,7 @@ class Button:
|
||||
tuple: Color (r, g, b).
|
||||
"""
|
||||
self.color = get_color(image, self.area)
|
||||
self.image = np.array(image.crop(self.area))
|
||||
self.image = crop(image, self.area)
|
||||
self.is_gif = False
|
||||
return self.color
|
||||
|
||||
@ -128,7 +128,7 @@ class Button:
|
||||
image = crop(image, self.area)
|
||||
self.image.append(image)
|
||||
else:
|
||||
self.image = np.array(Image.open(self.file).crop(self.area).convert('RGB'))
|
||||
self.image = crop(load_image(self.file), self.area)
|
||||
self._match_init = True
|
||||
|
||||
def match(self, image, offset=30, threshold=0.85):
|
||||
@ -151,7 +151,7 @@ class Button:
|
||||
offset = np.array(offset)
|
||||
else:
|
||||
offset = np.array((-3, -offset, 3, offset))
|
||||
image = np.array(image.crop(offset + self.area))
|
||||
image = crop(image, offset + self.area)
|
||||
|
||||
if self.is_gif:
|
||||
for template in self.image:
|
||||
@ -293,6 +293,9 @@ class ButtonGrid:
|
||||
draw.rectangle((button.area[:2], button.button[2:]), fill=(255, 255, 255), outline=None)
|
||||
return image
|
||||
|
||||
def show_mask(self):
|
||||
self.gen_mask().show()
|
||||
|
||||
def save_mask(self):
|
||||
"""
|
||||
Save mask to {name}.png
|
||||
|
@ -1,26 +1,18 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from module.base.template import Template
|
||||
|
||||
|
||||
def get_image_channel(image):
|
||||
"""
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
|
||||
Returns:
|
||||
int: 0 for monochrome, 3 for RGB.
|
||||
"""
|
||||
return 3 if len(image.shape) == 3 else 0
|
||||
from module.base.utils import load_image, rgb2gray, image_channel
|
||||
|
||||
|
||||
class Mask(Template):
|
||||
@property
|
||||
def image(self):
|
||||
if self._image is None:
|
||||
self._image = np.array(Image.open(self.file).convert('L'))
|
||||
image = load_image(self.file)
|
||||
if image_channel(image) == 3:
|
||||
image = rgb2gray(image)
|
||||
self._image = image
|
||||
|
||||
return self._image
|
||||
|
||||
@ -36,15 +28,15 @@ class Mask(Template):
|
||||
Returns:
|
||||
bool: If changed.
|
||||
"""
|
||||
image_channel = get_image_channel(self.image)
|
||||
mask_channel = image_channel(self.image)
|
||||
if channel == 0:
|
||||
if image_channel == 0:
|
||||
if mask_channel == 0:
|
||||
return False
|
||||
else:
|
||||
self._image, _, _ = cv2.split(self._image)
|
||||
return True
|
||||
else:
|
||||
if image_channel == 0:
|
||||
if mask_channel == 0:
|
||||
self._image = cv2.merge([self._image] * 3)
|
||||
return True
|
||||
else:
|
||||
@ -61,5 +53,5 @@ class Mask(Template):
|
||||
np.ndarray:
|
||||
"""
|
||||
image = np.array(image)
|
||||
self.set_channel(get_image_channel(image))
|
||||
self.set_channel(image_channel(image))
|
||||
return cv2.bitwise_and(image, self.image)
|
||||
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
import imageio
|
||||
from PIL import Image
|
||||
|
||||
import module.config.server as server
|
||||
from module.base.button import Button
|
||||
@ -31,7 +30,7 @@ class Template:
|
||||
image = image[:, :, :3] if len(image.shape) == 3 else image
|
||||
self._image += [image, cv2.flip(image, 1)]
|
||||
else:
|
||||
self._image = np.array(Image.open(self.file))
|
||||
self._image = load_image(self.file)
|
||||
|
||||
return self._image
|
||||
|
||||
@ -86,7 +85,7 @@ class Template:
|
||||
name = self.name
|
||||
area = area_offset(area=(0, 0, *self.size), offset=point)
|
||||
button = Button(area=area, color=(), button=area, name=name)
|
||||
if isinstance(image, Image.Image):
|
||||
if image is not None:
|
||||
button.load_color(image)
|
||||
return button
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import ImageStat
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def random_normal_distribution_int(a, b, n=3):
|
||||
@ -352,8 +352,50 @@ def location2node(location):
|
||||
return chr(location[0] + 64 + 1) + str(location[1] + 1)
|
||||
|
||||
|
||||
def load_image(file):
|
||||
"""
|
||||
Load an image like pillow and drop alpha channel.
|
||||
|
||||
Args:
|
||||
file (str):
|
||||
|
||||
Returns:
|
||||
np.ndarray:
|
||||
"""
|
||||
image = np.array(Image.open(file))
|
||||
channel = image.shape[2] if len(image.shape) > 2 else 1
|
||||
if channel > 3:
|
||||
image = image[:, :, :3]
|
||||
return image
|
||||
|
||||
|
||||
# image = cv2.imread(file)
|
||||
# channel = image.shape[2] if len(image.shape) > 2 else 1
|
||||
# if channel > 3:
|
||||
# image = image[:, :, :3]
|
||||
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
# elif channel == 3:
|
||||
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
# return image
|
||||
|
||||
|
||||
def save_image(image, file):
|
||||
"""
|
||||
Save an image like pillow.
|
||||
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
file (str):
|
||||
"""
|
||||
# image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
||||
# cv2.imwrite(file, image)
|
||||
Image.fromarray(image).save(file)
|
||||
|
||||
|
||||
def crop(image, area):
|
||||
"""Crop image like pillow, when using opencv / numpy
|
||||
"""
|
||||
Crop image like pillow, when using opencv / numpy.
|
||||
Provides a black background if cropping outside of image.
|
||||
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
@ -362,11 +404,52 @@ def crop(image, area):
|
||||
Returns:
|
||||
np.ndarray:
|
||||
"""
|
||||
x1, y1, x2, y2 = area
|
||||
x1, y1, x2, y2 = map(int, map(round, area))
|
||||
h, w = image.shape[:2]
|
||||
border = np.maximum((0 - y1, y2 - h, 0 - x1, x2 - w), 0)
|
||||
x1, y1, x2, y2 = np.maximum((x1, y1, x2, y2), 0)
|
||||
return cv2.copyMakeBorder(image[y1:y2, x1:x2], *border, borderType=cv2.BORDER_CONSTANT, value=(0, 0, 0))
|
||||
image = image[y1:y2, x1:x2]
|
||||
if sum(border) > 0:
|
||||
image = cv2.copyMakeBorder(image, *border, borderType=cv2.BORDER_CONSTANT, value=(0, 0, 0))
|
||||
return image
|
||||
|
||||
|
||||
def resize(image, size):
|
||||
"""
|
||||
Resize image like pillow image.resize(), but implement in opencv.
|
||||
Pillow uses PIL.Image.NEAREST by default.
|
||||
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
size: (x, y)
|
||||
|
||||
Returns:
|
||||
np.ndarray:
|
||||
"""
|
||||
return cv2.resize(image, size, interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
|
||||
def image_channel(image):
|
||||
"""
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
|
||||
Returns:
|
||||
int: 0 for grayscale, 3 for RGB.
|
||||
"""
|
||||
return image.shape[2] if len(image.shape) == 3 else 0
|
||||
|
||||
|
||||
def image_size(image):
|
||||
"""
|
||||
Args:
|
||||
image (np.ndarray):
|
||||
|
||||
Returns:
|
||||
int, int: width, height
|
||||
"""
|
||||
shape = image.shape
|
||||
return shape[1], shape[0]
|
||||
|
||||
|
||||
def rgb2gray(image):
|
||||
@ -404,15 +487,15 @@ def get_color(image, area):
|
||||
"""Calculate the average color of a particular area of the image.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot.
|
||||
image (np.ndarray): Screenshot.
|
||||
area (tuple): (upper_left_x, upper_left_y, bottom_right_x, bottom_right_y)
|
||||
|
||||
Returns:
|
||||
tuple: (r, g, b)
|
||||
"""
|
||||
temp = image.crop(area)
|
||||
stat = ImageStat.Stat(temp)
|
||||
return np.array(stat.mean)
|
||||
temp = crop(image, area)
|
||||
color = cv2.mean(temp)
|
||||
return color[:3]
|
||||
|
||||
|
||||
def color_similarity(color1, color2):
|
||||
@ -566,7 +649,7 @@ def color_bar_percentage(image, area, prev_color, reverse=False, starter=0, thre
|
||||
Returns:
|
||||
float: 0 to 1.
|
||||
"""
|
||||
image = np.array(image.crop(area))
|
||||
image = crop(image, area)
|
||||
image = image[:, ::-1, :] if reverse else image
|
||||
length = image.shape[1]
|
||||
prev_index = starter
|
||||
|
@ -184,7 +184,7 @@ class CampaignOcr(ModuleBase):
|
||||
self.stage_entrance: dict. Key, str, stage name. Value, Button, button to enter stage.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image):
|
||||
image (np.ndarray):
|
||||
"""
|
||||
self.stage_entrance = {}
|
||||
buttons = self.campaign_extract_name_image(image)
|
||||
|
@ -162,7 +162,7 @@ class GemsFarming(CampaignRun, Dock, EquipmentChange):
|
||||
list_level = ocr.ocr(self.device.image)
|
||||
|
||||
for button, level in zip(card_grids.buttons, list_level):
|
||||
if level == 1 and template.match(self.device.image.crop(button.area), similarity=SIM_VALUE):
|
||||
if level == 1 and template.match(self.image_crop(button), similarity=SIM_VALUE):
|
||||
return button
|
||||
|
||||
logger.info('No specific CV was found, try reversed order.')
|
||||
@ -171,7 +171,7 @@ class GemsFarming(CampaignRun, Dock, EquipmentChange):
|
||||
list_level = ocr.ocr(self.device.image)
|
||||
|
||||
for button, level in zip(card_grids.buttons, list_level):
|
||||
if level == 1 and template.match(self.device.image.crop(button.area), similarity=SIM_VALUE):
|
||||
if level == 1 and template.match(self.image_crop(button), similarity=SIM_VALUE):
|
||||
return button
|
||||
|
||||
return None
|
||||
|
@ -72,7 +72,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
similarity, button = TEMPLATE_COMBAT_LOADING.match_result(self.image_area((0, 620, 1280, 720)))
|
||||
similarity, button = TEMPLATE_COMBAT_LOADING.match_result(self.image_crop((0, 620, 1280, 720)))
|
||||
if similarity > 0.85:
|
||||
loading = (button.area[0] + 38 - LOADING_BAR.area[0]) / (LOADING_BAR.area[2] - LOADING_BAR.area[0])
|
||||
logger.attr('Loading', f'{int(loading * 100)}%')
|
||||
@ -85,7 +85,7 @@ class Combat(Level, HPBalancer, Retirement, SubmarineCall, CombatAuto, CombatMan
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
return self.appear(PAUSE) and np.max(self.image_area(PAUSE_DOUBLE_CHECK)) < 153
|
||||
return self.appear(PAUSE) and np.max(self.image_crop(PAUSE_DOUBLE_CHECK)) < 153
|
||||
|
||||
def ensure_combat_oil_loaded(self):
|
||||
self.wait_until_stable(COMBAT_OIL_LOADING)
|
||||
|
@ -38,10 +38,10 @@ class CombatManual(ModuleBase):
|
||||
return True
|
||||
|
||||
def handle_combat_weapon_release(self):
|
||||
if self.appear_then_click(READY_AIR_RAID, interval=5):
|
||||
return True
|
||||
if self.appear_then_click(READY_TORPEDO, interval=5):
|
||||
return True
|
||||
# if self.appear_then_click(READY_AIR_RAID, interval=5):
|
||||
# return True
|
||||
# if self.appear_then_click(READY_TORPEDO, interval=5):
|
||||
# return True
|
||||
|
||||
return False
|
||||
|
||||
|
@ -43,7 +43,7 @@ class RewardCommission(UI, InfoHandler):
|
||||
commission = []
|
||||
# Find white lines under each commission to locate them.
|
||||
# (597, 0, 619, 720) is somewhere with white lines only.
|
||||
color_height = np.mean(image.crop((597, 0, 619, 720)).convert('L'), axis=1)
|
||||
color_height = np.mean(rgb2gray(crop(image, (597, 0, 619, 720))), axis=1)
|
||||
parameters = {'height': 200, 'distance': 100}
|
||||
peaks, _ = signal.find_peaks(color_height, **parameters)
|
||||
# 67 is the height of commission list header
|
||||
|
@ -44,7 +44,7 @@ class Device(Screenshot, Control, AppControl):
|
||||
def screenshot(self):
|
||||
"""
|
||||
Returns:
|
||||
PIL.Image.Image:
|
||||
np.ndarray:
|
||||
"""
|
||||
self.stuck_record_check()
|
||||
super().screenshot()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import numpy as np
|
||||
from adbutils.errors import AdbError
|
||||
|
||||
from module.device.connection import Connection
|
||||
@ -57,7 +57,10 @@ class Adb(Connection):
|
||||
else:
|
||||
raise ScriptError(f'Unknown method to load screenshots: {method}')
|
||||
|
||||
return Image.open(BytesIO(screenshot)).convert('RGB')
|
||||
image = np.fromstring(screenshot, np.uint8)
|
||||
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
return image
|
||||
|
||||
def __process_screenshot(self, screenshot):
|
||||
for method in self.__screenshot_method_fixed:
|
||||
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
import lz4.block
|
||||
from PIL import Image
|
||||
from adbutils.errors import AdbError
|
||||
|
||||
from module.base.utils import *
|
||||
@ -139,7 +138,6 @@ class AScreenCap(Connection):
|
||||
image = cv2.flip(image, 0)
|
||||
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(image)
|
||||
return image
|
||||
|
||||
def __process_screenshot(self, screenshot):
|
||||
|
@ -69,8 +69,10 @@ class Uiautomator2(Connection):
|
||||
|
||||
@retry
|
||||
def screenshot_uiautomator2(self):
|
||||
image = self.u2.screenshot()
|
||||
# u2 already converted to RGB
|
||||
image = self.u2.screenshot(format='raw')
|
||||
image = np.fromstring(image, np.uint8)
|
||||
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
return image
|
||||
|
||||
@retry
|
||||
|
@ -3,6 +3,8 @@ import time
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from module.base.decorator import cached_property
|
||||
@ -21,13 +23,13 @@ class Screenshot(Adb, Uiautomator2, AScreenCap):
|
||||
_minicap_uninstalled = False
|
||||
_screenshot_interval_timer = Timer(0.1)
|
||||
_last_save_time = {}
|
||||
image: Image.Image
|
||||
image: np.ndarray
|
||||
|
||||
@timer
|
||||
def screenshot(self):
|
||||
"""
|
||||
Returns:
|
||||
PIL.Image.Image:
|
||||
np.ndarray:
|
||||
"""
|
||||
self._screenshot_interval_timer.wait()
|
||||
self._screenshot_interval_timer.reset()
|
||||
@ -80,7 +82,7 @@ class Screenshot(Adb, Uiautomator2, AScreenCap):
|
||||
os.mkdir(folder)
|
||||
|
||||
file = os.path.join(folder, file)
|
||||
self.image.save(file)
|
||||
self.image_save(file)
|
||||
self._last_save_time[genre] = now
|
||||
return True
|
||||
else:
|
||||
@ -97,6 +99,14 @@ class Screenshot(Adb, Uiautomator2, AScreenCap):
|
||||
logger.info(f'Screenshot interval set to {interval}s')
|
||||
self._screenshot_interval_timer.limit = interval
|
||||
|
||||
def image_show(self, image=None):
|
||||
if image is None:
|
||||
image = self.image
|
||||
Image.fromarray(image).show()
|
||||
|
||||
def image_save(self, file):
|
||||
cv2.imwrite(file, self.image)
|
||||
|
||||
def check_screen_size(self):
|
||||
"""
|
||||
Screen size must be 1280x720.
|
||||
@ -107,7 +117,7 @@ class Screenshot(Adb, Uiautomator2, AScreenCap):
|
||||
else:
|
||||
self._screen_size_checked = True
|
||||
# Check screen size
|
||||
width, height = self.image.size
|
||||
height, width, channel = self.image.shape
|
||||
logger.attr('Screen_size', f'{width}x{height}')
|
||||
if not (width == 1280 and height == 720):
|
||||
logger.critical(f'Resolution not supported: {width}x{height}')
|
||||
|
@ -1,8 +1,6 @@
|
||||
import re
|
||||
import time
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from module.base.button import ButtonGrid
|
||||
from module.base.decorator import Config, cached_property
|
||||
from module.base.filter import Filter
|
||||
@ -52,7 +50,6 @@ class RewardDorm(UI):
|
||||
out: page_dorm, with info_bar
|
||||
"""
|
||||
image = MASK_DORM.apply(np.array(self.device.image))
|
||||
image = Image.fromarray(image)
|
||||
loves = TEMPLATE_DORM_LOVE.match_multi(image, name='DORM_LOVE')
|
||||
coins = TEMPLATE_DORM_COIN.match_multi(image, name='DORM_COIN')
|
||||
logger.info(f'Dorm loves: {len(loves)}, Dorm coins: {len(coins)}')
|
||||
@ -181,7 +178,7 @@ class RewardDorm(UI):
|
||||
return Digit(grids.buttons, letter=(255, 255, 255), threshold=128, name='OCR_DORM_FOOD')
|
||||
|
||||
def _dorm_has_food(self, button):
|
||||
return np.min(rgb2gray(np.array(self.image_area(button)))) < 127
|
||||
return np.min(rgb2gray(np.array(self.image_crop(button)))) < 127
|
||||
|
||||
def _dorm_feed_click(self, button, count):
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ class EquipmentChange(Equipment):
|
||||
index = 0
|
||||
self.equipping_list = []
|
||||
for button in EQUIPMENT_GRID.buttons:
|
||||
crop_image = np.array(self.device.image.crop(button.area))
|
||||
crop_image = np.array(self.image_crop(button))
|
||||
edge_value = abs(np.mean(cv2.Sobel(crop_image, 3, 1, 1)))
|
||||
if edge_value > 0.1:
|
||||
self.equipping_list.append(index)
|
||||
@ -57,7 +57,7 @@ class EquipmentChange(Equipment):
|
||||
self.ui_click(click_button=UPGRADE_ENTER,
|
||||
check_button=UPGRADE_ENTER_CHECK, skip_first_screenshot=True)
|
||||
logger.info('Save equipment tamplate')
|
||||
self.equip_list[index] = self.image_area(EQUIP_SAVE)
|
||||
self.equip_list[index] = self.image_crop(EQUIP_SAVE)
|
||||
logger.info('Quit upgrade inform')
|
||||
self.ui_click(
|
||||
click_button=UPGRADE_QUIT, check_button=EQUIPMENT_OPEN, appear_button=UPGRADE_ENTER_CHECK,
|
||||
|
@ -15,7 +15,7 @@ class ExerciseCombat(HpDaemon, OpponentChoose, ExerciseEquipment):
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
return self.appear(PAUSE) and np.max(self.image_area(PAUSE_DOUBLE_CHECK)) < 153
|
||||
return self.appear(PAUSE) and np.max(self.image_crop(PAUSE_DOUBLE_CHECK)) < 153
|
||||
|
||||
def _combat_preparation(self):
|
||||
logger.info('Combat preparation')
|
||||
|
@ -24,7 +24,7 @@ class HpDaemon(ModuleBase):
|
||||
Returns:
|
||||
float: HP. 0 to 1.
|
||||
"""
|
||||
# bar = np.array(image.crop(area))
|
||||
# bar = crop(image, area)
|
||||
# length = bar.shape[1]
|
||||
# bar = np.swapaxes(bar, 0, 1)
|
||||
# bar = bar[::-1, :, :] if reverse else bar
|
||||
|
@ -18,7 +18,7 @@ class GuildLobby(GuildBase):
|
||||
Button: Button to enter guild report.
|
||||
"""
|
||||
# Find red color in the area of GUILD_REPORT_AVAILABLE
|
||||
image = color_similarity_2d(self.image_area(GUILD_REPORT_AVAILABLE), color=(255, 8, 8))
|
||||
image = color_similarity_2d(self.image_crop(GUILD_REPORT_AVAILABLE), color=(255, 8, 8))
|
||||
points = np.array(np.where(image > 221)).T[:, ::-1]
|
||||
if len(points):
|
||||
# The center of red dot
|
||||
|
@ -144,7 +144,7 @@ class GuildOperations(GuildBase):
|
||||
|
||||
list_expand = []
|
||||
list_enter = []
|
||||
dots = TEMPLATE_OPERATIONS_RED_DOT.match_multi(self.image_area(detection_area), threshold=5)
|
||||
dots = TEMPLATE_OPERATIONS_RED_DOT.match_multi(self.image_crop(detection_area), threshold=5)
|
||||
logger.info(f'Active operations found: {len(dots)}')
|
||||
for button in dots:
|
||||
button = button.move(vector=detection_area[:2])
|
||||
|
@ -56,7 +56,7 @@ class AmbushHandler(Combat):
|
||||
self.wait_until_appear_then_click(MAP_AMBUSH_EVADE)
|
||||
|
||||
self.wait_until_appear(INFO_BAR_1)
|
||||
image = info_letter_preprocess(np.array(self.image_area(INFO_BAR_DETECT)))
|
||||
image = info_letter_preprocess(np.array(self.image_crop(INFO_BAR_DETECT)))
|
||||
|
||||
if TEMPLATE_AMBUSH_EVADE_SUCCESS.match(image):
|
||||
logger.attr('Ambush_evade', 'success')
|
||||
@ -120,7 +120,7 @@ class AmbushHandler(Combat):
|
||||
return False
|
||||
|
||||
_ = self._load_walk_template
|
||||
image = info_letter_preprocess(np.array(self.image_area(INFO_BAR_DETECT)))
|
||||
image = info_letter_preprocess(np.array(self.image_crop(INFO_BAR_DETECT)))
|
||||
if TEMPLATE_MAP_WALK_OUT_OF_STEP.match(image):
|
||||
logger.warning('Map walk out of step.')
|
||||
self.handle_info_bar()
|
||||
|
@ -205,7 +205,7 @@ class AutoSearchHandler(EnemySearchingHandler):
|
||||
if drop:
|
||||
drop.handle_add(main=self, before=4)
|
||||
self.device.click(AUTO_SEARCH_MENU_EXIT)
|
||||
self.device.sleep(0.5)
|
||||
self.interval_reset(AUTO_SEARCH_MENU_EXIT)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -213,7 +213,7 @@ class InfoHandler(ModuleBase):
|
||||
Returns:
|
||||
list[Button]: List of story options, from upper to bottom. If no option found, return an empty list.
|
||||
"""
|
||||
image = color_similarity_2d(self.image_area(self._story_option_area), color=self._story_option_color) > 225
|
||||
image = color_similarity_2d(self.image_crop(self._story_option_area), color=self._story_option_color) > 225
|
||||
x_count = np.where(np.sum(image, axis=0) > 40)[0]
|
||||
if not len(x_count):
|
||||
return []
|
||||
|
@ -1,7 +1,5 @@
|
||||
import re
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from module.base.mask import Mask
|
||||
from module.ui.page import *
|
||||
|
||||
@ -9,33 +7,18 @@ MASK_MAIN = Mask('./assets/mask/MASK_MAIN.png')
|
||||
MASK_PLAYER = Mask('./assets/mask/MASK_PLAYER.png')
|
||||
|
||||
|
||||
def put_image_mask(image, mask):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image):
|
||||
mask (str): Filename
|
||||
|
||||
Returns:
|
||||
PIL.Image.Image:
|
||||
"""
|
||||
mask = Image.open(f'./assets/mask/{mask}.png').convert('L')
|
||||
new = Image.new('RGB', image.size, (0, 0, 0))
|
||||
new.paste(image, box=(0, 0, image.size[0], image.size[1]), mask=mask)
|
||||
return new
|
||||
|
||||
|
||||
def handle_sensitive_image(image):
|
||||
"""
|
||||
Args:
|
||||
image:
|
||||
|
||||
Returns:
|
||||
PIL.Image.Image:
|
||||
np.ndarray:
|
||||
"""
|
||||
if PLAYER_CHECK.match(image, offset=(30, 30)):
|
||||
image = Image.fromarray(MASK_PLAYER.apply(image), mode='RGB')
|
||||
image = MASK_PLAYER.apply(image)
|
||||
if MAIN_CHECK.match(image, offset=(30, 30)):
|
||||
image = Image.fromarray(MASK_MAIN.apply(image), mode='RGB')
|
||||
image = MASK_MAIN.apply(image)
|
||||
|
||||
return image
|
||||
|
||||
|
@ -105,7 +105,7 @@ class StrategyHandler(InfoHandler):
|
||||
Returns:
|
||||
int: Formation index.
|
||||
"""
|
||||
image = np.array(self.image_area(MAP_BUFF))
|
||||
image = np.array(self.image_crop(MAP_BUFF))
|
||||
if TEMPLATE_FORMATION_2.match(image):
|
||||
buff = 'double_line'
|
||||
elif TEMPLATE_FORMATION_1.match(image):
|
||||
|
@ -1,11 +1,10 @@
|
||||
import numpy as np
|
||||
from PIL import ImageStat
|
||||
from scipy import signal
|
||||
|
||||
from module.base.base import ModuleBase
|
||||
from module.base.button import Button
|
||||
from module.base.timer import Timer
|
||||
from module.base.utils import area_offset, color_similarity_2d, rgb2gray
|
||||
from module.base.utils import *
|
||||
from module.logger import logger
|
||||
from module.map.assets import *
|
||||
|
||||
@ -37,16 +36,17 @@ class FleetOperator:
|
||||
def parse_fleet_bar(self, image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Image of dropdown menu.
|
||||
image (np.ndarray): Image of dropdown menu.
|
||||
|
||||
Returns:
|
||||
list: List of int. Currently selected fleet ranges from 1 to 6.
|
||||
"""
|
||||
width, height = image_size(image)
|
||||
result = []
|
||||
for index, y in enumerate(range(0, image.size[1], self.FLEET_BAR_SHAPE_Y + self.FLEET_BAR_MARGIN_Y)):
|
||||
area = (0, y, image.size[0], y + self.FLEET_BAR_SHAPE_Y)
|
||||
stat = ImageStat.Stat(image.crop(area))
|
||||
if np.std(stat.mean, ddof=1) > self.FLEET_BAR_ACTIVE_STD:
|
||||
for index, y in enumerate(range(0, height, self.FLEET_BAR_SHAPE_Y + self.FLEET_BAR_MARGIN_Y)):
|
||||
area = (0, y, width, y + self.FLEET_BAR_SHAPE_Y)
|
||||
mean = get_color(image, area)
|
||||
if np.std(mean, ddof=1) > self.FLEET_BAR_ACTIVE_STD:
|
||||
result.append(index + 1)
|
||||
logger.info('Current selected: %s' % str(result))
|
||||
return result
|
||||
@ -173,7 +173,7 @@ class FleetOperator:
|
||||
Returns:
|
||||
list: List of int. Currently selected fleet ranges from 1 to 6.
|
||||
"""
|
||||
data = self.parse_fleet_bar(self.main.device.image.crop(self._bar.area))
|
||||
data = self.parse_fleet_bar(self.main.image_crop(self._bar))
|
||||
return data
|
||||
|
||||
def in_use(self):
|
||||
@ -187,7 +187,7 @@ class FleetOperator:
|
||||
|
||||
# Cropping FLEET_*_IN_USE to avoid detecting info_bar, also do the trick.
|
||||
# It also avoids wasting time on handling the info_bar.
|
||||
image = rgb2gray(np.array(self.main.image_area(self._in_use)))
|
||||
image = rgb2gray(np.array(self.main.image_crop(self._in_use)))
|
||||
return np.std(image.flatten(), ddof=1) > self.FLEET_IN_USE_STD
|
||||
|
||||
def bar_opened(self):
|
||||
@ -196,7 +196,7 @@ class FleetOperator:
|
||||
bool: If dropdown menu appears.
|
||||
"""
|
||||
# Check the brightness of the rightest column of the bar area.
|
||||
luma = rgb2gray(np.array(self.main.image_area(self._bar)))[:, -1]
|
||||
luma = rgb2gray(np.array(self.main.image_crop(self._bar)))[:, -1]
|
||||
return np.sum(luma > 127) / luma.size > 0.5
|
||||
|
||||
def ensure_to_be(self, index):
|
||||
@ -227,7 +227,7 @@ class FleetPreparation(ModuleBase):
|
||||
bool:
|
||||
"""
|
||||
area = (208, 130, 226, 551)
|
||||
image = color_similarity_2d(self.image_area(area), color=(249, 199, 0))
|
||||
image = color_similarity_2d(self.image_crop(area), color=(249, 199, 0))
|
||||
height = np.max(image, axis=1)
|
||||
parameters = {'height': 180, 'distance': 5}
|
||||
peaks, _ = signal.find_peaks(height, **parameters)
|
||||
|
@ -278,7 +278,7 @@ class MapOperation(MysteryHandler, FleetPreparation, Retirement, FastForwardHand
|
||||
"""
|
||||
if not self.map_cat_attack_timer.reached():
|
||||
return False
|
||||
if np.sum(color_similarity_2d(self.image_area(MAP_CAT_ATTACK), (255, 231, 123)) > 221) > 100:
|
||||
if np.sum(color_similarity_2d(self.image_crop(MAP_CAT_ATTACK), (255, 231, 123)) > 221) > 100:
|
||||
logger.info('Skip map cat attack')
|
||||
self.device.click(MAP_CAT_ATTACK)
|
||||
self.map_cat_attack_timer.reset()
|
||||
|
@ -109,7 +109,7 @@ class Homography:
|
||||
perspective_.load(image)
|
||||
self.load_homography(perspective=perspective_)
|
||||
elif file is not None:
|
||||
image_ = np.array(Image.open(file).convert('RGB'))
|
||||
image_ = load_image(file)
|
||||
perspective_ = Perspective(self.config)
|
||||
perspective_.load(image_)
|
||||
self.load_homography(perspective=perspective_)
|
||||
|
@ -94,13 +94,21 @@ class Ocr:
|
||||
return result
|
||||
|
||||
def ocr(self, image, direct_ocr=False):
|
||||
"""
|
||||
Args:
|
||||
image (np.ndarray, list[np.ndarray]):
|
||||
direct_ocr (bool): True to skip preprocess.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
self.cnocr.set_cand_alphabet(self.alphabet)
|
||||
if direct_ocr:
|
||||
image_list = [self.pre_process(np.array(i)) for i in image]
|
||||
image_list = [self.pre_process(i) for i in image]
|
||||
else:
|
||||
image_list = [self.pre_process(np.array(image.crop(area))) for area in self.buttons]
|
||||
image_list = [self.pre_process(crop(image, area)) for area in self.buttons]
|
||||
|
||||
# This will show the images feed to OCR model
|
||||
# self.cnocr.debug(image_list)
|
||||
|
@ -164,7 +164,7 @@ class OSFleet(OSCamera, Combat, Fleet, OSAsh):
|
||||
"""
|
||||
super().hp_get()
|
||||
ship_icon = self._hp_grid().crop((0, -67, 67, 0))
|
||||
need_repair = [TEMPLATE_EMPTY_HP.match(self.image_area(button)) for button in ship_icon.buttons]
|
||||
need_repair = [TEMPLATE_EMPTY_HP.match(self.image_crop(button)) for button in ship_icon.buttons]
|
||||
self.need_repair = need_repair
|
||||
logger.attr('Repair icon', need_repair)
|
||||
|
||||
|
@ -175,7 +175,7 @@ class GlobeCamera(GlobeOperation, ZoneManager):
|
||||
screen = self.globe2screen(location).flatten().round()
|
||||
screen = np.round(screen).astype(int).tolist()
|
||||
# Average color of whirlpool center
|
||||
center = np.array(self.image_area(screen))
|
||||
center = np.array(self.image_crop(screen))
|
||||
center = np.array([[cv2.mean(center), ], ]).astype(np.uint8)
|
||||
h, s, v = rgb2hsv(center)[0][0]
|
||||
# hsv usually to be (338, 74.9, 100)
|
||||
|
@ -1,7 +1,5 @@
|
||||
import time
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from module.base.utils import *
|
||||
from module.config.config import AzurLaneConfig
|
||||
from module.logger import logger
|
||||
@ -49,7 +47,7 @@ class GlobeDetection:
|
||||
logger.info('Loading OS globe map')
|
||||
|
||||
# Load GLOBE_MAP
|
||||
image = np.array(Image.open(GLOBE_MAP))
|
||||
image = load_image(GLOBE_MAP)
|
||||
image = self.find_peaks(image, para=self.config.OS_GLOBE_FIND_PEAKS_PARAMETERS)
|
||||
pad = self.config.OS_GLOBE_IMAGE_PAD
|
||||
image = np.pad(image, ((pad, pad), (pad, pad)), mode='constant', constant_values=0)
|
||||
|
@ -98,7 +98,7 @@ class GlobeOperation(ActionPointHandler, MapEventHandler):
|
||||
Returns:
|
||||
bool: If current zone has switch.
|
||||
"""
|
||||
# image = self.image_area(ZONE_SWITCH)
|
||||
# image = self.image_crop(ZONE_SWITCH)
|
||||
# center = np.array(image.size) / 2
|
||||
# count = 0
|
||||
# for corner in area2corner((0, 0, *image.size)):
|
||||
|
@ -46,16 +46,17 @@ class FleetSelector:
|
||||
def parse_fleet_bar(self, image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Image of dropdown menu.
|
||||
image (np.ndarray): Image of dropdown menu.
|
||||
|
||||
Returns:
|
||||
list: List of int. Currently selected fleet ranges from 1 to 4.
|
||||
"""
|
||||
width, height = image_size(image)
|
||||
result = []
|
||||
for index, y in enumerate(range(0, image.size[1], self.FLEET_BAR_SHAPE_Y + self.FLEET_BAR_MARGIN_Y)):
|
||||
area = (0, y, image.size[0], y + self.FLEET_BAR_SHAPE_Y)
|
||||
stat = ImageStat.Stat(image.crop(area))
|
||||
if np.std(stat.mean, ddof=1) > self.FLEET_BAR_ACTIVE_STD:
|
||||
for index, y in enumerate(range(0, height, self.FLEET_BAR_SHAPE_Y + self.FLEET_BAR_MARGIN_Y)):
|
||||
area = (0, y, width, y + self.FLEET_BAR_SHAPE_Y)
|
||||
mean = get_color(image, area)
|
||||
if np.std(mean, ddof=1) > self.FLEET_BAR_ACTIVE_STD:
|
||||
result.append(4 - index)
|
||||
|
||||
logger.info('Current selected: %s' % str(result))
|
||||
@ -66,7 +67,7 @@ class FleetSelector:
|
||||
Returns:
|
||||
list: List of int. Currently selected fleet ranges from 1 to 4.
|
||||
"""
|
||||
data = self.parse_fleet_bar(self.main.device.image.crop(self._bar.area))
|
||||
data = self.parse_fleet_bar(self.main.image_crop(self._bar))
|
||||
return data
|
||||
|
||||
def get_button(self, index):
|
||||
|
@ -1,5 +1,3 @@
|
||||
from PIL import Image
|
||||
|
||||
from module.base.mask import Mask
|
||||
from module.base.utils import *
|
||||
from module.config.config import AzurLaneConfig
|
||||
@ -44,7 +42,7 @@ class RadarGrid:
|
||||
config (AzurLaneConfig):
|
||||
"""
|
||||
self.location = location
|
||||
self.image = image
|
||||
self.image: np.ndarray = image
|
||||
self.center = center
|
||||
self.config = config
|
||||
self.is_fleet = np.sum(np.abs(location)) == 0
|
||||
@ -115,7 +113,7 @@ class RadarGrid:
|
||||
Returns:
|
||||
bool:
|
||||
"""
|
||||
image = self.image.crop(area_offset(area, self.center))
|
||||
image = crop(self.image, area_offset(area, self.center))
|
||||
mask = color_similarity_2d(image, color=color) > threshold
|
||||
return np.sum(mask) > count
|
||||
|
||||
@ -196,7 +194,7 @@ class Radar:
|
||||
Returns:
|
||||
|
||||
"""
|
||||
image = Image.fromarray(MASK_RADAR.apply(image))
|
||||
image = MASK_RADAR.apply(image)
|
||||
for grid in self:
|
||||
grid.image = image
|
||||
grid.reset()
|
||||
@ -231,7 +229,7 @@ class Radar:
|
||||
Such as [57.70732954 50.89636818].
|
||||
"""
|
||||
radius = (15, 82)
|
||||
image = image.crop(area_offset((-radius[1], -radius[1], radius[1], radius[1]), self.center))
|
||||
image = crop(image, area_offset((-radius[1], -radius[1], radius[1], radius[1]), self.center))
|
||||
# image.show()
|
||||
points = np.where(color_similarity_2d(image, color=(255, 255, 255)) > 250)
|
||||
points = np.array(points).T[:, ::-1] - (radius[1], radius[1])
|
||||
|
@ -145,7 +145,7 @@ class MapOrderHandler(MapOperation, ActionPointHandler, MapEventHandler, ZoneMan
|
||||
"""
|
||||
if not self.map_cat_attack_timer.reached():
|
||||
return False
|
||||
if np.sum(color_similarity_2d(self.image_area(MAP_CAT_ATTACK), (255, 231, 123)) > 221) > 100:
|
||||
if np.sum(color_similarity_2d(self.image_crop(MAP_CAT_ATTACK), (255, 231, 123)) > 221) > 100:
|
||||
logger.info('Skip map cat attack')
|
||||
self.device.click(CLICK_SAFE_AREA)
|
||||
self.map_cat_attack_timer.reset()
|
||||
|
@ -22,7 +22,7 @@ class MissionHandler(GlobeOperation, ZoneManager):
|
||||
"""
|
||||
area = (341, 72, 1217, 648)
|
||||
# Points of the yellow `!`
|
||||
image = color_similarity_2d(self.image_area(area), color=(255, 207, 66))
|
||||
image = color_similarity_2d(self.image_crop(area), color=(255, 207, 66))
|
||||
points = np.array(np.where(image > 235)).T[:, ::-1]
|
||||
if not len(points):
|
||||
logger.warning('Unable to find mission on OS mission map')
|
||||
|
@ -49,7 +49,7 @@ def get_research_series(image):
|
||||
------- --- v
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image):
|
||||
image (np.ndarray):
|
||||
|
||||
Returns:
|
||||
list[int]: Such as [1, 1, 1, 2, 3]
|
||||
@ -62,7 +62,7 @@ def get_research_series(image):
|
||||
parameters = {'height': 160, 'prominence': 50}
|
||||
|
||||
for button in RESEARCH_SERIES:
|
||||
im = color_similarity_2d(image.crop(button.area).resize((46, 25)), color=(255, 255, 255))
|
||||
im = color_similarity_2d(resize(crop(image, button.area), (46, 25)), color=(255, 255, 255))
|
||||
peaks = [len(signal.find_peaks(row, **parameters)[0]) for row in im[5:-5]]
|
||||
upper, lower = max(peaks), min(peaks)
|
||||
# print(peaks)
|
||||
@ -81,7 +81,7 @@ def get_research_series(image):
|
||||
def get_research_name(image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image):
|
||||
image (np.ndarray):
|
||||
|
||||
Returns:
|
||||
list[str]: Such as ['D-057-UL', 'D-057-UL', 'D-057-UL', 'D-057-UL', 'D-057-UL']
|
||||
@ -137,8 +137,8 @@ def parse_time(string):
|
||||
def match_template(image, template, area, offset=30, threshold=0.85):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
template (PIL.Image.Image):
|
||||
image (np.ndarray): Screenshot
|
||||
template (np.ndarray):
|
||||
area (tuple): Crop area of image.
|
||||
offset (int, tuple): Detection area offset.
|
||||
threshold (float): 0-1. Similarity. Lower than this value will return float(0).
|
||||
@ -149,7 +149,7 @@ def match_template(image, template, area, offset=30, threshold=0.85):
|
||||
offset = np.array((-offset[0], -offset[1], offset[0], offset[1]))
|
||||
else:
|
||||
offset = np.array((0, -offset, 0, offset))
|
||||
image = np.array(image.crop(offset + area))
|
||||
image = crop(image, offset + area)
|
||||
template = np.array(template)
|
||||
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
|
||||
_, sim, _, point = cv2.minMaxLoc(res)
|
||||
@ -162,7 +162,7 @@ def get_research_series_jp(image):
|
||||
Almost the same as get_research_series except the button area.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Returns:
|
||||
series (string):
|
||||
@ -172,7 +172,7 @@ def get_research_series_jp(image):
|
||||
|
||||
area = SERIES_DETAIL.area
|
||||
# Resize is not needed because only one area will be checked in JP server.
|
||||
im = color_similarity_2d(image.crop(area), color=(255, 255, 255))
|
||||
im = color_similarity_2d(crop(image, area), color=(255, 255, 255))
|
||||
peaks = [len(signal.find_peaks(row, **parameters)[0]) for row in im[2:-2]]
|
||||
upper, lower = max(peaks), min(peaks)
|
||||
# print(upper, lower)
|
||||
@ -190,7 +190,7 @@ def get_research_series_jp(image):
|
||||
def get_research_duration_jp(image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Returns:
|
||||
duration (int): number of seconds
|
||||
@ -203,7 +203,7 @@ def get_research_duration_jp(image):
|
||||
def get_research_genre_jp(image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Returns:
|
||||
genre (string):
|
||||
@ -227,7 +227,7 @@ def get_research_cost_jp(image):
|
||||
so simply setting a lower threshold while matching can do the job.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Returns:
|
||||
costs (string): dict
|
||||
@ -238,7 +238,8 @@ def get_research_cost_jp(image):
|
||||
templates = load_folder(folder)
|
||||
costs = {'coin': False, 'cube': False, 'plate': False}
|
||||
for name, template in templates.items():
|
||||
template = load_image(template).resize(size=size_template).crop(area_template)
|
||||
template = load_image(template)
|
||||
template = crop(resize(template, size_template), area_template)
|
||||
sim = match_template(image=image,
|
||||
template=template,
|
||||
area=DETAIL_COST.area,
|
||||
@ -264,7 +265,7 @@ def get_research_ship_jp(image):
|
||||
so the button DETAIL_BLUEPRINT should not cover only the first one of 4 items.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Returns:
|
||||
ship (string):
|
||||
@ -290,7 +291,7 @@ def get_research_ship_jp(image):
|
||||
def research_jp_detect(image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
|
||||
Return:
|
||||
project (ResearchProjectJp):
|
||||
@ -527,7 +528,7 @@ class ResearchSelector(UI):
|
||||
Adding this argument is just to eusure all "research_detect" have the same arguments.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshots
|
||||
image (np.ndarray): Screenshots
|
||||
"""
|
||||
projects = []
|
||||
proj_sorted = []
|
||||
@ -560,7 +561,7 @@ class ResearchSelector(UI):
|
||||
def research_detect(self, image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshots
|
||||
image (np.ndarray): Screenshots
|
||||
"""
|
||||
projects = []
|
||||
for name, series in zip(get_research_name(image), get_research_series(image)):
|
||||
|
@ -154,7 +154,7 @@ class RewardResearch(ResearchSelector):
|
||||
else:
|
||||
self.device.screenshot()
|
||||
|
||||
max_rgb = np.max(rgb2gray(np.array(self.image_area(RESEARCH_UNAVAILABLE))))
|
||||
max_rgb = np.max(rgb2gray(np.array(self.image_crop(RESEARCH_UNAVAILABLE))))
|
||||
|
||||
# Don't use interval here, RESEARCH_CHECK already appeared 5 seconds ago
|
||||
if click_timer.reached() and self.appear(RESEARCH_CHECK, offset=(20, 20)):
|
||||
|
@ -152,7 +152,7 @@ class Enhancement(Dock):
|
||||
|
||||
# Respond accordingly based on info_bar information
|
||||
if self.info_bar_count():
|
||||
image = info_letter_preprocess(np.array(self.image_area(INFO_BAR_DETECT)))
|
||||
image = info_letter_preprocess(np.array(self.image_crop(INFO_BAR_DETECT)))
|
||||
if TEMPLATE_ENHANCE_SUCCESS.match(image):
|
||||
enhanced = True
|
||||
elif TEMPLATE_ENHANCE_FAILED.match(image):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from module.base.button import ButtonGrid
|
||||
from module.base.timer import Timer
|
||||
from module.base.utils import get_color, color_similar
|
||||
from module.base.utils import get_color, color_similar, resize
|
||||
from module.combat.assets import GET_ITEMS_1
|
||||
from module.exception import RequestHumanTakeover, ScriptError
|
||||
from module.logger import logger
|
||||
@ -329,7 +329,7 @@ class Retirement(Enhancement):
|
||||
if self.config.GemsFarming_CommonCV == 'any':
|
||||
for commen_cv_name in ['BOGUE', 'HERMES', 'LANGLEY', 'RANGER']:
|
||||
template = globals()[f'TEMPLATE_{commen_cv_name}']
|
||||
sim, button = template.match_result(self.device.image.resize(size=(1189, 669)))
|
||||
sim, button = template.match_result(resize(self.device.image, size=(1189, 669)))
|
||||
|
||||
if sim > self.config.COMMON_CV_THRESHOLD:
|
||||
return Button(button=tuple(_ * 155 // 144 for _ in button.button), area=button.area,
|
||||
@ -340,7 +340,7 @@ class Retirement(Enhancement):
|
||||
else:
|
||||
|
||||
template = globals()[f'TEMPLATE_{self.config.GemsFarming_CommonCV.upper()}']
|
||||
sim, button = template.match_result(self.device.image.resize(size=(1189, 669)))
|
||||
sim, button = template.match_result(resize(self.device.image, size=(1189, 669)))
|
||||
|
||||
if sim > self.config.COMMON_CV_THRESHOLD:
|
||||
return Button(button=tuple(_ * 155 // 144 for _ in button.button), area=button.area, color=button.color,
|
||||
|
@ -163,7 +163,7 @@ class CampaignSos(CampaignRun, CampaignBase):
|
||||
self.device.screenshot()
|
||||
|
||||
if self.appear(SIGNAL_LIST_CHECK, offset=(20, 20), interval=2):
|
||||
image = self.image_area(area_pad(entrance.area, pad=-30))
|
||||
image = self.image_crop(area_pad(entrance.area, pad=-30))
|
||||
if TEMPLATE_SIGNAL_SEARCH.match(image):
|
||||
self.device.click(entrance)
|
||||
if TEMPLATE_SIGNAL_GOTO.match(image):
|
||||
|
@ -1,35 +1,20 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import requests
|
||||
from PIL import Image
|
||||
from requests.adapters import HTTPAdapter
|
||||
|
||||
from module.base.utils import save_image
|
||||
from module.config.config import AzurLaneConfig
|
||||
from module.logger import logger
|
||||
from module.statistics.utils import *
|
||||
|
||||
|
||||
def pack(img_list):
|
||||
"""
|
||||
Stack images vertically.
|
||||
|
||||
Args:
|
||||
img_list (list): List of pillow image
|
||||
|
||||
Returns:
|
||||
Pillow image
|
||||
"""
|
||||
img_list = [np.array(i) for i in img_list]
|
||||
image = cv2.vconcat(img_list)
|
||||
image = Image.fromarray(image, mode='RGB')
|
||||
return image
|
||||
from module.statistics.utils import pack
|
||||
|
||||
|
||||
class DropImage:
|
||||
def __init__(self, stat, genre, save, upload):
|
||||
def __init__(self, stat, genre, save, upload, info=''):
|
||||
"""
|
||||
Args:
|
||||
stat (AzurStats):
|
||||
@ -41,6 +26,7 @@ class DropImage:
|
||||
self.genre = str(genre)
|
||||
self.save = bool(save)
|
||||
self.upload = bool(upload)
|
||||
self.info = info
|
||||
self.images = []
|
||||
|
||||
def add(self, image):
|
||||
@ -76,7 +62,7 @@ class DropImage:
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self:
|
||||
self.stat.commit(images=self.images, genre=self.genre, save=self.save, upload=self.upload)
|
||||
self.stat.commit(images=self.images, genre=self.genre, save=self.save, upload=self.upload, info=self.info)
|
||||
|
||||
|
||||
class AzurStats:
|
||||
@ -93,21 +79,21 @@ class AzurStats:
|
||||
def _user_agent(self):
|
||||
return f'Alas ({str(self.config.DropRecord_AzurStatsID)})'
|
||||
|
||||
def _upload(self, image, genre, timestamp):
|
||||
def _upload(self, image, genre, filename):
|
||||
"""
|
||||
Args:
|
||||
image: Image to upload.
|
||||
genre (str):
|
||||
timestamp (int): Millisecond timestamp.
|
||||
filename (str): 'xxx.png'
|
||||
|
||||
Returns:
|
||||
bool: If success
|
||||
"""
|
||||
output = io.BytesIO()
|
||||
image.save(output, format='png')
|
||||
Image.fromarray(image, mode='RGB').save(output, format='png')
|
||||
output.seek(0)
|
||||
|
||||
data = {'file': (f'{timestamp}.png', output, 'image/png')}
|
||||
data = {'file': (filename, output, 'image/png')}
|
||||
headers = {'user-agent': self._user_agent()}
|
||||
session = requests.Session()
|
||||
session.mount('http://', HTTPAdapter(max_retries=5))
|
||||
@ -133,12 +119,12 @@ class AzurStats:
|
||||
f'status_code: {resp.status_code}, returns: {resp.text}')
|
||||
return False
|
||||
|
||||
def _save(self, image, genre, timestamp):
|
||||
def _save(self, image, genre, filename):
|
||||
"""
|
||||
Args:
|
||||
image: Image to save.
|
||||
genre (str): Name of sub folder.
|
||||
timestamp (int): Millisecond timestamp.
|
||||
filename (str): 'xxx.png'
|
||||
|
||||
Returns:
|
||||
bool: If success
|
||||
@ -146,8 +132,8 @@ class AzurStats:
|
||||
try:
|
||||
folder = os.path.join(str(self.config.DropRecord_SaveFolder), genre)
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
file = os.path.join(folder, f'{timestamp}.png')
|
||||
image.save(file)
|
||||
file = os.path.join(folder, filename)
|
||||
save_image(image, file)
|
||||
logger.info(f'Image save success, file: {file}')
|
||||
return True
|
||||
except Exception as e:
|
||||
@ -155,13 +141,14 @@ class AzurStats:
|
||||
|
||||
return False
|
||||
|
||||
def commit(self, images, genre, save=False, upload=False):
|
||||
def commit(self, images, genre, save=False, upload=False, info=''):
|
||||
"""
|
||||
Args:
|
||||
images (list): List of pillow images
|
||||
genre (str):
|
||||
save (bool): If save image to local file system.
|
||||
upload (bool): If upload image to Azur Stats.
|
||||
info (str): Extra info append to filename.
|
||||
|
||||
Returns:
|
||||
bool: If commit.
|
||||
@ -174,21 +161,27 @@ class AzurStats:
|
||||
image = pack(images)
|
||||
now = int(time.time() * 1000)
|
||||
|
||||
if info:
|
||||
filename = f'{now}_{info}.png'
|
||||
else:
|
||||
filename = f'{now}.png'
|
||||
|
||||
if save:
|
||||
self._save(image, genre=genre, timestamp=now)
|
||||
self._save(image, genre=genre, filename=filename)
|
||||
if upload:
|
||||
self._upload(image, genre=genre, timestamp=now)
|
||||
self._upload(image, genre=genre, filename=filename)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, genre, save=False, upload=False):
|
||||
def new(self, genre, save=False, upload=False, info=''):
|
||||
"""
|
||||
Args:
|
||||
genre (str):
|
||||
save (bool): If save image to local file system.
|
||||
upload (bool): If upload image to Azur Stats.
|
||||
info (str): Extra info append to filename.
|
||||
|
||||
Returns:
|
||||
DropImage:
|
||||
"""
|
||||
return DropImage(stat=self, genre=genre, save=save, upload=upload)
|
||||
return DropImage(stat=self, genre=genre, save=save, upload=upload, info=info)
|
||||
|
@ -144,7 +144,7 @@ if __name__ == '__main__':
|
||||
# Folder to load templates and save new templates.
|
||||
# This will load {DROP_FOLDER}/{TEMPLATE_FOLDER}.
|
||||
# If folder doesn't exist, auto copy from './assets/stats_basic'
|
||||
DropStatistics.TEMPLATE_FOLDER = 'item_templates'
|
||||
DropStatistics.TEMPLATE_FOLDER = 'campaign_13_1_template'
|
||||
# 'cpu' or 'gpu', default to 'cpu'.
|
||||
# Use 'gpu' for faster prediction, but you must have the gpu version of mxnet installed.
|
||||
DropStatistics.CNOCR_CONTEXT = 'cpu'
|
||||
@ -155,11 +155,11 @@ if __name__ == '__main__':
|
||||
DropStatistics.CSV_OVERWRITE = True
|
||||
# Usually to be 'utf-8'.
|
||||
# For better Chinese export to Excel, use 'gbk'.
|
||||
DropStatistics.CSV_ENCODING = 'utf-8'
|
||||
DropStatistics.CSV_ENCODING = 'gbk'
|
||||
# campaign names to export under DROP_FOLDER.
|
||||
# This will load {DROP_FOLDER}/{CAMPAIGN}.
|
||||
# Just a demonstration here, you should modify it to your own.
|
||||
CAMPAIGNS = ['t1', 't2', 't3', 't4']
|
||||
CAMPAIGNS = ['campaign_13_1']
|
||||
|
||||
stat = DropStatistics()
|
||||
|
||||
@ -184,5 +184,5 @@ if __name__ == '__main__':
|
||||
After run, comment again.
|
||||
Results are saved in {DROP_FOLDER}/{CSV_FILE}.
|
||||
"""
|
||||
# for i in CAMPAIGNS:
|
||||
# stat.extract_drop(i)
|
||||
for i in CAMPAIGNS:
|
||||
stat.extract_drop(i)
|
||||
|
@ -36,7 +36,7 @@ class GetItemsStatistics:
|
||||
Returns:
|
||||
bool: If the number of items in row is odd.
|
||||
"""
|
||||
image = np.array(image.crop(GET_ITEMS_ODD.area))
|
||||
image = crop(image, GET_ITEMS_ODD.area)
|
||||
return np.mean(rgb2gray(image) > 127) > 0.1
|
||||
|
||||
def _stats_get_items_load(self, image):
|
||||
@ -85,11 +85,11 @@ class GetItemsStatistics:
|
||||
def extract_template(self, image, folder):
|
||||
"""
|
||||
Args:
|
||||
image: Pillow image.
|
||||
image:
|
||||
folder: Folder to save new templates.
|
||||
"""
|
||||
self._stats_get_items_load(image)
|
||||
if ITEM_GROUP.grids is not None:
|
||||
new = ITEM_GROUP.extract_template(image)
|
||||
for name, im in new.items():
|
||||
im.save(os.path.join(folder, f'{name}.png'))
|
||||
cv2.imwrite(os.path.join(folder, f'{name}.png'), im)
|
||||
|
@ -35,7 +35,9 @@ class Item:
|
||||
"""
|
||||
self.image_raw = image
|
||||
self._button = button
|
||||
image = np.array(image.crop(button.area))
|
||||
image = crop(image, button.area)
|
||||
from PIL import Image
|
||||
Image.fromarray(image).crop()
|
||||
if image.shape == self.IMAGE_SHAPE:
|
||||
self.image = image
|
||||
else:
|
||||
@ -93,7 +95,7 @@ class Item:
|
||||
return self._button.button
|
||||
|
||||
def crop(self, area):
|
||||
return self.image_raw.crop(area_offset(area, offset=self._button.area[:2]))
|
||||
return crop(self.image_raw, area_offset(area, offset=self._button.area[:2]))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other
|
||||
@ -228,7 +230,7 @@ class ItemGrid:
|
||||
for item in self.items:
|
||||
name = self.match_template(item.image)
|
||||
if name not in prev:
|
||||
new[name] = Image.fromarray(item.image)
|
||||
new[name] = item.image
|
||||
|
||||
return new
|
||||
|
||||
@ -242,7 +244,7 @@ class ItemGrid:
|
||||
Returns:
|
||||
str: Template name.
|
||||
"""
|
||||
image = np.array(item.crop(self.cost_area))
|
||||
image = item.crop(self.cost_area)
|
||||
names = np.array(list(self.cost_templates.keys()))[np.argsort(list(self.cost_templates_hit.values()))][::-1]
|
||||
for name in names:
|
||||
res = cv2.matchTemplate(image, self.cost_templates[name], cv2.TM_CCOEFF_NORMED)
|
||||
@ -254,7 +256,7 @@ class ItemGrid:
|
||||
# self.next_cost_template_index += 1
|
||||
# name = str(self.next_cost_template_index)
|
||||
# logger.info(f'New template: {name}')
|
||||
# self.cost_templates[name] = np.array(item.crop(self.cost_area))
|
||||
# self.cost_templates[name] = item.crop(self.cost_area)
|
||||
# self.cost_templates_hit[name] = self.cost_templates_hit.get(name, 0) + 1
|
||||
# return name
|
||||
|
||||
|
@ -2,7 +2,8 @@ import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from module.base.utils import crop, image_size
|
||||
|
||||
|
||||
class ImageError(Exception):
|
||||
@ -31,17 +32,6 @@ def load_folder(folder):
|
||||
return out
|
||||
|
||||
|
||||
def load_image(file):
|
||||
"""
|
||||
Args:
|
||||
file (str): Path to file.
|
||||
|
||||
Returns:
|
||||
Pillow image.
|
||||
"""
|
||||
return Image.open(file).convert('RGB')
|
||||
|
||||
|
||||
def pack(img_list):
|
||||
"""
|
||||
Stack images vertically.
|
||||
@ -54,7 +44,6 @@ def pack(img_list):
|
||||
"""
|
||||
img_list = [np.array(i) for i in img_list]
|
||||
image = cv2.vconcat(img_list)
|
||||
image = Image.fromarray(image, mode='RGB')
|
||||
return image
|
||||
|
||||
|
||||
@ -68,10 +57,10 @@ def unpack(image):
|
||||
Returns:
|
||||
list: List of pillow image.
|
||||
"""
|
||||
if image.size == (1280, 720):
|
||||
size = image_size(image)
|
||||
if size == (1280, 720):
|
||||
return [image]
|
||||
else:
|
||||
size = image.size
|
||||
if size[0] != 1280 or size[1] % 720 != 0:
|
||||
raise ImageError(f'Unexpected image size: {size}')
|
||||
return [image.crop((0, n * 720, 1280, (n + 1) * 720)) for n in range(size[1] // 720)]
|
||||
return [crop(image, (0, n * 720, 1280, (n + 1) * 720)) for n in range(size[1] // 720)]
|
||||
|
@ -68,10 +68,10 @@ class Book:
|
||||
def __init__(self, image, button):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image):
|
||||
image (np.ndarray):
|
||||
button (Button):
|
||||
"""
|
||||
image = image.crop(button.area)
|
||||
image = crop(image, button.area)
|
||||
self.button = button
|
||||
|
||||
self.genre = 0
|
||||
@ -86,7 +86,7 @@ class Book:
|
||||
if color_similar(color1=color, color2=value, threshold=30):
|
||||
self.tier = key
|
||||
|
||||
color = color_similarity_2d(image.crop((15, 0, 97, 13)), color=(148, 251, 99))
|
||||
color = color_similarity_2d(crop(image, (15, 0, 97, 13)), color=(148, 251, 99))
|
||||
self.exp = bool(np.sum(color > 221) > 50)
|
||||
|
||||
self.valid = bool(self.genre and self.tier)
|
||||
@ -100,11 +100,11 @@ class Book:
|
||||
def check_selected(self, image):
|
||||
"""
|
||||
Args:
|
||||
image (PIL.Image.Image): Screenshot
|
||||
image (np.ndarray): Screenshot
|
||||
"""
|
||||
area = self.button.area
|
||||
check_area = tuple([area[0], area[3] + 2, area[2], area[3] + 4])
|
||||
im = np.array(image.crop(check_area).convert('L'))
|
||||
im = rgb2gray(crop(image, check_area))
|
||||
return True if np.mean(im) > 127 else False
|
||||
|
||||
def __str__(self):
|
||||
@ -130,7 +130,7 @@ class RewardTacticalClass(UI):
|
||||
"""
|
||||
# Area of the white line under student cards.
|
||||
area = (360, 680, 1280, 700)
|
||||
mask = color_similarity_2d(self.image_area(area), color=(255, 255, 255)) > 235
|
||||
mask = color_similarity_2d(self.image_crop(area), color=(255, 255, 255)) > 235
|
||||
points = np.array(np.where(mask)).T
|
||||
# Width of card is 200 px
|
||||
points = Points(points).group(threshold=210)
|
||||
|
@ -44,7 +44,7 @@ class Scroll:
|
||||
Returns:
|
||||
np.ndarray: Shape (n,), dtype bool.
|
||||
"""
|
||||
image = np.array(main.image_area(self.area))
|
||||
image = np.array(main.image_crop(self.area))
|
||||
image = color_similarity_2d(image, color=self.color)
|
||||
mask = np.max(image, axis=1 if self.is_vertical else 0) > self.color_threshold
|
||||
self.length = np.sum(mask)
|
||||
|
Loading…
Reference in New Issue
Block a user