def detect(self, image): """ Args: image: Screenshot. Returns: bool: If success. """ start_time = time.time() image = np.array(image) self.image = image # Image initialization image = rgb2gray(crop(image, self.config.DETECTING_AREA)) # Perspective transform image_trans = cv2.warpPerspective(image, self.homo_data, self.homo_size) # Edge detection image_edge = cv2.Canny(image_trans, 100, 150) image_edge = cv2.bitwise_and(image_edge, self.ui_mask_homo_stroke) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) image_edge = cv2.morphologyEx(image_edge, cv2.MORPH_CLOSE, kernel) # Image.fromarray(image_edge, mode='L').show() # Find free tile if self.search_tile_center( image_edge, threshold_good=self.config.HOMO_CENTER_GOOD_THRESHOLD, threshold=self.config.HOMO_CENTER_THRESHOLD): pass elif self.search_tile_corner( image_edge, threshold=self.config.HOMO_CORNER_THRESHOLD): pass elif self.search_tile_rectangle( image_edge, threshold=self.config.HOMO_RECTANGLE_THRESHOLD): pass else: raise MapDetectionError('Failed to find a free tile') self.homo_loca %= self.config.HOMO_TILE # Detect map edges image_edge = cv2.bitwise_and( cv2.dilate(image_edge, kernel), cv2.inRange(image_trans, *self.config.HOMO_EDGE_COLOR_RANGE)) self.detect_edges(image_edge, hough_th=self.config.HOMO_EDGE_HOUGHLINES_THRESHOLD) # Log time_cost = round(time.time() - start_time, 3) logger.info('%ss %s edge_lines: %s hori, %s vert' % (float2str(time_cost), '_' if self.lower_edge else ' ', self._map_edge_count[1], self._map_edge_count[0])) logger.info( 'Edges: %s%s%s homo_loca: %s' % ('/' if self.left_edge else ' ', '_' if self.upper_edge else ' ', '\\' if self.right_edge else ' ', point2str(*self.homo_loca, length=3)))
def load_homography(self, storage=None, perspective=None, image=None, file=None): """ Args: storage (tuple): ((x, y), [upper-left, upper-right, bottom-left, bottom-right]) perspective (Perspective): image (np.ndarray): file (str): File path of image """ if storage is not None: self.find_homography(*storage) elif perspective is not None: hori = perspective.horizontal[0].add(perspective.horizontal[-1]) vert = perspective.vertical[0].add(perspective.vertical[-1]) src_pts = hori.cross(vert).points x = len(perspective.vertical) - 1 y = len(perspective.horizontal) - 1 self.find_homography(size=(x, y), src_pts=src_pts) elif image is not None: perspective_ = Perspective(self.config) perspective_.load(image) self.load_homography(perspective=perspective_) elif file is not None: image_ = np.array(Image.open(file).convert('RGB')) perspective_ = Perspective(self.config) perspective_.load(image_) self.load_homography(perspective=perspective_) else: raise MapDetectionError( 'No data feed to load_homography, please input at least one.')
def load(self, image): """ Args: image: """ image = self._image_clear_ui(np.array(image)) self.image = image super().load(image) # Create local view map grids = {} for loca, points in self.generate(): if area_in_area(area1=corner2area(points), area2=self.config.DETECTING_AREA): grids[loca] = self.grid_class(location=loca, image=image, corner=points, config=self.config) # Handle grids offset offset = list(grids.keys()) if not len(offset): raise MapDetectionError('No map grids found') offset = np.min(offset, axis=0) if np.sum(np.abs(offset)) > 0: logger.attr_align('grids_offset', tuple(offset.tolist())) self.grids = {} for loca, grid in grids.items(): x, y = np.subtract(loca, offset) grid.location = (x, y) self.grids[(x, y)] = grid else: self.grids = grids self.shape = np.max(list(self.grids.keys()), axis=0) # Find local view center for loca, grid in self.grids.items(): offset = grid.screen2grid([self.config.SCREEN_CENTER])[0].astype(int) points = grid.grid2screen(np.add([[0.5, 0], [-0.5, 0], [0, 0.5], [0, -0.5]], offset)) self.swipe_base = np.array([np.linalg.norm(points[0] - points[1]), np.linalg.norm(points[2] - points[3])]) self.center_loca = tuple(np.add(loca, offset).tolist()) logger.attr_align('center_loca', self.center_loca) if self.center_loca in self: self.center_offset = self.grids[self.center_loca].screen2grid([self.config.SCREEN_CENTER])[0] else: x = max(self.center_loca[0] - self.shape[0], 0) if self.center_loca[0] > 0 else self.center_loca[0] y = max(self.center_loca[1] - self.shape[1], 0) if self.center_loca[1] > 0 else self.center_loca[1] self.center_offset = offset - self.center_loca raise MapDetectionError(f'Camera outside map: offset=({x}, {y})') break
def get_current_zone(self): """ Returns: Zone: Raises: MapDetectionError: If failed to parse zone name. ScriptError: """ name = self.get_zone_name() logger.info(f'Map name processed: {name}') try: self.zone = self.name_to_zone(name) except ScriptError as e: raise MapDetectionError(*e.args) logger.attr('Zone', self.zone) return self.zone
def load(self, image): """ Args: image (np.ndarray): Shape (720, 1280, 3) """ start_time = time.time() self.image = image # Image initialisation image = self.load_image(image) # Lines detection inner_h = self.detect_lines( image, is_horizontal=True, param=self.config.INTERNAL_LINES_FIND_PEAKS_PARAMETERS, threshold=self.config.INTERNAL_LINES_HOUGHLINES_THRESHOLD, theta=self.config.HORIZONTAL_LINES_THETA_THRESHOLD).move( *self.config.DETECTING_AREA[:2]) inner_v = self.detect_lines( image, is_horizontal=False, param=self.config.INTERNAL_LINES_FIND_PEAKS_PARAMETERS, threshold=self.config.INTERNAL_LINES_HOUGHLINES_THRESHOLD, theta=self.config.VERTICAL_LINES_THETA_THRESHOLD).move( *self.config.DETECTING_AREA[:2]) edge_h = self.detect_lines( image, is_horizontal=True, param=self.config.EDGE_LINES_FIND_PEAKS_PARAMETERS, threshold=self.config.EDGE_LINES_HOUGHLINES_THRESHOLD, theta=self.config.HORIZONTAL_LINES_THETA_THRESHOLD, pad=self.config.DETECTING_AREA[2] - self.config.DETECTING_AREA[0]).move( *self.config.DETECTING_AREA[:2]) edge_v = self.detect_lines( image, is_horizontal=False, param=self.config.EDGE_LINES_FIND_PEAKS_PARAMETERS, threshold=self.config.EDGE_LINES_HOUGHLINES_THRESHOLD, theta=self.config.VERTICAL_LINES_THETA_THRESHOLD, pad=self.config.DETECTING_AREA[3] - self.config.DETECTING_AREA[1]).move( *self.config.DETECTING_AREA[:2]) # Lines pre-cleansing horizontal = inner_h.add(edge_h).group() vertical = inner_v.add(edge_v).group() edge_h = edge_h.group() edge_v = edge_v.group() if not self.config.TRUST_EDGE_LINES: edge_h = edge_h.delete(inner_h) # Experimental, reduce edge lines. edge_v = edge_v.delete(inner_v) self.horizontal = horizontal self.vertical = vertical # Calculate perspective self.crossings = self.horizontal.cross(self.vertical) self.vanish_point = optimize.brute(self._vanish_point_value, self.config.VANISH_POINT_RANGE) distance_point_x = optimize.brute( self._distant_point_value, self.config.DISTANCE_POINT_X_RANGE)[0] self.distant_point = (distance_point_x, self.vanish_point[1]) logger.attr_align('vanish_point', point2str(*self.vanish_point, length=5)) logger.attr_align('distant_point', point2str(*self.distant_point, length=5)) if np.linalg.norm(np.subtract(self.vanish_point, self.distant_point)) < 10: raise MapDetectionError('Vanish point and distant point too close') # Re-generate lines. Useless after mid_cleanse function added. # self.horizontal = self.crossings.link(None, is_horizontal=True).group() # self.vertical = self.crossings.link(self.vanish_point).group() # self.draw(self.crossings.link(self.distant_point)) # print(edge_h) # print(inner_h.group()) # Lines cleansing # self.draw() self.map_inner = get_map_inner(self.crossings.points) self.horizontal, self.lower_edge, self.upper_edge = self.line_cleanse( self.horizontal, inner=inner_h.group(), edge=edge_h) self.vertical, self.left_edge, self.right_edge = self.line_cleanse( self.vertical, inner=inner_v.group(), edge=edge_v) # self.draw() # print(self.horizontal) # print(self.lower_edge, self.upper_edge) # print(self.vertical) # print(self.left_edge, self.right_edge) # Log time_cost = round(time.time() - start_time, 3) logger.info('%ss %s Horizontal: %s (%s inner, %s edge)' % (float2str(time_cost), '_' if self.lower_edge else ' ', len(self.horizontal), len(horizontal), len(edge_h))) logger.info( 'Edges: %s%s%s Vertical: %s (%s inner, %s edge)' % ('/' if self.left_edge else ' ', '_' if self.upper_edge else ' ', '\\' if self.right_edge else ' ', len( self.vertical), len(vertical), len(edge_v)))
def update(self, camera=True): """Update map image Args: camera: True to update camera position and perspective data. """ self.device.screenshot() if not camera: self.view.update(image=self.device.image) return True self._view_init() try: if not self.is_in_map() \ and not self.is_in_strategy_submarine_move(): raise MapDetectionError('Image to detect is not in_map') self.view.load(self.device.image) except MapDetectionError as e: if self.info_bar_count(): logger.info('Perspective error cause by info bar. Waiting.') self.handle_info_bar() return self.update(camera=camera) elif self.appear(GET_ITEMS_1): logger.warning('Items got. Trying handling mystery.') self.handle_mystery() return self.update(camera=camera) elif self.handle_story_skip(): logger.warning('Perspective error cause by story. Handling.') self.ensure_no_story(skip_first_screenshot=False) return self.update(camera=camera) elif self.is_in_stage(): logger.warning('Image is in stage') raise CampaignEnd('Image is in stage') elif self.appear(AUTO_SEARCH_MENU_CONTINUE, offset=self._auto_search_menu_offset): logger.warning('Image is in auto search menu') self.ensure_auto_search_exit() raise CampaignEnd('Image is in auto search menu') elif not self.is_in_map() \ and not self.is_in_strategy_submarine_move(): logger.warning('Image to detect is not in_map') if self.appear_then_click(GAME_TIPS, offset=(20, 20)): logger.warning('Game tips found, retrying') self.device.screenshot() self.view.load(self.device.image) else: raise e elif 'Camera outside map' in str(e): string = str(e) logger.warning(string) x, y = string.split('=')[1].strip('() ').split(',') self._map_swipe((-int(x.strip()), -int(y.strip()))) else: raise e if self._prev_view is not None and np.linalg.norm(self._prev_swipe) > 0: if self.config.MAP_SWIPE_PREDICT: swipe = self._prev_view.predict_swipe( self.view, with_current_fleet=self.config.MAP_SWIPE_PREDICT_WITH_CURRENT_FLEET, with_sea_grids=self.config.MAP_SWIPE_PREDICT_WITH_SEA_GRIDS ) if swipe is not None: self._prev_swipe = swipe self.camera = tuple(np.add(self.camera, self._prev_swipe)) self._prev_view = None self._prev_swipe = None self.show_camera() # Set camera position if self.view.left_edge: x = 0 + self.view.center_loca[0] elif self.view.right_edge: x = self.map.shape[0] - self.view.shape[0] + self.view.center_loca[0] else: x = self.camera[0] if self.view.upper_edge: y = self.map.shape[1] - self.view.shape[1] + self.view.center_loca[1] elif self.view.lower_edge: y = 0 + self.view.center_loca[1] else: y = self.camera[1] if self.camera != (x, y): logger.attr_align('camera_corrected', f'{location2node(self.camera)} -> {location2node((x, y))}') self.camera = (x, y) self.show_camera() self.predict()
def _update(self, camera=True): """Update map image Args: camera: True to update camera position and perspective data. """ self.device.screenshot() if not camera: self.view.update(image=self.device.image) return True self._view_init() try: if not self.is_in_map() \ and not self.is_in_strategy_submarine_move(): logger.warning('Image to detect is not in_map') raise MapDetectionError('Image to detect is not in_map') self.view.load(self.device.image) except MapDetectionError as e: if self.info_bar_count(): logger.warning('Perspective error caused by info bar') self.handle_info_bar() return False elif self.appear(GET_ITEMS_1): logger.warning('Perspective error caused by get_items') self.handle_mystery() return False elif self.handle_story_skip(): logger.warning('Perspective error caused by story') self.ensure_no_story(skip_first_screenshot=False) return False elif self.is_in_stage(): logger.warning('Image is in stage') raise CampaignEnd('Image is in stage') elif self.appear(AUTO_SEARCH_MENU_CONTINUE, offset=self._auto_search_menu_offset): logger.warning('Image is in auto search menu') self.ensure_auto_search_exit() raise CampaignEnd('Image is in auto search menu') elif self.appear(GLOBE_GOTO_MAP, offset=(20, 20)): logger.warning('Image is in OS globe map') self.ui_click(GLOBE_GOTO_MAP, check_button=self.is_in_map, offset=(20, 20), retry_wait=3, skip_first_screenshot=True) return False elif self.appear(AUTO_SEARCH_REWARD, offset=(50, 50)): logger.warning( 'Perspective error caused by AUTO_SEARCH_REWARD') if hasattr(self, 'os_auto_search_quit'): self.os_auto_search_quit() return False else: logger.warning( 'Cannot find method os_auto_search_quit(), use ui_click() instead' ) self.ui_click(AUTO_SEARCH_REWARD, check_button=self.is_in_map, offset=(50, 50), retry_wait=3, skip_first_screenshot=True) return False elif 'opsi' in self.config.task.command.lower( ) and self.handle_popup_confirm('OPSI'): # Always confirm popups in OpSi, same popups in os_map_goto_globe() logger.warning('Perspective error caused by popups') return False elif not self.is_in_map() \ and not self.is_in_strategy_submarine_move(): if self.appear(GAME_TIPS, offset=(20, 20)): logger.warning('Perspective error caused by game tips') self.device.click(GAME_TIPS) return False else: raise e elif 'Camera outside map' in str(e): string = str(e) logger.warning(string) x, y = string.split('=')[1].strip('() ').split(',') self._map_swipe((-int(x.strip()), -int(y.strip()))) else: raise e if self._prev_view is not None and np.linalg.norm( self._prev_swipe) > 0: if self.config.MAP_SWIPE_PREDICT: swipe = self._prev_view.predict_swipe( self.view, with_current_fleet=self.config. MAP_SWIPE_PREDICT_WITH_CURRENT_FLEET, with_sea_grids=self.config.MAP_SWIPE_PREDICT_WITH_SEA_GRIDS ) if swipe is not None: self._prev_swipe = swipe self.camera = tuple(np.add(self.camera, self._prev_swipe)) self._prev_view = None self._prev_swipe = None self.show_camera() # Set camera position if self.view.left_edge: x = 0 + self.view.center_loca[0] elif self.view.right_edge: x = self.map.shape[0] - self.view.shape[0] + self.view.center_loca[ 0] else: x = self.camera[0] if self.view.upper_edge: y = self.map.shape[1] - self.view.shape[1] + self.view.center_loca[ 1] elif self.view.lower_edge: y = 0 + self.view.center_loca[1] else: y = self.camera[1] if self.camera != (x, y): logger.attr_align( 'camera_corrected', f'{location2node(self.camera)} -> {location2node((x, y))}') self.camera = (x, y) self.show_camera() self.predict() return True