devmeta commited on
Commit
48701bd
ยท
verified ยท
1 Parent(s): 34807c0

Upload 2 files

Browse files

# ๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ CNN ๊ธฐ๋ฐ˜ ์›Œํ„ฐ๋งˆํ‚น ์‹œ์Šคํ…œ

์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ์›Œํ„ฐ๋งˆํ‚น ๋ฐ ๊ฒ€์ฆ์„ ์œ„ํ•œ ๊ฒฝ๋Ÿ‰ํ™”๋œ ๋”ฅ๋Ÿฌ๋‹ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

## ๐Ÿš€ ์ฃผ์š” ๊ธฐ๋Šฅ

### ๐Ÿ”’ ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
- **์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ**: ๊ฒฝ๋Ÿ‰ํ™”๋œ CNN ๋ชจ๋ธ๋กœ ๋น ๋ฅธ ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
- **์ ์‘ํ˜• ํ•ด์ƒ๋„**: ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ์— ์ž๋™ ์ ์‘
- **์‚ฌ์šฉ์ž๋ณ„ ๊ณ ์œ  ํŒจํ„ด**: ์‚ฌ์šฉ์ž ID ๊ธฐ๋ฐ˜ ๊ฐœ์ธํ™”๋œ ์›Œํ„ฐ๋งˆํฌ ์ƒ์„ฑ

### ๐Ÿ” ์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ
- **ํŒจํ„ด ์ถ”์ถœ**: ์ด๋ฏธ์ง€์—์„œ ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด ์ถ”์ถœ ๋ฐ ์‹œ๊ฐํ™”
- **์‹ ๋ขฐ๋„ ๋ถ„์„**: ์›Œํ„ฐ๋งˆํฌ ์กด์žฌ ์—ฌ๋ถ€์˜ ํ™•๋ฅ ์  ํŒ๋‹จ
- **๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฒ€์ฆ**: ์›๋ณธ ์ •๋ณด์™€์˜ ๊ต์ฐจ ๊ฒ€์ฆ

### ๐Ÿ“Š ํ’ˆ์งˆ ๋ถ„์„
- **PSNR ์ธก์ •**: ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž… ํ›„ ํ™”์งˆ ๋ณ€ํ™” ์ •๋Ÿ‰ ๋ถ„์„
- **์ฐจ์ด ์‹œ๊ฐํ™”**: ์›๋ณธ๊ณผ ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€์˜ ์ฐจ์ด์  ํ‘œ์‹œ

## ๐Ÿ—๏ธ ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

### CNN ๋ชจ๋ธ ๊ตฌ์กฐ
- **์ธ์ฝ”๋”**: MobileNet ๊ธฐ๋ฐ˜ ๊ฒฝ๋Ÿ‰ํ™” ์•„ํ‚คํ…์ฒ˜
- Depthwise Separable Convolution ํ™œ์šฉ
- ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ ์ตœ์ ํ™”
- **๋””์ฝ”๋”**: ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด ์ถ”์ถœ์šฉ CNN
- Adaptive Pooling์œผ๋กœ ํ•ด์ƒ๋„ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ

### ๊ธฐ์ˆ ์  ํŠน์ง•
- **๋””๋ฐ”์ด์Šค**: CPU/GPU ์ž๋™ ๊ฐ์ง€ ๋ฐ ํ™œ์šฉ
- **์›Œํ„ฐ๋งˆํฌ ํฌ๊ธฐ**: 32x32 ํ”ฝ์…€ ํŒจํ„ด
- **์ง€์› ํฌ๋งท**: JPG, PNG, BMP
- **์ฒ˜๋ฆฌ ์†๋„**: < 1์ดˆ (๋ชฉํ‘œ)

## ๐ŸŽฏ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

### 1๋‹จ๊ณ„: ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
1. ์›๋ณธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
2. ์‚ฌ์šฉ์ž ID ์ž…๋ ฅ (์˜ˆ: user123)
3. ์ถœ๋ ฅ ํฌ๋งท ์„ ํƒ (PNG/JPG)
4. "์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…" ๋ฒ„ํŠผ ํด๋ฆญ

### 2๋‹จ๊ณ„: ์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ
1. ์˜์‹ฌ์Šค๋Ÿฌ์šด ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
2. (์„ ํƒ์‚ฌํ•ญ) ์›๋ณธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ž…๋ ฅ
3. "์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ" ๋ฒ„ํŠผ ํด๋ฆญ
4. ์‹ ๋ขฐ๋„ ๋ฐ ๊ฒ€์ฆ ๊ฒฐ๊ณผ ํ™•์ธ

### 3๋‹จ๊ณ„: ํ’ˆ์งˆ ๋ถ„์„
1. ์›๋ณธ ์ด๋ฏธ์ง€์™€ ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
2. "ํ’ˆ์งˆ ๋น„๊ต" ๋ฒ„ํŠผ ํด๋ฆญ
3. PSNR ๊ฐ’ ๋ฐ ์ฐจ์ด ๋ถ„์„ ํ™•์ธ

## ๐Ÿ“ˆ ์‹ ๋ขฐ๋„ ํ•ด์„ ๊ฐ€์ด๋“œ

| ์‹ ๋ขฐ๋„ ๋ฒ”์œ„ | ํŒ์ • | ์„ค๋ช… |
|------------|------|------|
| 0.8 ์ด์ƒ | โœ… ๋†’์Œ | ์›Œํ„ฐ๋งˆํฌ ํ™•์‹คํžˆ ์กด์žฌ |
| 0.6~0.8 | โš ๏ธ ๋ณดํ†ต | ์›Œํ„ฐ๋งˆํฌ ์กด์žฌ ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ |
| 0.3~0.6 | โ“ ๋‚ฎ์Œ | ์›Œํ„ฐ๋งˆํฌ ์กด์žฌ ๋ถˆํ™•์‹ค |
| 0.3 ๋ฏธ๋งŒ | โŒ ๋งค์šฐ ๋‚ฎ์Œ | ์›Œํ„ฐ๋งˆํฌ ์—†์Œ |

## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

- **์‹คํ—˜์šฉ ํ”„๋กœํ† ํƒ€์ž…**: ์—ฐ๊ตฌ ๋ฐ ๊ต์œก ๋ชฉ์ ์œผ๋กœ ๊ฐœ๋ฐœ๋จ
- **๋ณด์•ˆ ๊ฐ•ํ™” ํ•„์š”**: ์ƒ์šฉ ํ™˜๊ฒฝ์—์„œ๋Š” ์ถ”๊ฐ€ ์•”ํ˜ธํ™” ๊ธฐ๋ฒ• ์ ์šฉ ๊ถŒ์žฅ
- **์„ฑ๋Šฅ ์ตœ์ ํ™”**: ์‹ค์ œ ๋ฐฐํฌ์‹œ ๋ชจ๋ธ ๊ฒฝ๋Ÿ‰ํ™” ๋ฐ ํ•˜๋“œ์›จ์–ด ๊ฐ€์† ํ•„์š”

## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

- **Framework**: PyTorch, Gradio
- **Computer Vision**: OpenCV, PIL
- **Data Processing**: NumPy, Matplotlib
- **Deployment**: Hugging Face Spaces

## ๐Ÿ“ ๋ผ์ด์„ ์Šค

์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ต์œก ๋ฐ ์—ฐ๊ตฌ ๋ชฉ์ ์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

---

**๊ฐœ๋ฐœ์ž**: AI ์›Œํ„ฐ๋งˆํ‚น ์—ฐ๊ตฌํŒ€
**๋ฒ„์ „**: 1.0.0
**์ตœ์ข… ์—…๋ฐ์ดํŠธ**: 2025๋…„ 6์›”

