mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-12-12 07:29:03 +00:00
194 lines
7.3 KiB
Python
194 lines
7.3 KiB
Python
import os
|
|
from functools import cached_property
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from module.base.utils import (
|
|
color_similarity_2d,
|
|
crop,
|
|
get_bbox,
|
|
get_bbox_reversed,
|
|
image_paste,
|
|
image_size
|
|
)
|
|
from module.config.utils import iter_folder
|
|
from tasks.map.minimap.utils import map_image_preprocess, rotate_bound
|
|
from tasks.map.resource.const import ResourceConst
|
|
|
|
|
|
def register_output(output):
|
|
def register_wrapper(func):
|
|
def wrapper(self, *args, **kwargs):
|
|
image = func(self, *args, **kwargs)
|
|
self.DICT_GENERATE[output] = image
|
|
return image
|
|
|
|
return wrapper
|
|
|
|
return register_wrapper
|
|
|
|
|
|
class ResourceGenerator(ResourceConst):
|
|
DICT_GENERATE = {}
|
|
|
|
"""
|
|
Input images
|
|
"""
|
|
|
|
@cached_property
|
|
@register_output('./srcmap/direction/Arrow.png')
|
|
def Arrow(self):
|
|
return self.load_image('./resources/direction/Arrow.png')
|
|
|
|
"""
|
|
Output images
|
|
"""
|
|
|
|
@cached_property
|
|
def _ArrowRorateDict(self):
|
|
"""
|
|
Returns:
|
|
|
|
"""
|
|
image = self.Arrow
|
|
arrows = {}
|
|
for degree in range(0, 360):
|
|
rotated = rotate_bound(image, degree)
|
|
rotated = crop(rotated, area=get_bbox(rotated, threshold=15), copy=False)
|
|
# rotated = cv2.resize(rotated, None, fx=self.ROTATE, fy=self.ROTATE, interpolation=cv2.INTER_NEAREST)
|
|
rotated = color_similarity_2d(rotated, color=self.DIRECTION_ARROW_COLOR)
|
|
arrows[degree] = rotated
|
|
return arrows
|
|
|
|
@cached_property
|
|
@register_output('./srcmap/direction/ArrowRotateMap.png')
|
|
def ArrowRotateMap(self):
|
|
radius = self.DIRECTION_RADIUS
|
|
image = np.zeros((10 * radius * 2, 9 * radius * 2), dtype=np.uint8)
|
|
for degree in range(0, 360, 5):
|
|
y, x = divmod(degree / 5, 8)
|
|
rotated = self._ArrowRorateDict.get(degree)
|
|
point = (radius + int(x) * radius * 2, radius + int(y) * radius * 2)
|
|
# print(degree, y, x, point[0],point[0] + radius, point[1],point[1] + rotated.shape[1])
|
|
image_paste(rotated, image, origin=point)
|
|
image = cv2.resize(image, None,
|
|
fx=self.DIRECTION_SEARCH_SCALE, fy=self.DIRECTION_SEARCH_SCALE,
|
|
interpolation=cv2.INTER_NEAREST)
|
|
return image
|
|
|
|
@cached_property
|
|
@register_output('./srcmap/direction/ArrowRotateMapAll.png')
|
|
def ArrowRotateMapAll(self):
|
|
radius = self.DIRECTION_RADIUS
|
|
image = np.zeros((136 * radius * 2, 9 * radius * 2), dtype=np.uint8)
|
|
for degree in range(360 * 3):
|
|
y, x = divmod(degree, 8)
|
|
rotated = self._ArrowRorateDict.get(degree % 360)
|
|
point = (radius + int(x) * radius * 2, radius + int(y) * radius * 2)
|
|
# print(degree, y, x, point)
|
|
image_paste(rotated, image, origin=point)
|
|
image = cv2.resize(image, None,
|
|
fx=self.DIRECTION_SEARCH_SCALE, fy=self.DIRECTION_SEARCH_SCALE,
|
|
interpolation=cv2.INTER_NEAREST)
|
|
return image
|
|
|
|
@cached_property
|
|
def _map_background(self):
|
|
image = self.load_image('./resources/position/background.png')
|
|
height, width, channel = image.shape
|
|
grid = (10, 10)
|
|
|
|
background = np.zeros((height * grid[0], width * grid[1], channel), dtype=np.uint8)
|
|
for y in range(grid[0]):
|
|
for x in range(grid[1]):
|
|
image_paste(image, background, origin=(width * x, height * y))
|
|
background = background.copy()
|
|
return background
|
|
|
|
def _map_image_standardize(self, image, padding=0):
|
|
"""
|
|
Remove existing paddings
|
|
Map stroke color is about 127~134, background is 199~208
|
|
"""
|
|
image = crop(image, get_bbox_reversed(image, threshold=160))
|
|
if padding > 0:
|
|
size = np.array((padding, padding)) * 2 + image_size(image)
|
|
background = crop(self._map_background, area=(0, 0, *size))
|
|
image_paste(image, background, origin=(padding, padding))
|
|
return background
|
|
else:
|
|
return image
|
|
|
|
def _map_image_extract_feat(self, image):
|
|
"""
|
|
Extract a feature image for positioning.
|
|
"""
|
|
image = self._map_image_standardize(image, padding=ResourceConst.POSITION_FEATURE_PAD)
|
|
image = map_image_preprocess(image)
|
|
scale = self.POSITION_SEARCH_SCALE
|
|
image = cv2.resize(image, None, fx=scale, fy=scale, interpolation=cv2.INTER_AREA)
|
|
return image
|
|
|
|
def _map_image_extract_area(self, image):
|
|
"""
|
|
Extract accessible area on map.
|
|
*.area.png has `area` in red, extract into a binary image.
|
|
"""
|
|
# To the same size as feature map
|
|
image = self._map_image_standardize(image, padding=ResourceConst.POSITION_FEATURE_PAD)
|
|
image = color_similarity_2d(image, color=(255, 0, 0))
|
|
scale = self.POSITION_SEARCH_SCALE
|
|
image = cv2.resize(image, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
|
|
_, image = cv2.threshold(image, 180, 255, cv2.THRESH_BINARY)
|
|
# Make the area a little bit larger
|
|
kernel = self.POSITION_AREA_DILATE
|
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel, kernel))
|
|
image = cv2.dilate(image, kernel)
|
|
|
|
# Black area on white background
|
|
# image = cv2.subtract(255, image)
|
|
return image
|
|
|
|
@cached_property
|
|
def GernerateMapFloors(self):
|
|
for world in iter_folder(self.filepath('./resources/position'), is_dir=True):
|
|
world_name = os.path.basename(world)
|
|
for floor in iter_folder(world, ext='.png'):
|
|
print(f'Read image: {floor}')
|
|
image = self.load_image(floor)
|
|
floor_name = os.path.basename(floor)[:-4]
|
|
if floor_name.endswith('.area'):
|
|
# ./srcmap/position/{world_name}/xxx.area.png
|
|
output = f'./srcmap/position/{world_name}/{floor_name}.png'
|
|
register_output(output)(ResourceGenerator._map_image_extract_area)(self, image)
|
|
else:
|
|
output = f'./srcmap/position/{world_name}/{floor_name}.png'
|
|
register_output(output)(ResourceGenerator._map_image_standardize)(self, image)
|
|
output = f'./srcmap/position/{world_name}/{floor_name}.feat.png'
|
|
register_output(output)(ResourceGenerator._map_image_extract_feat)(self, image)
|
|
|
|
# Floor images are cached already, no need to return a real value
|
|
return True
|
|
|
|
def generate_output(self):
|
|
os.makedirs(self.filepath('./srcmap'), exist_ok=True)
|
|
# Calculate all resources
|
|
for method in self.__dir__():
|
|
if not method.startswith('__') and not method.islower():
|
|
_ = getattr(self, method)
|
|
# Create output folder
|
|
folders = set([os.path.dirname(file) for file in self.DICT_GENERATE.keys()])
|
|
for output in folders:
|
|
output = self.filepath(output)
|
|
os.makedirs(output, exist_ok=True)
|
|
# Save image
|
|
for output, image in self.DICT_GENERATE.items():
|
|
self.save_image(image, file=output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
os.chdir(os.path.join(os.path.dirname(__file__), '../../../'))
|
|
ResourceConst.SRCMAP = '../srcmap'
|
|
ResourceGenerator().generate_output()
|