stable diffusion 모델 활용 플로우 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 개요

  • 백엔드에서 이미지 파일명과 스타일명을 입력한다.
  • CPU 서버는 이를 바탕으로 스타일 프롬프트를 Gemini API로 생성하고,
  • 해당 프롬프트 및 변환 설정을 GPU 서버에서 동작 중인 Stable Diffusion WebUI에 API 요청한다.
  • 결과 이미지는 base64 디코딩 후 GCS에 업로드되고, 해당 URL이 백엔드에 반환된다.

2. 전체 처리 흐름 요약

[1] 백엔드에서 이미지 파일명 + 스타일명 입력
 ↓
[2] 이미지 base64 변환
 ↓
[3] Gemini API에 텍스트 프롬프트 요청
 ↓
[4] 최종 프롬프트 구성
 ↓
[5] Stable Diffusion 서버에 요청 전송 (HTTP API)
 ↓
[6] base64 이미지 응답 → 디코딩 & GCS 업로드
 ↓
[7] GCS URL 반환

3. 단계별 설명 (CPU 서버 관점)

a. 백엔드 입력 예시

{
  "filename": "img1.jpg",
  "style": "ghibli"
}

b. 이미지 base64 변환

import base64

def image_to_base64(image_path: str) -> str:
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

c. Gemini API 요청 프롬프트 구성

  • Gemini에게 요청 시 예시 프롬프트를 함께 제공하여 스타일을 유도함:
style = "ghibli"

gemini_instruction = (
    f"'{style}' 스타일의 애니메이션 이미지에서 자주 등장하는 요소들을 쉼표로 구분된 태그로 묘사해줘.\n"
    "예시는 다음과 같아:\n"
    "1girl, 1boy, looking at viewer, smiling, close-up, upper body, black hair, parted bangs, "
    "white shirt, soft lighting, expressive eyes, friendly atmosphere, simple background, anime style, "
    "studio ghibli style, warm color tones, cinematic framing, casual clothing"
)

d. 최종 프롬프트 조합 코드

def build_final_prompt(style: str, gemini_tags: str) -> str:
    base_tags = "clean lineart, detailed face, gentle expressions ((masterpiece)), ((best quality)), ((illustration))"
    lora_tag = f"<lora:{style}_style_offset:1.1>"
    return f"{style} style, {gemini_tags}, {base_tags}, {lora_tag}"
  • 예시 출력
ghibli style, 1girl, 1boy, looking at viewer, ..., clean lineart, ..., <lora:ghibli_style_offset:1.1>

e. Stable Diffusion 고정 negative_prompt (코드 내 상수)

NEGATIVE_PROMPT = (
    "(painting by bad-artist-anime:0.9), (painting by bad-artist:0.9), watermark, text, error, "
    "blurry, jpeg artifacts, cropped, worst quality, low quality, normal quality, jpeg artifacts, "
    "signature, watermark, username, artist name, (worst quality, low quality:1.4), bad anatomy"
)

f. Stable Diffusion 서버 요청 코드 예시

import requests

def request_sd_image(prompt: str, image_base64: str) -> str:
    payload = {
        "prompt": prompt,
        "negative_prompt": NEGATIVE_PROMPT,
        "seed": 1178811602,
        "steps": 20,
        "width": 512,
        "height": 768,
        "cfg_scale": 8,
        "sampler_name": "Euler a",
        "denoising_strength": 0.5,
        "init_images": [image_base64],
        "restore_faces": True,
        "enable_hr": False,
        "override_settings": {
            "sd_model_checkpoint": "anyloraCheckpoint_bakedvaeBlessedFp16",
            "CLIP_stop_at_last_layers": 2
        },
        "alwayson_scripts": {
            "ControlNet": {
                "args": [
                    {
                        "enabled": True,
                        "module": "mediapipe_face",
                        "model": "control_v2p_sd15_mediapipe_face [0653d983]",
                        "image": {"image": image_base64, "mask": None}
                    },
                    {
                        "enabled": True,
                        "module": "depth_midas",
                        "model": "diffusers_xl_depth_mid [39c49e13]",
                        "image": {"image": image_base64, "mask": None}
                    },
                    {
                        "enabled": True,
                        "module": "softedge_hed",
                        "model": "control_v11p_sd15_softedge [a8575a2a]",
                        "image": {"image": image_base64, "mask": None}
                    },
                    {
                        "enabled": True,
                        "module": "canny",
                        "model": "diffusers_xl_canny_mid [112a778d]",
                        "threshold_a": 100,
                        "threshold_b": 200,
                        "image": {"image": image_base64, "mask": None}
                    }
                ]
            }
        }
    }

    response = requests.post("http://gpu-server:7860/sdapi/v1/img2img", json=payload)
    response.raise_for_status()
    return response.json()["images"][0]

g. Stable Diffusion 응답 base64 → 이미지 디코딩 및 저장/반환

import base64
import uuid
from pathlib import Path

def save_base64_image(base64_str: str, save_dir: str = "./results/") -> str:
    # 고유 파일명 생성
    Path(save_dir).mkdir(parents=True, exist_ok=True)
    filename = f"{uuid.uuid4().hex}.jpg"
    save_path = os.path.join(save_dir, filename)

    # base64 디코딩 후 저장
    image_data = base64.b64decode(base64_str)
    with open(save_path, "wb") as f:
        f.write(image_data)

    return save_path  # 또는 GCS 업로드 후 URL 반환
  • 사용 예시:

    result_path = save_base64_image(sd_response_base64)
    
    • GCS에 업로드하기 위해, upload_to_gcs() 함수에 result_path를 넘기기

    • upload_to_gcs() 함수 (GCS 업로드 + URL 반환)

      from google.cloud import storage
      
      def upload_to_gcs(
          local_path: str,
          bucket_name: str,
          gcs_path: str,
          credentials_path: str
      ) -> str:
          """
          로컬 파일을 GCS에 업로드하고 공개 URL 반환
      
          Args:
              local_path (str): 업로드할 로컬 파일 경로
              bucket_name (str): GCS 버킷 이름
              gcs_path (str): GCS 내 저장 경로 (예: "stylized/user1/image.jpg")
              credentials_path (str): 서비스 계정 키 JSON 경로
      
          Returns:
              str: 업로드된 이미지의 GCS 공개 URL
          """
          # GCS 클라이언트 초기화
          client = storage.Client.from_service_account_json(credentials_path)
          bucket = client.bucket(bucket_name)
          blob = bucket.blob(gcs_path)
      
          # 파일 업로드
          blob.upload_from_filename(local_path)
      
          # 공개 URL 반환
          return f"https://storage.googleapis.com/{bucket_name}/{gcs_path}"
      

h. 플로우 요약 문장

  • 백엔드에서 이미지 파일명과 원하는 스타일명이 전달되면, CPU 서버는 해당 이미지를 base64로 인코딩하고,
    • Gemini API에 예시 프롬프트와 함께 요청을 보내 이미지 스타일에 적합한 태그 기반 텍스트 프롬프트를 생성.
  • 이후 이 프롬프트에 스타일명, 고정된 표현 방식, LoRA 태그를 조합하여 최종 프롬프트를 구성하고,
    • 고정된 negative prompt와 함께 Stable Diffusion GPU 서버로 API 요청을 전송.
    • 응답으로 받은 base64 이미지는 로컬에 저장되거나 GCS에 업로드되며, 최종적으로 저장된 이미지의 경로(URL)가 반환.