diff --git a/dev_tools/item_stastistics.py b/dev_tools/item_stastistics.py new file mode 100644 index 000000000..55523271e --- /dev/null +++ b/dev_tools/item_stastistics.py @@ -0,0 +1,241 @@ +import os + +# os.chdir('../') +import module.config.server as server + +server.server = 'cn' # Don't need to edit, it's used to avoid error. +print(os.getcwd()) + +import numpy as np +from PIL import Image +import cv2 +import time +from module.combat.assets import GET_ITEMS_1, GET_ITEMS_2 +from module.handler.assets import INFO_BAR_1 +from module.base.button import ButtonGrid, Button +from module.base.ocr import Ocr +from module.logger import logger + + +""" +Set your folder here +Examples: xxx/campaign_7_2 +""" +IMAGE_FOLDER = '' +STATUS_ITEMS_INTERVAL = 10 +TEMPLATE_THRESHOLD = 0.9 + +BATTLE_STATUS_FOLDER = f'{IMAGE_FOLDER}/status' +GET_ITEMS_FOLDER = f'{IMAGE_FOLDER}/get_items' +TEMPLATE_FOLDER = f'{IMAGE_FOLDER}/item_template' +for f_ in [TEMPLATE_FOLDER]: + if not os.path.exists(f_): + os.mkdir(f_) +BATTLE_STATUS_TIMESTAMP = np.array([int(f.split('.')[0]) for f in os.listdir(BATTLE_STATUS_FOLDER)]) +ITEM_GRIDS_1_ODD = ButtonGrid(origin=(336, 298), delta=(128, 0), button_shape=(96, 96), grid_shape=(5, 1)) +ITEM_GRIDS_1_EVEN = ButtonGrid(origin=(400, 298), delta=(128, 0), button_shape=(96, 96), grid_shape=(4, 1)) +ITEM_GRIDS_2 = ButtonGrid(origin=(336, 227), delta=(128, 142), button_shape=(96, 96), grid_shape=(5, 2)) +ENEMY_GENRE_BUTTON = Button(area=(782, 285, 961, 319), color=(), button=(), name='ENEMY_GENRE') + + +class AmountOcr(Ocr): + def ocr(self, image): + start_time = time.time() + + image_list = [self.pre_process(i) for i in image] + result_list = self.cnocr.ocr_for_single_lines(image_list) + result_list = [self.after_process(result) for result in result_list] + + if len(self.buttons) == 1: + result_list = result_list[0] + logger.attr(name='%s %ss' % (self.name, str(round(time.time() - start_time, 3)).ljust(5, '0')), + text=str(result_list)) + + return result_list + + def after_process(self, raw): + """ + Returns: + int: + """ + raw = super().after_process(raw) + if not raw: + result = 0 + else: + result = int(raw) + + return result + + +AMOUNT_OCR = AmountOcr([], back=(-200, -200, -200), lang='digit', name='Amount_ocr') +ENEMY_GENRE_OCR = Ocr(ENEMY_GENRE_BUTTON, lang='cnocr', use_binary=False, back=(127, 127, 127)) + + +class ImageError(Exception): + pass + + +class ItemTemplate: + def __init__(self, image): + self.image = np.array(image) + + def match(self, image): + res = cv2.matchTemplate(self.image, np.array(image), cv2.TM_CCOEFF_NORMED) + _, similarity, _, _ = cv2.minMaxLoc(res) + return similarity > TEMPLATE_THRESHOLD + + def save(self, name): + image = Image.fromarray(self.image) + image.save(f'{TEMPLATE_FOLDER}/{name}.png') + + +class ItemTemplateGroup: + def __init__(self): + self.templates = {} + for file in os.listdir(TEMPLATE_FOLDER): + name = file[:-4] + image = Image.open(f'{TEMPLATE_FOLDER}/{file}').convert('RGB') + self.templates[name] = ItemTemplate(image) + + def match(self, item): + for name, template in self.templates.items(): + if template.match(item.image): + return name + + template = ItemTemplate(item.get_template()) + name = [int(n) for n in self.templates.keys() if n.isdigit()] + if len(name): + name = str(max(name) + 1) + else: + name = str(len(self.templates.keys()) + 1) + + logger.info(f'New item template: {name}') + self.templates[name] = template + template.save(name) + return name + + +template_group = ItemTemplateGroup() + + +class Item: + def __init__(self, image): + self.image = image + self.is_valid = np.mean(np.array(image.convert('L')) > 127) > 0.1 + self.name = 'Default_item' + self.amount = 1 + if self.is_valid: + self.name = template_group.match(self) + if not self.name.startswith('_') and '_' in self.name: + self.name = '_'.join(self.name.split('_')[:-1]) + + def __str__(self): + return f'{self.name}_x{self.amount}' + + @property + def has_amount(self): + return 'T' in self.name or self.name == '物资' + + def get_template(self): + # return self.image.crop((5, 5, 90, 68)) + return self.image.crop((40, 21, 89, 70)) + + def get_amount(self): + return self.image.crop((60, 75, 91, 88)) + + +class Items: + def __init__(self, timestamp): + self.timestamp = timestamp + self.get_items = Image.open(f'{GET_ITEMS_FOLDER}/{timestamp}.png').convert('RGB') + + # Enemy genre + interval = np.abs(BATTLE_STATUS_TIMESTAMP - timestamp) + if np.min(interval) > STATUS_ITEMS_INTERVAL * 1000: + raise ImageError(f'Timestamp: {timestamp}, battle_status image not found.') + self.status_timestamp = BATTLE_STATUS_TIMESTAMP[np.argmin(interval)] + self.enemy = 'Default_enemy' + + # get_item image properties + if INFO_BAR_1.appear_on(self.get_items): + raise ImageError(f'Timestamp: {timestamp}, Info bar') + if GET_ITEMS_1.appear_on(self.get_items): + self.row = 1 + self.is_odd = self.get_is_odd(self.get_items) + self.grids = ITEM_GRIDS_1_ODD if self.is_odd else ITEM_GRIDS_1_EVEN + elif GET_ITEMS_2.appear_on(self.get_items): + self.row = 2 + self.is_odd = True + self.grids = ITEM_GRIDS_2 + else: + raise ImageError(f'Timestamp: {timestamp}, Image is not a get_items image.') + + # Crop items + self.items = [] + for button in self.grids.buttons(): + item = Item(self.get_items.crop(button.area)) + if item.is_valid: + self.items.append(item) + + @staticmethod + def get_is_odd(image): + image = image.crop((628, 294, 651, 396)) + return np.mean(np.array(image.convert('L')) > 127) > 0.1 + + def predict(self): + self.battle_status = Image.open(f'{BATTLE_STATUS_FOLDER}/{self.status_timestamp}.png').convert('RGB') + self.enemy = ENEMY_GENRE_OCR.ocr(self.battle_status) + enemy = self.enemy + # Delete wrong OCR result + for letter in '-一个―~(': + enemy = enemy.replace(letter, '') + self.enemy = enemy + + amount_items = [item for item in self.items if item.has_amount] + amount = AMOUNT_OCR.ocr([item.get_amount() for item in amount_items]) + for a, i in zip(amount, amount_items): + i.amount = a + + def get_data(self): + return [[self.timestamp, self.status_timestamp, self.enemy, item.name, item.amount] for item in self.items] + + +""" +These code is for testing +Set your image name here +Examples: 159022xxxxxxx (int) +""" +# ts = 1590227624035 +# items = Items(ts) +# for item in items.items: +# print(item.amount, item.name) + +""" +These code is for template extracting +""" +# from tqdm import tqdm +# for ts in tqdm([int(f.split('.')[0]) for f in os.listdir(GET_ITEMS_FOLDER)]): +# try: +# items = Items(ts) +# except Exception: +# logger.warning(f'Error image: {ts}') +# continue + +""" +These code is for final statistic +Set your csv file name here +Examples: c64.csv +""" +# csv_file = 'c64.csv' +# import csv +# from tqdm import tqdm +# with open(csv_file, 'a', newline='') as file: +# writer = csv.writer(file) +# for ts in tqdm([int(f.split('.')[0]) for f in os.listdir(GET_ITEMS_FOLDER)]): +# try: +# items = Items(ts) +# items.predict() +# writer.writerows(items.get_data()) +# except Exception: +# logger.warning(f'Error image: {ts}') +# continue diff --git a/dev_tools/item_template/主炮部件T1.png b/dev_tools/item_template/主炮部件T1.png new file mode 100644 index 000000000..4f8e706d6 Binary files /dev/null and b/dev_tools/item_template/主炮部件T1.png differ diff --git a/dev_tools/item_template/主炮部件T2.png b/dev_tools/item_template/主炮部件T2.png new file mode 100644 index 000000000..a5571782a Binary files /dev/null and b/dev_tools/item_template/主炮部件T2.png differ diff --git a/dev_tools/item_template/主炮部件T3.png b/dev_tools/item_template/主炮部件T3.png new file mode 100644 index 000000000..1f9af44d8 Binary files /dev/null and b/dev_tools/item_template/主炮部件T3.png differ diff --git a/dev_tools/item_template/物资.png b/dev_tools/item_template/物资.png new file mode 100644 index 000000000..2c3caa4ed Binary files /dev/null and b/dev_tools/item_template/物资.png differ diff --git a/dev_tools/item_template/科技箱T1.png b/dev_tools/item_template/科技箱T1.png new file mode 100644 index 000000000..d5b6bfa21 Binary files /dev/null and b/dev_tools/item_template/科技箱T1.png differ diff --git a/dev_tools/item_template/科技箱T2.png b/dev_tools/item_template/科技箱T2.png new file mode 100644 index 000000000..e681c1ff9 Binary files /dev/null and b/dev_tools/item_template/科技箱T2.png differ diff --git a/dev_tools/item_template/科技箱T3.png b/dev_tools/item_template/科技箱T3.png new file mode 100644 index 000000000..a07a42033 Binary files /dev/null and b/dev_tools/item_template/科技箱T3.png differ diff --git a/dev_tools/item_template/科技箱T4.png b/dev_tools/item_template/科技箱T4.png new file mode 100644 index 000000000..934726d45 Binary files /dev/null and b/dev_tools/item_template/科技箱T4.png differ diff --git a/dev_tools/item_template/舰载机部件T1.png b/dev_tools/item_template/舰载机部件T1.png new file mode 100644 index 000000000..164c7cd84 Binary files /dev/null and b/dev_tools/item_template/舰载机部件T1.png differ diff --git a/dev_tools/item_template/舰载机部件T2.png b/dev_tools/item_template/舰载机部件T2.png new file mode 100644 index 000000000..dd694442a Binary files /dev/null and b/dev_tools/item_template/舰载机部件T2.png differ diff --git a/dev_tools/item_template/舰载机部件T3.png b/dev_tools/item_template/舰载机部件T3.png new file mode 100644 index 000000000..c7289f3d7 Binary files /dev/null and b/dev_tools/item_template/舰载机部件T3.png differ diff --git a/dev_tools/item_template/通用部件T1.png b/dev_tools/item_template/通用部件T1.png new file mode 100644 index 000000000..8aaffa6fe Binary files /dev/null and b/dev_tools/item_template/通用部件T1.png differ diff --git a/dev_tools/item_template/通用部件T2.png b/dev_tools/item_template/通用部件T2.png new file mode 100644 index 000000000..f69e0b330 Binary files /dev/null and b/dev_tools/item_template/通用部件T2.png differ diff --git a/dev_tools/item_template/通用部件T3.png b/dev_tools/item_template/通用部件T3.png new file mode 100644 index 000000000..d06813f3d Binary files /dev/null and b/dev_tools/item_template/通用部件T3.png differ diff --git a/dev_tools/item_template/防空炮部件T1.png b/dev_tools/item_template/防空炮部件T1.png new file mode 100644 index 000000000..4ac5ca9a7 Binary files /dev/null and b/dev_tools/item_template/防空炮部件T1.png differ diff --git a/dev_tools/item_template/防空炮部件T2.png b/dev_tools/item_template/防空炮部件T2.png new file mode 100644 index 000000000..a26ecd6c9 Binary files /dev/null and b/dev_tools/item_template/防空炮部件T2.png differ diff --git a/dev_tools/item_template/防空炮部件T3.png b/dev_tools/item_template/防空炮部件T3.png new file mode 100644 index 000000000..06590e0f6 Binary files /dev/null and b/dev_tools/item_template/防空炮部件T3.png differ diff --git a/dev_tools/item_template/鱼雷部件T1.png b/dev_tools/item_template/鱼雷部件T1.png new file mode 100644 index 000000000..6fbc6be7e Binary files /dev/null and b/dev_tools/item_template/鱼雷部件T1.png differ diff --git a/dev_tools/item_template/鱼雷部件T2.png b/dev_tools/item_template/鱼雷部件T2.png new file mode 100644 index 000000000..80d3de097 Binary files /dev/null and b/dev_tools/item_template/鱼雷部件T2.png differ diff --git a/dev_tools/item_template/鱼雷部件T3.png b/dev_tools/item_template/鱼雷部件T3.png new file mode 100644 index 000000000..aee5330e3 Binary files /dev/null and b/dev_tools/item_template/鱼雷部件T3.png differ diff --git a/doc/item_statistics_en.md b/doc/item_statistics_en.md new file mode 100644 index 000000000..8ff4aed37 --- /dev/null +++ b/doc/item_statistics_en.md @@ -0,0 +1,141 @@ +# How to do statistics on item drop rate + +This document will show how to use `dev_tools/item_statistics.py` + +## Enable statistics in alas + +In alas GUI, + +- set `enable_drop_screenshot` to `yes`, if enabled, alas will add a 1s sleep before screenshot, to avoid capture the flash. There's a flash light when item shows up. +- set `drop_screenshot_folder` to be the folder you want to save. It is recommended to save it in SSD. + +After few hours or few days of running, you will get a folder structure like: + +``` + + campaign_7_2 + get_items + 158323xxxxxxx.png + 158323xxxxxxx.png + 158323xxxxxxx.png + get_mission + get_ship + mystery + status + campaign_10_4_HARD + get_items + get_mission + get_ship + status + d3 + get_items + get_mission + get_ship + status +``` + +Screenshot are named after millesecond timestamp. + +## Prepare a new environment + +- Prepare another virtual environment, accoring to `requirements.txt`. But use the GPU version of `mxnet`. + + I am using GTX1080Ti, and I installed `mxnet-cu80==1.4.1`, `CUDA8.0`, `cuDNN`. Google `mxnet gpu install`, and see how to do in details. You may intall other version of CUDA, and mxnet for that CUDA, because you are using another graphic card. + +- Look for the cnocr in your virtual environment. Replace site-packages\cnocr\cn_ocr.py line 89 + +``` +mod = mx.mod.Module(symbol=sym, context=mx.cpu(), data_names=data_names, label_names=None) +``` + +​ to be: + +``` +mod = mx.mod.Module(symbol=sym, context=mx.gpu(), data_names=data_names, label_names=None) +``` + +​ Now cnocr will run on GPU. + +​ You can skip these anyway, and use the same environment as alas, but the OCR will run really slow. + +- Install tqdm, a package to show progressbar. + +``` +pip install tqdm +``` + +## Extract item_template + +Copy folder `dev_tools\item_template` to the map folder such as `\campaign_7_2`. + +Change the folder in line 24 + +These template are named in chinese, rename them in English. + +>**How to a name template image** +> +>You should use their full name, such as "138.6mm单装炮Mle1929T3", instead of short name or nickname, such as "DD_gun". +> +>If you have same item with different image, use names like `torpedo_part.png`, `torpedo_part_2.png`, they will a classified as torpedo_part + +Uncomment the part for item extract in dev_tools/item_statistics.py, and run, you will have some new item templates. Here's an example log: + +``` + 1%| | 107/12668 [00:05<10:24, 20.10it/s]2020-06-03 10:39:42.609 | INFO | New item template: 50 + 1%| | 158/12668 [00:07<10:42, 19.47it/s]2020-06-03 10:39:45.098 | INFO | New item template: 51 + 2%|▏ | 207/12668 [00:10<10:33, 19.66it/s]2020-06-03 10:39:47.772 | INFO | New item template: 52 + 2%|▏ | 215/12668 [00:10<11:20, 18.29it/s]2020-06-03 10:39:48.304 | INFO | New item template: 53 +100%|██████████| 12668/12668 [10:33<00:00, 19.99it/s] +``` + +Rename those new templates. + +If you find some items haven't been extracted, try use line 140, instead of 141. + +## Final statistic + +Uncomment the part for final statistic, configure the csv file you wang to save. + +The ocr model may not works fine in EN. + +Here's an example log: + +``` +2020-06-03 12:23:55.355 | INFO | [ENEMY_GENRE 0.007s] 中型侦查舰队 +2020-06-03 12:23:55.363 | INFO | [Amount_ocr 0.009s] [1, 1, 22] +100%|█████████▉| 14916/14919 [20:32<00:00, 13.20it/s]2020-06-03 12:23:55.442 | INFO | [ENEMY_GENRE 0.007s] 大型航空舰队 +2020-06-03 12:23:55.455 | INFO | [Amount_ocr 0.013s] [1, 1, 1, 17] +2020-06-03 12:23:55.539 | INFO | [ENEMY_GENRE 0.007s] 敌方旗舰 +2020-06-03 12:23:55.549 | INFO | [Amount_ocr 0.010s] [1, 2, 1, 63] +100%|█████████▉| 14918/14919 [20:33<00:00, 12.35it/s]2020-06-03 12:23:55.623 | INFO | [ENEMY_GENRE 0.007s] 精英舰队 +2020-06-03 12:23:55.633 | INFO | [Amount_ocr 0.010s] [1, 1, 1, 17] +100%|██████████| 14919/14919 [20:33<00:00, 12.10it/s] +``` + +Now you got a csv file, formated to be: + +``` +, , , , +``` + +like this: + +``` +1590271317900,1590271315841,中型主力舰队,主炮部件T3,1 +1590271317900,1590271315841,中型主力舰队,物资,23 +1590271359374,1590271357251,小型侦查舰队,通用部件T1,1 +1590271359374,1590271357251,小型侦查舰队,鱼雷部件T2,1 +1590271359374,1590271357251,小型侦查舰队,物资,13 +1590271415308,1590271413207,敌方旗舰,彗星,1 +1590271415308,1590271413207,敌方旗舰,通用部件T3,1 +1590271415308,1590271413207,敌方旗舰,科技箱T1,1 +1590271415308,1590271413207,敌方旗舰,物资,42 +1590271415308,1590271413207,敌方旗舰,_比萨研发物资,1 +1590271415308,1590271413207,敌方旗舰,_鸢尾之印,1 +``` + +You can open it in Excel or load it into database. + +## Improvement + +These code is running on single thread, you can try adding multiprocess to speed up. I didn't do that because it's still acceptable (20it/s without ocr, 12it/s with ocr) \ No newline at end of file