import osimport tracebackfrom PIL import ImageDrawfrom module.base.decorator import cached_propertyfrom module.base.resource import Resourcefrom module.base.utils import *from module.config.server import VALID_SERVERfrom module.logger import loggerclass Button(Resource):def __init__(self, area, color, button, file=None, name=None):"""Initialize a Button instance.Args:area (dict[tuple], tuple): Area that the button would appear on the image.(upper_left_x, upper_left_y, bottom_right_x, bottom_right_y)color (dict[tuple], tuple): Color we expect the area would be.(r, g, b)button (dict[tuple], tuple): Area to be click if button appears on the image.(upper_left_x, upper_left_y, bottom_right_x, bottom_right_y)If tuple is empty, this object can be use as a checker.Examples:BATTLE_PREPARATION = Button(area=(1562, 908, 1864, 1003),color=(231, 181, 90),button=(1562, 908, 1864, 1003))"""self.raw_area = areaself.raw_color = colorself.raw_button = buttonself.raw_file = fileself.raw_name = nameself._button_offset = Noneself._match_init = Falseself._match_binary_init = Falseself._match_luma_init = Falseself.image = Noneself.image_binary = Noneself.image_luma = Noneif self.file:self.resource_add(key=self.file)cached = ['area', 'color', '_button', 'file', 'name', 'is_gif']@cached_propertydef area(self):return self.parse_property(self.raw_area)@cached_propertydef color(self):return self.parse_property(self.raw_color)@cached_propertydef _button(self):return self.parse_property(self.raw_button)@cached_propertydef file(self):return self.parse_property(self.raw_file)@cached_propertydef name(self):if self.raw_name:return self.raw_nameelif self.file:return os.path.splitext(os.path.split(self.file)[1])[0]else:return 'BUTTON'@cached_propertydef is_gif(self):if self.file:return os.path.splitext(self.file)[1] == '.gif'else:return Falsedef __str__(self):return self.name__repr__ = __str__def __eq__(self, other):return str(self) == str(other)def __hash__(self):return hash(self.name)def __bool__(self):return True@propertydef button(self):if self._button_offset is None:return self._buttonelse:return self._button_offsetdef appear_on(self, image, threshold=10):"""Check if the button appears on the image.Args:image (np.ndarray): Screenshot.threshold (int): Default to 10.Returns:bool: True if button appears on screenshot."""return color_similar(color1=get_color(image, self.area),color2=self.color,threshold=threshold)def load_color(self, image):"""Load color from the specific area of the given image.This method is irreversible, this would be only use in some special occasion.Args:image: Another screenshot.Returns:tuple: Color (r, g, b)."""self.__dict__['color'] = get_color(image, self.area)self.image = crop(image, self.area)self.__dict__['is_gif'] = Falsereturn self.colordef load_offset(self, button):"""Load offset from another button.Args:button (Button):"""offset = np.subtract(button.button, button._button)[:2]self._button_offset = area_offset(self._button, offset=offset)def clear_offset(self):self._button_offset = Nonedef ensure_template(self):"""Load asset image.If needs to call self.match, call this first."""if not self._match_init:if self.is_gif:self.image = []import imageiofor image in imageio.mimread(self.file):image = image[:, :, :3].copy() if len(image.shape) == 3 else imageimage = crop(image, self.area)self.image.append(image)else:self.image = load_image(self.file, self.area)self._match_init = Truedef ensure_binary_template(self):"""Load asset image.If needs to call self.match, call this first."""if not self._match_binary_init:if self.is_gif:self.image_binary = []for image in self.image:image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)_, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)self.image_binary.append(image_binary)else:image_gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)_, self.image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)self._match_binary_init = Truedef ensure_luma_template(self):if not self._match_luma_init:if self.is_gif:self.image_luma = []for image in self.image:luma = rgb2luma(image)self.image_luma.append(luma)else:self.image_luma = rgb2luma(self.image)self._match_luma_init = Truedef resource_release(self):super().resource_release()self.image = Noneself.image_binary = Noneself.image_luma = Noneself._match_init = Falseself._match_binary_init = Falseself._match_luma_init = Falsedef match(self, image, offset=30, similarity=0.85):"""Detects button by template matching. To Some button, its location may not be static.Args:image: Screenshot.offset (int, tuple): Detection area offset.similarity (float): 0-1. Similarity.Returns:bool."""self.ensure_template()if isinstance(offset, tuple):if len(offset) == 2:offset = np.array((-offset[0], -offset[1], offset[0], offset[1]))else:offset = np.array(offset)else:offset = np.array((-3, -offset, 3, offset))image = crop(image, offset + self.area, copy=False)if self.is_gif:for template in self.image:res = cv2.matchTemplate(template, image, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))if sim > similarity:return Truereturn Falseelse:res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))return sim > similaritydef match_binary(self, image, offset=30, similarity=0.85):"""Detects button by template matching. To Some button, its location may not be static.This method will apply template matching under binarization.Args:image: Screenshot.offset (int, tuple): Detection area offset.similarity (float): 0-1. Similarity.Returns:bool."""self.ensure_template()self.ensure_binary_template()if isinstance(offset, tuple):if len(offset) == 2:offset = np.array((-offset[0], -offset[1], offset[0], offset[1]))else:offset = np.array(offset)else:offset = np.array((-3, -offset, 3, offset))image = crop(image, offset + self.area, copy=False)if self.is_gif:for template in self.image_binary:# grayingimage_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# binarization_, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)# template matchingres = cv2.matchTemplate(template, image_binary, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))if sim > similarity:return Truereturn Falseelse:# grayingimage_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# binarization_, image_binary = cv2.threshold(image_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)# template matchingres = cv2.matchTemplate(self.image_binary, image_binary, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))return sim > similaritydef match_luma(self, image, offset=30, similarity=0.85):"""Detects button by template matching under Y channel (Luminance)Args:image: Screenshot.offset (int, tuple): Detection area offset.similarity (float): 0-1. Similarity.Returns:bool."""self.ensure_template()self.ensure_luma_template()if isinstance(offset, tuple):if len(offset) == 2:offset = np.array((-offset[0], -offset[1], offset[0], offset[1]))else:offset = np.array(offset)else:offset = np.array((-3, -offset, 3, offset))image = crop(image, offset + self.area, copy=False)if self.is_gif:image_luma = rgb2luma(image)for template in self.image_luma:res = cv2.matchTemplate(template, image_luma, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))if sim > similarity:return Trueelse:image_luma = rgb2luma(image)res = cv2.matchTemplate(self.image_luma, image_luma, cv2.TM_CCOEFF_NORMED)_, sim, _, point = cv2.minMaxLoc(res)self._button_offset = area_offset(self._button, offset[:2] + np.array(point))return sim > similaritydef match_template_color(self, image, offset=(20, 20), similarity=0.85, threshold=30):"""Template match first, color match thenArgs:image: Screenshot.offset (int, tuple): Detection area offset.similarity (float): 0-1.threshold (int): Default to 30.Returns:bool."""if self.match_luma(image, offset=offset, similarity=similarity):diff = np.subtract(self.button, self._button)[:2]area = area_offset(self.area, offset=diff)color = get_color(image, area)return color_similar(color1=color, color2=self.color, threshold=threshold)else:return Falsedef crop(self, area, image=None, name=None):"""Get a new button by relative coordinates.Args:area (tuple):image (np.ndarray): Screenshot. If provided, load color and image from it.name (str):Returns:Button:"""if name is None:name = self.namenew_area = area_offset(area, offset=self.area[:2])new_button = area_offset(area, offset=self.button[:2])button = Button(area=new_area, color=self.color, button=new_button, file=self.file, name=name)if image is not None:button.load_color(image)return buttondef move(self, vector, image=None, name=None):"""Move button.Args:vector (tuple):image (np.ndarray): Screenshot. If provided, load color and image from it.name (str):Returns:Button:"""if name is None:name = self.namenew_area = area_offset(self.area, offset=vector)new_button = area_offset(self.button, offset=vector)button = Button(area=new_area, color=self.color, button=new_button, file=self.file, name=name)if image is not None:button.load_color(image)return buttondef split_server(self):"""Split into 4 server specific buttons.Returns:dict[str, Button]:"""out = {}for s in VALID_SERVER:out[s] = Button(area=self.parse_property(self.raw_area, s),color=self.parse_property(self.raw_color, s),button=self.parse_property(self.raw_button, s),file=self.parse_property(self.raw_file, s),name=self.name)return outclass ButtonGrid:def __init__(self, origin, delta, button_shape, grid_shape, name=None):self.origin = np.array(origin)self.delta = np.array(delta)self.button_shape = np.array(button_shape)self.grid_shape = np.array(grid_shape)if name:self._name = nameelse:(filename, line_number, function_name, text) = traceback.extract_stack()[-2]self._name = text[:text.find('=')].strip()def __getitem__(self, item):base = np.round(np.array(item) * self.delta + self.origin).astype(int)area = tuple(np.append(base, base + self.button_shape))return Button(area=area, color=(), button=area, name='%s_%s_%s' % (self._name, item[0], item[1]))def generate(self):for y in range(self.grid_shape[1]):for x in range(self.grid_shape[0]):yield x, y, self[x, y]@cached_propertydef buttons(self):return list([button for _, _, button in self.generate()])def crop(self, area, name=None):"""Args:area (tuple): Area related to self.originname (str): Name of the new ButtonGrid instance.Returns:ButtonGrid:"""if name is None:name = self._nameorigin = self.origin + area[:2]button_shape = np.subtract(area[2:], area[:2])return ButtonGrid(origin=origin, delta=self.delta, button_shape=button_shape, grid_shape=self.grid_shape, name=name)def move(self, vector, name=None):"""Args:vector (tuple): Move vector.name (str): Name of the new ButtonGrid instance.Returns:ButtonGrid:"""if name is None:name = self._nameorigin = self.origin + vectorreturn ButtonGrid(origin=origin, delta=self.delta, button_shape=self.button_shape, grid_shape=self.grid_shape, name=name)def gen_mask(self):"""Generate a mask image to display this ButtonGrid object for debugging.Returns:PIL.Image.Image: Area in white, background in black."""image = Image.new("RGB", (1280, 720), (0, 0, 0))draw = ImageDraw.Draw(image)for button in self.buttons:draw.rectangle((button.area[:2], button.button[2:]), fill=(255, 255, 255), outline=None)return imagedef show_mask(self):self.gen_mask().show()def save_mask(self):"""Save mask to {name}.png"""self.gen_mask().save(f'{self._name}.png')
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。