1
0
mirror of https://github.com/sui-feng-cb/AzurLaneAutoScript1.git synced 2026-07-11 02:15:37 +08:00

Opt: using template matching for commission suffix recognition (#5731)

* Opt: using pHash and template matching for commission suffix recognition

* Refactor: improve suffix image processing and hash calculation
This commit is contained in:
guoh064
2026-06-25 19:33:22 +08:00
committed by GitHub
parent 50ea845d83
commit 934ecda6eb

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
import hashlib
import module.config.server as server
from module.base.decorator import Config from module.base.decorator import Config
from module.base.filter import Filter from module.base.filter import Filter
from module.base.utils import * from module.base.utils import *
@@ -23,20 +23,53 @@ COMMISSION_FILTER = Filter(
) )
class SuffixOcr(Ocr): def crop_suffix_image(image, area):
def pre_process(self, image): """
image = super().pre_process(image) Args:
image (np.ndarray):
area (tuple): Commission name area.
left = np.where(np.min(image[5:-5, :], axis=0) < 85)[0] Returns:
# Look back several pixels np.ndarray | None: Cropped suffix image, black letters on white background.
if server.server in ['jp']: """
look_back = 21 name_image = crop(image, area)
else: name_image = extract_letters(name_image, letter=(255, 255, 255), threshold=128).astype(np.uint8)
look_back = 18
if len(left):
image = image[:, left[-1] - look_back:]
return image line = cv2.reduce(name_image[5:-5, :], 0, cv2.REDUCE_AVG).flatten()
columns = np.where(line < 250)[0]
if not len(columns):
return None
# Look back several pixels from the rightmost letter to include Roman numerals.
threshold = 250
look_back = 10
for i in range(columns[-1], 0, -1):
if line[i] > threshold:
if columns[-1] - i > look_back:
look_back = columns[-1] - i
break
left = columns[-1] - look_back
right = columns[-1] + 1
x1, y1 = area[0:2]
suffix_area = area_offset((left - 3, -3, right + 3, name_image.shape[0] + 3), (x1, y1))
image = crop(image, suffix_area)
image = extract_letters(image, letter=(255, 255, 255), threshold=128).astype(np.uint8)
return image
def image_hash(image):
"""
Args:
image (np.ndarray):
Returns:
str:
"""
if image is None:
return ''
return hashlib.md5(image.tobytes()).hexdigest()
class Commission: class Commission:
@@ -46,10 +79,10 @@ class Commission:
name: str name: str
# If success to parse commission name # If success to parse commission name
valid: bool valid: bool
# Suffix in roman numerals # Cropped suffix image, black letters on white background, or None
# May be wrong if commission does not have a suffix suffix_image: np.ndarray
# Value: ⅠⅡⅢⅤⅣⅥ # Hash of suffix image, used only for logging, or empty string if suffix_image is None
suffix: str suffix_hash: str
# Genre name in project_data.py # Genre name in project_data.py
# Value: major_comm, daily_resource, urgent_cube, ... # Value: major_comm, daily_resource, urgent_cube, ...
genre: str genre: str
@@ -113,8 +146,8 @@ class Commission:
self.genre = self.commission_name_parse(self.name) self.genre = self.commission_name_parse(self.name)
# Suffix # Suffix
ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') self.suffix_image = crop_suffix_image(self.image, self.button.area)
self.suffix = self.beautify_name(ocr.ocr(self.image)) self.suffix_hash = image_hash(self.suffix_image)
# Duration time # Duration time
area = area_offset((290, 68, 390, 95), self.area[0:2]) area = area_offset((290, 68, 390, 95), self.area[0:2])
@@ -160,8 +193,8 @@ class Commission:
self.genre = self.commission_name_parse(self.name) self.genre = self.commission_name_parse(self.name)
# Suffix # Suffix
ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') self.suffix_image = crop_suffix_image(self.image, self.button.area)
self.suffix = self.beautify_name(ocr.ocr(self.image)) self.suffix_hash = image_hash(self.suffix_image)
# Duration time # Duration time
area = area_offset((290, 68, 390, 95), self.area[0:2]) area = area_offset((290, 68, 390, 95), self.area[0:2])
@@ -209,8 +242,8 @@ class Commission:
self.genre = self.commission_name_parse(self.name) self.genre = self.commission_name_parse(self.name)
# Suffix # Suffix
ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') self.suffix_image = crop_suffix_image(self.image, self.button.area)
self.suffix = self.beautify_name(ocr.ocr(self.image)) self.suffix_hash = image_hash(self.suffix_image)
# Duration time # Duration time
area = area_offset((290, 68, 390, 95), self.area[0:2]) area = area_offset((290, 68, 390, 95), self.area[0:2])
@@ -254,8 +287,8 @@ class Commission:
self.genre = self.commission_name_parse(self.name) self.genre = self.commission_name_parse(self.name)
# Suffix # Suffix
ocr = SuffixOcr(button, lang='azur_lane', letter=(255, 255, 255), threshold=128, alphabet='IV') self.suffix_image = crop_suffix_image(self.image, self.button.area)
self.suffix = self.beautify_name(ocr.ocr(self.image)) self.suffix_hash = image_hash(self.suffix_image)
# Duration time # Duration time
area = area_offset((290, 68, 390, 95), self.area[0:2]) area = area_offset((290, 68, 390, 95), self.area[0:2])
@@ -288,7 +321,7 @@ class Commission:
self.status = dic[int(np.argmax(color))] self.status = dic[int(np.argmax(color))]
def __str__(self): def __str__(self):
name = f'{self.name} | {self.suffix}' name = f'{self.name} | {self.suffix_hash}' if self.suffix_hash else self.name
if not self.valid: if not self.valid:
return f'{name} (Invalid)' return f'{name} (Invalid)'
info = {'Genre': self.genre, 'Status': self.status, 'Duration': self.duration} info = {'Genre': self.genre, 'Status': self.status, 'Duration': self.duration}
@@ -315,7 +348,7 @@ class Commission:
if self.genre != other.genre or self.status != other.status: if self.genre != other.genre or self.status != other.status:
return False return False
if self.category_str == 'daily': if self.category_str == 'daily':
if self.suffix != other.suffix: if not self.suffix_match(other):
return False return False
if self.genre == 'urgent_box': if self.genre == 'urgent_box':
for tag in ['NYB', 'BIW']: for tag in ['NYB', 'BIW']:
@@ -332,7 +365,7 @@ class Commission:
return False return False
if self.repeat_count != other.repeat_count: if self.repeat_count != other.repeat_count:
return False return False
if self.genre in ['extra_oil', 'night_oil'] and self.suffix != other.suffix: if self.genre in ['extra_oil', 'night_oil'] and not self.suffix_match(other):
return False return False
return True return True
@@ -340,6 +373,35 @@ class Commission:
def __hash__(self): def __hash__(self):
return hash(f'{self.genre}_{self.name}') return hash(f'{self.genre}_{self.name}')
def suffix_match(self, other, similarity=0.75):
"""
Args:
other (Commission):
similarity (float): 0-1. Similarity.
Returns:
bool:
"""
if self.suffix_image is None and other.suffix_image is None:
return True
if self.suffix_image is None or other.suffix_image is None:
return False
def match(image, template):
template = crop(template, (3, 3, template.shape[1] - 3, template.shape[0] - 3), copy=False)
if image.shape[0] < template.shape[0] or image.shape[1] < template.shape[1]:
return 0.0
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
_, sim, _, _ = cv2.minMaxLoc(res)
return sim
sim = max(
match(self.suffix_image, other.suffix_image),
match(other.suffix_image, self.suffix_image)
)
return sim >= similarity
def parse_time(self, string): def parse_time(self, string):
""" """
Args: Args: