import gradio as gr import cv2 import numpy as np import pandas as pd import json import os import threading import gc from datetime import datetime try: from pyngrok import ngrok NGROK_AVAILABLE = True except ImportError: NGROK_AVAILABLE = False try: from skimage.metrics import structural_similarity as ssim from skimage.metrics import peak_signal_noise_ratio as psnr except ImportError: # scikit-image가 없는 경우 대체 방법 사용 def ssim(img1, img2): # 간단한 MSE 기반 유사도 계산 mse = np.mean((img1 - img2) ** 2) return 1 / (1 + mse / 1000) def psnr(img1, img2): mse = np.mean((img1 - img2) ** 2) if mse == 0: return 100 return 20 * np.log10(255.0 / np.sqrt(mse)) import io from PIL import Image import torch import torchvision.models as models import torchvision.transforms as transforms import ssl # Fix SSL error for model download (macOS specific) ssl._create_default_https_context = ssl._create_unverified_context class ImageSimilarityLeaderboard: def __init__(self, reference_image_path="label2.jpg", data_file="leaderboard.json"): self.reference_image_path = reference_image_path self.data_file = data_file self.admin_password = "9900" # 관리자 비밀번호 self.admin_authenticated = False # 관리자 인증 상태 # 메모리 최적화를 위한 캐시 및 락 (먼저 초기화) self._ref_image_cache = None self._ref_embedding = None # ResNet 임베딩 캐시 self._cache_loaded = False self._file_lock = threading.Lock() # 파일 I/O 동시성 제어 self._processing_lock = threading.Lock() # 처리 동시성 제어 # 락 초기화 후 데이터 로드 self.leaderboard_data = self.load_leaderboard() self.last_modified = self.get_file_modified_time() # ResNet 모델 초기화 (한 번만 로드) self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") try: # ResNet50 (ImageNet weights) - 마지막 FC 레이어 제외 resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) self.resnet_model = torch.nn.Sequential(*(list(resnet.children())[:-1])).to(self.device) self.resnet_model.eval() # 전처리 파이프라인 self.preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) print(f"✅ ResNet 모델 로드 완료 (Device: {self.device})") except Exception as e: print(f"⚠️ ResNet 모델 로드 실패: {e}") self.resnet_model = None # macOS 호환성을 위한 경고 억제 import warnings warnings.filterwarnings("ignore", category=UserWarning, module="cv2") def load_leaderboard(self): """리더보드 데이터를 로드합니다.""" with self._file_lock: # 파일 I/O 동시성 제어 if os.path.exists(self.data_file): try: with open(self.data_file, 'r', encoding='utf-8') as f: return json.load(f) except: return [] return [] def get_file_modified_time(self): """파일 수정 시간을 반환합니다.""" if os.path.exists(self.data_file): return os.path.getmtime(self.data_file) return 0 def save_leaderboard(self): """리더보드 데이터를 저장합니다.""" with self._file_lock: # 파일 I/O 동시성 제어 with open(self.data_file, 'w', encoding='utf-8') as f: json.dump(self.leaderboard_data, f, ensure_ascii=False, indent=2) self.last_modified = self.get_file_modified_time() def check_for_updates(self): """리더보드 업데이트 확인""" current_modified = self.get_file_modified_time() if current_modified > self.last_modified: self.leaderboard_data = self.load_leaderboard() self.last_modified = current_modified return True return False def _load_reference_image(self): """참조 이미지를 한 번만 로드하고 캐시합니다.""" if not self._cache_loaded: if os.path.exists(self.reference_image_path): ref_image = cv2.imread(self.reference_image_path) if ref_image is not None: # macOS 메모리 최적화 if ref_image.shape[0] > 1024 or ref_image.shape[1] > 1024: # 큰 이미지는 미리 리사이즈하여 메모리 사용량 감소 scale = min(1024 / ref_image.shape[0], 1024 / ref_image.shape[1]) if scale < 1: new_width = int(ref_image.shape[1] * scale) new_height = int(ref_image.shape[0] * scale) ref_image = cv2.resize(ref_image, (new_width, new_height)) self._ref_image_cache = cv2.cvtColor(ref_image, cv2.COLOR_BGR2RGB) # ResNet 임베딩 계산 및 캐시 if self.resnet_model is not None: try: pil_img = Image.fromarray(self._ref_image_cache) img_t = self.preprocess(pil_img).unsqueeze(0).to(self.device) with torch.no_grad(): self._ref_embedding = self.resnet_model(img_t).flatten() except Exception as e: print(f"참조 이미지 임베딩 실패: {e}") self._cache_loaded = True # 메모리 정리 del ref_image gc.collect() return self._ref_image_cache def _get_memory_usage(self): """현재 메모리 사용량을 반환합니다.""" try: import psutil process = psutil.Process() memory_info = process.memory_info() return memory_info.rss / 1024 / 1024 # MB 단위 except (ImportError, AttributeError): # macOS나 다른 환경에서 psutil이 없거나 동작하지 않는 경우 try: import resource # getrusage를 통한 메모리 사용량 측정 usage = resource.getrusage(resource.RUSAGE_SELF) return usage.ru_maxrss / 1024 # KB -> MB except: return 0 def calculate_similarity(self, image1, image2): try: # 1) ResNet Feature Similarity (Semantic Similarity) - 가장 중요 resnet_score = 0.0 if self.resnet_model is not None and self._ref_embedding is not None: try: # 사용자 이미지 전처리 pil_img = Image.fromarray(image2) # image2 is RGB numpy array img_t = self.preprocess(pil_img).unsqueeze(0).to(self.device) with torch.no_grad(): user_emb = self.resnet_model(img_t).flatten() # Cosine Similarity cos_sim = torch.nn.functional.cosine_similarity( self._ref_embedding.unsqueeze(0), user_emb.unsqueeze(0) ).item() # Sigmoid Scoring Formula # Sim 0.61 (Bad) -> Score 14 # Sim 0.77 (Good) -> Score 80 # Sim 0.92 (Perfect) -> Score 99 # Formula: 100 / (1 + exp(-20 * (sim - 0.7))) resnet_score = 100 / (1 + np.exp(-20 * (cos_sim - 0.7))) except Exception as e: print(f"ResNet 계산 오류: {e}") resnet_score = 0.0 # 2) 그레이스케일 변환 (기존 로직 유지) if image1.ndim == 3: gray1 = cv2.cvtColor(image1, cv2.COLOR_RGB2GRAY) else: gray1 = image1.copy() if image2.ndim == 3: gray2 = cv2.cvtColor(image2, cv2.COLOR_RGB2GRAY) else: gray2 = image2.copy() # 3) 각 이미지를 독립적으로 표준 크기로 리사이즈 (512x512) target_size = (512, 512) gray1 = cv2.resize(gray1, target_size, interpolation=cv2.INTER_LINEAR) gray2 = cv2.resize(gray2, target_size, interpolation=cv2.INTER_LINEAR) # 4) SSIM 계산 (구조적 유사도) try: ssim_score = ssim(gray1.astype(np.float32), gray2.astype(np.float32)) except: # SSIM 계산 실패시 MSE 기반 대체 mse = np.mean((gray1.astype(np.float32) - gray2.astype(np.float32)) ** 2) ssim_score = 1 / (1 + mse / 10000) # 5) PSNR 계산 (픽셀 단위 유사도) mse = np.mean((gray1.astype(np.float32) - gray2.astype(np.float32)) ** 2) if mse == 0: psnr_score = 1.0 else: psnr_score = 20 * np.log10(255.0 / np.sqrt(mse)) # PSNR을 0-1 범위로 더 관대하게 정규화 (보통 PSNR은 20-40 범위) psnr_score = min(psnr_score / 40.0, 1.0) # 6) 히스토그램 유사도 hist1 = cv2.calcHist([gray1], [0], None, [256], [0, 256]) hist2 = cv2.calcHist([gray2], [0], None, [256], [0, 256]) hist_corr = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL) hist_score = (hist_corr + 1) / 2 # -1~1 → 0~1 # 7) 최종 점수 계산 (ResNet 비중 대폭 강화) # ResNet 모델이 있으면 ResNet 80%, SSIM 10%, Hist 10% if self.resnet_model is not None: final_score = (resnet_score * 0.8) + (ssim_score * 100 * 0.1) + (hist_score * 100 * 0.1) else: print(f"ResNet 모델이 없어서 SSIM 70%, Hist 30%로 계산") final_score = (ssim_score * 0.7 + hist_score * 0.3) * 100 # 8) PSNR이 높으면 약간의 보너스 (최대 5점) if psnr_score > 0.8: bonus = min((psnr_score - 0.8) * 25, 5) final_score = min(final_score + bonus, 100) return { 'ssim': float(ssim_score), 'psnr': float(psnr_score * 100), 'histogram': float(hist_score), 'resnet': float(resnet_score), # 결과에 포함 'final_score': float(final_score) } except Exception as e: print(f"유사도 계산 오류: {e}") return {'ssim':0.0,'psnr':0.0,'histogram':0.0,'resnet':0.0,'final_score':0.0} def process_image(self, uploaded_image, username): """업로드된 이미지를 처리하고 점수를 계산합니다.""" if uploaded_image is None: return "📤 이미지를 업로드해주세요.", self.get_leaderboard_df() # 사용자명 검증 제거 - 어떤 이름이든 허용 if not username or not username.strip(): return "❌ 사용자 이름을 입력해주세요.", self.get_leaderboard_df() username = username.strip() # 동시성 제어 - 한 번에 하나의 이미지만 처리 with self._processing_lock: try: # 캐시된 참조 이미지 사용 ref_image = self._load_reference_image() if ref_image is None: return f"참조 이미지({self.reference_image_path})를 로드할 수 없습니다.", None # 업로드된 이미지를 numpy 배열로 변환 if isinstance(uploaded_image, str): # 파일 경로인 경우 user_image = cv2.imread(uploaded_image) if user_image is None: return "업로드된 이미지를 읽을 수 없습니다.", None user_image = cv2.cvtColor(user_image, cv2.COLOR_BGR2RGB) else: # PIL Image인 경우 user_image = np.array(uploaded_image) if user_image is None or user_image.size == 0: return "업로드된 이미지가 유효하지 않습니다.", None # 이미지 크기 확인 if user_image.shape[0] < 10 or user_image.shape[1] < 10: return "이미지가 너무 작습니다. 최소 10x10 픽셀 이상이어야 합니다.", None # 유사도 계산 similarity_scores = self.calculate_similarity(ref_image, user_image) # 메모리 정리 (macOS 최적화) del user_image if 'ref_image' in locals(): del ref_image gc.collect() # macOS에서 메모리 강제 정리 import platform if platform.system() == 'Darwin': # macOS import ctypes try: libc = ctypes.CDLL('libc.dylib') libc.malloc_trim(0) except: pass # 리더보드에 추가 (JSON 직렬화를 위해 float로 변환) entry = { 'username': username, 'score': float(round(similarity_scores['final_score'], 2)), 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'ssim': float(round(similarity_scores['ssim'], 4)), 'psnr': float(round(similarity_scores['psnr'], 2)), 'histogram': float(round(similarity_scores['histogram'], 4)), 'resnet': float(round(similarity_scores.get('resnet', 0.0), 2)) } # 같은 이름의 기존 기록이 있는지 확인하고, 더 높은 점수만 유지 existing_indices = [i for i, data in enumerate(self.leaderboard_data) if data['username'] == username] if existing_indices: # 기존 기록 중 가장 높은 점수 찾기 existing_scores = [self.leaderboard_data[i]['score'] for i in existing_indices] max_existing_score = max(existing_scores) # 새 점수가 더 높으면 기존 기록들을 모두 제거하고 새 기록 추가 if entry['score'] > max_existing_score: # 기존 기록들을 역순으로 제거 (인덱스 변경 방지) for i in sorted(existing_indices, reverse=True): del self.leaderboard_data[i] self.leaderboard_data.append(entry) self.save_leaderboard() # 갱신된 경우의 메시지 result_message = f"""🎉 대단해요! 새로운 최고 기록입니다! 👤 {username}님 🏆 점수: {entry['score']:.0f}점 📈 이전 최고: {max_existing_score:.0f}점 ✅ 리더보드에 등록되었습니다! 📅 {entry['date']}""" else: # 새 점수가 더 낮거나 같으면 리더보드는 업데이트하지 않음 result_message = f"""🎯 점수가 계산되었습니다! 👤 {username}님 🏆 현재 점수: {entry['score']:.0f}점 📈 최고 점수: {max_existing_score:.0f}점 💪 조금만 더 노력하면 최고 기록을 갱신할 수 있을 것 같아요! 다시 시도해보세요!""" else: # 기존 기록이 없으면 새로 추가 self.leaderboard_data.append(entry) self.save_leaderboard() # 새로 등록된 경우의 메시지 result_message = f"""🎉 첫 등록을 축하합니다! 👤 {username}님 🏆 점수: {entry['score']:.0f}점 ✅ 리더보드에 등록되었습니다! 📅 {entry['date']}""" return result_message, self.get_leaderboard_df() except Exception as e: import traceback error_msg = f"오류가 발생했습니다: {str(e)}\n\n상세 정보:\n{traceback.format_exc()}" return error_msg, None def get_leaderboard_df(self): """리더보드를 DataFrame으로 반환합니다.""" if not self.leaderboard_data: return pd.DataFrame(columns=['순위', '사용자명', '점수', '날짜']) # 점수 기준으로 정렬 sorted_data = sorted(self.leaderboard_data, key=lambda x: x['score'], reverse=True) # DataFrame 생성 df_data = [] for i, entry in enumerate(sorted_data, 1): df_data.append({ '순위': i, '사용자명': entry['username'], '점수': entry['score'], '날짜': entry['date'] }) return pd.DataFrame(df_data) def authenticate_admin(self, password): """관리자 인증""" if password == self.admin_password: self.admin_authenticated = True return "✅ 관리자 인증이 완료되었습니다.", True else: self.admin_authenticated = False return "❌ 잘못된 비밀번호입니다.", False def update_reference_image(self, new_image): """참조 이미지 업데이트""" if not self.admin_authenticated: return "❌ 관리자 인증이 필요합니다." try: if new_image is None: return "❌ 이미지를 업로드해주세요." # 이미지를 저장 new_image.save(self.reference_image_path) return f"✅ 참조 이미지가 업데이트되었습니다. ({self.reference_image_path})" except Exception as e: return f"❌ 이미지 저장 중 오류가 발생했습니다: {str(e)}" def clear_leaderboard(self): """리더보드를 초기화합니다.""" if not self.admin_authenticated: return "❌ 관리자 인증이 필요합니다.", self.get_leaderboard_df() self.leaderboard_data = [] self.save_leaderboard() return "✅ 리더보드가 초기화되었습니다.", pd.DataFrame(columns=['순위', '사용자명', '점수', '날짜']) # 전역 리더보드 인스턴스들 (5개 탭: 4개 반 + 연습) leaderboards = { 'respect': ImageSimilarityLeaderboard("label2.jpg", "respect.json"), 'challenge': ImageSimilarityLeaderboard("label2.jpg", "challenge.json"), 'originality': ImageSimilarityLeaderboard("label2.jpg", "originality.json"), 'bce': ImageSimilarityLeaderboard("label2.jpg", "bce.json"), 'practice': ImageSimilarityLeaderboard("label2.jpg", "practice.json") } def process_user_image_respect(image, username): """Respect반 사용자 이미지 처리 함수""" return leaderboards['respect'].process_image(image, username) def process_user_image_challenge(image, username): """Challenge반 사용자 이미지 처리 함수""" return leaderboards['challenge'].process_image(image, username) def process_user_image_originality(image, username): """Originality반 사용자 이미지 처리 함수""" return leaderboards['originality'].process_image(image, username) def process_user_image_bce(image, username): """BCE반 사용자 이미지 처리 함수""" return leaderboards['bce'].process_image(image, username) def get_current_leaderboard_respect(): """Respect반 현재 리더보드 반환""" return leaderboards['respect'].get_leaderboard_df() def get_current_leaderboard_challenge(): """Challenge반 현재 리더보드 반환""" return leaderboards['challenge'].get_leaderboard_df() def get_current_leaderboard_originality(): """Originality반 현재 리더보드 반환""" return leaderboards['originality'].get_leaderboard_df() def get_current_leaderboard_bce(): """BCE반 현재 리더보드 반환""" return leaderboards['bce'].get_leaderboard_df() def check_and_update_leaderboard_respect(): """Respect반 리더보드 업데이트 확인 및 반환""" leaderboards['respect'].check_for_updates() return leaderboards['respect'].get_leaderboard_df() def check_and_update_leaderboard_challenge(): """Challenge반 리더보드 업데이트 확인 및 반환""" leaderboards['challenge'].check_for_updates() return leaderboards['challenge'].get_leaderboard_df() def check_and_update_leaderboard_originality(): """Originality반 리더보드 업데이트 확인 및 반환""" leaderboards['originality'].check_for_updates() return leaderboards['originality'].get_leaderboard_df() def check_and_update_leaderboard_bce(): """BCE반 리더보드 업데이트 확인 및 반환""" leaderboards['bce'].check_for_updates() return leaderboards['bce'].get_leaderboard_df() def process_user_image_practice(image, username): """연습 탭 사용자 이미지 처리 함수""" return leaderboards['practice'].process_image(image, username) def get_current_leaderboard_practice(): """연습 탭 현재 리더보드 반환""" return leaderboards['practice'].get_leaderboard_df() def check_and_update_leaderboard_practice(): """연습 탭 리더보드 업데이트 확인 및 반환""" leaderboards['practice'].check_for_updates() return leaderboards['practice'].get_leaderboard_df() def create_interface(): """Gradio 인터페이스 생성""" with gr.Blocks(title="비슷한 이미지를 만들어주세요!", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🏆 롯데 비전 스튜디오 리더보드 """) # 탭 생성 with gr.Tabs(): # 연습 탭 (메인) with gr.Tab("🎓 연습"): gr.Markdown("### 💡 연습용 탭 (메인)") gr.Markdown("업로드 및 이미지 유사도를 테스트해볼 수 있는 연습 공간입니다.") gr.Markdown("---") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📤 이미지 업로드 (연습)") image_input_practice = gr.Image( label="비교할 이미지를 업로드하세요", type="pil", height=300 ) username_input_practice = gr.Textbox( label="사용자 이름 (연습용)", placeholder="이름을 입력하세요", max_lines=1 ) submit_btn_practice = gr.Button("🚀 유사도 테스트", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 테스트 결과") result_output_practice = gr.Textbox( label="유사도 분석 결과", lines=12, interactive=False ) gr.Markdown("### 🏅 연습 리더보드") leaderboard_output_practice = gr.Dataframe( headers=["순위", "사용자명", "점수", "날짜"], datatype=["number", "str", "number", "str"], interactive=False ) # 연습 탭 이벤트 핸들러 submit_btn_practice.click( fn=process_user_image_practice, inputs=[image_input_practice, username_input_practice], outputs=[result_output_practice, leaderboard_output_practice] ) # Respect반 탭 with gr.Tab("🎯 Respect반"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📤 이미지 업로드 (Respect반)") image_input_respect = gr.Image( label="비교할 이미지를 업로드하세요", type="pil", height=300 ) username_input_respect = gr.Textbox( label="사용자 이름", placeholder="이름을 입력하세요", max_lines=1 ) submit_btn_respect = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 결과 (Respect반)") result_output_respect = gr.Textbox( label="계산 결과", lines=10, interactive=False ) gr.Markdown("### 🏅 Respect반 리더보드") leaderboard_output_respect = gr.Dataframe( headers=["순위", "사용자명", "점수", "날짜"], datatype=["number", "str", "number", "str"], interactive=False ) # Respect반 이벤트 핸들러 submit_btn_respect.click( fn=process_user_image_respect, inputs=[image_input_respect, username_input_respect], outputs=[result_output_respect, leaderboard_output_respect] ) # Challenge반 탭 with gr.Tab("⚡ Challenge반"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📤 이미지 업로드 (Challenge반)") image_input_challenge = gr.Image( label="비교할 이미지를 업로드하세요", type="pil", height=300 ) username_input_challenge = gr.Textbox( label="사용자 이름", placeholder="이름을 입력하세요", max_lines=1 ) submit_btn_challenge = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 결과 (Challenge반)") result_output_challenge = gr.Textbox( label="계산 결과", lines=10, interactive=False ) gr.Markdown("### 🏅 Challenge반 리더보드") leaderboard_output_challenge = gr.Dataframe( headers=["순위", "사용자명", "점수", "날짜"], datatype=["number", "str", "number", "str"], interactive=False ) # Challenge반 이벤트 핸들러 submit_btn_challenge.click( fn=process_user_image_challenge, inputs=[image_input_challenge, username_input_challenge], outputs=[result_output_challenge, leaderboard_output_challenge] ) # Originality반 탭 with gr.Tab("🎨 Originality반"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📤 이미지 업로드 (Originality반)") image_input_originality = gr.Image( label="비교할 이미지를 업로드하세요", type="pil", height=300 ) username_input_originality = gr.Textbox( label="사용자 이름", placeholder="이름을 입력하세요", max_lines=1 ) submit_btn_originality = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 결과 (Originality반)") result_output_originality = gr.Textbox( label="계산 결과", lines=10, interactive=False ) gr.Markdown("### 🏅 Originality반 리더보드") leaderboard_output_originality = gr.Dataframe( headers=["순위", "사용자명", "점수", "날짜"], datatype=["number", "str", "number", "str"], interactive=False ) # Originality반 이벤트 핸들러 submit_btn_originality.click( fn=process_user_image_originality, inputs=[image_input_originality, username_input_originality], outputs=[result_output_originality, leaderboard_output_originality] ) # BCE반 탭 with gr.Tab("🔥 BCE반"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📤 이미지 업로드 (BCE반)") image_input_bce = gr.Image( label="비교할 이미지를 업로드하세요", type="pil", height=300 ) username_input_bce = gr.Textbox( label="사용자 이름", placeholder="이름을 입력하세요", max_lines=1 ) submit_btn_bce = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg") with gr.Column(scale=1): gr.Markdown("### 📊 결과 (BCE반)") result_output_bce = gr.Textbox( label="계산 결과", lines=10, interactive=False ) gr.Markdown("### 🏅 BCE반 리더보드") leaderboard_output_bce = gr.Dataframe( headers=["순위", "사용자명", "점수", "날짜"], datatype=["number", "str", "number", "str"], interactive=False ) # BCE반 이벤트 핸들러 submit_btn_bce.click( fn=process_user_image_bce, inputs=[image_input_bce, username_input_bce], outputs=[result_output_bce, leaderboard_output_bce] ) # 페이지 로드 시 모든 리더보드 표시 demo.load( fn=lambda: ( get_current_leaderboard_practice(), get_current_leaderboard_respect(), get_current_leaderboard_challenge(), # get_current_leaderboard_originality(), # get_current_leaderboard_bce() ), outputs=[ leaderboard_output_practice, leaderboard_output_respect, leaderboard_output_challenge, # leaderboard_output_originality, # leaderboard_output_bce, ] ) # 실시간 업데이트 (10초마다 - 모든 탭 업데이트) timer = gr.Timer(value=10) timer.tick( fn=lambda: ( get_current_leaderboard_practice(), get_current_leaderboard_respect(), get_current_leaderboard_challenge(), # get_current_leaderboard_originality(), # get_current_leaderboard_bce() ), outputs=[ leaderboard_output_practice, leaderboard_output_respect, leaderboard_output_challenge, # leaderboard_output_originality, # leaderboard_output_bce, ] ) return demo def main(): """메인 함수""" print("🏆 이미지 유사도 리더보드 시스템을 시작합니다...") print("📁 참조 이미지: label2.jpg") print("💾 데이터 파일:") print(" • Respect반: respect.json") print(" • Challenge반: challenge.json") print(" • Originality반: originality.json") print(" • BCE반: bce.json") print(" • 연습: practice.json") # 각 반별 메모리 사용량 체크 for class_name, lb in leaderboards.items(): initial_memory = lb._get_memory_usage() print(f"💾 {class_name} 초기 메모리 사용량: {initial_memory:.1f}MB") if not os.path.exists("label2.jpg"): print("⚠️ 경고: 참조 이미지(label2.jpg)를 찾을 수 없습니다!") print(" label2.jpg 파일을 프로젝트 루트에 배치해주세요.") # Gradio 인터페이스 생성 및 실행 demo = create_interface() print("🚀 서버를 시작합니다...") print("📊 최적화 사항:") print(" • 참조 이미지 캐싱 활성화") print(" • 이미지 크기 제한: 512x512") print(" • 동시성 제어 활성화") print(" • 메모리 정리 자동화") print(" • 실시간 업데이트: 10초 간격") print(" • 5개 탭별 독립 리더보드 운영 (4개 반 + 연습)") # 외부 공유 우선 시도 print("\n🌐 외부 접근 시도 중...") try: demo.launch( # server_name="0.0.0.0", # server_port=9900, # share=False, # 로컬에서 share=True는 불필요한 경고를 유발하므로 False로 변경 # show_error=False, # quiet=False, # ssr_mode=False # SSR 모드 비활성화로 실험적 기능 경고 제거 ) except Exception as e: print(f"❌ 외부 공유 실패: {e}") print("🔄 로컬 네트워크 모드로 전환...") demo.launch( server_name="0.0.0.0", server_port=9900, share=False, # 로컬 환경에서는 share=False로 유지 show_error=True, quiet=False, ssr_mode=False # SSR 모드 비활성화 ) if __name__ == "__main__": main()