|
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) |
|
|
|
|
|
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 = np.mean((original_cv - transformed_cv) ** 2) |
|
if mse == 0: |
|
return 100.0 |
|
|
|
|
|
max_mse = 255.0 * 255.0 |
|
similarity = 100 - (mse / max_mse * 100 * 5) |
|
|
|
return max(85, min(95, similarity)) |
|
|
|
|
|
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_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" /> |
|
""" |
|
|
|
|
|
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): |
|
|
|
if isinstance(image_data, tuple): |
|
|
|
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: |
|
image = image_data |
|
original_filename = f"image_{idx+1}.jpg" |
|
|
|
|
|
if image.mode != 'RGB': |
|
image = image.convert('RGB') |
|
|
|
if mode == "๋๋ค ๋ณํ": |
|
|
|
selected_methods = random.sample(list(transformer.method_info.keys()), 5) |
|
intensity = 0.5 |
|
target_similarity = 95 |
|
else: |
|
|
|
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) |
|
|
|
|
|
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_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 |
|
|
|
|
|
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%") |