innored commited on
Commit
739b2d6
Β·
verified Β·
1 Parent(s): 60489ca

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +42 -0
  2. app.py +349 -0
  3. requirements.txt +10 -0
README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI Upscaler
3
+ emoji: 🎨
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 5.49.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ ---
12
+
13
+ # 🎨 AI 이미지/μ˜μƒ μ—…μŠ€μΌ€μΌλŸ¬
14
+
15
+ Real-ESRGAN을 μ‚¬μš©ν•œ κ³ ν’ˆμ§ˆ 이미지 및 μ˜μƒ μ—…μŠ€μΌ€μΌλ§ λ„κ΅¬μž…λ‹ˆλ‹€.
16
+
17
+ ## κΈ°λŠ₯
18
+
19
+ - πŸ“· **이미지 μ—…μŠ€μΌ€μΌ**: 2λ°°/4λ°° ν™•λŒ€
20
+ - 🎬 **μ˜μƒ μ—…μŠ€μΌ€μΌ**: 2λ°°/4λ°° ν™•λŒ€
21
+ - πŸ’Ύ **λ‹€μ–‘ν•œ 포맷 지원**: PNG, JPG, WebP
22
+
23
+ ## μ‚¬μš© 방법
24
+
25
+ 1. νƒ­μ—μ„œ 이미지 λ˜λŠ” μ˜μƒ 선택
26
+ 2. 파일 μ—…λ‘œλ“œ
27
+ 3. 배율 선택
28
+ 4. μ—…μŠ€μΌ€μΌ μ‹œμž‘ λ²„νŠΌ 클릭
29
+ 5. κ²°κ³Ό λ‹€μš΄λ‘œλ“œ
30
+
31
+ ## μ£Όμ˜μ‚¬ν•­
32
+
33
+ ⚠️ **μ˜μƒ μ²˜λ¦¬λŠ” μ‹œκ°„μ΄ 였래 κ±Έλ¦½λ‹ˆλ‹€**
34
+ - CPU λͺ¨λ“œ: 10초 μ˜μƒ = 10-30λΆ„ μ†Œμš”
35
+ - 짧은 μ˜μƒ(5-10초)으둜 ν…ŒμŠ€νŠΈ ꢌμž₯
36
+ - GPU ν™œμ„±ν™” μ‹œ 처리 속도 ν–₯상
37
+
38
+ ## 기술 μŠ€νƒ
39
+
40
+ - [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN)
41
+ - [Gradio](https://gradio.app/)
42
+ - PyTorch
app.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from PIL import Image
3
+ import numpy as np
4
+ import io
5
+ import cv2
6
+ import tempfile
7
+ import os
8
+
9
+ # μ „μ—­ λ³€μˆ˜
10
+ image_upsampler = None
11
+ video_upsampler = None
12
+
13
+ def load_model(model_type="image"):
14
+ """λͺ¨λΈ λ‘œλ“œ (이미지/μ˜μƒ 곡톡)"""
15
+ global image_upsampler, video_upsampler
16
+
17
+ try:
18
+ from basicsr.archs.rrdbnet_arch import RRDBNet
19
+ from realesrgan import RealESRGANer
20
+
21
+ # 이미 λ‘œλ“œλœ 경우
22
+ if model_type == "image" and image_upsampler is not None:
23
+ return image_upsampler
24
+ if model_type == "video" and video_upsampler is not None:
25
+ return video_upsampler
26
+
27
+ # λͺ¨λΈ μ„€μ •
28
+ model = RRDBNet(
29
+ num_in_ch=3,
30
+ num_out_ch=3,
31
+ num_feat=64,
32
+ num_block=23,
33
+ num_grow_ch=32,
34
+ scale=4
35
+ )
36
+
37
+ # Real-ESRGAN μ—…μƒ˜ν”ŒλŸ¬ 생성
38
+ upsampler = RealESRGANer(
39
+ scale=4,
40
+ model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth',
41
+ model=model,
42
+ tile=400, # 타일 크기 증가 (λ©”λͺ¨λ¦¬ μΆ©λΆ„ν•˜λ©΄)
43
+ tile_pad=10,
44
+ pre_pad=0,
45
+ half=False # CPUμ—μ„œλŠ” False, GPUμ—μ„œλŠ” True κ°€λŠ₯
46
+ )
47
+
48
+ # μ „μ—­ λ³€μˆ˜μ— μ €μž₯
49
+ if model_type == "image":
50
+ image_upsampler = upsampler
51
+ print("βœ… 이미지 λͺ¨λΈ λ‘œλ“œ μ™„λ£Œ")
52
+ else:
53
+ video_upsampler = upsampler
54
+ print("βœ… μ˜μƒ λͺ¨λΈ λ‘œλ“œ μ™„λ£Œ")
55
+
56
+ return upsampler
57
+
58
+ except Exception as e:
59
+ print(f"❌ λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨: {e}")
60
+ return None
61
+
62
+ def upscale_image(image, scale_factor, output_format):
63
+ """이미지 μ—…μŠ€μΌ€μΌλ§"""
64
+ if image is None:
65
+ return None, None, "⚠️ 이미지λ₯Ό λ¨Όμ € μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”"
66
+
67
+ try:
68
+ # μƒνƒœ μ—…λ°μ΄νŠΈ
69
+ status_loading = f"πŸ”„ λͺ¨λΈ λ‘œλ”© 쀑...\n(처음 μ‹€ν–‰ μ‹œ μ‹œκ°„μ΄ κ±Έλ¦½λ‹ˆλ‹€)"
70
+
71
+ # λͺ¨λΈ λ‘œλ“œ
72
+ upsampler = load_model("image")
73
+ if upsampler is None:
74
+ error_msg = """❌ λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨
75
+
76
+ ν•„μš”ν•œ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ„€μΉ˜λ˜μ–΄ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”:
77
+ - realesrgan
78
+ - basicsr
79
+ - torch"""
80
+ return None, None, error_msg
81
+
82
+ # 이미지 검증
83
+ img = np.array(image)
84
+ if img is None or img.size == 0:
85
+ return None, None, "❌ μœ νš¨ν•˜μ§€ μ•Šμ€ μ΄λ―Έμ§€μž…λ‹ˆλ‹€"
86
+
87
+ # 이미지 크기 μ œν•œ (λ„ˆλ¬΄ 큰 이미지 λ°©μ§€)
88
+ max_size = 2000
89
+ h, w = img.shape[:2]
90
+ if max(h, w) > max_size:
91
+ scale = max_size / max(h, w)
92
+ new_w = int(w * scale)
93
+ new_h = int(h * scale)
94
+ img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
95
+ print(f"이미지 크기 μ‘°μ •: {w}x{h} -> {new_w}x{new_h}")
96
+
97
+ # RGB to BGR
98
+ img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
99
+
100
+ # μ—…μŠ€μΌ€μΌ μˆ˜ν–‰
101
+ print(f"μ—…μŠ€μΌ€μΌ μ‹œμž‘: {img.shape} -> {scale_factor}λ°°")
102
+ output, _ = upsampler.enhance(img_bgr, outscale=scale_factor)
103
+
104
+ # BGR to RGB
105
+ result = cv2.cvtColor(output, cv2.COLOR_BGR2RGB)
106
+ result_img = Image.fromarray(result)
107
+
108
+ # 포맷에 맞게 μ €μž₯
109
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=f'.{output_format.lower()}')
110
+
111
+ if output_format == "PNG":
112
+ result_img.save(temp_file.name, format="PNG", optimize=True)
113
+ elif output_format == "JPG":
114
+ result_img = result_img.convert("RGB")
115
+ result_img.save(temp_file.name, format="JPEG", quality=95, optimize=True)
116
+ else: # WebP
117
+ result_img.save(temp_file.name, format="WebP", quality=95)
118
+
119
+ temp_file.close()
120
+
121
+ # μƒνƒœ λ©”μ‹œμ§€
122
+ original_size = f"{image.size[0]} Γ— {image.size[1]}"
123
+ new_size = f"{result_img.size[0]} Γ— {result_img.size[1]}"
124
+
125
+ status = f"""βœ… μ—…μŠ€μΌ€μΌ μ™„λ£Œ!
126
+
127
+ πŸ“ 원본: {original_size}px
128
+ πŸ“ κ²°κ³Ό: {new_size}px
129
+ πŸ“ 배율: {scale_factor}λ°°
130
+ πŸ’Ύ 포맷: {output_format}
131
+ 🎨 ν’ˆμ§ˆ: 졜고"""
132
+
133
+ return result_img, temp_file.name, status
134
+
135
+ except Exception as e:
136
+ import traceback
137
+ error_msg = f"❌ 였λ₯˜ λ°œμƒ:\n{str(e)}\n\n{traceback.format_exc()}"
138
+ print(error_msg)
139
+ return None, None, error_msg
140
+
141
+ def upscale_video(video, scale_factor, progress=gr.Progress()):
142
+ """μ˜μƒ μ—…μŠ€μΌ€μΌλ§ (μ§„ν–‰λ₯  ν‘œμ‹œ κ°œμ„ )"""
143
+ if video is None:
144
+ return None, "⚠️ μ˜μƒμ„ λ¨Όμ € μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”"
145
+
146
+ try:
147
+ progress(0, desc="λͺ¨λΈ λ‘œλ”© 쀑...")
148
+
149
+ # λͺ¨λΈ λ‘œλ“œ
150
+ upsampler = load_model("video")
151
+ if upsampler is None:
152
+ return None, "❌ λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨"
153
+
154
+ progress(0.1, desc="μ˜μƒ 정보 μ½λŠ” 쀑...")
155
+ print(f"μ˜μƒ 처리 μ‹œμž‘: {video}")
156
+
157
+ # μ˜μƒ μ—΄κΈ°
158
+ cap = cv2.VideoCapture(video)
159
+ if not cap.isOpened():
160
+ return None, "❌ μ˜μƒ νŒŒμΌμ„ μ—΄ 수 μ—†μŠ΅λ‹ˆλ‹€"
161
+
162
+ # μ˜μƒ 정보
163
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
164
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
165
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
166
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
167
+
168
+ print(f"μ˜μƒ 정보: {width}x{height}, {fps}fps, {total_frames}ν”„λ ˆμž„")
169
+
170
+ # ν”„λ ˆμž„ 수 μ œν•œ (λ„ˆλ¬΄ κΈ΄ μ˜μƒ λ°©μ§€)
171
+ max_frames = 300 # μ•½ 10초 @ 30fps
172
+ if total_frames > max_frames:
173
+ return None, f"❌ μ˜μƒμ΄ λ„ˆλ¬΄ κΉλ‹ˆλ‹€. {max_frames}ν”„λ ˆμž„ μ΄ν•˜λ‘œ μ—…λ‘œλ“œν•΄μ£Όμ„Έμš”."
174
+
175
+ progress(0.2, desc="첫 ν”„λ ˆμž„ 처리 쀑...")
176
+
177
+ # 첫 ν”„λ ˆμž„μœΌλ‘œ 좜λ ₯ 크기 κ²°μ •
178
+ ret, first_frame = cap.read()
179
+ if not ret:
180
+ cap.release()
181
+ return None, "❌ 첫 ν”„λ ˆμž„μ„ 읽을 수 μ—†μŠ΅λ‹ˆλ‹€"
182
+
183
+ output_frame, _ = upsampler.enhance(first_frame, outscale=scale_factor)
184
+ out_height, out_width = output_frame.shape[:2]
185
+
186
+ # 좜λ ₯ 파일 μ„€μ •
187
+ temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
188
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
189
+ out = cv2.VideoWriter(temp_output, fourcc, fps, (out_width, out_height))
190
+
191
+ if not out.isOpened():
192
+ cap.release()
193
+ return None, "❌ 좜λ ₯ μ˜μƒμ„ 생성할 수 μ—†μŠ΅λ‹ˆλ‹€"
194
+
195
+ # 첫 ν”„λ ˆμž„ μ“°κΈ°
196
+ out.write(output_frame)
197
+
198
+ # λ‚˜λ¨Έμ§€ ν”„λ ˆμž„ 처리
199
+ frame_count = 1
200
+
201
+ while True:
202
+ ret, frame = cap.read()
203
+ if not ret:
204
+ break
205
+
206
+ # μ—…μŠ€μΌ€μΌ
207
+ output_frame, _ = upsampler.enhance(frame, outscale=scale_factor)
208
+ out.write(output_frame)
209
+
210
+ frame_count += 1
211
+
212
+ # μ§„ν–‰λ₯  μ—…λ°μ΄νŠΈ
213
+ progress_value = 0.2 + (frame_count / total_frames) * 0.8
214
+ progress(progress_value, desc=f"처리 쀑: {frame_count}/{total_frames} ν”„λ ˆμž„")
215
+
216
+ if frame_count % 5 == 0:
217
+ print(f"처리 쀑: {frame_count}/{total_frames}")
218
+
219
+ # λ¦¬μ†ŒμŠ€ ν•΄μ œ
220
+ cap.release()
221
+ out.release()
222
+
223
+ duration = total_frames / fps if fps > 0 else 0
224
+
225
+ status = f"""βœ… μ˜μƒ μ—…μŠ€μΌ€μΌ μ™„λ£Œ!
226
+
227
+ πŸ“ 원본: {width}Γ—{height}px
228
+ πŸ“ κ²°κ³Ό: {out_width}Γ—{out_height}px
229
+ 🎬 ν”„λ ˆμž„: {total_frames}개
230
+ 🎬 FPS: {fps}
231
+ ⏱️ 길이: {duration:.1f}초
232
+ πŸ“ 배율: {scale_factor}λ°°
233
+
234
+ ⚠️ 처리 μ‹œκ°„: μ•½ {frame_count}ν”„λ ˆμž„ 처리"""
235
+
236
+ print(f"μ˜μƒ 처리 μ™„λ£Œ: {temp_output}")
237
+ return temp_output, status
238
+
239
+ except Exception as e:
240
+ import traceback
241
+ error_msg = f"❌ 였λ₯˜ λ°œμƒ:\n{str(e)}\n\n{traceback.format_exc()}"
242
+ print(error_msg)
243
+ return None, error_msg
244
+
245
+ # CSS
246
+ custom_css = """
247
+ .upload-box {
248
+ border: 2px dashed #4F46E5 !important;
249
+ border-radius: 16px !important;
250
+ background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%) !important;
251
+ min-height: 350px !important;
252
+ }
253
+
254
+ .result-box {
255
+ border: 2px solid #10B981 !important;
256
+ border-radius: 16px !important;
257
+ background: #000000 !important;
258
+ min-height: 350px !important;
259
+ }
260
+
261
+ .gradio-button {
262
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
263
+ border: none !important;
264
+ border-radius: 12px !important;
265
+ font-weight: 600 !important;
266
+ font-size: 16px !important;
267
+ padding: 16px 32px !important;
268
+ }
269
+
270
+ .status-box {
271
+ background: white !important;
272
+ border-radius: 12px !important;
273
+ padding: 16px !important;
274
+ border-left: 4px solid #4F46E5 !important;
275
+ font-family: monospace !important;
276
+ white-space: pre-wrap !important;
277
+ }
278
+ """
279
+
280
+ # Gradio UI
281
+ with gr.Blocks(
282
+ theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="purple"),
283
+ css=custom_css,
284
+ title="AI Upscaler"
285
+ ) as demo:
286
+
287
+ gr.Markdown("# 🎨 AI 이미지/μ˜μƒ μ—…μŠ€μΌ€μΌλŸ¬")
288
+ gr.Markdown("### Real-ESRGAN으둜 μ €ν™”μ§ˆμ„ κ³ ν™”μ§ˆλ‘œ λ³€ν™˜")
289
+
290
+ with gr.Tabs():
291
+ # 이미지 νƒ­
292
+ with gr.Tab("πŸ“· 이미지"):
293
+ with gr.Row():
294
+ with gr.Column():
295
+ gr.Markdown("### πŸ“€ 원본 이미지")
296
+ input_img = gr.Image(label="이미지 μ—…λ‘œλ“œ", type="pil")
297
+ img_scale = gr.Radio([("2배", 2), ("4배", 4)], value=4, label="배율")
298
+ img_format = gr.Radio(["PNG", "JPG", "WebP"], value="PNG", label="포맷")
299
+ img_btn = gr.Button("πŸš€ μ—…μŠ€μΌ€μΌ μ‹œμž‘", variant="primary", size="lg")
300
+
301
+ with gr.Column():
302
+ gr.Markdown("### ✨ 결과")
303
+ output_img = gr.Image(label="μ—…μŠ€μΌ€μΌ 이미지")
304
+ download_img = gr.File(label="πŸ’Ύ λ‹€μš΄λ‘œλ“œ")
305
+ img_status = gr.Textbox(label="μƒνƒœ", value="λŒ€κΈ° 쀑...", lines=8, interactive=False)
306
+
307
+ # μ˜μƒ νƒ­
308
+ with gr.Tab("🎬 μ˜μƒ"):
309
+ gr.Markdown("""
310
+ ### ⚠️ μ€‘μš” μ•ˆλ‚΄
311
+ - **ν”„λ ˆμž„ μ œν•œ**: μ΅œλŒ€ 300ν”„λ ˆμž„ (μ•½ 10초)
312
+ - **처리 μ‹œκ°„**: CPU λͺ¨λ“œμ—μ„œ 맀우 느림
313
+ - **ꢌμž₯**: 5초 μ΄ν•˜μ˜ 짧은 클립으둜 ν…ŒμŠ€νŠΈ
314
+ """)
315
+
316
+ with gr.Row():
317
+ with gr.Column():
318
+ gr.Markdown("### πŸ“€ 원본 μ˜μƒ")
319
+ input_video = gr.Video(label="μ˜μƒ μ—…λ‘œλ“œ")
320
+ video_scale = gr.Radio([("2배", 2), ("4배", 4)], value=2, label="배율")
321
+ video_btn = gr.Button("πŸš€ μ—…μŠ€μΌ€μΌ μ‹œμž‘", variant="primary", size="lg")
322
+
323
+ with gr.Column():
324
+ gr.Markdown("### ✨ 결과")
325
+ output_video = gr.Video(label="μ—…μŠ€μΌ€μΌ μ˜μƒ")
326
+ video_status = gr.Textbox(label="μƒνƒœ", value="λŒ€κΈ° 쀑...", lines=10, interactive=False)
327
+
328
+ # 이벀트 μ—°κ²°
329
+ img_btn.click(
330
+ fn=upscale_image,
331
+ inputs=[input_img, img_scale, img_format],
332
+ outputs=[output_img, download_img, img_status]
333
+ )
334
+
335
+ video_btn.click(
336
+ fn=upscale_video,
337
+ inputs=[input_video, video_scale],
338
+ outputs=[output_video, video_status]
339
+ )
340
+
341
+ gr.Markdown("""
342
+ ---
343
+ <div style='text-align: center; color: #999;'>
344
+ Powered by <strong>Real-ESRGAN</strong>
345
+ </div>
346
+ """)
347
+
348
+ if __name__ == "__main__":
349
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ opencv-python-headless==4.8.1.78
3
+ numpy==1.24.3
4
+ pillow==10.0.0
5
+ torch==2.0.1
6
+ torchvision==0.15.2
7
+ basicsr==1.4.2
8
+ realesrgan==0.3.0
9
+ facexlib==0.3.0
10
+ gfpgan==1.3.8