linoyts HF Staff commited on
Commit
7d44049
Β·
verified Β·
1 Parent(s): 8ea4163

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,13 +1,27 @@
1
  ---
2
- title: Smart Character Swap Flux2 Klein
3
- emoji: πŸ“Š
4
- colorFrom: pink
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.18.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Smart Character Swap - FLUX.2 Klein
3
+ emoji: 🎭
4
+ colorFrom: purple
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 6.11.0
 
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Occlusion-aware character & face swap with FLUX.2 [klein]
11
+ models:
12
+ - black-forest-labs/FLUX.2-klein-9B
13
+ - nhathoangfoto/Flux.2-Klein-9B-SmartCharacterSwap
14
  ---
15
 
16
+ # 🎭 Smart Character Swap · FLUX.2 [klein]
17
+
18
+ Swap an **identity** into a **target scene** while preserving the scene's pose,
19
+ lighting, color grading, and occlusions (hands, veils, foreground objects).
20
+
21
+ Upload the identity source (the face/character to bring in) and the target scene,
22
+ then hit **Swap**. The before/after slider shows the original scene against the result.
23
+
24
+ - **LoRA:** [`nhathoangfoto/Flux.2-Klein-9B-SmartCharacterSwap`](https://huggingface.co/nhathoangfoto/Flux.2-Klein-9B-SmartCharacterSwap)
25
+ - **Base model:** [`black-forest-labs/FLUX.2-klein-9B`](https://huggingface.co/black-forest-labs/FLUX.2-klein-9B)
26
+
27
+ Trigger word `jhuangswap` is added automatically.
app.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+
4
+ import gradio as gr
5
+ import numpy as np
6
+ import spaces
7
+ import torch
8
+ from diffusers import Flux2KleinPipeline
9
+ from PIL import Image
10
+
11
+ # ──────────────────────────────────────────────────────────────────────────────
12
+ # Smart Character Swap β€” FLUX.2 [klein]
13
+ #
14
+ # Identity source β†’ the face / character to bring in
15
+ # Target scene β†’ the photo whose pose, lighting, occlusions and color grade
16
+ # should be kept while the identity is swapped in
17
+ #
18
+ # Occlusion-aware, lighting-matched character / face swap, trained on FLUX.2 [klein].
19
+ # ──────────────────────────────────────────────────────────────────────────────
20
+
21
+ MAX_SEED = np.iinfo(np.int32).max
22
+ dtype = torch.bfloat16
23
+ device = "cuda" if torch.cuda.is_available() else "cpu"
24
+
25
+ BASE_MODEL = "black-forest-labs/FLUX.2-klein-9B"
26
+ LORA_REPO = "nhathoangfoto/Flux.2-Klein-9B-SmartCharacterSwap"
27
+ LORA_WEIGHTS = "Klein2-9B-SmartCharacterSwap.safetensors"
28
+ TRIGGER = "jhuangswap"
29
+
30
+ DEFAULT_PROMPT = (
31
+ "jhuangswap, masterpiece, high-end photography, realistic portrait, "
32
+ "matching target lighting, highly detailed skin texture, 8k resolution"
33
+ )
34
+
35
+ print("Loading FLUX.2 [klein] 9B...")
36
+ pipe = Flux2KleinPipeline.from_pretrained(BASE_MODEL, torch_dtype=dtype)
37
+ pipe.to("cuda")
38
+ pipe.load_lora_weights(LORA_REPO, weight_name=LORA_WEIGHTS, adapter_name="swap")
39
+ print("Pipeline ready.")
40
+
41
+
42
+ def _fit(img, target=1024, mult=16):
43
+ """Resize so the longest side ~= target, snapped to a multiple of `mult`."""
44
+ img = img.convert("RGB")
45
+ w, h = img.size
46
+ scale = target / max(w, h)
47
+ nw = max(mult, int(round(w * scale / mult)) * mult)
48
+ nh = max(mult, int(round(h * scale / mult)) * mult)
49
+ return img.resize((nw, nh), Image.LANCZOS)
50
+
51
+
52
+ @spaces.GPU(duration=120)
53
+ def swap(identity, scene, prompt, lora_scale, steps, guidance,
54
+ seed, randomize_seed, progress=gr.Progress(track_tqdm=True)):
55
+ if identity is None:
56
+ raise gr.Error("Please upload an identity source (the face/character to bring in).")
57
+ if scene is None:
58
+ raise gr.Error("Please upload a target scene (the photo to swap the identity into).")
59
+
60
+ pipe.set_adapters(["swap"], adapter_weights=[lora_scale])
61
+
62
+ scene_f = _fit(scene, target=1024)
63
+ identity_f = _fit(identity, target=1024)
64
+
65
+ full_prompt = prompt.strip() if prompt and prompt.strip() else DEFAULT_PROMPT
66
+ if TRIGGER not in full_prompt:
67
+ full_prompt = f"{TRIGGER}, {full_prompt}"
68
+
69
+ if randomize_seed:
70
+ seed = random.randint(0, MAX_SEED)
71
+ generator = torch.Generator(device=device).manual_seed(int(seed))
72
+
73
+ # Output keeps the target scene's composition; identity is the reference.
74
+ result = pipe(
75
+ image=[scene_f, identity_f],
76
+ prompt=full_prompt,
77
+ height=scene_f.height,
78
+ width=scene_f.width,
79
+ num_inference_steps=int(steps),
80
+ guidance_scale=guidance,
81
+ generator=generator,
82
+ ).images[0]
83
+
84
+ return (scene_f, result), seed
85
+
86
+
87
+ css = """
88
+ #col { max-width: 1200px; margin: 0 auto; }
89
+ .header { text-align:center; padding: 8px 0 4px; }
90
+ .header h1 { font-size: 1.7rem; margin: 0; font-weight: 700; }
91
+ .header p { color: var(--body-text-color-subdued); margin: 4px 0 0; font-size: 0.95rem; }
92
+ .header a { color: #6366f1; text-decoration: none; font-weight: 600; }
93
+ """
94
+
95
+ with gr.Blocks(css=css) as demo:
96
+ with gr.Column(elem_id="col"):
97
+ gr.HTML(
98
+ """
99
+ <div class="header">
100
+ <h1>🎭 Smart Character Swap · FLUX.2 [klein]</h1>
101
+ <p>Swap an <b>identity</b> into a <b>target scene</b> β€” occlusion-aware, with the scene's
102
+ own lighting and color grade preserved.
103
+ &nbsp;Β·&nbsp;
104
+ <a href="https://huggingface.co/nhathoangfoto/Flux.2-Klein-9B-SmartCharacterSwap" target="_blank">Model card</a></p>
105
+ </div>
106
+ """
107
+ )
108
+
109
+ with gr.Row(equal_height=False):
110
+ with gr.Column():
111
+ with gr.Row():
112
+ identity = gr.Image(label="Identity source (face to bring in)", type="pil", height=300)
113
+ scene = gr.Image(label="Target scene (pose / lighting to keep)", type="pil", height=300)
114
+
115
+ prompt = gr.Textbox(
116
+ label="Prompt",
117
+ value=DEFAULT_PROMPT,
118
+ lines=2,
119
+ info="Trigger 'jhuangswap' is added automatically if you remove it.",
120
+ )
121
+
122
+ with gr.Accordion("Advanced", open=False):
123
+ lora_scale = gr.Slider(0.5, 1.2, value=0.9, step=0.05, label="LoRA strength")
124
+ steps = gr.Slider(4, 30, value=8, step=1, label="Steps")
125
+ guidance = gr.Slider(1.0, 10.0, value=4.0, step=0.1, label="Guidance scale")
126
+ with gr.Row():
127
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
128
+ randomize_seed = gr.Checkbox(value=True, label="Randomize")
129
+
130
+ run_btn = gr.Button("Swap", variant="primary", size="lg")
131
+
132
+ with gr.Column():
133
+ result = gr.ImageSlider(label="Target scene β†’ Swapped result", type="pil", height=480)
134
+
135
+ gr.Examples(
136
+ examples=[
137
+ ["examples/identity_B.jpg", "examples/scene_B.jpg"],
138
+ ["examples/identity_A.jpg", "examples/scene_A.jpg"],
139
+ ],
140
+ inputs=[identity, scene],
141
+ outputs=[result, seed],
142
+ fn=lambda i, s: swap(i, s, DEFAULT_PROMPT, 0.9, 8, 4.0, 0, True),
143
+ cache_examples=True,
144
+ cache_mode="lazy",
145
+ label="Identity + scene examples",
146
+ )
147
+
148
+ swap_inputs = [identity, scene, prompt, lora_scale, steps, guidance, seed, randomize_seed]
149
+ run_btn.click(fn=swap, inputs=swap_inputs, outputs=[result, seed])
150
+ prompt.submit(fn=swap, inputs=swap_inputs, outputs=[result, seed])
151
+
152
+ if __name__ == "__main__":
153
+ demo.launch(theme=gr.themes.Citrus(), show_error=True, ssr_mode=False)
examples/identity_A.jpg ADDED
examples/identity_B.jpg ADDED
examples/scene_A.jpg ADDED
examples/scene_B.jpg ADDED
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ git+https://github.com/huggingface/diffusers.git
2
+ transformers
3
+ accelerate
4
+ safetensors
5
+ bitsandbytes
6
+ torchao
7
+ kernels
8
+ peft