Files changed (2) hide show
  1. app.py +682 -0
  2. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,682 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """์›Œํ„ฐ๋งˆํ‚น์‹œ์Šคํ…œ.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/17EOpDL6hwJ6f3G_-7bpUspm-1-XcgL_w
8
+ """
9
+
10
+ import gradio as gr
11
+ import numpy as np
12
+ import cv2
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ from PIL import Image
17
+ import io
18
+ import base64
19
+ import json
20
+ import time
21
+ from typing import Tuple, Optional
22
+ import matplotlib.pyplot as plt
23
+ import tempfile
24
+ import os
25
+
26
+ # ===== ๊ฒฝ๋Ÿ‰ํ™” CNN ์›Œํ„ฐ๋งˆํ‚น ๋ชจ๋ธ =====
27
+ class MobileWatermarkEncoder(nn.Module):
28
+ """๋ชจ๋ฐ”์ผ ์ตœ์ ํ™” ์›Œํ„ฐ๋งˆํฌ ์ธ์ฝ”๋”"""
29
+ def __init__(self, watermark_size=32):
30
+ super().__init__()
31
+ self.watermark_size = watermark_size
32
+
33
+ # ๊ฒฝ๋Ÿ‰ํ™” ์ธ์ฝ”๋” (MobileNet ์Šคํƒ€์ผ)
34
+ self.encoder = nn.Sequential(
35
+ # ์ดˆ๊ธฐ ํŠน์ง• ์ถ”์ถœ
36
+ nn.Conv2d(3, 32, 3, padding=1),
37
+ nn.BatchNorm2d(32),
38
+ nn.ReLU(inplace=True),
39
+
40
+ # Depthwise Separable Convolution
41
+ nn.Conv2d(32, 32, 3, padding=1, groups=32), # Depthwise
42
+ nn.Conv2d(32, 64, 1), # Pointwise
43
+ nn.BatchNorm2d(64),
44
+ nn.ReLU(inplace=True),
45
+
46
+ nn.Conv2d(64, 64, 3, padding=1, groups=64),
47
+ nn.Conv2d(64, 128, 1),
48
+ nn.BatchNorm2d(128),
49
+ nn.ReLU(inplace=True),
50
+
51
+ # ์ถœ๋ ฅ์ธต
52
+ nn.Conv2d(128, 3, 3, padding=1),
53
+ nn.Tanh()
54
+ )
55
+
56
+ # ์›Œํ„ฐ๋งˆํฌ ์ž„๋ฒ ๋”ฉ ๊ฐ•๋„ ์กฐ์ ˆ
57
+ self.alpha = nn.Parameter(torch.tensor(0.1))
58
+
59
+ def forward(self, image, watermark_pattern):
60
+ # ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด์„ ์ด๋ฏธ์ง€ ํฌ๊ธฐ์— ๋งž๊ฒŒ ํ™•์žฅ
61
+ h, w = image.shape[2], image.shape[3]
62
+ watermark = F.interpolate(watermark_pattern, size=(h, w), mode='bilinear')
63
+
64
+ # ์›Œํ„ฐ๋งˆํฌ ์ž„๋ฒ ๋”ฉ
65
+ watermark_noise = self.encoder(image)
66
+ watermarked = image + self.alpha * watermark_noise * watermark
67
+
68
+ return torch.clamp(watermarked, 0, 1)
69
+
70
+ class MobileWatermarkDecoder(nn.Module):
71
+ """๋ชจ๋ฐ”์ผ ์ตœ์ ํ™” ์›Œํ„ฐ๋งˆํฌ ๋””์ฝ”๋”"""
72
+ def __init__(self, watermark_size=32):
73
+ super().__init__()
74
+ self.watermark_size = watermark_size
75
+
76
+ self.decoder = nn.Sequential(
77
+ nn.Conv2d(3, 32, 3, padding=1),
78
+ nn.ReLU(inplace=True),
79
+ nn.Conv2d(32, 64, 3, padding=1),
80
+ nn.ReLU(inplace=True),
81
+ nn.AdaptiveAvgPool2d((watermark_size, watermark_size)),
82
+ nn.Conv2d(64, 1, 1),
83
+ nn.Sigmoid()
84
+ )
85
+
86
+ def forward(self, image):
87
+ return self.decoder(image)
88
+
89
+ # ===== ์›Œํ„ฐ๋งˆํ‚น ์‹œ์Šคํ…œ ํด๋ž˜์Šค =====
90
+ class MobileWatermarkingSystem:
91
+ def __init__(self):
92
+ self.encoder = MobileWatermarkEncoder()
93
+ self.decoder = MobileWatermarkDecoder()
94
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
95
+
96
+ # ๋ชจ๋ธ์„ device๋กœ ์ด๋™
97
+ self.encoder.to(self.device)
98
+ self.decoder.to(self.device)
99
+
100
+ # ๊ฐ„๋‹จํ•œ ํ›ˆ๋ จ์šฉ ๋”๋ฏธ ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”
101
+ self._initialize_models()
102
+
103
+ def _initialize_models(self):
104
+ """๋ชจ๋ธ ์ดˆ๊ธฐํ™” (์‹ค์ œ๋กœ๋Š” ์‚ฌ์ „ ํ›ˆ๋ จ๋œ ๊ฐ€์ค‘์น˜ ๋กœ๋“œ)"""
105
+ # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํ•œ ์ดˆ๊ธฐํ™”๋งŒ ์ˆ˜ํ–‰
106
+ # ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ์‚ฌ์ „ ํ›ˆ๋ จ๋œ ๋ชจ๋ธ ๋กœ๋“œ
107
+ pass
108
+
109
+ def generate_watermark_pattern(self, user_id: str, timestamp: str) -> torch.Tensor:
110
+ """์‚ฌ์šฉ์ž ID์™€ ํƒ€์ž„์Šคํƒฌํ”„๋กœ ๊ณ ์œ  ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด ์ƒ์„ฑ"""
111
+ # ๊ฐ„๋‹จํ•œ ํŒจํ„ด ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” ๋” ๋ณต์žกํ•œ ๋ฐฉ๋ฒ• ์‚ฌ์šฉ)
112
+ seed = hash(user_id + timestamp) % 10000
113
+ torch.manual_seed(seed)
114
+ pattern = torch.randn(1, 1, 32, 32)
115
+ return torch.sigmoid(pattern)
116
+
117
+ def embed_watermark(self, image: np.ndarray, user_id: str) -> Tuple[np.ndarray, dict]:
118
+ """์ด๋ฏธ์ง€์— ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…"""
119
+ start_time = time.time()
120
+
121
+ # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
122
+ if len(image.shape) == 3:
123
+ image_tensor = torch.from_numpy(image.transpose(2, 0, 1)).float() / 255.0
124
+ else:
125
+ image_tensor = torch.from_numpy(image).float() / 255.0
126
+ image_tensor = image_tensor.unsqueeze(0).repeat(3, 1, 1)
127
+
128
+ image_tensor = image_tensor.unsqueeze(0).to(self.device)
129
+
130
+ # ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด ์ƒ์„ฑ
131
+ timestamp = str(int(time.time()))
132
+ watermark_pattern = self.generate_watermark_pattern(user_id, timestamp)
133
+ watermark_pattern = watermark_pattern.to(self.device)
134
+
135
+ # ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
136
+ with torch.no_grad():
137
+ watermarked_tensor = self.encoder(image_tensor, watermark_pattern)
138
+
139
+ # ํ›„์ฒ˜๋ฆฌ
140
+ watermarked_image = watermarked_tensor.squeeze(0).cpu().numpy()
141
+ watermarked_image = (watermarked_image.transpose(1, 2, 0) * 255).astype(np.uint8)
142
+
143
+ processing_time = time.time() - start_time
144
+
145
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
146
+ metadata = {
147
+ 'user_id': user_id,
148
+ 'timestamp': timestamp,
149
+ 'processing_time': processing_time,
150
+ 'image_size': image.shape,
151
+ 'watermark_strength': float(self.encoder.alpha.item())
152
+ }
153
+
154
+ return watermarked_image, metadata
155
+
156
+ def extract_watermark(self, image: np.ndarray) -> Tuple[np.ndarray, float]:
157
+ """์ด๋ฏธ์ง€์—์„œ ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ ๋ฐ ๊ฒ€์ฆ"""
158
+ start_time = time.time()
159
+
160
+ # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
161
+ if len(image.shape) == 3:
162
+ image_tensor = torch.from_numpy(image.transpose(2, 0, 1)).float() / 255.0
163
+ else:
164
+ image_tensor = torch.from_numpy(image).float() / 255.0
165
+ image_tensor = image_tensor.unsqueeze(0).repeat(3, 1, 1)
166
+
167
+ image_tensor = image_tensor.unsqueeze(0).to(self.device)
168
+
169
+ # ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ
170
+ with torch.no_grad():
171
+ extracted_watermark = self.decoder(image_tensor)
172
+
173
+ # ํ›„์ฒ˜๋ฆฌ
174
+ watermark_array = extracted_watermark.squeeze().cpu().numpy()
175
+ confidence = np.mean(watermark_array) # ๊ฐ„๋‹จํ•œ ์‹ ๋ขฐ๋„ ๊ณ„์‚ฐ
176
+
177
+ processing_time = time.time() - start_time
178
+
179
+ return watermark_array, confidence
180
+
181
+ def verify_watermark(self, original_metadata: dict, extracted_confidence: float) -> dict:
182
+ """์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ"""
183
+ threshold = 0.3 # ๊ฒ€์ฆ ์ž„๊ณ„๊ฐ’
184
+ is_valid = bool(extracted_confidence > threshold) # bool() ๋ช…์‹œ์  ๋ณ€ํ™˜
185
+
186
+ return {
187
+ 'is_valid': is_valid,
188
+ 'confidence': float(extracted_confidence), # float() ๋ช…์‹œ์  ๋ณ€ํ™˜
189
+ 'threshold': float(threshold),
190
+ 'original_metadata': original_metadata
191
+ }
192
+
193
+ # ===== ์ „์—ญ ์‹œ์Šคํ…œ ์ธ์Šคํ„ด์Šค =====
194
+ watermarking_system = MobileWatermarkingSystem()
195
+
196
+ # ===== Gradio ์ธํ„ฐํŽ˜์ด์Šค ํ•จ์ˆ˜๋“ค =====
197
+ def embed_watermark_interface(image, user_id, output_format):
198
+ """์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž… ์ธํ„ฐํŽ˜์ด์Šค"""
199
+ if image is None:
200
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", None, None
201
+
202
+ if not user_id.strip():
203
+ return None, "์‚ฌ์šฉ์ž ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", None, None
204
+
205
+ try:
206
+ # ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
207
+ watermarked_image, metadata = watermarking_system.embed_watermark(image, user_id)
208
+
209
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜
210
+ metadata_json = json.dumps(metadata, indent=2)
211
+
212
+ # ๋‹ค์šด๋กœ๋“œ์šฉ ํŒŒ์ผ ์ƒ์„ฑ
213
+ download_file = create_download_file(watermarked_image, user_id, metadata, output_format)
214
+
215
+ # ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€
216
+ result_msg = f"""
217
+ โœ… ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž… ์™„๋ฃŒ!
218
+ ๐Ÿ“Š ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {metadata['processing_time']:.3f}์ดˆ
219
+ ๐Ÿ“ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {metadata['image_size']}
220
+ ๐Ÿ’ช ์›Œํ„ฐ๋งˆํฌ ๊ฐ•๋„: {metadata['watermark_strength']:.3f}
221
+ ๐Ÿ†” ์‚ฌ์šฉ์ž ID: {metadata['user_id']}
222
+ โฐ ํƒ€์ž„์Šคํƒฌํ”„: {metadata['timestamp']}
223
+ ๐Ÿ“„ ํฌ๋งท: {output_format.upper()}
224
+ """
225
+
226
+ return watermarked_image, result_msg, metadata_json, download_file
227
+
228
+ except Exception as e:
229
+ return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", None, None
230
+
231
+ def create_download_file(image, user_id, metadata, output_format):
232
+ """๋‹ค์šด๋กœ๋“œ์šฉ ํŒŒ์ผ ์ƒ์„ฑ"""
233
+ try:
234
+ # PIL Image๋กœ ๋ณ€ํ™˜
235
+ if isinstance(image, np.ndarray):
236
+ pil_image = Image.fromarray(image)
237
+ else:
238
+ pil_image = image
239
+
240
+ # ํŒŒ์ผ๋ช… ์ƒ์„ฑ
241
+ timestamp = metadata['timestamp']
242
+ filename = f"watermarked_{user_id}_{timestamp}.{output_format.lower()}"
243
+
244
+ # ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
245
+ temp_dir = tempfile.mkdtemp()
246
+ temp_path = os.path.join(temp_dir, filename)
247
+
248
+ if output_format.lower() == 'jpg':
249
+ # JPG๋Š” RGB ๋ชจ๋“œ ํ•„์š”
250
+ if pil_image.mode in ('RGBA', 'LA', 'P'):
251
+ # ํˆฌ๋ช…๋„๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ๊ณผ ํ•ฉ์„ฑ
252
+ background = Image.new('RGB', pil_image.size, (255, 255, 255))
253
+ if pil_image.mode == 'P':
254
+ pil_image = pil_image.convert('RGBA')
255
+ background.paste(pil_image, mask=pil_image.split()[-1] if pil_image.mode == 'RGBA' else None)
256
+ pil_image = background
257
+
258
+ pil_image.save(temp_path, format='JPEG', quality=95, optimize=True)
259
+
260
+ elif output_format.lower() == 'png':
261
+ # PNG ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
262
+ pnginfo = Image.PngImagePlugin.PngInfo()
263
+ pnginfo.add_text("User_ID", user_id)
264
+ pnginfo.add_text("Timestamp", timestamp)
265
+ pnginfo.add_text("Watermark_Strength", str(metadata['watermark_strength']))
266
+ pnginfo.add_text("Software", "Mobile Watermarking System")
267
+
268
+ pil_image.save(temp_path, format='PNG', pnginfo=pnginfo, optimize=True)
269
+
270
+ return temp_path
271
+
272
+ except Exception as e:
273
+ print(f"๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
274
+ return None
275
+
276
+ def extract_watermark_interface(image, metadata_json):
277
+ """์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ ๋ฐ ๊ฒ€์ฆ ์ธํ„ฐํŽ˜์ด์Šค"""
278
+ if image is None:
279
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", None
280
+
281
+ try:
282
+ # ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ
283
+ watermark_pattern, confidence = watermarking_system.extract_watermark(image)
284
+
285
+ # ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด ์‹œ๊ฐํ™” (์ œ๋ชฉ๋งŒ ์˜์–ด, ํฐํŠธ ์„ค์ •)
286
+ plt.figure(figsize=(6, 6))
287
+ plt.rcParams['font.family'] = 'DejaVu Sans' # ์˜์–ด ํฐํŠธ ์„ค์ •
288
+ plt.rcParams['font.size'] = 10
289
+
290
+ plt.imshow(watermark_pattern, cmap='viridis')
291
+ plt.title(f'Extracted Watermark Pattern (Confidence: {confidence:.3f})',
292
+ fontsize=12, fontweight='bold')
293
+ plt.colorbar(label='Pattern Intensity')
294
+ plt.axis('off')
295
+
296
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
297
+ watermark_viz = plt.gcf()
298
+
299
+ # ์‹ ๋ขฐ๋„๋ณ„ ํ•ด์„ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ (ํ•œ๊ธ€)
300
+ def get_confidence_interpretation(conf):
301
+ if conf >= 0.8:
302
+ return {
303
+ 'level': '๋†’์Œ',
304
+ 'emoji': 'โœ…',
305
+ 'message': '์›Œํ„ฐ๋งˆํฌ๊ฐ€ ๋ช…ํ™•ํžˆ ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
306
+ 'detail': '์ด ์ด๋ฏธ์ง€๋Š” ์›Œํ„ฐ๋งˆํฌ๊ฐ€ ์‚ฝ์ž…๋œ ์ด๋ฏธ์ง€๋กœ ํŒ๋‹จ๋ฉ๋‹ˆ๋‹ค.',
307
+ 'color': '๐ŸŸข'
308
+ }
309
+ elif conf >= 0.6:
310
+ return {
311
+ 'level': '๋ณดํ†ต',
312
+ 'emoji': 'โš ๏ธ',
313
+ 'message': '์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด์ด ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
314
+ 'detail': '์›Œํ„ฐ๋งˆํฌ๊ฐ€ ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์ง€๋งŒ ์ถ”๊ฐ€ ๊ฒ€์ฆ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.',
315
+ 'color': '๐ŸŸก'
316
+ }
317
+ elif conf >= 0.3:
318
+ return {
319
+ 'level': '๋‚ฎ์Œ',
320
+ 'emoji': 'โ“',
321
+ 'message': '์•ฝํ•œ ์›Œํ„ฐ๋งˆํฌ ์‹ ํ˜ธ๊ฐ€ ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
322
+ 'detail': '์›Œํ„ฐ๋งˆํฌ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์ง€๋งŒ ๋…ธ์ด์ฆˆ์ผ ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.',
323
+ 'color': '๐ŸŸ '
324
+ }
325
+ else:
326
+ return {
327
+ 'level': '๋งค์šฐ ๋‚ฎ์Œ',
328
+ 'emoji': 'โŒ',
329
+ 'message': '์›Œํ„ฐ๋งˆํฌ๊ฐ€ ๊ฐ์ง€๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.',
330
+ 'detail': '์ด ์ด๋ฏธ์ง€์—๋Š” ์›Œํ„ฐ๋งˆํฌ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์†์ƒ๋˜์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.',
331
+ 'color': '๐Ÿ”ด'
332
+ }
333
+
334
+ confidence_info = get_confidence_interpretation(confidence)
335
+
336
+ # ๊ฒ€์ฆ ์ˆ˜ํ–‰
337
+ verification_result = None
338
+ if metadata_json and metadata_json.strip():
339
+ try:
340
+ original_metadata = json.loads(metadata_json)
341
+ verification_result = watermarking_system.verify_watermark(
342
+ original_metadata, confidence
343
+ )
344
+
345
+ # JSON ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ฐ์ดํ„ฐ ํƒ€์ž… ๋ณด์žฅ
346
+ verification_result = {
347
+ 'is_valid': bool(verification_result['is_valid']),
348
+ 'confidence': float(verification_result['confidence']),
349
+ 'threshold': float(verification_result['threshold']),
350
+ 'original_metadata': verification_result['original_metadata'],
351
+ 'confidence_level': confidence_info['level'],
352
+ 'interpretation': confidence_info['message']
353
+ }
354
+
355
+ except json.JSONDecodeError as e:
356
+ verification_result = {
357
+ 'error': f'๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {str(e)}',
358
+ 'confidence': float(confidence),
359
+ 'threshold': 0.3,
360
+ 'confidence_level': confidence_info['level'],
361
+ 'interpretation': confidence_info['message']
362
+ }
363
+ except Exception as e:
364
+ verification_result = {
365
+ 'error': f'๊ฒ€์ฆ ์ค‘ ์˜ค๋ฅ˜: {str(e)}',
366
+ 'confidence': float(confidence),
367
+ 'threshold': 0.3,
368
+ 'confidence_level': confidence_info['level'],
369
+ 'interpretation': confidence_info['message']
370
+ }
371
+
372
+ # ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ (ํ•œ๊ธ€)
373
+ result_msg = f"""
374
+ ๐Ÿ” ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ ์™„๋ฃŒ!
375
+ ๐Ÿ“Š ์‹ ๋ขฐ๋„: {confidence:.3f}
376
+ {confidence_info['color']} ์‹ ๋ขฐ๋„ ์ˆ˜์ค€: {confidence_info['level']}
377
+
378
+ {confidence_info['emoji']} {confidence_info['message']}
379
+ ๐Ÿ’ก {confidence_info['detail']}
380
+ """
381
+
382
+ if verification_result and 'error' not in verification_result:
383
+ status = "โœ… ์œ ํšจ" if verification_result['is_valid'] else "โŒ ๋ฌดํšจ"
384
+ result_msg += f"""
385
+
386
+ ๐Ÿ›ก๏ธ ๊ฒ€์ฆ ๊ฒฐ๊ณผ: {status}
387
+ ๐Ÿ“ ์ž„๊ณ„๊ฐ’: {verification_result['threshold']}
388
+ """
389
+
390
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ถ”๊ฐ€ ์ •๋ณด
391
+ if 'original_metadata' in verification_result:
392
+ orig_meta = verification_result['original_metadata']
393
+ result_msg += f"""
394
+ ๐Ÿ†” ์›๋ณธ ์‚ฌ์šฉ์ž: {orig_meta.get('user_id', 'N/A')}
395
+ โฐ ์ƒ์„ฑ ์‹œ๊ฐ„: {orig_meta.get('timestamp', 'N/A')}
396
+ ๐Ÿ’ช ์›๋ณธ ๊ฐ•๋„: {orig_meta.get('watermark_strength', 'N/A')}
397
+ """
398
+ elif verification_result and 'error' in verification_result:
399
+ result_msg += f"""
400
+
401
+ โš ๏ธ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {verification_result['error']}
402
+ """
403
+
404
+ # ์ถ”๊ฐ€ ํ•ด์„ ๊ฐ€์ด๋“œ (ํ•œ๊ธ€)
405
+ result_msg += f"""
406
+
407
+ ๐Ÿ“– ํ•ด์„ ๊ฐ€์ด๋“œ:
408
+ โ€ข 0.8 ์ด์ƒ: ์›Œํ„ฐ๋งˆํฌ ํ™•์‹คํžˆ ์กด์žฌ โœ…
409
+ โ€ข 0.6~0.8: ์›Œํ„ฐ๋งˆํฌ ์กด์žฌ ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ โš ๏ธ
410
+ โ€ข 0.3~0.6: ์›Œํ„ฐ๋งˆํฌ ์กด์žฌ ๋ถˆํ™•์‹ค โ“
411
+ โ€ข 0.3 ๋ฏธ๋งŒ: ์›Œํ„ฐ๋งˆํฌ ์—†์Œ โŒ
412
+ """
413
+
414
+ plt.close()
415
+
416
+ # JSON ์ง๋ ฌํ™” ๊ฒ€์ฆ
417
+ verification_json = None
418
+ if verification_result:
419
+ try:
420
+ verification_json = json.dumps(verification_result, indent=2, ensure_ascii=False)
421
+ except Exception as e:
422
+ verification_json = json.dumps({
423
+ 'error': f'JSON ์ง๋ ฌํ™” ์˜ค๋ฅ˜: {str(e)}',
424
+ 'confidence': float(confidence),
425
+ 'confidence_level': confidence_info['level'],
426
+ 'interpretation': confidence_info['message']
427
+ }, indent=2, ensure_ascii=False)
428
+
429
+ return watermark_viz, result_msg, verification_json
430
+
431
+ except Exception as e:
432
+ plt.close()
433
+ return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", json.dumps({
434
+ 'error': f'์ถ”์ถœ ์ค‘ ์˜ค๋ฅ˜: {str(e)}'
435
+ }, indent=2, ensure_ascii=False)
436
+
437
+ def compare_images(original, watermarked):
438
+ """์›๋ณธ๊ณผ ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€ ๋น„๊ต"""
439
+ if original is None or watermarked is None:
440
+ return None, "๋‘ ์ด๋ฏธ์ง€๊ฐ€ ๋ชจ๋‘ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
441
+
442
+ try:
443
+ # ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋งž์ถ”๊ธฐ
444
+ h1, w1 = original.shape[:2]
445
+ h2, w2 = watermarked.shape[:2]
446
+
447
+ if (h1, w1) != (h2, w2):
448
+ watermarked = cv2.resize(watermarked, (w1, h1))
449
+
450
+ # PSNR ๊ณ„์‚ฐ
451
+ mse = np.mean((original.astype(float) - watermarked.astype(float)) ** 2)
452
+ if mse == 0:
453
+ psnr = float('inf')
454
+ else:
455
+ psnr = 20 * np.log10(255.0 / np.sqrt(mse))
456
+
457
+ # ์ฐจ์ด ์ด๋ฏธ์ง€ ์ƒ์„ฑ
458
+ diff = np.abs(original.astype(float) - watermarked.astype(float))
459
+ diff = (diff / diff.max() * 255).astype(np.uint8)
460
+
461
+ # ์‹œ๊ฐํ™” (์ œ๋ชฉ๋งŒ ์˜์–ด๋กœ, ํฐํŠธ ์„ค์ •)
462
+ plt.rcParams['font.family'] = 'DejaVu Sans'
463
+ plt.rcParams['font.size'] = 10
464
+
465
+ fig, axes = plt.subplots(1, 3, figsize=(15, 5))
466
+
467
+ axes[0].imshow(original)
468
+ axes[0].set_title('Original Image', fontweight='bold')
469
+ axes[0].axis('off')
470
+
471
+ axes[1].imshow(watermarked)
472
+ axes[1].set_title('Watermarked Image', fontweight='bold')
473
+ axes[1].axis('off')
474
+
475
+ axes[2].imshow(diff, cmap='hot')
476
+ axes[2].set_title(f'Difference (PSNR: {psnr:.2f}dB)', fontweight='bold')
477
+ axes[2].axis('off')
478
+
479
+ plt.tight_layout()
480
+
481
+ result_msg = f"""
482
+ ๐Ÿ“Š ์ด๋ฏธ์ง€ ํ’ˆ์งˆ ๋ถ„์„:
483
+ - PSNR: {psnr:.2f} dB
484
+ - MSE: {mse:.2f}
485
+ - ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {original.shape}
486
+ """
487
+
488
+ comparison_fig = plt.gcf()
489
+ plt.close()
490
+
491
+ return comparison_fig, result_msg
492
+
493
+ except Exception as e:
494
+ return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
495
+
496
+ # ===== Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ =====
497
+ def create_gradio_interface():
498
+ """Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ"""
499
+
500
+ with gr.Blocks(title="๋ชจ๋ฐ”์ผ ์›Œํ„ฐ๋งˆํ‚น ์‹คํ—˜ ์‹œ์Šคํ…œ", theme=gr.themes.Soft()) as demo:
501
+ gr.Markdown("""
502
+ # ๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ CNN ๊ธฐ๋ฐ˜ ์›Œํ„ฐ๋งˆํ‚น ์‹œ์Šคํ…œ
503
+
504
+ ์ด ์‹œ์Šคํ…œ์€ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋œ ์‹ค์‹œ๊ฐ„ ์ด๋ฏธ์ง€ ์›Œํ„ฐ๋งˆํ‚น ๊ธฐ์ˆ ์„ ์‹คํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
505
+
506
+ ## ๐Ÿ”ฌ ์ฃผ์š” ๊ธฐ๋Šฅ
507
+ - **์‹ค์‹œ๊ฐ„ ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…**: ๊ฒฝ๋Ÿ‰ํ™”๋œ CNN ๋ชจ๋ธ๋กœ ๋น ๋ฅธ ์ฒ˜๋ฆฌ
508
+ - **์ ์‘ํ˜• ํ•ด์ƒ๋„ ์ง€์›**: ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ์— ์ž๋™ ์ ์‘
509
+ - **์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ**: ์‚ฝ์ž…๋œ ์›Œํ„ฐ๋งˆํฌ์˜ ์ถ”์ถœ ๋ฐ ๊ฒ€์ฆ
510
+ - **ํ’ˆ์งˆ ๋ถ„์„**: ์›๋ณธ ๋Œ€๋น„ ํ™”์งˆ ๋ณ€ํ™” ์ธก์ •
511
+ """)
512
+
513
+ with gr.Tabs():
514
+ # ํƒญ 1: ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…
515
+ with gr.Tab("๐Ÿ”’ ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…"):
516
+ with gr.Row():
517
+ with gr.Column():
518
+ embed_input_image = gr.Image(
519
+ label="๐Ÿ“ท ์›๋ณธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ",
520
+ type="numpy"
521
+ )
522
+ embed_user_id = gr.Textbox(
523
+ label="๐Ÿ†” ์‚ฌ์šฉ์ž ID",
524
+ placeholder="์˜ˆ: user123",
525
+ value="demo_user"
526
+ )
527
+ output_format = gr.Radio(
528
+ label="๐Ÿ“„ ์ถœ๋ ฅ ํฌ๋งท",
529
+ choices=["PNG", "JPG"],
530
+ value="PNG"
531
+ )
532
+ embed_btn = gr.Button("๐Ÿ”’ ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…", variant="primary")
533
+
534
+ with gr.Column():
535
+ embed_output_image = gr.Image(label="๐Ÿ” ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€")
536
+ embed_result_text = gr.Textbox(
537
+ label="๐Ÿ“Š ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ",
538
+ lines=8,
539
+ interactive=False
540
+ )
541
+ download_btn = gr.DownloadButton(
542
+ label="๐Ÿ’พ ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ",
543
+ variant="secondary"
544
+ )
545
+
546
+ embed_metadata = gr.JSON(label="๐Ÿ“‹ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ", visible=False)
547
+
548
+ embed_btn.click(
549
+ fn=embed_watermark_interface,
550
+ inputs=[embed_input_image, embed_user_id, output_format],
551
+ outputs=[embed_output_image, embed_result_text, embed_metadata, download_btn]
552
+ )
553
+
554
+ # ํƒญ 2: ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ ๋ฐ ๊ฒ€์ฆ
555
+ with gr.Tab("๐Ÿ” ์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ"):
556
+ with gr.Row():
557
+ with gr.Column():
558
+ extract_input_image = gr.Image(
559
+ label="๐Ÿ” ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ",
560
+ type="numpy"
561
+ )
562
+ extract_metadata = gr.Textbox(
563
+ label="๐Ÿ“‹ ์›๋ณธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์„ ํƒ์‚ฌํ•ญ)",
564
+ placeholder="์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž… ์‹œ ์ƒ์„ฑ๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ถ™์—ฌ๋„ฃ์œผ์„ธ์š”",
565
+ lines=5
566
+ )
567
+ extract_btn = gr.Button("๐Ÿ” ์›Œํ„ฐ๋งˆํฌ ์ถ”์ถœ", variant="primary")
568
+
569
+ with gr.Column():
570
+ extract_output_viz = gr.Plot(label="๐ŸŽจ ์ถ”์ถœ๋œ ์›Œํ„ฐ๋งˆํฌ ํŒจํ„ด")
571
+ extract_result_text = gr.Textbox(
572
+ label="๐Ÿ“Š ์ถ”์ถœ ๊ฒฐ๊ณผ",
573
+ lines=5,
574
+ interactive=False
575
+ )
576
+
577
+ extract_verification = gr.JSON(label="๐Ÿ›ก๏ธ ๊ฒ€์ฆ ๊ฒฐ๊ณผ", visible=True)
578
+
579
+ extract_btn.click(
580
+ fn=extract_watermark_interface,
581
+ inputs=[extract_input_image, extract_metadata],
582
+ outputs=[extract_output_viz, extract_result_text, extract_verification]
583
+ )
584
+
585
+ # ํƒญ 3: ์ด๋ฏธ์ง€ ํ’ˆ์งˆ ๋น„๊ต
586
+ with gr.Tab("๐Ÿ“Š ํ’ˆ์งˆ ๋ถ„์„"):
587
+ with gr.Row():
588
+ with gr.Column():
589
+ compare_original = gr.Image(
590
+ label="๐Ÿ“ท ์›๋ณธ ์ด๋ฏธ์ง€",
591
+ type="numpy"
592
+ )
593
+ compare_watermarked = gr.Image(
594
+ label="๐Ÿ” ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€",
595
+ type="numpy"
596
+ )
597
+ compare_btn = gr.Button("๐Ÿ“Š ํ’ˆ์งˆ ๋น„๊ต", variant="primary")
598
+
599
+ with gr.Column():
600
+ compare_output_plot = gr.Plot(label="๐Ÿ”ฌ ๋น„๊ต ๋ถ„์„ ๊ฒฐ๊ณผ")
601
+ compare_result_text = gr.Textbox(
602
+ label="๐Ÿ“ˆ ๋ถ„์„ ๊ฒฐ๊ณผ",
603
+ lines=6,
604
+ interactive=False
605
+ )
606
+
607
+ compare_btn.click(
608
+ fn=compare_images,
609
+ inputs=[compare_original, compare_watermarked],
610
+ outputs=[compare_output_plot, compare_result_text]
611
+ )
612
+
613
+ # ํƒญ 4: ์‹œ์Šคํ…œ ์ •๋ณด
614
+ with gr.Tab("โ„น๏ธ ์‹œ์Šคํ…œ ์ •๋ณด"):
615
+ gr.Markdown(f"""
616
+ ## ๐Ÿ”ง ์‹œ์Šคํ…œ ์‚ฌ์–‘
617
+
618
+ - **๋””๋ฐ”์ด์Šค**: {watermarking_system.device}
619
+ - **CNN ์•„ํ‚คํ…์ฒ˜**: MobileNet ๊ธฐ๋ฐ˜ ๊ฒฝ๋Ÿ‰ํ™” ๋ชจ๋ธ
620
+ - **์›Œํ„ฐ๋งˆํฌ ํฌ๊ธฐ**: 32x32 ํ”ฝ์…€
621
+ - **์ง€์› ํฌ๋งท**: JPG, PNG, BMP
622
+
623
+ ## ๐Ÿ“ˆ ์„ฑ๋Šฅ ํŠน์ง•
624
+
625
+ - **์ฒ˜๋ฆฌ ์†๋„**: < 1์ดˆ (๋ชฉํ‘œ)
626
+ - **๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ**: ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™”
627
+ - **ํ•ด์ƒ๋„ ์ ์‘**: ๋™์  ํฌ๊ธฐ ์กฐ์ ˆ
628
+ - **๊ฒฌ๊ณ ์„ฑ**: ์••์ถ•/๋ณ€ํ™˜ ๊ณต๊ฒฉ ์ €ํ•ญ
629
+
630
+ ## ๐ŸŽฏ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
631
+
632
+ 1. **์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž…**: ์›๋ณธ ์ด๋ฏธ์ง€์™€ ์‚ฌ์šฉ์ž ID ์ž…๋ ฅ
633
+ 2. **์›Œํ„ฐ๋งˆํฌ ๊ฒ€์ฆ**: ์˜์‹ฌ๋˜๋Š” ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ํ›„ ์ถ”์ถœ
634
+ 3. **ํ’ˆ์งˆ ๋ถ„์„**: ์›๋ณธ๊ณผ ์›Œํ„ฐ๋งˆํฌ๋œ ์ด๋ฏธ์ง€ ๋น„๊ต
635
+
636
+ ## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ
637
+
638
+ - ์ด๋Š” ์‹คํ—˜์šฉ ํ”„๋กœํ† ํƒ€์ž…์ž…๋‹ˆ๋‹ค
639
+ - ์‹ค์ œ ์ƒ์šฉ ํ™˜๊ฒฝ์—์„œ๋Š” ์ถ”๊ฐ€ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
640
+ - ๋ณด์•ˆ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•ด ๋” ๋ณต์žกํ•œ ์•”ํ˜ธํ™” ๊ธฐ๋ฒ• ์ ์šฉ ๊ถŒ์žฅ
641
+ """)
642
+
643
+ # ์—ฐ๊ฒฐ ๊ธฐ๋Šฅ: ์›Œํ„ฐ๋งˆํฌ ์‚ฝ์ž… ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆ ํƒญ์œผ๋กœ ์ „๋‹ฌ
644
+ embed_output_image.change(
645
+ fn=lambda x: x,
646
+ inputs=[embed_output_image],
647
+ outputs=[extract_input_image]
648
+ )
649
+
650
+ embed_metadata.change(
651
+ fn=lambda x: json.dumps(x, indent=2) if x else "",
652
+ inputs=[embed_metadata],
653
+ outputs=[extract_metadata]
654
+ )
655
+
656
+ # ์—ฐ๊ฒฐ ๊ธฐ๋Šฅ: ๋น„๊ต ๋ถ„์„์„ ์œ„ํ•œ ์ด๋ฏธ์ง€ ์ „๋‹ฌ
657
+ embed_input_image.change(
658
+ fn=lambda x: x,
659
+ inputs=[embed_input_image],
660
+ outputs=[compare_original]
661
+ )
662
+
663
+ embed_output_image.change(
664
+ fn=lambda x: x,
665
+ inputs=[embed_output_image],
666
+ outputs=[compare_watermarked]
667
+ )
668
+
669
+ return demo
670
+
671
+ # ===== ๋ฉ”์ธ ์‹คํ–‰ =====
672
+ if __name__ == "__main__":
673
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
674
+ demo = create_gradio_interface()
675
+
676
+ # Colab ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰
677
+ demo.launch(
678
+ share=True, # ๊ณต๊ฐœ ๋งํฌ ์ƒ์„ฑ
679
+ debug=True, # ๋””๋ฒ„๊ทธ ๋ชจ๋“œ
680
+ server_name="0.0.0.0", # ๋ชจ๋“  IP์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅ
681
+ server_port=7860 # ํฌํŠธ ์ง€์ •
682
+ )
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.12.0
2
+ torch>=2.0.0
3
+ torchvision>=0.15.0
4
+ numpy>=1.24.0
5
+ opencv-python>=4.8.0
6
+ Pillow>=10.0.0
7
+ matplotlib>=3.7.0