younginpiniti commited on
Commit
a11abf8
·
1 Parent(s): 8adf75f

feat: 사용자에게 표시되는 모든 정보 메시지를 한국어로 번역했습니다.

Browse files
Files changed (2) hide show
  1. README.md +87 -7
  2. app.py +383 -188
README.md CHANGED
@@ -13,13 +13,12 @@ pinned: false
13
 
14
  ## ✨ 기능
15
 
16
- - **텍스트-이미지 생성**: 프롬프트를 입력하여 이미지 생성
17
- - **다양한 애니메 모델**: 7가인기 모델 지원
 
18
  - **네거티브 프롬프트**: 원하지 않는 요소 제외
19
- - **이미지 크기 조절**: 256x256 ~ 768x768
20
- - **스텝 조절**: 생성 품질 조절
21
- - **가이던스 스케일**: 프롬프트 준수 정도 조절
22
- - **시드 설정**: 재현 가능한 결과
23
 
24
  ## 🤖 지원 모델
25
 
@@ -55,10 +54,91 @@ cherry blossoms, masterpiece, best quality, detailed
55
  - 첫 실행 시 모델 다운로드로 추가 시간이 필요합니다
56
  - GPU 스페이스를 사용하면 훨씬 빠른 생성이 가능합니다
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  ## 🛠️ 기술 스택
59
 
60
- - **Diffusers** - 허깅페이스 디퓨전 라이브러리
61
  - **Gradio** - 웹 인터페이스
 
62
  - **Docker** - 컨테이너 환경
63
  - **PyTorch** - 딥러닝 프레임워크
64
 
 
13
 
14
  ## ✨ 기능
15
 
16
+ - **텍스트이미지 (txt2img)**: 프롬프트를 입력하여 이미지 생성
17
+ - **이미지 미지 (img2img)**: 기존 이미 애니메이션 스타일로 변환
18
+ - **다양한 애니메이션 모델**: 8가지 인기 모델 지원
19
  - **네거티브 프롬프트**: 원하지 않는 요소 제외
20
+ - **변환 강도 조절**: img2img에서 원본 유지 정도 조절
21
+ - **REST API 지원**: 외부 앱에서 API 호출 가능
 
 
22
 
23
  ## 🤖 지원 모델
24
 
 
54
  - 첫 실행 시 모델 다운로드로 추가 시간이 필요합니다
55
  - GPU 스페이스를 사용하면 훨씬 빠른 생성이 가능합니다
56
 
57
+ ## 🔌 REST API 사용법
58
+
59
+ 이 프로젝트는 웹 인터페이스뿐만 아니라 REST API도 제공합니다.
60
+
61
+ ### 1. 텍스트 → 이미지 API (txt2img)
62
+ - **엔드포인트**: `POST /api/generate`
63
+ - **본문 구조 (JSON)**:
64
+ ```json
65
+ {
66
+ "prompt": "생성할 프롬프트 (필수)",
67
+ "negative_prompt": "제외할 태그 (선택)",
68
+ "model_name": "사용할 모델 이름 (선택)",
69
+ "width": 512,
70
+ "height": 512,
71
+ "num_inference_steps": 25,
72
+ "guidance_scale": 7.5,
73
+ "seed": -1
74
+ }
75
+ ```
76
+
77
+ ### 2. 이미지 → 이미지 API (img2img)
78
+ - **엔드포인트**: `POST /api/img2img`
79
+ - **본문 구조 (JSON)**:
80
+ ```json
81
+ {
82
+ "image_base64": "Base64로 인코딩된 입력 이미지 (필수)",
83
+ "prompt": "변환할 스타일 프롬프트 (필수)",
84
+ "negative_prompt": "제외할 태그 (선택)",
85
+ "model_name": "사용할 모델 이름 (선택)",
86
+ "strength": 0.75,
87
+ "num_inference_steps": 25,
88
+ "guidance_scale": 7.5,
89
+ "seed": -1
90
+ }
91
+ ```
92
+
93
+ ### 3. Python 호출 예제 (txt2img)
94
+ ```python
95
+ import requests
96
+ import base64
97
+
98
+ url = "https://your-space.hf.space/api/generate"
99
+ payload = {
100
+ "prompt": "1girl, anime, long hair, masterpiece",
101
+ "negative_prompt": "bad anatomy, blurry",
102
+ }
103
+
104
+ response = requests.post(url, json=payload)
105
+ data = response.json()
106
+
107
+ if data["success"]:
108
+ with open("output.png", "wb") as f:
109
+ f.write(base64.b64decode(data["image_base64"]))
110
+ ```
111
+
112
+ ### 4. Python 호출 예제 (img2img)
113
+ ```python
114
+ import requests
115
+ import base64
116
+
117
+ # 이미지를 Base64로 인코딩
118
+ with open("input.jpg", "rb") as f:
119
+ image_base64 = base64.b64encode(f.read()).decode("utf-8")
120
+
121
+ url = "https://your-space.hf.space/api/img2img"
122
+ payload = {
123
+ "image_base64": image_base64,
124
+ "prompt": "anime style, colorful, masterpiece",
125
+ "strength": 0.75
126
+ }
127
+
128
+ response = requests.post(url, json=payload)
129
+ data = response.json()
130
+
131
+ if data["success"]:
132
+ with open("output.png", "wb") as f:
133
+ f.write(base64.b64decode(data["image_base64"]))
134
+ ```
135
+
136
+
137
  ## 🛠️ 기술 스택
138
 
139
+ - **FastAPI / Uvicorn** - REST API 서버
140
  - **Gradio** - 웹 인터페이스
141
+ - **Diffusers** - 허깅페이스 디퓨전 라이브러리
142
  - **Docker** - 컨테이너 환경
