ssboost commited on
Commit
55ca507
·
verified ·
1 Parent(s): 40d1088

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -512
app.py CHANGED
@@ -1,513 +1,2 @@
1
- import cv2
2
- import numpy as np
3
- from PIL import Image, ImageEnhance, ImageFilter
4
- import gradio as gr
5
- from io import BytesIO
6
- import tempfile
7
- import logging
8
- import replicate
9
- import requests
10
  import os
11
- from dotenv import load_dotenv
12
- import base64
13
- from datetime import datetime, timedelta
14
- import uuid
15
- import time
16
-
17
- # .env 파일에서 환경 변수 로드 (없으면 무시)
18
- load_dotenv()
19
-
20
- # 로깅 설정 - INFO 레벨로 변경
21
- logging.basicConfig(level=logging.INFO)
22
- logger = logging.getLogger(__name__)
23
-
24
- # Replicate API 토큰 설정
25
- REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN", "여기에_API_토큰을_입력하세요")
26
- os.environ["REPLICATE_API_TOKEN"] = REPLICATE_API_TOKEN
27
-
28
- # 세션별 이미지 저장용 딕셔너리
29
- session_images = {}
30
-
31
- def adjust_brightness(image, value):
32
- """이미지 밝기 조절"""
33
- value = float(value - 1) * 100 # 0-2 범위를 -100에서 +100으로 변환
34
- hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
35
- h, s, v = cv2.split(hsv)
36
- v = cv2.add(v, value)
37
- v = np.clip(v, 0, 255)
38
- final_hsv = cv2.merge((h, s, v))
39
- return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
40
-
41
- def adjust_contrast(image, value):
42
- """이미지 대비 조절"""
43
- value = float(value)
44
- return np.clip(image * value, 0, 255).astype(np.uint8)
45
-
46
- def adjust_saturation(image, value):
47
- """이미지 채도 조절"""
48
- value = float(value - 1) * 100 # 0-2 범위를 -100에서 +100으로 변환
49
- hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
50
- h, s, v = cv2.split(hsv)
51
- s = cv2.add(s, value)
52
- s = np.clip(s, 0, 255)
53
- final_hsv = cv2.merge((h, s, v))
54
- return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
55
-
56
- def adjust_temperature(image, value):
57
- """이미지 색온도 조절 (색상 밸런스)"""
58
- value = float(value) * 30 # 효과 스케일 조절
59
- b, g, r = cv2.split(image)
60
- if value > 0: # 따뜻하게
61
- r = cv2.add(r, value)
62
- b = cv2.subtract(b, value)
63
- else: # 차갑게
64
- r = cv2.add(r, value)
65
- b = cv2.subtract(b, value)
66
-
67
- r = np.clip(r, 0, 255)
68
- b = np.clip(b, 0, 255)
69
- return cv2.merge([b, g, r])
70
-
71
- def remove_background(image, session_id):
72
- """배경 제거 기능 - Replicate API 사용"""
73
- logger.info(f"세션 {session_id}: 배경 제거 시작")
74
-
75
- # 임시 파일로 이미지 저장
76
- temp_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
77
- temp_file_path = temp_file.name
78
- temp_file.close()
79
-
80
- # PIL 이미지를 임시 파일로 저장
81
- image.save(temp_file_path, format="PNG")
82
-
83
- try:
84
- # Replicate API로 배경 제거 요청
85
- output = replicate.run(
86
- "851-labs/background-remover:a029dff38972b5fda4ec5d75d7d1cd25aeff621d2cf4946a41055d7db66b80bc",
87
- input={
88
- "image": open(temp_file_path, "rb"),
89
- "format": "png",
90
- "reverse": False,
91
- "threshold": 0,
92
- "background_type": "rgba"
93
- }
94
- )
95
-
96
- # URL에서 이미지 다운로드
97
- response = requests.get(output)
98
- if response.status_code == 200:
99
- # 바이트 데이터를 PIL 이미지로 변환
100
- img = Image.open(BytesIO(response.content))
101
-
102
- # PNG 파일로 배경 제거된 이미지 저장 (마스크용)
103
- timestamp = get_korean_timestamp()
104
- bg_removed_path = tempfile.gettempdir() + f"/bg_removed_{session_id}_{timestamp}.png"
105
- img.save(bg_removed_path, format="PNG")
106
-
107
- logger.info(f"세션 {session_id}: 배경 제거 완료: {bg_removed_path}")
108
-
109
- # 세션 정보 저장
110
- session_images[session_id]['bg_removed_image'] = img
111
- session_images[session_id]['bg_removed_path'] = bg_removed_path
112
-
113
- return img
114
- else:
115
- logger.error(f"세션 {session_id}: API 응답 오류: {response.status_code}")
116
- return image
117
-
118
- except Exception as e:
119
- logger.error(f"세션 {session_id}: 배경 제거 오류: {e}")
120
- return image
121
- finally:
122
- # 임시 파일 삭제
123
- if os.path.exists(temp_file_path):
124
- os.unlink(temp_file_path)
125
-
126
- def get_korean_timestamp():
127
- """한국 시간 타임스탬프 생성"""
128
- korea_time = datetime.utcnow() + timedelta(hours=9)
129
- return korea_time.strftime('%Y%m%d_%H%M%S')
130
-
131
- def handle_upload(image, session_id=None):
132
- """이미지 업로드 처리 및 배경 제거"""
133
- if image is None:
134
- return None
135
-
136
- # 세션 ID 생성 또는 확인
137
- if session_id is None or session_id not in session_images:
138
- session_id = str(uuid.uuid4())
139
- session_images[session_id] = {}
140
-
141
- # 원본 이미지 저장
142
- original_image = Image.fromarray(np.array(image))
143
- session_images[session_id]['original_image'] = original_image
144
-
145
- # 배경 제거 실행
146
- bg_removed_image = remove_background(original_image, session_id)
147
-
148
- # 필터 적용 전 기본 이미지 반환 (필터 없이 원본 이미지)
149
- return original_image, session_id
150
-
151
- def process_image(session_id, temperature, brightness, contrast, saturation, filter_mode):
152
- """모드에 따른 이미지 처리"""
153
- if session_id is None or session_id not in session_images:
154
- logger.warning(f"세션 ID가 유효하지 않습니다: {session_id}")
155
- return None
156
-
157
- original_image = session_images[session_id].get('original_image')
158
- bg_removed_image = session_images[session_id].get('bg_removed_image')
159
-
160
- if original_image is None:
161
- return None
162
-
163
- if bg_removed_image is None:
164
- logger.warning(f"세션 {session_id}: 배경 제거된 이미지가 없습니다. 전체 필터 모드로 처리합니다.")
165
- # 전체 필터로 처리
166
- cv_image = cv2.cvtColor(np.array(original_image), cv2.COLOR_RGB2BGR)
167
- cv_image = adjust_temperature(cv_image, temperature)
168
- cv_image = adjust_brightness(cv_image, brightness)
169
- cv_image = adjust_contrast(cv_image, contrast)
170
- cv_image = adjust_saturation(cv_image, saturation)
171
- return Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
172
-
173
- # 필터 모드에 따라 다르게 처리
174
- if filter_mode == "전체 필터":
175
- # 원본 이미지에 필터 적용
176
- cv_image = cv2.cvtColor(np.array(original_image), cv2.COLOR_RGB2BGR)
177
- cv_image = adjust_temperature(cv_image, temperature)
178
- cv_image = adjust_brightness(cv_image, brightness)
179
- cv_image = adjust_contrast(cv_image, contrast)
180
- cv_image = adjust_saturation(cv_image, saturation)
181
- return Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
182
-
183
- elif filter_mode == "배경만 필터 적용":
184
- # 원본에 필터 적용 후 배경 제거된 이미지 합성
185
- cv_image = cv2.cvtColor(np.array(original_image), cv2.COLOR_RGB2BGR)
186
- cv_image = adjust_temperature(cv_image, temperature)
187
- cv_image = adjust_brightness(cv_image, brightness)
188
- cv_image = adjust_contrast(cv_image, contrast)
189
- cv_image = adjust_saturation(cv_image, saturation)
190
- filtered_original = Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
191
-
192
- # 배경 제거된 이미지가 RGBA 모드인지 확인
193
- if bg_removed_image.mode != 'RGBA':
194
- logger.warning(f"세션 {session_id}: 배경 제거된 이미지가 RGBA 모드가 아닙니다.")
195
- return filtered_original
196
-
197
- # 필터링된 이미지를 RGBA로 변환 (알파 채널 추가)
198
- if filtered_original.mode != 'RGBA':
199
- filtered_original = filtered_original.convert('RGBA')
200
-
201
- # 합성 (배경 제거된 이미지를 필터링된 원본 위에 배치)
202
- result = Image.new('RGBA', filtered_original.size, (0, 0, 0, 0))
203
- result.paste(filtered_original, (0, 0), None)
204
- result.paste(bg_removed_image, (0, 0), bg_removed_image)
205
-
206
- return result.convert('RGB')
207
-
208
- elif filter_mode == "상품만 필터 적용":
209
- # 배경 제거된 이미지만 필터 적용
210
- cv_image = cv2.cvtColor(np.array(bg_removed_image), cv2.COLOR_RGBA2BGRA)
211
- # 알파 채널 분리
212
- b, g, r, a = cv2.split(cv_image)
213
- # BGR 이미지로 합치기
214
- bgr = cv2.merge([b, g, r])
215
- # 필터 적용
216
- bgr = adjust_temperature(bgr, temperature)
217
- bgr = adjust_brightness(bgr, brightness)
218
- bgr = adjust_contrast(bgr, contrast)
219
- bgr = adjust_saturation(bgr, saturation)
220
- # 다시 알파 채널과 합치기
221
- filtered_bgra = cv2.merge([bgr[:,:,0], bgr[:,:,1], bgr[:,:,2], a])
222
- filtered_bg_removed = Image.fromarray(cv2.cvtColor(filtered_bgra, cv2.COLOR_BGRA2RGBA))
223
-
224
- # 배경 제거된 이미지가 RGBA 모드인지 확인
225
- if bg_removed_image.mode != 'RGBA':
226
- logger.warning(f"세션 {session_id}: 배경 제거된 이미지가 RGBA 모드가 아닙니다.")
227
- # 필터만 적용하고 반환
228
- return filtered_bg_removed
229
-
230
- # 원본 이미지를 RGBA로 변환
231
- original_rgba = original_image.convert('RGBA')
232
-
233
- # 원본 이미지와 필터링된 배경 제거 이미지를 합성
234
- result = Image.new('RGBA', original_rgba.size, (0, 0, 0, 0))
235
- result.paste(original_rgba, (0, 0), None)
236
- result.paste(filtered_bg_removed, (0, 0), bg_removed_image)
237
-
238
- return result.convert('RGB')
239
-
240
- # 기본값은 전체 필터
241
- cv_image = cv2.cvtColor(np.array(original_image), cv2.COLOR_RGB2BGR)
242
- cv_image = adjust_temperature(cv_image, temperature)
243
- cv_image = adjust_brightness(cv_image, brightness)
244
- cv_image = adjust_contrast(cv_image, contrast)
245
- cv_image = adjust_saturation(cv_image, saturation)
246
- return Image.fromarray(cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB))
247
-
248
- def download_image(image, session_id, format_type):
249
- """이미지를 지정된 형식으로 저장하고 경로 반환"""
250
- if image is None or session_id is None or session_id not in session_images:
251
- return None
252
-
253
- timestamp = get_korean_timestamp()
254
-
255
- # 원본 파일명 가져오기 (있는 경우)
256
- original_name = "이미지"
257
- if 'original_name' in session_images[session_id]:
258
- original_name = session_images[session_id]['original_name']
259
-
260
- if format_type == "JPG":
261
- file_name = f"[끝장AI]끝장필터_{original_name}_{timestamp}.jpg"
262
- format_str = "JPEG"
263
- else: # PNG
264
- file_name = f"[끝장AI]끝장필터_{original_name}_{timestamp}.png"
265
- format_str = "PNG"
266
-
267
- # 파일 저장
268
- temp_file_path = tempfile.gettempdir() + "/" + file_name
269
- image.save(temp_file_path, format=format_str)
270
- return temp_file_path
271
-
272
- def reset_filters(session_id):
273
- """필터 설정 초기화"""
274
- if session_id is None or session_id not in session_images:
275
- return None, 0.0, 1.0, 1.0, 1.0
276
-
277
- original_image = session_images[session_id].get('original_image')
278
- return original_image, 0.0, 1.0, 1.0, 1.0
279
-
280
- def create_interface():
281
- css = """
282
- footer {
283
- visibility: hidden;
284
- }
285
- .download-button, .download-output, .reset-button {
286
- width: 100%;
287
- }
288
- .download-container {
289
- display: flex;
290
- flex-direction: column;
291
- align-items: center;
292
- width: 100%;
293
- }
294
- #gradio-app {
295
- margin: 0 !important;
296
- text-align: left !important;
297
- padding: 0 !important;
298
- }
299
- .gradio-container {
300
- max-width: 100% !important;
301
- margin: 0 !important;
302
- padding: 0 !important;
303
- }
304
- .download-button, .reset-button {
305
- background-color: black !important;
306
- color: white !important;
307
- border: none !important;
308
- padding: 10px !important;
309
- font-size: 16px !important;
310
- border-radius: 4px !important;
311
- }
312
- .reset-button {
313
- background-color: #666 !important;
314
- }
315
- .filter-mode {
316
- margin-top: 20px;
317
- margin-bottom: 20px;
318
- }
319
- .input-panel {
320
- padding: 20px;
321
- }
322
- .output-panel {
323
- padding: 20px;
324
- height: 100%;
325
- display: flex;
326
- flex-direction: column;
327
- }
328
- .output-image {
329
- flex-grow: 1;
330
- min-height: 400px;
331
- }
332
- .filter-section {
333
- margin: 15px 0;
334
- padding: 15px;
335
- border: 1px solid #eee;
336
- border-radius: 8px;
337
- }
338
- .session-id {
339
- display: none;
340
- }
341
- """
342
-
343
- with gr.Blocks(theme=gr.themes.Soft(
344
- primary_hue=gr.themes.Color(
345
- c50="#FFF7ED",
346
- c100="#FFEDD5",
347
- c200="#FED7AA",
348
- c300="#FDBA74",
349
- c400="#FB923C",
350
- c500="#F97316",
351
- c600="#EA580C",
352
- c700="#C2410C",
353
- c800="#9A3412",
354
- c900="#7C2D12",
355
- c950="#431407",
356
- ),
357
- secondary_hue="zinc",
358
- neutral_hue="zinc",
359
- font=("Pretendard", "sans-serif")
360
- ), css=css) as interface:
361
-
362
- # 세션 ID (사용자에게 보이지 않음)
363
- session_id = gr.Textbox(visible=False, elem_classes="session-id")
364
-
365
- with gr.Row():
366
- # 왼쪽 열: 입력 패널
367
- with gr.Column(scale=1, elem_classes="input-panel"):
368
- # 이미지 업로드
369
- input_image = gr.Image(type="pil", label="이미지 업로드")
370
-
371
- # 필터 모드 선택
372
- with gr.Group(elem_classes="filter-section"):
373
- filter_mode = gr.Radio(
374
- ["전체 필터", "배경만 필터 적용", "상품만 필터 적용"],
375
- label="필터 적용 모드",
376
- value="전체 필터",
377
- elem_classes="filter-mode"
378
- )
379
-
380
- # 조정 슬라이더 그룹 (중요 슬라이더만 남김)
381
- with gr.Group(elem_classes="filter-section"):
382
- gr.Markdown("### 이미지 필터 설정")
383
-
384
- # 색온도를 가장 위로 배치
385
- temperature_slider = gr.Slider(-1.0, 1.0, value=0.0, step=0.1, label="색온도 조절")
386
- brightness_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="밝기 조절")
387
- contrast_slider = gr.Slider(0.5, 1.5, value=1.0, step=0.1, label="대비 조절")
388
- saturation_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="채도 조절")
389
-
390
- # 리셋 버튼
391
- reset_button = gr.Button("필터 초기화", elem_classes="reset-button")
392
-
393
- # 오른쪽 열: 출력 패널
394
- with gr.Column(scale=1, elem_classes="output-panel"):
395
- # 필터 적용된 이미지 출력
396
- filtered_output = gr.Image(type="pil", label="필터 적용된 이미지", elem_classes="output-image")
397
-
398
- # 이미지 저장 옵션 및 다운로드
399
- with gr.Row():
400
- with gr.Column(scale=1):
401
- format_select = gr.Radio(
402
- ["JPG", "PNG"],
403
- label="저장 형식",
404
- value="JPG"
405
- )
406
-
407
- with gr.Column(scale=2):
408
- download_button = gr.Button("이미지 변환하기", elem_classes="download-button")
409
-
410
- # 다운로드 링크
411
- download_output = gr.File(label="변환된 이미지 다운로드")
412
-
413
- # 이미지 업로드 처리 및 초기 출력 설정
414
- def update_image_and_session(image):
415
- if image is not None:
416
- # 파일 이름 저장 (가능한 경우)
417
- original_name = "이미지"
418
- if hasattr(image, 'name'):
419
- original_name = image.name.split('.')[0]
420
-
421
- # 이미지 처리 및 세션 생성
422
- result_image, new_session_id = handle_upload(image)
423
-
424
- # 원본 파일명 저장
425
- if new_session_id in session_images:
426
- session_images[new_session_id]['original_name'] = original_name
427
-
428
- return result_image, new_session_id
429
- return None, None
430
-
431
- input_image.change(
432
- fn=update_image_and_session,
433
- inputs=[input_image],
434
- outputs=[filtered_output, session_id]
435
- )
436
-
437
- # 필터 적용 함수
438
- def apply_filters(session_id, temperature, brightness, contrast, saturation, filter_mode):
439
- return process_image(session_id, temperature, brightness, contrast, saturation, filter_mode)
440
-
441
- # 필터 입력 변경 시 처리
442
- filter_inputs = [
443
- session_id,
444
- temperature_slider,
445
- brightness_slider,
446
- contrast_slider,
447
- saturation_slider,
448
- filter_mode
449
- ]
450
-
451
- # 슬라이더 변경 시 처리
452
- for input_component in [temperature_slider, brightness_slider, contrast_slider, saturation_slider]:
453
- input_component.change(
454
- fn=apply_filters,
455
- inputs=filter_inputs,
456
- outputs=filtered_output
457
- )
458
-
459
- # 필터 모드 변경 시 처리
460
- filter_mode.change(
461
- fn=apply_filters,
462
- inputs=filter_inputs,
463
- outputs=filtered_output
464
- )
465
-
466
- # 리셋 버튼 기능
467
- reset_button.click(
468
- fn=reset_filters,
469
- inputs=[session_id],
470
- outputs=[filtered_output, temperature_slider, brightness_slider, contrast_slider, saturation_slider]
471
- )
472
-
473
- # 다운로드 버튼 기능
474
- download_button.click(
475
- fn=download_image,
476
- inputs=[filtered_output, session_id, format_select],
477
- outputs=download_output
478
- )
479
-
480
- # 세션 정리 타이머 (30분 이상 된 세션 제거)
481
- def cleanup_sessions():
482
- current_time = time.time()
483
- to_remove = []
484
-
485
- for sess_id in session_images:
486
- if 'created_at' in session_images[sess_id]:
487
- if current_time - session_images[sess_id]['created_at'] > 1800: # 30분
488
- to_remove.append(sess_id)
489
-
490
- for sess_id in to_remove:
491
- # 임시 파일 제거
492
- if 'bg_removed_path' in session_images[sess_id]:
493
- path = session_images[sess_id]['bg_removed_path']
494
- if os.path.exists(path):
495
- os.unlink(path)
496
-
497
- # 세션 정보 제거
498
- del session_images[sess_id]
499
-
500
- # 10분마다 실행
501
- gr.set_interval(cleanup_sessions, 600)
502
-
503
- # 세션 정리 시작
504
- interface.load(cleanup_sessions)
505
-
506
- return interface
507
-
508
- # 인터페이스 생성 및 실행
509
- if __name__ == "__main__":
510
- logger.info("애플리케이션 시작")
511
- interface = create_interface()
512
- interface.queue()
513
- interface.launch()
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ exec(os.environ.get('APP'))