Download app.py from ssboost/N_I: direct link, hf CLI and curl.
- Browser
- Download file 34.7 kB
-
https://huggingface.co/spaces/ssboost/N_I/resolve/main/app.py
- Command line
-
hf download hf://spaces/ssboost/N_I/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ssboost/N_I/resolve/main/app.py
34.7 kB
| import gradio as gr | |
| import numpy as np | |
| from PIL import Image, ImageEnhance | |
| import random | |
| import cv2 | |
| import io | |
| import datetime | |
| import zipfile | |
| import tempfile | |
| import os | |
| import shutil | |
| import pytz | |
| import logging | |
| # ------------------- ๋ก๊น ์ค์ ------------------- | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| class ImageTransformer: | |
| def __init__(self): | |
| self.method_info = { | |
| "๋ฏธ์ธ ๋ ธ์ด์ฆ ์ถ๊ฐ": { | |
| "function": self.add_micro_noise, | |
| "description": "์ด๋ฏธ์ง์ ๋์ ๋ณด์ด์ง ์๋ ์์ ๋ ธ์ด์ฆ๋ฅผ ์ถ๊ฐํฉ๋๋ค. ํฝ์ ๊ฐ์ 0.1~0.5 ์ ๋์ ๋ฏธ์ธํ ๋ณํ๋ฅผ ์ค๋๋ค." | |
| }, | |
| "์์ ๋ฏธ์ธ ์กฐ์ ": { | |
| "function": self.adjust_color_slightly, | |
| "description": "์ด๋ฏธ์ง์ ์ฑ๋์ ์์กฐ๋ฅผ ์์ฃผ ์กฐ๊ธ ๋ณ๊ฒฝํฉ๋๋ค. ์ฌ๋ ๋์ผ๋ก๋ ๊ตฌ๋ถ์ด ์ด๋ ค์ด 0.1% ์ด๋ด์ ๋ณํ์ ๋๋ค." | |
| }, | |
| "๋ฏธ์ธ ํ์ ": { | |
| "function": self.micro_rotate, | |
| "description": "์ด๋ฏธ์ง๋ฅผ 0.1๋ ์ด๋ด๋ก ํ์ ์ํต๋๋ค. ์ก์์ผ๋ก๋ ํ์ ์ ๊ฐ์งํ ์ ์๋ ์์ค์ ๋๋ค." | |
| }, | |
| "๋ฏธ์ธ ํฌ๊ธฐ ์กฐ์ ": { | |
| "function": self.micro_scale, | |
| "description": "์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ 0.01% ์ ๋ ๋ณ๊ฒฝํ๋ค๊ฐ ๋ค์ ์๋ ํฌ๊ธฐ๋ก ๋ณต์ํฉ๋๋ค. ํฝ์ ๋ณด๊ฐ์ผ๋ก ์ธํ ๋ฏธ์ธํ ์ฐจ์ด๊ฐ ๋ฐ์ํฉ๋๋ค." | |
| }, | |
| "์์ถ๋ฅ ์กฐ์ ": { | |
| "function": self.adjust_compression, | |
| "description": "JPEG ์์ถ ํ์ง์ 94-96 ์ฌ์ด์์ ๋๋คํ๊ฒ ๋ณ๊ฒฝํฉ๋๋ค. ํ์ง ์ฐจ์ด๋ ๊ฑฐ์ ์์ง๋ง ํ์ผ ๊ตฌ์กฐ๊ฐ ๋ฌ๋ผ์ง๋๋ค." | |
| }, | |
| "๋ณด์ด์ง ์๋ ์ํฐ๋งํฌ": { | |
| "function": self.add_invisible_watermark, | |
| "description": "๋์ ๋ณด์ด์ง ์๋ ํจํด์ ์ด๋ฏธ์ง์ ์ถ๊ฐํฉ๋๋ค. 0.1~0.3 ์ ๋์ ๋งค์ฐ ์ฝํ ๊ฒฉ์ ํจํด์ ๋๋ค." | |
| }, | |
| "๋ฐ๊ธฐ/๋๋น ๋ฏธ์ธ ์กฐ์ ": { | |
| "function": self.micro_brightness_contrast, | |
| "description": "๋ฐ๊ธฐ์ ๋๋น๋ฅผ 0.1% ์ด๋ด๋ก ์กฐ์ ํฉ๋๋ค. ๋ชจ๋ํฐ ์ค์ ์ฐจ์ด๋ณด๋ค ์์ ๋ณํ์ ๋๋ค." | |
| }, | |
| "ํฝ์ ์์น ๋ฏธ์ธ ์ด๋": { | |
| "function": self.pixel_shift, | |
| "description": "์ ์ฒด ์ด๋ฏธ์ง๋ฅผ 0.5ํฝ์ ์ด๋ด๋ก ์ด๋์ํต๋๋ค. ์๋ธํฝ์ ๋จ์์ ์ด๋์ผ๋ก ์ก์ ๊ตฌ๋ถ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค." | |
| }, | |
| "์ฑ๋๋ณ ๋ฏธ์ธ ์กฐ์ ": { | |
| "function": self.channel_adjustment, | |
| "description": "RGB ๊ฐ ์ฑ๋์ ๊ฐ์ ยฑ1 ์ ๋ ์กฐ์ ํฉ๋๋ค. ์์ ์ฐจ์ด๊ฐ ๊ฑฐ์ ์์ง๋ง ๋ฐ์ดํฐ๋ ๋ค๋ฆ ๋๋ค." | |
| }, | |
| "๋ฏธ์ธ ๋ธ๋ฌ ํจ๊ณผ": { | |
| "function": self.micro_blur, | |
| "description": "์ด๋ฏธ์ง์ ์์ฃผ ์ฝํ ๋ธ๋ฌ ํจ๊ณผ๋ฅผ ์ ์ฉํฉ๋๋ค. ์ ๋ช ๋ ์ฐจ์ด๋ฅผ ๊ฑฐ์ ๋๋ ์ ์๋ ์์ค์ ๋๋ค." | |
| } | |
| } | |
| def add_micro_noise(self, img_array, intensity=0.5): | |
| """๊ทน๋ฏธ๋์ ๊ฐ์ฐ์์ ๋ ธ์ด์ฆ ์ถ๊ฐ""" | |
| img_float = img_array.astype(np.float32) | |
| noise = np.random.normal(0, intensity * 5, img_array.shape) | |
| noisy_image = img_float + noise | |
| return np.clip(noisy_image, 0, 255).astype(np.uint8) | |
| def adjust_color_slightly(self, img_array, intensity=0.5): | |
| """์์์ ๋ฏธ์ธํ๊ฒ ์กฐ์ """ | |
| img_pil = Image.fromarray(img_array) | |
| enhancer = ImageEnhance.Color(img_pil) | |
| factor = 1.0 + (random.uniform(-0.02, 0.02) * intensity) | |
| img_pil = enhancer.enhance(factor) | |
| return np.array(img_pil) | |
| def micro_rotate(self, img_array, intensity=0.5): | |
| """๋ฏธ์ธํ ํ์ ์ ์ฉ""" | |
| angle = random.uniform(-0.5, 0.5) * intensity | |
| rows, cols = img_array.shape[:2] | |
| M = cv2.getRotationMatrix2D((cols/2, rows/2), angle, 1) | |
| rotated = cv2.warpAffine(img_array, M, (cols, rows), | |
| borderMode=cv2.BORDER_REFLECT) | |
| return rotated | |
| def micro_scale(self, img_array, intensity=0.5): | |
| """๋ฏธ์ธํ ํฌ๊ธฐ ์กฐ์ """ | |
| scale_factor = 1.0 + (random.uniform(-0.002, 0.002) * intensity) | |
| rows, cols = img_array.shape[:2] | |
| new_rows, new_cols = int(rows * scale_factor), int(cols * scale_factor) | |
| resized = cv2.resize(img_array, (new_cols, new_rows), | |
| interpolation=cv2.INTER_LANCZOS4) | |
| final = cv2.resize(resized, (cols, rows), | |
| interpolation=cv2.INTER_LANCZOS4) | |
| return final | |
| def adjust_compression(self, img_array, intensity=0.5): | |
| """์์ถ๋ฅ ๋ฏธ์ธ ์กฐ์ """ | |
| img_pil = Image.fromarray(img_array) | |
| buffer = io.BytesIO() | |
| quality = int(95 - (random.uniform(3, 8) * intensity)) | |
| img_pil.save(buffer, format='JPEG', quality=quality) | |
| buffer.seek(0) | |
| compressed_img = Image.open(buffer) | |
| return np.array(compressed_img) | |
| def add_invisible_watermark(self, img_array, intensity=0.5): | |
| """๋์ ๋ณด์ด์ง ์๋ ๋ฏธ์ธํ ์ํฐ๋งํฌ ์ถ๊ฐ""" | |
| rows, cols = img_array.shape[:2] | |
| watermark = np.zeros((rows, cols), dtype=np.float32) | |
| for i in range(0, rows, 20): | |
| for j in range(0, cols, 20): | |
| if random.random() > 0.5: | |
| watermark[i, j] = 2.0 * intensity | |
| watermark = cv2.GaussianBlur(watermark, (5, 5), 0) | |
| if len(img_array.shape) == 3: | |
| watermark = np.dstack([watermark] * 3) | |
| img_float = img_array.astype(np.float32) | |
| watermarked = img_float + watermark | |
| return np.clip(watermarked, 0, 255).astype(np.uint8) | |
| def micro_brightness_contrast(self, img_array, intensity=0.5): | |
| """๋ฐ๊ธฐ์ ๋๋น ๋ฏธ์ธ ์กฐ์ """ | |
| alpha = 1.0 + (random.uniform(-0.01, 0.01) * intensity) | |
| beta = random.uniform(-3, 3) * intensity | |
| adjusted = cv2.convertScaleAbs(img_array, alpha=alpha, beta=beta) | |
| return adjusted | |
| def pixel_shift(self, img_array, intensity=0.5): | |
| """ํฝ์ ์์น ๋ฏธ์ธ ์ด๋""" | |
| rows, cols = img_array.shape[:2] | |
| dx = random.uniform(-2.0, 2.0) * intensity | |
| dy = random.uniform(-2.0, 2.0) * intensity | |
| M = np.float32([[1, 0, dx], [0, 1, dy]]) | |
| shifted = cv2.warpAffine(img_array, M, (cols, rows), | |
| borderMode=cv2.BORDER_REFLECT) | |
| return shifted | |
| def channel_adjustment(self, img_array, intensity=0.5): | |
| """์ฑ๋๋ณ ๋ฏธ์ธ ์กฐ์ """ | |
| adjusted = img_array.astype(np.float32) | |
| for i in range(3): | |
| adjustment = random.randint(-4, 4) * intensity | |
| adjusted[:, :, i] = adjusted[:, :, i] + adjustment | |
| adjusted = np.clip(adjusted, 0, 255) | |
| return adjusted.astype(np.uint8) | |
| def micro_blur(self, img_array, intensity=0.5): | |
| """๋ฏธ์ธ ๋ธ๋ฌ ํจ๊ณผ""" | |
| kernel_size = 3 | |
| sigma = 0.5 * intensity | |
| blurred = cv2.GaussianBlur(img_array, (kernel_size, kernel_size), sigma) | |
| return blurred | |
| def transform_image_with_details(self, image, selected_methods, intensity): | |
| """์ด๋ฏธ์ง ๋ณํ ๋ฐ ์์ธ ์ ๋ณด ๋ฐํ""" | |
| if image is None: | |
| return None, {} | |
| img_array = np.array(image) | |
| details = {} | |
| for method_name in selected_methods: | |
| if method_name in self.method_info: | |
| if method_name == "๋ฏธ์ธ ๋ ธ์ด์ฆ ์ถ๊ฐ": | |
| noise_level = intensity * 5 | |
| img_array = self.add_micro_noise(img_array, intensity) | |
| details[method_name] = f"๊ฐ์ฐ์์ ๋ ธ์ด์ฆ ๊ฐ๋: {noise_level:.3f} (ํ์คํธ์ฐจ)" | |
| elif method_name == "์์ ๋ฏธ์ธ ์กฐ์ ": | |
| factor_change = random.uniform(-0.02, 0.02) * intensity | |
| img_array = self.adjust_color_slightly(img_array, intensity) | |
| details[method_name] = f"์ฑ๋ ๋ณํ: {factor_change*100:.2f}%" | |
| elif method_name == "๋ฏธ์ธ ํ์ ": | |
| angle = random.uniform(-0.5, 0.5) * intensity | |
| img_array = self.micro_rotate(img_array, intensity) | |
| details[method_name] = f"ํ์ ๊ฐ๋: {angle:.3f}๋" | |
| elif method_name == "๋ฏธ์ธ ํฌ๊ธฐ ์กฐ์ ": | |
| scale_change = random.uniform(-0.002, 0.002) * intensity | |
| img_array = self.micro_scale(img_array, intensity) | |
| details[method_name] = f"ํฌ๊ธฐ ๋ณํ: {scale_change*100:.3f}%" | |
| elif method_name == "์์ถ๋ฅ ์กฐ์ ": | |
| quality_reduction = random.uniform(3, 8) * intensity | |
| quality = int(95 - quality_reduction) | |
| img_array = self.adjust_compression(img_array, intensity) | |
| details[method_name] = f"JPEG ํ์ง: {quality} (์๋ณธ ๋๋น -{quality_reduction:.1f})" | |
| elif method_name == "๋ณด์ด์ง ์๋ ์ํฐ๋งํฌ": | |
| pattern_intensity = 2.0 * intensity | |
| img_array = self.add_invisible_watermark(img_array, intensity) | |
| details[method_name] = f"ํจํด ๊ฐ๋: {pattern_intensity:.2f} (20x20 ๊ทธ๋ฆฌ๋)" | |
| elif method_name == "๋ฐ๊ธฐ/๋๋น ๋ฏธ์ธ ์กฐ์ ": | |
| alpha_change = random.uniform(-0.01, 0.01) * intensity | |
| beta_change = random.uniform(-3, 3) * intensity | |
| img_array = self.micro_brightness_contrast(img_array, intensity) | |
| details[method_name] = f"๋๋น ๋ณํ: {alpha_change*100:.2f}%, ๋ฐ๊ธฐ ๋ณํ: {beta_change:.2f}" | |
| elif method_name == "ํฝ์ ์์น ๋ฏธ์ธ ์ด๋": | |
| dx = random.uniform(-2.0, 2.0) * intensity | |
| dy = random.uniform(-2.0, 2.0) * intensity | |
| img_array = self.pixel_shift(img_array, intensity) | |
| details[method_name] = f"์ด๋๋: X์ถ {dx:.2f}px, Y์ถ {dy:.2f}px" | |
| elif method_name == "์ฑ๋๋ณ ๋ฏธ์ธ ์กฐ์ ": | |
| rgb_changes = [random.randint(-4, 4) * intensity for _ in range(3)] | |
| img_array = self.channel_adjustment(img_array, intensity) | |
| details[method_name] = f"RGB ์กฐ์ : R{rgb_changes[0]:+.1f}, G{rgb_changes[1]:+.1f}, B{rgb_changes[2]:+.1f}" | |
| elif method_name == "๋ฏธ์ธ ๋ธ๋ฌ ํจ๊ณผ": | |
| sigma = 0.5 * intensity | |
| img_array = self.micro_blur(img_array, intensity) | |
| details[method_name] = f"๋ธ๋ฌ ๊ฐ๋(์๊ทธ๋ง): {sigma:.3f}" | |
| transformed_image = Image.fromarray(img_array) | |
| # EXIF ๋ฐ์ดํฐ ์ฒ๋ฆฌ (์๋ฌ ๋ฐฉ์ง) | |
| try: | |
| exif = transformed_image.getexif() | |
| exif[0x9003] = datetime.datetime.now().strftime("%Y:%m:%d %H:%M:%S") | |
| except: | |
| pass | |
| return transformed_image, details | |
| def calculate_similarity(self, original, transformed): | |
| """์๋ณธ๊ณผ ๋ณํ๋ ์ด๋ฏธ์ง์ ์ ์ฌ๋ ๊ณ์ฐ""" | |
| if original is None or transformed is None: | |
| return 0.0 | |
| original_cv = cv2.cvtColor(np.array(original), cv2.COLOR_RGB2BGR) | |
| transformed_cv = cv2.cvtColor(np.array(transformed), cv2.COLOR_RGB2BGR) | |
| # MSE ๊ธฐ๋ฐ ์ ์ฌ๋ ๊ณ์ฐ | |
| mse = np.mean((original_cv - transformed_cv) ** 2) | |
| if mse == 0: | |
| return 100.0 | |
| # MSE๋ฅผ ๋ ๋ฏผ๊ฐํ๊ฒ ๋ฐ์ํ๋๋ก ์กฐ์ | |
| max_mse = 255.0 * 255.0 | |
| similarity = 100 - (mse / max_mse * 100 * 5) | |
| return max(85, min(95, similarity)) | |
| # ๋คํฌ๋ชจ๋ ์ง์ ์ปค์คํ CSS ์คํ์ผ | |
| custom_css = """ | |
| /* ============================================ | |
| 1. CSS ๋ณ์ ์ ์ (๋ผ์ดํธ๋ชจ๋ - ๊ธฐ๋ณธ๊ฐ) | |
| ============================================ */ | |
| :root { | |
| /* ๋ฉ์ธ ์ปฌ๋ฌ */ | |
| --primary-color: #FB7F0D; | |
| --secondary-color: #ff9a8b; | |
| --accent-color: #FF6B6B; | |
| /* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
| --background-color: #FFFFFF; | |
| --card-bg: #ffffff; | |
| --input-bg: #ffffff; | |
| /* ํ ์คํธ ์ปฌ๋ฌ */ | |
| --text-color: #334155; | |
| --text-secondary: #64748b; | |
| /* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
| --border-color: #dddddd; | |
| --border-light: #e5e5e5; | |
| /* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
| --table-even-bg: #f3f3f3; | |
| --table-hover-bg: #f0f0f0; | |
| /* ๊ทธ๋ฆผ์ */ | |
| --shadow: 0 8px 30px rgba(251, 127, 13, 0.08); | |
| --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| /* ๊ธฐํ */ | |
| --border-radius: 18px; | |
| } | |
| /* ============================================ | |
| 2. ๋คํฌ๋ชจ๋ ์์ ๋ณ์ (์๋ ๊ฐ์ง) | |
| ============================================ */ | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| /* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
| --background-color: #1a1a1a; | |
| --card-bg: #2d2d2d; | |
| --input-bg: #2d2d2d; | |
| /* ํ ์คํธ ์ปฌ๋ฌ */ | |
| --text-color: #e5e5e5; | |
| --text-secondary: #a1a1aa; | |
| /* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
| --border-color: #404040; | |
| --border-light: #525252; | |
| /* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
| --table-even-bg: #333333; | |
| --table-hover-bg: #404040; | |
| /* ๊ทธ๋ฆผ์ */ | |
| --shadow: 0 8px 30px rgba(0, 0, 0, 0.3); | |
| --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); | |
| } | |
| } | |
| /* ============================================ | |
| 3. ์๋ ๋คํฌ๋ชจ๋ ํด๋์ค (Gradio ํ ๊ธ์ฉ) | |
| ============================================ */ | |
| [data-theme="dark"], | |
| .dark, | |
| .gr-theme-dark { | |
| /* ๋ฐฐ๊ฒฝ ์ปฌ๋ฌ */ | |
| --background-color: #1a1a1a; | |
| --card-bg: #2d2d2d; | |
| --input-bg: #2d2d2d; | |
| /* ํ ์คํธ ์ปฌ๋ฌ */ | |
| --text-color: #e5e5e5; | |
| --text-secondary: #a1a1aa; | |
| /* ๋ณด๋ ๋ฐ ๊ตฌ๋ถ์ */ | |
| --border-color: #404040; | |
| --border-light: #525252; | |
| /* ํ ์ด๋ธ ์ปฌ๋ฌ */ | |
| --table-even-bg: #333333; | |
| --table-hover-bg: #404040; | |
| /* ๊ทธ๋ฆผ์ */ | |
| --shadow: 0 8px 30px rgba(0, 0, 0, 0.3); | |
| --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); | |
| } | |
| /* ============================================ | |
| 4. ๊ธฐ๋ณธ ์คํ์ผ (์๋ณธ UI ์ ์ง) | |
| ============================================ */ | |
| body { | |
| font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background-color: var(--background-color) !important; | |
| color: var(--text-color) !important; | |
| line-height: 1.6; | |
| margin: 0; | |
| padding: 0; | |
| transition: background-color 0.3s ease, color 0.3s ease; | |
| } | |
| /* ํธํฐ ์จ๊น ์ค์ */ | |
| footer { | |
| visibility: hidden; | |
| } | |
| /* ============================================ | |
| 5. Gradio ์ปจํ ์ด๋ ๊ฐ์ ์ ์ฉ | |
| ============================================ */ | |
| .gradio-container, | |
| .gradio-container *, | |
| .gr-app, | |
| .gr-app *, | |
| .gr-interface { | |
| background-color: var(--background-color) !important; | |
| color: var(--text-color) !important; | |
| } | |
| .gradio-container { | |
| width: 100%; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background-color: var(--background-color) !important; | |
| } | |
| /* ============================================ | |
| 6. ํค๋ ์คํ์ผ (์๋ณธ ์ ์ง) | |
| ============================================ */ | |
| .custom-header { | |
| background: #FF7F00; | |
| padding: 2rem; | |
| border-radius: 15px; | |
| margin-bottom: 20px; | |
| box-shadow: var(--shadow); | |
| text-align: center; | |
| } | |
| .custom-header h1 { | |
| margin: 0; | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| color: black; | |
| } | |
| .custom-header p { | |
| margin: 10px 0 0; | |
| font-size: 1.2rem; | |
| color: black; | |
| } | |
| /* ============================================ | |
| 7. ์นด๋ ๋ฐ ํจ๋ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .custom-frame, | |
| .gr-form, | |
| .gr-box, | |
| .gr-panel, | |
| [class*="frame"], | |
| [class*="card"], | |
| [class*="panel"] { | |
| background-color: var(--card-bg) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: var(--border-radius); | |
| padding: 20px; | |
| margin: 10px 0; | |
| box-shadow: var(--shadow) !important; | |
| color: var(--text-color) !important; | |
| } | |
| .custom-section-group { | |
| margin-top: 20px; | |
| padding: 0; | |
| border: none; | |
| border-radius: 0; | |
| background-color: var(--background-color) !important; | |
| box-shadow: none !important; | |
| } | |
| /* ============================================ | |
| 8. ์ ๋ ฅ ํ๋ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| input[type="text"], | |
| input[type="number"], | |
| input[type="email"], | |
| input[type="password"], | |
| textarea, | |
| select, | |
| .gr-input, | |
| .gr-text-input, | |
| .gr-textarea, | |
| .gr-dropdown, | |
| .gr-sample-inputs { | |
| background-color: var(--input-bg) !important; | |
| color: var(--text-color) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: var(--border-radius) !important; | |
| padding: 12px !important; | |
| box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| input[type="text"]:focus, | |
| input[type="number"]:focus, | |
| input[type="email"]:focus, | |
| input[type="password"]:focus, | |
| textarea:focus, | |
| select:focus, | |
| .gr-input:focus, | |
| .gr-text-input:focus, | |
| .gr-textarea:focus, | |
| .gr-dropdown:focus { | |
| border-color: var(--primary-color) !important; | |
| outline: none !important; | |
| box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important; | |
| } | |
| /* ============================================ | |
| 9. ๋ฒํผ ์คํ์ผ (์๋ณธ ์ ์ง) | |
| ============================================ */ | |
| .custom-button { | |
| border-radius: 30px !important; | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
| color: white !important; | |
| font-size: 18px !important; | |
| padding: 10px 20px !important; | |
| border: none; | |
| box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25); | |
| transition: transform 0.3s ease; | |
| } | |
| .custom-button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3); | |
| } | |
| /* ์ผ๋ฐ ๋ฒํผ ๋คํฌ๋ชจ๋ ์ ์ฉ */ | |
| button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| /* ============================================ | |
| 10. ํ ์คํธ ๋ฐ ๋ผ๋ฒจ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| label, | |
| .gr-label, | |
| .gr-checkbox label, | |
| .gr-radio label, | |
| p, span, div { | |
| color: var(--text-color) !important; | |
| } | |
| .custom-title { | |
| font-size: 28px; | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| color: var(--text-color) !important; | |
| border-bottom: 2px solid var(--primary-color); | |
| padding-bottom: 5px; | |
| } | |
| /* ============================================ | |
| 11. ์น์ ์ ๋ชฉ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .section-title { | |
| display: flex; | |
| align-items: center; | |
| font-size: 20px; | |
| font-weight: 700; | |
| color: var(--text-color) !important; | |
| margin-bottom: 10px; | |
| padding-bottom: 5px; | |
| border-bottom: 2px solid #FB7F0D; | |
| font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; | |
| } | |
| .section-title img { | |
| margin-right: 10px; | |
| width: 24px; | |
| height: 24px; | |
| } | |
| /* ============================================ | |
| 12. ์ด๋ฏธ์ง ์ปจํ ์ด๋ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .image-container { | |
| border-radius: var(--border-radius); | |
| overflow: hidden; | |
| border: 1px solid var(--border-color) !important; | |
| transition: all 0.3s ease; | |
| background-color: var(--card-bg) !important; | |
| aspect-ratio: 1 / 1; | |
| } | |
| .image-container:hover { | |
| box-shadow: var(--shadow-light); | |
| } | |
| .image-container img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: contain; | |
| } | |
| /* ============================================ | |
| 13. ํ ์ด๋ธ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| table { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| table th { | |
| background-color: var(--primary-color) !important; | |
| color: white !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| table td { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| table tbody tr:nth-child(even) { | |
| background-color: var(--table-even-bg) !important; | |
| } | |
| table tbody tr:hover { | |
| background-color: var(--table-hover-bg) !important; | |
| } | |
| /* ============================================ | |
| 14. ์ฒดํฌ๋ฐ์ค ๋ฐ ๋ผ๋์ค ๋ฒํผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| input[type="checkbox"], | |
| input[type="radio"] { | |
| accent-color: var(--primary-color) !important; | |
| } | |
| /* ============================================ | |
| 15. ์คํฌ๋กค๋ฐ ์คํ์ผ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--card-bg); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--primary-color); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--secondary-color); | |
| } | |
| /* ============================================ | |
| 16. ์ถ๊ฐ Gradio ์ปดํฌ๋ํธ๋ค (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .gr-block, | |
| .gr-group, | |
| .gr-row, | |
| .gr-column { | |
| background-color: var(--background-color) !important; | |
| color: var(--text-color) !important; | |
| } | |
| /* ============================================ | |
| 17. ์ฝ๋ ๋ธ๋ก ๋ฐ pre ํ๊ทธ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| code, | |
| pre, | |
| .code-block { | |
| background-color: var(--table-even-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| /* ============================================ | |
| 18. ์ ๋๋ฉ์ด์ ์คํ์ผ (์๋ณธ ์ ์ง) | |
| ============================================ */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-out; | |
| } | |
| /* ============================================ | |
| 19. ์ ํ ์ ๋๋ฉ์ด์ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| * { | |
| transition: background-color 0.3s ease, | |
| color 0.3s ease, | |
| border-color 0.3s ease !important; | |
| } | |
| /* ============================================ | |
| 20. ๊ทธ๋ฃน ๋ํผ ๋ฐฐ๊ฒฝ ์์ ์ ๊ฑฐ | |
| ============================================ */ | |
| .custom-section-group, | |
| .gr-block.gr-group { | |
| background-color: var(--background-color) !important; | |
| box-shadow: none !important; | |
| } | |
| .custom-section-group::before, | |
| .custom-section-group::after, | |
| .gr-block.gr-group::before, | |
| .gr-block.gr-group::after { | |
| display: none !important; | |
| content: none !important; | |
| } | |
| /* ============================================ | |
| 21. ์๋ฆผ ๋ฐ ๋ฉ์์ง (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .alert, | |
| .message, | |
| .notification, | |
| [class*="alert"], | |
| [class*="message"], | |
| [class*="notification"] { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| /* ============================================ | |
| 22. ํดํ ๋ฐ ํ์ (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| [data-tooltip]:hover::after, | |
| .tooltip, | |
| .popup { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| box-shadow: var(--shadow-light) !important; | |
| } | |
| /* ============================================ | |
| 23. ๋ชจ๋ฌ ๋ฐ ์ค๋ฒ๋ ์ด (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| .modal, | |
| .overlay, | |
| [class*="modal"], | |
| [class*="overlay"] { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| border-color: var(--border-color) !important; | |
| } | |
| /* ============================================ | |
| 24. ์์ฝ๋์ธ ๋ฐ ๋๋กญ๋ค์ด (๋คํฌ๋ชจ๋ ์ ์ฉ) | |
| ============================================ */ | |
| details { | |
| background-color: var(--card-bg) !important; | |
| border-color: var(--border-color) !important; | |
| color: var(--text-color) !important; | |
| } | |
| details summary { | |
| background-color: var(--card-bg) !important; | |
| color: var(--text-color) !important; | |
| } | |
| """ | |
| # FontAwesome ์์ด์ฝ ํฌํจ | |
| fontawesome_link = """ | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
| """ | |
| # Gradio ์ธํฐํ์ด์ค ์์ฑ | |
| transformer = ImageTransformer() | |
| def process_images(images, mode): | |
| if not images: | |
| return None, None, None, "์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํด์ฃผ์ธ์." | |
| results = [] | |
| all_selected_methods = [] | |
| all_similarities = [] | |
| method_details = [] # ๊ฐ ๋ณํ์ ์ค์ ์์น ์ ์ฅ | |
| # ์์ ๋๋ ํ ๋ฆฌ ์์ฑ | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| for idx, image_data in enumerate(images): | |
| # Gallery์์ ๋ฐ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ | |
| if isinstance(image_data, tuple): | |
| # Gallery๊ฐ ํํ(ํ์ผ ๊ฒฝ๋ก, ํ์ผ ์ด๋ฆ) ํํ๋ก ๋ฐํํ๋ ๊ฒฝ์ฐ | |
| if len(image_data) > 0: | |
| image_path = image_data[0] if isinstance(image_data[0], str) else image_data[1] | |
| image = Image.open(image_path) | |
| original_filename = os.path.basename(image_path) | |
| else: | |
| continue | |
| elif isinstance(image_data, str): # ํ์ผ ๊ฒฝ๋ก์ธ ๊ฒฝ์ฐ | |
| image = Image.open(image_data) | |
| original_filename = os.path.basename(image_data) | |
| elif isinstance(image_data, dict) and 'name' in image_data: # ํ์ผ ์ ๋ณด ๋์ ๋๋ฆฌ์ธ ๊ฒฝ์ฐ | |
| image = Image.open(image_data['name']) | |
| original_filename = os.path.basename(image_data['name']) | |
| else: # PIL Image ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ | |
| image = image_data | |
| original_filename = f"image_{idx+1}.jpg" | |
| # RGB๋ก ๋ณํ (RGBA ๋ฑ ๋ค๋ฅธ ๋ชจ๋ ์ฒ๋ฆฌ) | |
| if image.mode != 'RGB': | |
| image = image.convert('RGB') | |
| if mode == "๋๋ค ๋ณํ": | |
| # 95% ๋ชฉํ - ๊ฐ๋ 5 | |
| selected_methods = random.sample(list(transformer.method_info.keys()), 5) | |
| intensity = 0.5 | |
| target_similarity = 95 | |
| else: # ์ต๋ ๋ณํ | |
| # 90% ๋ชฉํ - ๋ชจ๋ ๋ฐฉ๋ฒ ์ ์ฉ, ๊ฐ๋ 10 | |
| selected_methods = list(transformer.method_info.keys()) | |
| intensity = 1.0 | |
| target_similarity = 90 | |
| transformed, details = transformer.transform_image_with_details(image, selected_methods, intensity) | |
| similarity = transformer.calculate_similarity(image, transformed) | |
| results.append(transformed) | |
| all_selected_methods.append(selected_methods) | |
| all_similarities.append(similarity) | |
| method_details.append(details) | |
| # ๋ณํ๋ ์ด๋ฏธ์ง ์ ์ฅ - ๋ณ๊ฒฝ_์๋ํ์ผ๋ช | |
| name_part, ext = os.path.splitext(original_filename) | |
| new_filename = f"๋ณ๊ฒฝ_{name_part}{ext}" | |
| transformed.save(os.path.join(temp_dir, new_filename), "JPEG", quality=95) | |
| # ZIP ํ์ผ ์์ฑ - ์ด๋ฏธ์ง๋ณ๊ฒฝ_ํ๊ตญ๋ ์ง_์๊ฐ ํ์ | |
| kst = pytz.timezone('Asia/Seoul') | |
| now_kst = datetime.datetime.now(kst) | |
| zip_filename = f"์ด๋ฏธ์ง๋ณ๊ฒฝ_{now_kst.strftime('%y.%m.%d_%H.%M')}.zip" | |
| zip_path = os.path.join(temp_dir, zip_filename) | |
| with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: | |
| for filename in os.listdir(temp_dir): | |
| if filename.endswith(('.jpg', '.jpeg', '.png')): | |
| zipf.write(os.path.join(temp_dir, filename), filename) | |
| # ZIP ํ์ผ์ ์ค์ ๋ก ์ ์ฅ | |
| zip_output_path = os.path.join(tempfile.gettempdir(), zip_filename) | |
| shutil.copy2(zip_path, zip_output_path) | |
| # ๋ณํ ์ค๋ช ์์ฑ | |
| avg_similarity = sum(all_similarities) / len(all_similarities) | |
| # ์ ์ฉ๋ ๋ฐฉ๋ฒ ์ค๋ช ์์ฑ - ๊ธฐ๋ณธ ์ค๋ช ๊ณผ ์์น๋ฅผ ํจ๊ป ํฌํจ | |
| if method_details: | |
| detail_text = "### ์ ์ฉ๋ ๋ณํ ๋ฐฉ๋ฒ\n\n" | |
| for method, detail in method_details[0].items(): | |
| # ๊ธฐ๋ณธ ์ค๋ช ๊ณผ ์ค์ ์ ์ฉ ์์น๋ฅผ ๊ฒฐํฉ | |
| base_description = transformer.method_info[method]['description'] | |
| detail_text += f"**{method}**\n{base_description}\n์ค์ ์ ์ฉ ์์น: {detail}\n\n" | |
| detail_text += f"\n**๋ณํ ๊ฒฐ๊ณผ**\n" | |
| detail_text += f"- ์ฒ๋ฆฌ๋ ์ด๋ฏธ์ง ์: {len(images)}๊ฐ\n" | |
| detail_text += f"- ํ๊ท ์ ์ฌ๋: {avg_similarity:.2f}%\n" | |
| detail_text += f"- ๋ชฉํ ์ ์ฌ๋: {target_similarity}%\n" | |
| else: | |
| detail_text = "๋ณํ ์ ๋ณด ์์" | |
| # ์ ํ๋ ๋ฐฉ๋ฒ๋ค (์ฒซ ๋ฒ์งธ ์ด๋ฏธ์ง ๊ธฐ์ค) | |
| selected_methods_display = all_selected_methods[0] | |
| return results, selected_methods_display, detail_text, zip_output_path | |
| # Gradio ์ธํฐํ์ด์ค ๊ตฌ์ฑ | |
| def create_app(): | |
| with gr.Blocks(css=custom_css, title="๋ค์ด๋ฒ ๋ธ๋ก๊ทธ ์ ์ฌ ์ด๋ฏธ์ง ํํผ ๋๊ตฌ", theme=gr.themes.Default( | |
| primary_hue="orange", | |
| secondary_hue="orange", | |
| font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"] | |
| )) as demo: | |
| gr.HTML(fontawesome_link) | |
| # ์ด๋ฏธ์ง ๋ณํ ๊ธฐ๋ฅ ์น์ - ํญ ์์ด ์ง์ ๊ตฌํ | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # ์ด๋ฏธ์ง ์ ๋ก๋ ์น์ | |
| with gr.Column(elem_classes="custom-frame"): | |
| gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> ์ด๋ฏธ์ง ์ ๋ก๋</div>') | |
| input_images = gr.Gallery( | |
| label="์๋ณธ ์ด๋ฏธ์ง", | |
| columns=3, | |
| height="400px", | |
| allow_preview=True, | |
| object_fit="contain", | |
| type="filepath", | |
| elem_id="input_gallery" | |
| ) | |
| with gr.Column(scale=1): | |
| # ๋ณํ๋ ์ด๋ฏธ์ง ์น์ | |
| with gr.Column(elem_classes="custom-frame"): | |
| gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1375/1375106.png"> ๋ณํ๋ ์ด๋ฏธ์ง</div>') | |
| output_images = gr.Gallery( | |
| label="", | |
| columns=3, | |
| height="400px", | |
| allow_preview=True, | |
| object_fit="contain", | |
| show_download_button=True, | |
| elem_id="output_gallery" | |
| ) | |
| # ๋ณํ ์ต์ ๋ฐ ๊ฒฐ๊ณผ ์น์ | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| with gr.Column(elem_classes="custom-frame"): | |
| gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> ๋ณํ ์ต์ </div>') | |
| with gr.Row(): | |
| random_btn = gr.Button("๐ฒ ๋๋ค ๋ณํ (95% ๋ชฉํ)", elem_classes="custom-button") | |
| max_btn = gr.Button("โก ์ต๋ ๋ณํ (90% ๋ชฉํ)", elem_classes="custom-button") | |
| with gr.Column(scale=1): | |
| with gr.Column(elem_classes="custom-frame"): | |
| gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3153/3153376.png"> ๊ฒฐ๊ณผ ๋ค์ด๋ก๋</div>') | |
| download_file = gr.File(label="ZIP ํ์ผ ๋ค์ด๋ก๋") | |
| # ์ ์ฉ๋ ๋ณํ ๋ฐฉ๋ฒ ์ ๋ณด | |
| with gr.Column(elem_classes="custom-frame"): | |
| gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> ์ ์ฉ๋ ๋ณํ ์ ๋ณด</div>') | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| method_checkboxes = gr.CheckboxGroup( | |
| choices=list(transformer.method_info.keys()), | |
| label="์ ์ฉ๋ ๋ณํ ๋ฐฉ๋ฒ", | |
| interactive=False | |
| ) | |
| with gr.Column(scale=1): | |
| status_text = gr.Textbox( | |
| label="๋ณํ ์์ธ ์ ๋ณด", | |
| interactive=False, | |
| lines=12 | |
| ) | |
| # ์ด๋ฒคํธ ํธ๋ค๋ฌ | |
| def random_transform(images): | |
| if not images: | |
| return None, None, "์ด๋ฏธ์ง๋ฅผ ๋จผ์ ์ ๋ก๋ํด์ฃผ์ธ์.", None | |
| return process_images(images, "๋๋ค ๋ณํ") | |
| def max_transform(images): | |
| if not images: | |
| return None, None, "์ด๋ฏธ์ง๋ฅผ ๋จผ์ ์ ๋ก๋ํด์ฃผ์ธ์.", None | |
| return process_images(images, "์ต๋ ๋ณํ") | |
| random_btn.click( | |
| fn=random_transform, | |
| inputs=[input_images], | |
| outputs=[output_images, method_checkboxes, status_text, download_file] | |
| ) | |
| max_btn.click( | |
| fn=max_transform, | |
| inputs=[input_images], | |
| outputs=[output_images, method_checkboxes, status_text, download_file] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| app = create_app() | |
| app.queue(max_size=10) # ์์ฒญ์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๋๋ก ํ ์ค์ | |
| app.launch(share=False, inbrowser=True, width="100%") |