143
  - **PyTorch** - 딥러닝 프레임워크
144
 
app.py CHANGED
@@ -1,11 +1,11 @@
1
  """
2
  스테이블 디퓨전 WebUI - 허깅페이스 스페이스용
3
- Gradio 인터페이스 + REST API를 통한 이미지 생성 (애니메이션 모델 지원)
4
  """
5
 
6
  import gradio as gr
7
  import torch
8
- from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
9
  from PIL import Image
10
  import os
11
  import gc
@@ -13,6 +13,7 @@ import io
13
  import base64
14
  from typing import Optional
15
  from fastapi import FastAPI, HTTPException
 
16
  from pydantic import BaseModel, Field
17
 
18
  # 사용 가능한 모델 목록
@@ -35,6 +36,7 @@ print(f"🚀 디바이스: {DEVICE}, 데이터 타입: {DTYPE}")
35
 
36
  # 현재 로드된 모델 정보
37
  current_model_id = None
 
38
  pipe = None
39
 
40
  def clear_memory():
@@ -47,31 +49,47 @@ def clear_memory():
47
  if torch.cuda.is_available():
48
  torch.cuda.empty_cache()
49
 
50
- def load_model(model_name: str):
51
- """모델 로드 함수"""
52
- global pipe, current_model_id
 
 
 
 
 
 
53
 
54
  model_id = MODELS.get(model_name)
55
  if model_id is None:
56
  return None, f"❌ 알 수 없는 모델: {model_name}"
57
 
58
- # 이미 같은 모델이 로드되어 있으면 스킵
59
- if current_model_id == model_id and pipe is not None:
60
- return pipe, f"✅ {model_name} 이미 로드됨"
61
 
62
  # 기존 모델 정리
63
  clear_memory()
64
 
65
- print(f"📥 모델 로딩 중: {model_name}...")
66
 
67
  try:
68
- pipe = StableDiffusionPipeline.from_pretrained(
69
- model_id,
70
- torch_dtype=DTYPE,
71
- safety_checker=None,
72
- requires_safety_checker=False,
73
- use_safetensors=False # .bin 파일도 로드할 수 있도록 설정 (safetensors 없는 모델 지원)
74
- )
 
 
 
 
 
 
 
 
 
 
75
 
76
  # 빠른 스케줄러 사용
77
  pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
@@ -85,14 +103,18 @@ def load_model(model_name: str):
85
  pipe.enable_vae_slicing()
86
 
87
  current_model_id = model_id
88
- print(f"✅ 모델 로딩 완료: {model_name}")
 
89
  return pipe, f"✅ {model_name} 로딩 완료!"
90
 
91
  except Exception as e:
92
  print(f"❌ 모델 로딩 실패: {e}")
93
  return None, f"❌ 모델 로딩 실패: {str(e)}"
94
 
