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