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)가 반환.