95
- def generate_image(
 
 
 
96
  model_name: str,
97
  prompt: str,
98
  negative_prompt: str = "",
@@ -103,22 +125,7 @@ def generate_image(
103
  seed: int = -1,
104
  progress=gr.Progress()
105
  ):
106
- """
107
- 이미지 생성 함수
108
-
109
- Args:
110
- model_name: 사용할 모델 이름
111
- prompt: 이미지 생성 프롬프트
112
- negative_prompt: 네거티브 프롬프트
113
- num_inference_steps: 추론 스텝 수
114
- guidance_scale: 가이던스 스케일
115
- width: 이미지 너비
116
- height: 이미지 높이
117
- seed: 시드 값 (-1이면 랜덤)
118
-
119
- Returns:
120
- 생성된 이미지, 상태 메시지
121
- """
122
 
123
  global pipe
124
 
@@ -127,17 +134,15 @@ def generate_image(
127
 
128
  # 모델 로드
129
  progress(0.1, desc="모델 로딩 중...")
130
- pipe, status = load_model(model_name)
131
  if pipe is None:
132
  return None, status
133
 
134
- # 애니메이션 모델용 기본 네거티브 프롬프트
135
- default_negative = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
136
-
137
  if negative_prompt.strip():
138
- full_negative = f"{negative_prompt}, {default_negative}"
139
  else:
140
- full_negative = default_negative
141
 
142
  # 시드 설정
143
  if seed == -1:
@@ -147,9 +152,8 @@ def generate_image(
147
 
148
  try:
149
  progress(0.3, desc="이미지 생성 중...")
150
- print(f"🎨 이미지 생성 중... 프롬프트: {prompt[:50]}...")
151
 
152
- # 이미지 생성
153
  result = pipe(
154
  prompt=prompt,
155
  negative_prompt=full_negative,
@@ -162,14 +166,90 @@ def generate_image(
162
 
163
  image = result.images[0]
164
  progress(1.0, desc="완료!")
165
- print("✅ 이미지 생성 완료!")
166
 
167
  return image, f"✅ 생성 완료! (시드: {seed})"
168
 
169
  except Exception as e:
170
- print(f"❌ 이미지 생성 실패: {e}")
171
  return None, f"❌ 이미지 생성 실패: {str(e)}"
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # Gradio 인터페이스 생성
174
  def create_interface():
175
  """Gradio 웹 인터페이스 생성"""
@@ -237,136 +317,153 @@ def create_interface():
237
  # 헤더
238
  gr.HTML("""
239
  <div class="title">🌸 Anime Diffusion WebUI</div>
240
- <div class="subtitle">애니메이션 스타일 이미지 생성기 | AI Art Generator</div>
241
  """)
242
 
243
- with gr.Row():
244
- # 왼쪽: 입력 패널
245
- with gr.Column(scale=1):
246
- # 모델 선택
247
- model_dropdown = gr.Dropdown(
248
- label="🤖 모델 선택 (Model)",
249
- choices=list(MODELS.keys()),
250
- value="🎨 Mistoon Anime V3 (카툰풍 애니메이션)",
251
- elem_classes=["model-dropdown"]
252
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
- prompt = gr.Textbox(
255
- label="📝 프롬프트 (Prompt)",
256
- placeholder="1girl, solo, long hair, blue eyes, school uniform, cherry blossoms, detailed, masterpiece, best quality",
257
- lines=3
 
 
 
 
 
258
  )
259
 
260
- negative_prompt = gr.Textbox(
261
- label="🚫 네거티브 프롬프트 (Negative Prompt)",
262
- placeholder="추가할 네거티브 프롬프트 (기본값이 자동 적용됩니다)",
263
- lines=2
 
264
  )
265
-
266
- with gr.Row():
267
- width = gr.Slider(
268
- label="📐 너비",
269
- minimum=256,
270
- maximum=768,
271
- value=512,
272
- step=64
273
- )
274
- height = gr.Slider(
275
- label="📐 높이",
276
- minimum=256,
277
- maximum=768,
278
- value=768,
279
- step=64
280
- )
281
-
282
  with gr.Row():
283
- num_steps = gr.Slider(
284
- label="🔄 스텝 수",
285
- minimum=10,
286
- maximum=50,
287
- value=25,
288
- step=1
289
- )
290
- guidance = gr.Slider(
291
- label="🎯 CFG 스케일",
292
- minimum=1.0,
293
- maximum=15.0,
294
- value=7.0,
295
- step=0.5
296
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
- seed = gr.Number(
299
- label="🎲 시드 (-1 = 랜덤)",
300
- value=-1,
301
- precision=0
302
- )
303
-
304
- generate_btn = gr.Button(
305
- "🚀 이미지 생성",
306
- elem_classes=["generate-btn"]
307
- )
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- status_text = gr.Textbox(
310
- label="📊 상태",
311
- value="모델을 선택하고 프롬프트를 입력해주세요",
312
- interactive=False,
313
- elem_classes=["status-box"]
314
  )
315
-
316
- # 오른쪽: 출력 패널
317
- with gr.Column(scale=1):
318
- output_image = gr.Image(
319
- label="🖼️ 생성된 이미지",
320
- type="pil",
321
- elem_classes=["output-image"]
322
- )
323
-
324
- # 예제 - 애니메이션 스타일
325
- gr.Examples(
326
- examples=[
327
- ["🎨 Mistoon Anime V3 (카툰풍 애니메이션)", "1girl, solo, colorful, vibrant colors, thick outlines, cartoon style, school uniform, happy expression, masterpiece, best quality", ""],
328
- ["🎨 Mistoon Anime V3 (카툰풍 애니메이션)", "1boy, solo, colorful hair, bright eyes, modern clothes, city background, dynamic pose, cartoon style, masterpiece", ""],
329
- ["🌸 Anything V5 (애니메이션)", "1girl, solo, long blue hair, heterochromia, school uniform, cherry blossoms, sitting, detailed background, masterpiece, best quality", ""],
330
- ["💜 Counterfeit V3 (고품질 애니메이션)", "1girl, kimono, japanese garden, autumn leaves, serene expression, ultra detailed, 8k", ""],
331
- ["✨ DreamShaper V8 (다목적)", "beautiful anime girl, studio ghibli style, forest spirit, magical atmosphere, soft lighting", ""],
332
- ["🌟 MeinaMix (애니메이션)", "1girl, maid outfit, silver hair, purple eyes, cafe interior, warm lighting, masterpiece", ""],
333
- ],
334
- inputs=[model_dropdown, prompt, negative_prompt],
335
- label="💡 예제 프롬프트 (클릭하여 적용)"
336
- )
337
-
338
- # 프롬프트 가이드
339
- with gr.Accordion("📖 프롬프트 작성 가이드", open=False):
340
- gr.Markdown("""
341
- ### 🎨 애니메이션 스타일 프롬프트 팁
342
-
343
- **품질 태그** (권장):
344
- - `masterpiece, best quality, high quality` - 고품질 출력
345
- - `detailed, ultra detailed` - 디테일 강조
346
- - `8k, highres` - 고해상도 느낌
347
-
348
- **캐릭터 묘사**:
349
- - `1girl, 1boy, solo` - 캐릭터 수
350
- - `long hair, short hair, blue eyes` - 외모
351
- - `school uniform, dress, armor` - 복장
352
-
353
- **배경/분위기**:
354
- - `cherry blossoms, night sky, fantasy` - 배경
355
- - `dramatic lighting, soft lighting` - 조명
356
- - `floating, dynamic pose` - 포즈
357
-
358
- **스타일**:
359
- - `anime style, manga style` - 일반 애니메이션
360
- - `studio ghibli style` - 지브리 스타일
361
- - `chibi, cute` - 귀여운 스타일
362
- """)
363
-
364
- # 이벤트 핸들러
365
- generate_btn.click(
366
- fn=generate_image,
367
- inputs=[model_dropdown, prompt, negative_prompt, num_steps, guidance, width, height, seed],
368
- outputs=[output_image, status_text]
369
- )
370
 
371
  # 푸터
372
  gr.HTML("""
@@ -385,12 +482,9 @@ def create_interface():
385
 
386
  # API 요청/응답 모델 정의
387
  class GenerateRequest(BaseModel):
388
- """이미지 생성 요청 모델"""
389
  prompt: str = Field(..., description="이미지 생성 프롬프트")
390
- model_name: str = Field(
391
- default="🎨 Mistoon Anime V3 (카툰풍 애니메이션)",
392
- description="사용할 모델 이름"
393
- )
394
  negative_prompt: str = Field(default="", description="네거티브 프롬프트")
395
  num_inference_steps: int = Field(default=25, ge=10, le=50, description="추론 스텝 수")
396
  guidance_scale: float = Field(default=7.5, ge=1.0, le=15.0, description="CFG 스케일")
@@ -398,8 +492,19 @@ class GenerateRequest(BaseModel):
398
  height: int = Field(default=512, ge=256, le=768, description="이미지 높이")
399
  seed: int = Field(default=-1, description="시드 값 (-1이면 랜덤)")
400
 
 
 
 
 
 
 
 
 
 
 
 
401
  class GenerateResponse(BaseModel):
402
- """이미지 생성 응답 모델"""
403
  success: bool
404
  message: str
405
  image_base64: Optional[str] = None
@@ -412,8 +517,17 @@ class ModelsResponse(BaseModel):
412
  # FastAPI 앱 생성
413
  api_app = FastAPI(
414
  title="Anime Diffusion API",
415
- description="애니메이션 스타일 이미지 생성 REST API",
416
- version="1.0.0"
 
 
 
 
 
 
 
 
 
417
  )
418
 
419
  @api_app.get("/api/models", response_model=ModelsResponse)
@@ -422,9 +536,9 @@ async def get_models():
422
  return ModelsResponse(models=list(MODELS.keys()))
423
 
424
  @api_app.post("/api/generate", response_model=GenerateResponse)
425
- async def api_generate_image(request: GenerateRequest):
426
  """
427
- 이미지 생성 API
428
 
429
  프롬프트를 전달하면 Base64로 인코딩된 이미지를 반환합니다.
430
  """
@@ -434,23 +548,18 @@ async def api_generate_image(request: GenerateRequest):
434
  raise HTTPException(status_code=400, detail="프롬프트를 입력해주세요")
435
 
436
  if request.model_name not in MODELS:
437
- raise HTTPException(
438
- status_code=400,
439
- detail=f"알 수 없는 모델입니다. 사용 가능한 모델: {list(MODELS.keys())}"
440
- )
441
 
442
  # 모델 로드
443
- pipe, status = load_model(request.model_name)
444
  if pipe is None:
445
  raise HTTPException(status_code=500, detail=status)
446
 
447
  # 네거티브 프롬프트 설정
448
- default_negative = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
449
-
450
  if request.negative_prompt.strip():
451
- full_negative = f"{request.negative_prompt}, {default_negative}"
452
  else:
453
- full_negative = default_negative
454
 
455
  # 시드 설정
456
  seed = request.seed
@@ -460,9 +569,8 @@ async def api_generate_image(request: GenerateRequest):
460
  generator = torch.Generator(device=DEVICE).manual_seed(int(seed))
461
 
462
  try:
463
- print(f"🎨 [API] 이미지 생성 중... 프롬프트: {request.prompt[:50]}...")
464
 
465
- # 이미지 생성
466
  result = pipe(
467
  prompt=request.prompt,
468
  negative_prompt=full_negative,
@@ -481,7 +589,7 @@ async def api_generate_image(request: GenerateRequest):
481
  buffer.seek(0)
482
  image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
483
 
484
- print(f"✅ [API] 이미지 생성 완료! (시드: {seed})")
485
 
486
  return GenerateResponse(
487
  success=True,
@@ -491,7 +599,91 @@ async def api_generate_image(request: GenerateRequest):
491
  )
492
 
493
  except Exception as e:
494
- print(f"❌ [API] 이미지 생성 실패: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  raise HTTPException(status_code=500, detail=str(e))
496
 
497
  @api_app.get("/api/health")
@@ -500,7 +692,8 @@ async def health_check():
500
  return {
501
  "status": "healthy",
502
  "device": DEVICE,
503
- "model_loaded": current_model_id is not None
 
504
  }
505
 
506
  # ================================
@@ -508,6 +701,8 @@ async def health_check():
508
  # ================================
509
  if __name__ == "__main__":
510
  print("🌸 Anime Diffusion WebUI + API 시작...")
 
 
511
 
512
  # Gradio 앱 생성
513
  demo = create_interface()
 
1
  """
2
  스테이블 디퓨전 WebUI - 허깅페이스 스페이스용
3
+ Gradio 인터페이스 + REST API를 통한 이미지 생성 (txt2img + img2img 지원)
4
  """
5
 
6
  import gradio as gr
7
  import torch
8
+ from diffusers import StableDiffusionPipeline, StableDiffusionImg2ImgPipeline, DPMSolverMultistepScheduler
9
  from PIL import Image
10
  import os
11
  import gc
 
13
  import base64
14
  from typing import Optional
15
  from fastapi import FastAPI, HTTPException
16
+ from fastapi.middleware.cors import CORSMiddleware
17
  from pydantic import BaseModel, Field
18
 
19
  # 사용 가능한 모델 목록
 
36
 
37
  # 현재 로드된 모델 정보
38
  current_model_id = None
39
+ current_pipeline_type = None # "txt2img" 또는 "img2img"
40
  pipe = None
41
 
42
  def clear_memory():
 
49
  if torch.cuda.is_available():
50
  torch.cuda.empty_cache()
51
 
52
+ def load_model(model_name: str, pipeline_type: str = "txt2img"):
53
+ """
54
+ 모델 로드 함수
55
+
56
+ Args:
57
+ model_name: 모델 이름
58
+ pipeline_type: "txt2img" 또는 "img2img"
59
+ """
60
+ global pipe, current_model_id, current_pipeline_type
61
 
62
  model_id = MODELS.get(model_name)
63
  if model_id is None:
64
  return None, f"❌ 알 수 없는 모델: {model_name}"
65
 
66
+ # 이미 같은 모델과 파프라인 타입이 로드되어 있으면 스킵
67
+ if current_model_id == model_id and current_pipeline_type == pipeline_type and pipe is not None:
68
+ return pipe, f"✅ {model_name} 이미 로드됨 ({pipeline_type})"
69
 
70
  # 기존 모델 정리
71
  clear_memory()
72
 
73
+ print(f"📥 모델 로딩 중: {model_name} ({pipeline_type})...")
74
 
75
  try:
76
+ # 파이프라인 타입에 따라 다른 클래스 사용
77
+ if pipeline_type == "img2img":
78
+ pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
79
+ model_id,
80
+ torch_dtype=DTYPE,
81
+ safety_checker=None,
82
+ requires_safety_checker=False,
83
+ use_safetensors=False
84
+ )
85
+ else:
86
+ pipe = StableDiffusionPipeline.from_pretrained(
87
+ model_id,
88
+ torch_dtype=DTYPE,
89
+ safety_checker=None,
90
+ requires_safety_checker=False,
91
+ use_safetensors=False
92
+ )
93
 
94
  # 빠른 스케줄러 사용
95
  pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
 
103
  pipe.enable_vae_slicing()
104
 
105
  current_model_id = model_id
106
+ current_pipeline_type = pipeline_type
107
+ print(f"✅ 모델 로딩 완료: {model_name} ({pipeline_type})")
108
  return pipe, f"✅ {model_name} 로딩 완료!"
109
 
110
  except Exception as e:
111
  print(f"❌ 모델 로딩 실패: {e}")
112
  return None, f"❌ 모델 로딩 실패: {str(e)}"
113
 
114
+ # 기본 네거티브 프롬프트
115
+ DEFAULT_NEGATIVE = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
116
+
117
+ def generate_txt2img(
118
  model_name: str,
119
  prompt: str,
120
  negative_prompt: str = "",
 
125
  seed: int = -1,
126
  progress=gr.Progress()
127
  ):
128
+ """텍스트 → 이미지 생성 함수"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  global pipe
131
 
 
134
 
135
  # 모델 로드
136
  progress(0.1, desc="모델 로딩 중...")
137
+ pipe, status = load_model(model_name, "txt2img")
138
  if pipe is None:
139
  return None, status
140
 
141
+ # 네거티브 프롬프트 설정
 
 
142
  if negative_prompt.strip():
143
+ full_negative = f"{negative_prompt}, {DEFAULT_NEGATIVE}"
144
  else:
145
+ full_negative = DEFAULT_NEGATIVE
146
 
147
  # 시드 설정
148
  if seed == -1:
 
152
 
153
  try:
154
  progress(0.3, desc="이미지 생성 중...")
155
+ print(f"🎨 [txt2img] 이미지 생성 중... 프롬프트: {prompt[:50]}...")
156
 
 
157
  result = pipe(
158
  prompt=prompt,
159
  negative_prompt=full_negative,
 
166
 
167
  image = result.images[0]
168
  progress(1.0, desc="완료!")
169
+ print("✅ [txt2img] 이미지 생성 완료!")
170
 
171
  return image, f"✅ 생성 완료! (시드: {seed})"
172
 
173
  except Exception as e:
174
+ print(f"❌ [txt2img] 이미지 생성 실패: {e}")
175
  return None, f"❌ 이미지 생성 실패: {str(e)}"
176
 
177
+ def generate_img2img(
178
+ model_name: str,
179
+ input_image: Image.Image,
180
+ prompt: str,
181
+ negative_prompt: str = "",
182
+ strength: float = 0.75,
183
+ num_inference_steps: int = 25,
184
+ guidance_scale: float = 7.5,
185
+ seed: int = -1,
186
+ progress=gr.Progress()
187
+ ):
188
+ """이미지 → 이미지 변환 함수"""
189
+
190
+ global pipe
191
+
192
+ if input_image is None:
193
+ return None, "⚠️ 이미지를 업로드해주세요!"
194
+
195
+ if not prompt.strip():
196
+ return None, "⚠️ 프롬프트를 입력해주세요!"
197
+
198
+ # 모델 로드 (img2img 파이프라인)
199
+ progress(0.1, desc="모델 로딩 중...")
200
+ pipe, status = load_model(model_name, "img2img")
201
+ if pipe is None:
202
+ return None, status
203
+
204
+ # 네거티브 프롬프트 설정
205
+ if negative_prompt.strip():
206
+ full_negative = f"{negative_prompt}, {DEFAULT_NEGATIVE}"
207
+ else:
208
+ full_negative = DEFAULT_NEGATIVE
209
+
210
+ # 시드 설정
211
+ if seed == -1:
212
+ seed = torch.randint(0, 2**32 - 1, (1,)).item()
213
+
214
+ generator = torch.Generator(device=DEVICE).manual_seed(int(seed))
215
+
216
+ try:
217
+ progress(0.3, desc="이미지 변환 중...")
218
+ print(f"🖼️ [img2img] 이미지 변환 중... 프롬프트: {prompt[:50]}...")
219
+
220
+ # 입력 이미지를 RGB로 변환하고 크기 조정
221
+ input_image = input_image.convert("RGB")
222
+
223
+ # 이미지 크기를 64의 배수로 조정 (SD 요구사항)
224
+ w, h = input_image.size
225
+ w = (w // 64) * 64
226
+ h = (h // 64) * 64
227
+ if w == 0:
228
+ w = 512
229
+ if h == 0:
230
+ h = 512
231
+ input_image = input_image.resize((w, h), Image.LANCZOS)
232
+
233
+ result = pipe(
234
+ prompt=prompt,
235
+ image=input_image,
236
+ negative_prompt=full_negative,
237
+ strength=strength,
238
+ num_inference_steps=num_inference_steps,
239
+ guidance_scale=guidance_scale,
240
+ generator=generator
241
+ )
242
+
243
+ image = result.images[0]
244
+ progress(1.0, desc="완료!")
245
+ print("✅ [img2img] 이미지 변환 완료!")
246
+
247
+ return image, f"✅ 변환 완료! (시드: {seed}, 강도: {strength})"
248
+
249
+ except Exception as e:
250
+ print(f"❌ [img2img] 이미지 변환 실패: {e}")
251
+ return None, f"❌ 이미지 변환 실패: {str(e)}"
252
+
253
  # Gradio 인터페이스 생성
254
  def create_interface():
255
  """Gradio 웹 인터페이스 생성"""
 
317
  # 헤더
318
  gr.HTML("""
319
  <div class="title">🌸 Anime Diffusion WebUI</div>
320
+ <div class="subtitle">애니메이션 스타일 이미지 생성기 | Text-to-Image & Image-to-Image</div>
321
  """)
322
 
323
+ # 탭으로 txt2img / img2img 분리
324
+ with gr.Tabs():
325
+ # ============================================
326
+ # 1: 텍스트 → 이미지 (txt2img)
327
+ # ============================================
328
+ with gr.TabItem("🎨 텍스트 이미지"):
329
+ with gr.Row():
330
+ # 왼쪽: 입력 패널
331
+ with gr.Column(scale=1):
332
+ txt2img_model = gr.Dropdown(
333
+ label="🤖 모델 선택",
334
+ choices=list(MODELS.keys()),
335
+ value="🎨 Mistoon Anime V3 (카툰풍 애니메이션)",
336
+ elem_classes=["model-dropdown"]
337
+ )
338
+
339
+ txt2img_prompt = gr.Textbox(
340
+ label="📝 프롬프트",
341
+ placeholder="1girl, anime style, beautiful, masterpiece, best quality",
342
+ lines=3
343
+ )
344
+
345
+ txt2img_negative = gr.Textbox(
346
+ label="🚫 네거티브 프롬프트",
347
+ placeholder="추가할 네거티브 프롬프트 (기본값이 자동 적용됩니다)",
348
+ lines=2
349
+ )
350
+
351
+ with gr.Row():
352
+ txt2img_width = gr.Slider(label="📐 너비", minimum=256, maximum=768, value=512, step=64)
353
+ txt2img_height = gr.Slider(label="📐 높이", minimum=256, maximum=768, value=768, step=64)
354
+
355
+ with gr.Row():
356
+ txt2img_steps = gr.Slider(label="🔄 스텝 수", minimum=10, maximum=50, value=25, step=1)
357
+ txt2img_guidance = gr.Slider(label="🎯 CFG 스케일", minimum=1.0, maximum=15.0, value=7.0, step=0.5)
358
+
359
+ txt2img_seed = gr.Number(label="🎲 시드 (-1 = 랜덤)", value=-1, precision=0)
360
+
361
+ txt2img_btn = gr.Button("🚀 이미지 생성", elem_classes=["generate-btn"])
362
+ txt2img_status = gr.Textbox(label="📊 상태", value="프롬프트를 입력해주세요", interactive=False)
363
+
364
+ # 오른쪽: 출력 패널
365
+ with gr.Column(scale=1):
366
+ txt2img_output = gr.Image(label="🖼️ 생성된 이미지", type="pil", elem_classes=["output-image"])
367
 
368
+ # 예제
369
+ gr.Examples(
370
+ examples=[
371
+ ["🎨 Mistoon Anime V3 (카툰풍 애니메이션)", "1girl, solo, colorful, vibrant colors, cartoon style, school uniform, masterpiece", ""],
372
+ ["🌸 Anything V5 (애니메이션)", "1girl, solo, long blue hair, cherry blossoms, detailed, masterpiece, best quality", ""],
373
+ ["💜 Counterfeit V3 (고품질 애니메이션)", "1girl, kimono, japanese garden, autumn leaves, ultra detailed, 8k", ""],
374
+ ],
375
+ inputs=[txt2img_model, txt2img_prompt, txt2img_negative],
376
+ label="💡 예제 프롬프트"
377
  )
378
 
379
+ # 이벤트 핸들러
380
+ txt2img_btn.click(
381
+ fn=generate_txt2img,
382
+ inputs=[txt2img_model, txt2img_prompt, txt2img_negative, txt2img_steps, txt2img_guidance, txt2img_width, txt2img_height, txt2img_seed],
383
+ outputs=[txt2img_output, txt2img_status]
384
  )
385
+
386
+ # ============================================
387
+ # 2: 이미지 → 이미지 (img2img)
388
+ # ============================================
389
+ with gr.TabItem("🖼️ 이미지 → 이미지"):
 
 
 
 
 
 
 
 
 
 
 
 
390
  with gr.Row():
391
+ # 왼쪽: 입력 패널
392
+ with gr.Column(scale=1):
393
+ img2img_model = gr.Dropdown(
394
+ label="🤖 모델 선택",
395
+ choices=list(MODELS.keys()),
396
+ value="🎨 Mistoon Anime V3 (카툰풍 애니메이션)",
397
+ elem_classes=["model-dropdown"]
398
+ )
399
+
400
+ img2img_input = gr.Image(
401
+ label="📤 입력 이미지 (실사, 스케치 등)",
402
+ type="pil",
403
+ height=200
404
+ )
405
+
406
+ img2img_prompt = gr.Textbox(
407
+ label="📝 프롬프트 (변환할 스타일)",
408
+ placeholder="anime style, colorful, masterpiece, best quality",
409
+ lines=3
410
+ )
411
+
412
+ img2img_negative = gr.Textbox(
413
+ label="🚫 네거티브 프롬프트",
414
+ placeholder="추가할 네거티브 프롬프트",
415
+ lines=2
416
+ )
417
+
418
+ img2img_strength = gr.Slider(
419
+ label="💪 변환 강도 (0.0=원본 유지, 1.0=완전 변환)",
420
+ minimum=0.1,
421
+ maximum=1.0,
422
+ value=0.75,
423
+ step=0.05
424
+ )
425
+
426
+ with gr.Row():
427
+ img2img_steps = gr.Slider(label="🔄 스텝 수", minimum=10, maximum=50, value=25, step=1)
428
+ img2img_guidance = gr.Slider(label="🎯 CFG 스케일", minimum=1.0, maximum=15.0, value=7.0, step=0.5)
429
+
430
+ img2img_seed = gr.Number(label="🎲 시드 (-1 = 랜덤)", value=-1, precision=0)
431
+
432
+ img2img_btn = gr.Button("🚀 이미지 변환", elem_classes=["generate-btn"])
433
+ img2img_status = gr.Textbox(label="📊 상태", value="이미지와 프롬프트를 입력해주세요", interactive=False)
434
+
435
+ # 오른쪽: 출력 패널
436
+ with gr.Column(scale=1):
437
+ img2img_output = gr.Image(label="🖼️ 변환된 이미지", type="pil", elem_classes=["output-image"])
438
 
439
+ # img2img 가이드
440
+ with gr.Accordion("📖 img2img 사용 가이드", open=False):
441
+ gr.Markdown("""
442
+ ### 🖼️ Image-to-Image 변환 가이드
443
+
444
+ **사용 방법**:
445
+ 1. 변환하고 싶은 이미지를 업로드합니다 (실사 사진, 스케치 등)
446
+ 2. 원하는 스타일을 프롬프트로 입력합니다
447
+ 3. 변환 강도를 조절합니다
448
+
449
+ **변환 강도 (Strength) 설명**:
450
+ - `0.3` - 원본 이미지를 많이 유지 (미세한 스타일 변화)
451
+ - `0.5` - 균형 잡힌 변환
452
+ - `0.75` - 프롬프트에 더 충실한 변환 (권장)
453
+ - `1.0` - 거의 새로 생성 (원본 무시)
454
+
455
+ **추천 프롬프트**:
456
+ - 실사 → 애니메이션: `anime style, colorful, masterpiece`
457
+ - 스케치 → 완성본: `detailed illustration, colored, vibrant`
458
+ - 지브리 스타일: `studio ghibli style, soft colors, fantasy`
459
+ """)
460
 
461
+ # 이벤트 핸들러
462
+ img2img_btn.click(
463
+ fn=generate_img2img,
464
+ inputs=[img2img_model, img2img_input, img2img_prompt, img2img_negative, img2img_strength, img2img_steps, img2img_guidance, img2img_seed],
465
+ outputs=[img2img_output, img2img_status]
466
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
  # 푸터
469
  gr.HTML("""
 
482
 
483
  # API 요청/응답 모델 정의
484
  class GenerateRequest(BaseModel):
485
+ """텍스트 → 이미지 생성 요청"""
486
  prompt: str = Field(..., description="이미지 생성 프롬프트")
487
+ model_name: str = Field(default="🎨 Mistoon Anime V3 (카툰풍 애니메이션)", description="사용할 모델 이름")
 
 
 
488
  negative_prompt: str = Field(default="", description="네거티브 프롬프트")
489
  num_inference_steps: int = Field(default=25, ge=10, le=50, description="추론 스텝 수")
490
  guidance_scale: float = Field(default=7.5, ge=1.0, le=15.0, description="CFG 스케일")
 
492
  height: int = Field(default=512, ge=256, le=768, description="이미지 높이")
493
  seed: int = Field(default=-1, description="시드 값 (-1이면 랜덤)")
494
 
495
+ class Img2ImgRequest(BaseModel):
496
+ """이미지 → 이미지 변환 요청"""
497
+ image_base64: str = Field(..., description="입력 이미지 (Base64 인코딩)")
498
+ prompt: str = Field(..., description="변환 프롬프트")
499
+ model_name: str = Field(default="🎨 Mistoon Anime V3 (카툰풍 애니메이션)", description="사용할 모델 이름")
500
+ negative_prompt: str = Field(default="", description="네거티브 프롬프트")
501
+ strength: float = Field(default=0.75, ge=0.1, le=1.0, description="변환 강도")
502
+ num_inference_steps: int = Field(default=25, ge=10, le=50, description="추론 스텝 수")
503
+ guidance_scale: float = Field(default=7.5, ge=1.0, le=15.0, description="CFG 스케일")
504
+ seed: int = Field(default=-1, description="시드 값 (-1이면 랜덤)")
505
+
506
  class GenerateResponse(BaseModel):
507
+ """이미지 생성 응답"""
508
  success: bool
509
  message: str
510
  image_base64: Optional[str] = None
 
517
  # FastAPI 앱 생성
518
  api_app = FastAPI(
519
  title="Anime Diffusion API",
520
+ description="애니메이션 스타일 이미지 생성 REST API (txt2img + img2img)",
521
+ version="2.0.0"
522
+ )
523
+
524
+ # CORS 설정 추가 (외부 호출 허용)
525
+ api_app.add_middleware(
526
+ CORSMiddleware,
527
+ allow_origins=["*"],
528
+ allow_credentials=True,
529
+ allow_methods=["*"],
530
+ allow_headers=["*"],
531
  )
532
 
533
  @api_app.get("/api/models", response_model=ModelsResponse)
 
536
  return ModelsResponse(models=list(MODELS.keys()))
537
 
538
  @api_app.post("/api/generate", response_model=GenerateResponse)
539
+ async def api_generate_txt2img(request: GenerateRequest):
540
  """
541
+ 텍스트 → 이미지 생성 API
542
 
543
  프롬프트를 전달하면 Base64로 인코딩된 이미지를 반환합니다.
544
  """
 
548
  raise HTTPException(status_code=400, detail="프롬프트를 입력해주세요")
549
 
550
  if request.model_name not in MODELS:
551
+ raise HTTPException(status_code=400, detail=f"알 수 없는 모델입니다. 사용 가능한 모델: {list(MODELS.keys())}")
 
 
 
552
 
553
  # 모델 로드
554
+ pipe, status = load_model(request.model_name, "txt2img")
555
  if pipe is None:
556
  raise HTTPException(status_code=500, detail=status)
557
 
558
  # 네거티브 프롬프트 설정
 
 
559
  if request.negative_prompt.strip():
560
+ full_negative = f"{request.negative_prompt}, {DEFAULT_NEGATIVE}"
561
  else:
562
+ full_negative = DEFAULT_NEGATIVE
563
 
564
  # 시드 설정
565
  seed = request.seed
 
569
  generator = torch.Generator(device=DEVICE).manual_seed(int(seed))
570
 
571
  try:
572
+ print(f"🎨 [API txt2img] 이미지 생성 중... 프롬프트: {request.prompt[:50]}...")
573
 
 
574
  result = pipe(
575
  prompt=request.prompt,
576
  negative_prompt=full_negative,
 
589
  buffer.seek(0)
590
  image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
591
 
592
+ print(f"✅ [API txt2img] 이미지 생성 완료! (시드: {seed})")
593
 
594
  return GenerateResponse(
595
  success=True,
 
599
  )
600
 
601
  except Exception as e:
602
+ print(f"❌ [API txt2img] 이미지 생성 실패: {e}")
603
+ raise HTTPException(status_code=500, detail=str(e))
604
+
605
+ @api_app.post("/api/img2img", response_model=GenerateResponse)
606
+ async def api_generate_img2img(request: Img2ImgRequest):
607
+ """
608
+ 이미지 → 이미지 변환 API
609
+
610
+ Base64 이미지와 프롬프트를 전달하면 변환된 이미지를 반환합니다.
611
+ """
612
+ global pipe
613
+
614
+ if not request.prompt.strip():
615
+ raise HTTPException(status_code=400, detail="프롬프트를 입력해주세요")
616
+
617
+ if request.model_name not in MODELS:
618
+ raise HTTPException(status_code=400, detail=f"알 수 없는 모델입니다. 사용 가능한 모델: {list(MODELS.keys())}")
619
+
620
+ # Base64 이미지 디코딩
621
+ try:
622
+ image_data = base64.b64decode(request.image_base64)
623
+ input_image = Image.open(io.BytesIO(image_data)).convert("RGB")
624
+ except Exception as e:
625
+ raise HTTPException(status_code=400, detail=f"이미지 디코딩 실패: {str(e)}")
626
+
627
+ # 모델 로드 (img2img)
628
+ pipe, status = load_model(request.model_name, "img2img")
629
+ if pipe is None:
630
+ raise HTTPException(status_code=500, detail=status)
631
+
632
+ # 네거티브 프롬프트 설정
633
+ if request.negative_prompt.strip():
634
+ full_negative = f"{request.negative_prompt}, {DEFAULT_NEGATIVE}"
635
+ else:
636
+ full_negative = DEFAULT_NEGATIVE
637
+
638
+ # 시드 설정
639
+ seed = request.seed
640
+ if seed == -1:
641
+ seed = torch.randint(0, 2**32 - 1, (1,)).item()
642
+
643
+ generator = torch.Generator(device=DEVICE).manual_seed(int(seed))
644
+
645
+ try:
646
+ print(f"🖼️ [API img2img] 이미지 변환 중... 프롬프트: {request.prompt[:50]}...")
647
+
648
+ # 이미지 크기를 64의 배수로 조정
649
+ w, h = input_image.size
650
+ w = (w // 64) * 64
651
+ h = (h // 64) * 64
652
+ if w == 0:
653
+ w = 512
654
+ if h == 0:
655
+ h = 512
656
+ input_image = input_image.resize((w, h), Image.LANCZOS)
657
+
658
+ result = pipe(
659
+ prompt=request.prompt,
660
+ image=input_image,
661
+ negative_prompt=full_negative,
662
+ strength=request.strength,
663
+ num_inference_steps=request.num_inference_steps,
664
+ guidance_scale=request.guidance_scale,
665
+ generator=generator
666
+ )
667
+
668
+ image = result.images[0]
669
+
670
+ # 이미지를 Base64로 인코딩
671
+ buffer = io.BytesIO()
672
+ image.save(buffer, format="PNG")
673
+ buffer.seek(0)
674
+ image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
675
+
676
+ print(f"✅ [API img2img] 이미지 변환 완료! (시드: {seed})")
677
+
678
+ return GenerateResponse(
679
+ success=True,
680
+ message="이미지 변환 완료",
681
+ image_base64=image_base64,
682
+ seed=seed
683
+ )
684
+
685
+ except Exception as e:
686
+ print(f"❌ [API img2img] 이미지 변환 실패: {e}")
687
  raise HTTPException(status_code=500, detail=str(e))
688
 
689
  @api_app.get("/api/health")
 
692
  return {
693
  "status": "healthy",
694
  "device": DEVICE,
695
+ "model_loaded": current_model_id is not None,
696
+ "pipeline_type": current_pipeline_type
697
  }
698
 
699
  # ================================
 
701
  # ================================
702
  if __name__ == "__main__":
703
  print("🌸 Anime Diffusion WebUI + API 시작...")
704
+ print(" - txt2img: 텍스트 → 이미지 생성")
705
+ print(" - img2img: 이미지 → 이미지 변환")
706
 
707
  # Gradio 앱 생성
708
  demo = create_interface()