scroll-count / app.py
SSEONG's picture
Update app.py
ffa6155 verified
# 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)
@st.cache_resource
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')