Refactor: Use numpy image cache instead of pillow image

This commit is contained in:
LmeSzinc 2022-01-23 15:50:15 +08:00
parent 6df7684a37
commit 65f166ad12
51 changed files with 286 additions and 228 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -44,7 +44,7 @@ class Device(Screenshot, Control, AppControl):
def screenshot(self):
"""
Returns:
PIL.Image.Image:
np.ndarray:
"""
self.stuck_record_check()
super().screenshot()

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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}')

View File

@ -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):
"""

View File

@ -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,

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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()

View File

@ -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

View File

@ -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 []

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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_)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)):

View File

@ -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):

View File

@ -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])

View File

@ -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()

View File

@ -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')

View File

@ -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)):

View File

@ -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)):

View File

@ -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):

View File

@ -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,

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)]

View File

@ -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)

View File

@ -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)