Spaces:
Sleeping
Sleeping
| # app.py | |
| import streamlit as st | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| import easyocr | |
| import os | |
| from streamlit_paste_button import paste_image_button as pbutton | |
| color_ranges = { | |
| 'fire': 'B50B0E', | |
| 'water': '015AB6', | |
| 'wind': '1F6A0B', | |
| 'earth': '623F23', | |
| 'light': 'DA8D09', | |
| 'dark': '502181' | |
| } | |
| def hex_to_rgb(hex_code): | |
| return tuple(int(hex_code[i:i+2], 16) for i in (0, 2, 4)) | |
| # # ์บ์ฑ์ผ๋ก Reader๋ฅผ ํ ๋ฒ๋ง ๋ก๋ | |
| # @st.cache_resource | |
| # def load_reader(): | |
| # return easyocr.Reader(['en'], gpu=False, verbose=False) | |
| def load_number_templates(): | |
| templates = {} | |
| template_dir = './templates' | |
| for num in range(1, 16): | |
| template_path = os.path.join(template_dir, f'{num}.png') | |
| if os.path.exists(template_path): | |
| # ๊ทธ๋ ์ด์ค์ผ์ผ๋ก ๋ก๋ | |
| template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE) | |
| if template is not None: | |
| # ์ ์ฒ๋ฆฌ: ์ด์งํ | |
| _, template = cv2.threshold(template, 127, 255, cv2.THRESH_BINARY) | |
| templates[num] = template | |
| else: | |
| st.warning(f"ํ ํ๋ฆฟ {num}.png๋ฅผ ๋ก๋ํ ์ ์์ต๋๋ค.") | |
| if not templates: | |
| st.error("ํ ํ๋ฆฟ ์ด๋ฏธ์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. templates/ ํด๋์ 1.png ~ 15.png ํ์ผ์ ์ถ๊ฐํด์ฃผ์ธ์.") | |
| return templates | |
| def extract_region(region): | |
| try: | |
| gray = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY) | |
| _, binary = cv2.threshold(gray, 215, 255, cv2.THRESH_BINARY) | |
| # ๋ ธ์ด์ฆ ์ ๊ฑฐ | |
| kernel = np.ones((2, 2), np.uint8) | |
| cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) | |
| cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_OPEN, kernel) | |
| return cleaned | |
| except Exception as e: | |
| return None | |
| def template_matching(region, templates): | |
| try: | |
| h, w = region.shape | |
| aspect_ratio = w / h | |
| new_width = int(25 * aspect_ratio) | |
| region = cv2.resize(region, (new_width, 25)) | |
| best_match = 0 | |
| best_score = -1 | |
| for num, template in templates.items(): | |
| try: | |
| result = cv2.matchTemplate(region, template, cv2.TM_CCOEFF_NORMED) | |
| _, max_val, _, _ = cv2.minMaxLoc(result) | |
| print(max_val) | |
| if max_val > best_score: | |
| best_score = max_val | |
| best_match = num | |
| except Exception as e: | |
| print(f"Error matching template {num}: {e}") | |
| continue | |
| if best_score > 0.65: | |
| return best_match | |
| else: | |
| return 0 | |
| except Exception as e: | |
| print(f"Error matching template: {e}") | |
| return 0 | |
| def find_items(img_array, color_range, templates): | |
| img_rgb = cv2.cvtColor(img_array, cv2.COLOR_BGR2RGB) | |
| results = {} | |
| for type, hex_color in color_range.items(): | |
| target_rgb = hex_to_rgb(hex_color) | |
| lower_c = np.array([max(0, c - 10) for c in target_rgb]) | |
| upper_c = np.array([min(255, c + 10) for c in target_rgb]) | |
| mask = cv2.inRange(img_rgb, lower_c, upper_c) | |
| contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| if contours: | |
| largest = max(contours, key=cv2.contourArea) | |
| if cv2.contourArea(largest) > 100: | |
| x, y, w, h = cv2.boundingRect(largest) | |
| number_region = img_rgb[y+int(h*1.2) :y+ int(h*1.7), x+int(w*1.4):x+int(w*2.2)] | |
| count = template_matching(extract_region(number_region), templates) | |
| results[type] = count | |
| else: | |
| results[type] = 0 | |
| else: | |
| results[type] = 0 | |
| return results | |
| def process_image(img, templates): | |
| st.image(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), caption='์ ๋ก๋๋ ์ด๋ฏธ์ง', use_container_width=True) | |
| with st.spinner('์์ดํ ๊ฐ์๋ฅผ ์ธ๋ ์ค...'): | |
| try: | |
| results = find_items(img, color_ranges, templates) | |
| # ๊ฒฐ๊ณผ ํ์ | |
| st.success('โ ๋ถ์ ์๋ฃ!') | |
| col1, col2, col3 = st.columns(3) | |
| emoji_map = { | |
| 'fire': '๐ฅ', | |
| 'water': '๐ง', | |
| 'wind': '๐จ', | |
| 'earth': '๐', | |
| 'light': 'โจ', | |
| 'dark': '๐' | |
| } | |
| korean_map = { | |
| 'fire': '๋ถ', | |
| 'water': '๋ฌผ', | |
| 'wind': '๋ฐ๋', | |
| 'earth': '๋์ง', | |
| 'light': '๋น', | |
| 'dark': '์ด๋ ' | |
| } | |
| counts_str = '' | |
| # ๋จ์ผ ์ด๋ก ํ์ | |
| for type, count in results.items(): | |
| st.metric( | |
| label=f"{emoji_map.get(type, '')} {korean_map.get(type, type)}", | |
| value=f"{count}๊ฐ" | |
| ) | |
| # cols = [col1, col2, col3] | |
| # counts_str = '' | |
| # for idx, (type, count) in enumerate(results.items()): | |
| # col = cols[idx % 3] | |
| # with col: | |
| # st.metric( | |
| # label=f"{emoji_map.get(type, '')} {korean_map.get(type, type)}", | |
| # value=f"{count}๊ฐ" | |
| # ) | |
| counts_str += f"{count % 10 if count >= 10 else count}/" | |
| st.markdown(f'{"/".join(korean_map.values())}') | |
| st.markdown(f'{counts_str}') | |
| except Exception as e: | |
| st.error(f"๋ถ์ ์ค ์ค๋ฅ ๋ฐ์: {e}") | |
| st.info("EasyOCR ๋ชจ๋ธ ๋ก๋ฉ์ ์คํจํ์ ์ ์์ต๋๋ค. Streamlit Cloud์ ๋ฉ๋ชจ๋ฆฌ ์ ํ ๋๋ฌธ์ผ ์ ์์ต๋๋ค.") | |
| # Streamlit UI | |
| st.set_page_config(page_title="์์ฑ ์ ์ฌ๋ ฅ ์ฃผ๋ฌธ์ ์นด์ดํฐ", page_icon="๐ฎ") | |
| st.title('๐ฎ ์์ฑ ์ ์ฌ๋ ฅ ์ฃผ๋ฌธ์ ์นด์ดํฐ') | |
| st.write('ํ๋ํ ์์ฑ ์ ์ฌ๋ ฅ ์ฃผ๋ฌธ์ ๊ฐ์๋ฅผ ์๋์ผ๋ก ์ธ์ด๋๋ฆฝ๋๋ค!') | |
| templates = load_number_templates() | |
| tab1, tab2 = st.tabs(["๐ ํ์ผ ์ ๋ก๋", "๐ ๋ถ์ฌ๋ฃ๊ธฐ"]) | |
| with tab1: | |
| st.write('์ด๋ฏธ์ง ํ์ผ์ ์ ๋ก๋ํ์ธ์') | |
| uploaded_file = st.file_uploader("์คํฌ๋ฆฐ์ท์ ์ ๋ก๋ํ์ธ์", type=['png', 'jpg', 'jpeg']) | |
| if uploaded_file is not None: | |
| file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8) | |
| img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) | |
| process_image(img, templates) | |
| with tab2: | |
| st.write('์ด๋ฏธ์ง๋ฅผ ๋ถ์ฌ๋ฃ์ผ์ธ์') | |
| paste_result = pbutton( | |
| label="๐ ์ฌ๊ธฐ๋ฅผ ํด๋ฆญํ๊ณ Ctrl+V", | |
| background_color="#FF4B4B", | |
| hover_background_color="#FF6B6B", | |
| ) | |
| if paste_result.image_data is not None: | |
| pil_image = paste_result.image_data | |
| # st.markdown(f'{type(pil_image)}') | |
| img = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) | |
| process_image(img, templates) | |
| st.markdown('---') | |
| st.caption('Made by โค๏ธsseong') |