File size: 5,479 Bytes
b446b48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
๋ชฝ๊ธ€๋งˆ์„ ํ”ผ๋“œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ v2 (์ด๋ฏธ์ง€ ์ž…๋ ฅ ํŒŒ์ดํ”„๋ผ์ธ ์ „์šฉ)

feed_generator_1.py์™€์˜ ์ฐจ์ด์ :
- ์บ๋ฆญํ„ฐ ์™ธํ˜•์„ ํ”„๋กฌํ”„ํŠธ ์•ž์ชฝ์— ๋ฐฐ์น˜ (SDXL ํ† ํฐ ๊ฐ€์ค‘์น˜ ํ™œ์šฉ)
- appearance_str์— limbs ํฌํ•จ (๋น„์ „ํ˜• ์บ๋ฆญํ„ฐ ์‹ ์ฒด ๋ฐ˜์˜)
- NEGATIVE์— human/person ์ถ”๊ฐ€
- char LoRA 0.9 / bg LoRA 1.1 (์™ธํ˜• ๊ฐ•์ œ๋ ฅ ์ƒํ–ฅ)
- guidance_scale 8.5 (ํ”„๋กฌํ”„ํŠธ ์ถ”์ข…๋ ฅ ๊ฐ•ํ™”)
- ์–‘์ชฝ ์ธ์ฝ”๋” ๋ชจ๋‘ scene ์ •๋ณด ์ „๋‹ฌ
"""
import os as _os; _os.chdir(_os.path.dirname(_os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))))
import os, gc
from pathlib import Path
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"

from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
import torch

CHARACTER_LORA = "models/lora_v3_32bit/pytorch_lora_weights.safetensors"
BG_LORA        = "models/lora_bg_v1/pytorch_lora_weights.safetensors"

NEGATIVE = (
    "realistic, 3d render, photograph, blurry, dark, gloomy, "
    "watercolor, sketch, painterly, smooth illustration, "
    "modern city, urban, scary, violence, text, watermark, "
    "multiple characters, tiling, repeated, "
    "human, person, girl, boy, man, woman, humanoid, human body, human face, "
    "skin, hair, clothes, dress, shirt"
)


def _to_appearance_str(appearance) -> str:
    if isinstance(appearance, str):
        return appearance.strip()
    if not isinstance(appearance, dict):
        return str(appearance).strip()

    animal    = appearance.get("animal_type", "bear")
    color     = appearance.get("body_color", "")
    shape     = appearance.get("body_shape", "")
    eye       = appearance.get("eye_style", "")
    nose      = appearance.get("nose_mouth") or appearance.get("nose_shape", "")
    cheeks    = appearance.get("cheeks", "")
    ears      = appearance.get("ear_shape", "")
    limbs     = appearance.get("limbs", "")
    texture   = appearance.get("texture", "")
    acc       = appearance.get("accessories", "")
    secondary = appearance.get("secondary_colors", [])

    parts = [f"a {color} pixel art stuffed {animal} character".strip()]
    if shape: parts.append(shape)
    if isinstance(secondary, list):
        parts.extend(s for s in secondary[:2] if s and len(str(s).split()) <= 3)
    if eye:   parts.append(eye)
    if cheeks and "no" not in cheeks.lower(): parts.append(cheeks)
    if ears and "not visible" not in ears.lower(): parts.append(ears)
    if nose and "not visible" not in nose.lower(): parts.append(nose)
    if limbs and "not visible" not in limbs.lower(): parts.append(limbs)
    if texture: parts.append(texture)
    if isinstance(acc, list):
        acc = ", ".join(str(a) for a in acc if a)
    if acc and str(acc).lower() not in ("none", ""): parts.append(acc)

    return ", ".join(p for p in parts if p)


def build_prompt(appearance, quest_en: str):
    """
    ๋‘ ์ธ์ฝ”๋” ์—ญํ•  ๋ถ„๋ฆฌ:
    prompt   (CLIP-L)       โ†’ ์žฅ๋ฉด/๋ฐฐ๊ฒฝ ๋‹ด๋‹น: scene ์•ž์— ๋ฐฐ์น˜
    prompt_2 (OpenCLIP-bigG) โ†’ ์บ๋ฆญํ„ฐ ์™ธํ˜• ๋‹ด๋‹น: appearance ์•ž์— ๋ฐฐ์น˜
    """
    appearance_str = _to_appearance_str(appearance)

    animal = "plush mascot"
    if isinstance(appearance, dict):
        animal = appearance.get("animal_type", "plush mascot")

    # CLIP-L: ์žฅ๋ฉด ๋จผ์ € โ†’ ๋ฐฐ๊ฒฝ ์ƒ์„ฑ ๋‹ด๋‹น
    # ์บ๋ฆญํ„ฐ ํƒ€์ž…๋งŒ ๊ฐ„๋‹จํžˆ ๋ช…์‹œํ•ด human ๋ฐฉ์ง€
    prompt = (
        f"monglestyle, {quest_en}, "
        f"stuffed {animal} plush toy mascot, "
        f"cozy pastel sky island village, "
        f"32-bit pixel art scene, detailed background, outdoor scenery, pastel colors"
    )
    # OpenCLIP-bigG: ์™ธํ˜• ๋จผ์ € โ†’ ์บ๋ฆญํ„ฐ ์ •์ฒด์„ฑ ๋‹ด๋‹น
    # ์žฅ๋ฉด ํžŒํŠธ๋„ ๋’ค์— ํฌํ•จํ•ด ๋ฐฐ๊ฒฝ ๋ณด์กฐ
    prompt_2 = (
        f"monglestyle, stuffed {animal} plush toy mascot, "
        f"{appearance_str}, "
        f"full body in action, {quest_en}, "
        f"pixel art background scene"
    )
    return prompt, prompt_2


def load_pipeline():
    print("SDXL ๋กœ๋“œ ์ค‘ (์บ๋ฆญํ„ฐ LoRA + ๋ฐฐ๊ฒฝ LoRA)...")
    pipe = StableDiffusionXLPipeline.from_pretrained(
        "stabilityai/stable-diffusion-xl-base-1.0",
        torch_dtype=torch.float16,
        use_safetensors=True,
    ).to("cuda")

    if not Path(CHARACTER_LORA).exists():
        raise FileNotFoundError(f"Character LoRA not found: {CHARACTER_LORA}")
    if not Path(BG_LORA).exists():
        raise FileNotFoundError(f"Background LoRA not found: {BG_LORA}")

    pipe.load_lora_weights(CHARACTER_LORA, adapter_name="character")
    pipe.load_lora_weights(BG_LORA,        adapter_name="bg")
    pipe.set_adapters(
        ["character", "bg"],
        adapter_weights=[0.9, 1.1],
    )
    pipe.scheduler = DPMSolverMultistepScheduler.from_config(
        pipe.scheduler.config, use_karras_sigmas=True
    )
    pipe.enable_attention_slicing()
    print("๋กœ๋“œ ์™„๋ฃŒ!\n")
    return pipe


def generate(appearance, quest_en: str, pipe, seed: int = 42):
    prompt, prompt_2 = build_prompt(appearance, quest_en)
    print(f"  prompt  : {prompt}")
    print(f"  prompt_2: {prompt_2}")

    return pipe(
        prompt=prompt,
        prompt_2=prompt_2,
        negative_prompt=NEGATIVE,
        num_inference_steps=25,
        guidance_scale=8.5,
        height=1024,
        width=1024,
        generator=torch.Generator("cuda").manual_seed(seed),
    ).images[0]


def unload_pipeline(pipe):
    del pipe
    gc.collect()
    torch.cuda.empty_cache()
    print("ํŒŒ์ดํ”„๋ผ์ธ VRAM ํ•ด์ œ ์™„๋ฃŒ")