sdfdsfsf32e3 commited on
Commit
d4e28af
·
verified ·
1 Parent(s): d6d032c

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +9 -7
  2. app.py +286 -0
  3. requirements.txt +7 -0
README.md CHANGED
@@ -1,13 +1,15 @@
1
  ---
2
- title: Img2
3
- emoji:
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 6.25.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: Swap Lab
3
+ emoji: 🦀
4
+ colorFrom: red
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 6.5.1
8
  python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
+ preload_from_hub:
12
+ - thornmaze/FLUX2-klein-9B model_index.json,scheduler/scheduler_config.json,text_encoder/config.json,text_encoder/generation_config.json,text_encoder/model-00001-of-00004.safetensors,text_encoder/model-00002-of-00004.safetensors,text_encoder/model-00003-of-00004.safetensors,text_encoder/model-00004-of-00004.safetensors,text_encoder/model.safetensors.index.json,tokenizer/added_tokens.json,tokenizer/chat_template.jinja,tokenizer/merges.txt,tokenizer/special_tokens_map.json,tokenizer/tokenizer.json,tokenizer/tokenizer_config.json,tokenizer/vocab.json,transformer/config.json,transformer/diffusion_pytorch_model-00001-of-00002.safetensors,transformer/diffusion_pytorch_model-00002-of-00002.safetensors,transformer/diffusion_pytorch_model.safetensors.index.json,vae/config.json,vae/diffusion_pytorch_model.safetensors 43d29778843756cbf5f39dacedea1f0b95dbf8a3
13
+ - thornmaze/BFS-Best-Face-Swap bfs_head_v1_flux-klein_9b_step3750_rank64.safetensors a75b3759440d582abf9360c125d8e914b86ec307
14
+ short_description: Internal tooling
15
  ---
 
 
app.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import numpy as np
4
+ import random
5
+ import spaces
6
+ import torch
7
+ from diffusers import Flux2KleinPipeline
8
+ from PIL import Image
9
+
10
+ dtype = torch.bfloat16
11
+ device = "cuda" if torch.cuda.is_available() else "cpu"
12
+
13
+ MAX_SEED = np.iinfo(np.int32).max
14
+
15
+ # Model repository ID for 9B distilled
16
+ REPO_ID_DISTILLED = "thornmaze/FLUX2-klein-9B"
17
+
18
+ # LoRA repository and file
19
+ LORA_REPO_ID = "thornmaze/BFS-Best-Face-Swap"
20
+ LORA_FILENAME = "bfs_head_v1_flux-klein_9b_step3750_rank64.safetensors"
21
+
22
+ # Fixed prompt for face swapping
23
+ #FACE_SWAP_PROMPT = "head_swap: start with Picture 1 as the base image, keeping its lighting, environment, and background. remove the head from Picture 1 completely and replace it with the head from Picture 2, strictly preserving the hair, eye color, nose structure of Picture 2. copy the direction of the eye, head rotation, micro expressions from Picture 1, high quality, sharp details, 4k."
24
+
25
+ FACE_SWAP_PROMPT = """head_swap: start with Picture 1 as the base image, keeping its lighting, environment, and background. Remove the head from Picture 1 completely and replace it with the head from Picture 2.
26
+
27
+ FROM PICTURE 1 (strictly preserve):
28
+ - Scene: lighting conditions, shadows, highlights, color temperature, environment, background
29
+ - Head positioning: exact rotation angle, tilt, direction the head is facing
30
+ - Expression: facial expression, micro-expressions, eye gaze direction, mouth position, emotion
31
+
32
+ FROM PICTURE 2 (strictly preserve identity):
33
+ - Facial structure: face shape, bone structure, jawline, chin
34
+ - All facial features: eye color, eye shape, nose structure, lip shape and fullness, eyebrows
35
+ - Hair: color, style, texture, hairline
36
+ - Skin: texture, tone, complexion
37
+
38
+ The replaced head must seamlessly match Picture 1's lighting and expression while maintaining the complete identity from Picture 2. High quality, photorealistic, sharp details, 4k."""
39
+
40
+ print("Loading FLUX.2 Klein 9B Distilled model...")
41
+ pipe = Flux2KleinPipeline.from_pretrained(REPO_ID_DISTILLED, torch_dtype=dtype)
42
+ pipe.to(device)
43
+
44
+ print(f"Loading LoRA from {LORA_REPO_ID}...")
45
+ pipe.load_lora_weights(LORA_REPO_ID, weight_name=LORA_FILENAME)
46
+ print("LoRA loaded successfully!")
47
+
48
+ def update_dimensions_from_image(target_image):
49
+ """
50
+ Update width/height based on target image aspect ratio.
51
+
52
+ Keeps one side at 1024 and scales the other proportionally,
53
+ with both sides as multiples of 8.
54
+
55
+ Args:
56
+ target_image: PIL Image of the target/body image.
57
+
58
+ Returns:
59
+ tuple: A tuple of (width, height) integers, both multiples of 8.
60
+ """
61
+ if target_image is None:
62
+ return 1024, 1024 # Default dimensions
63
+
64
+ img_width, img_height = target_image.size
65
+
66
+ aspect_ratio = img_width / img_height
67
+
68
+ if aspect_ratio >= 1: # Landscape or square
69
+ new_width = 1024
70
+ new_height = int(1024 / aspect_ratio)
71
+ else: # Portrait
72
+ new_height = 1024
73
+ new_width = int(1024 * aspect_ratio)
74
+
75
+ # Round to nearest multiple of 8
76
+ new_width = round(new_width / 8) * 8
77
+ new_height = round(new_height / 8) * 8
78
+
79
+ # Ensure within valid range (minimum 256, maximum 1024)
80
+ new_width = max(256, min(1024, new_width))
81
+ new_height = max(256, min(1024, new_height))
82
+
83
+ return new_width, new_height
84
+
85
+
86
+ @spaces.GPU(duration=35)
87
+ def face_swap(
88
+ reference_face: Image.Image,
89
+ target_image: Image.Image,
90
+ seed: int = 42,
91
+ randomize_seed: bool = False,
92
+ width: int = 1024,
93
+ height: int = 1024,
94
+ num_inference_steps: int = 4,
95
+ guidance_scale: float = 1.0,
96
+ progress=gr.Progress(track_tqdm=True)
97
+ ):
98
+ """
99
+ Perform face swapping using FLUX.2 Klein 9B with LoRA.
100
+
101
+ Args:
102
+ reference_face: The face image to swap in (Picture 2).
103
+ target_image: The target body/base image (Picture 1).
104
+ seed: Random seed for reproducible generation.
105
+ randomize_seed: Set to True to use a random seed.
106
+ width: Output image width in pixels (256-1024, must be multiple of 8).
107
+ height: Output image height in pixels (256-1024, must be multiple of 8).
108
+ num_inference_steps: Number of denoising steps (default 4 for distilled).
109
+ guidance_scale: How closely to follow the prompt (default 1.0 for distilled).
110
+
111
+ Returns:
112
+ tuple: A tuple containing the generated PIL Image and the seed used.
113
+ """
114
+ if reference_face is None or target_image is None:
115
+ raise gr.Error("Please provide both a reference face and a target image!")
116
+
117
+ if randomize_seed:
118
+ seed = random.randint(0, MAX_SEED)
119
+
120
+ generator = torch.Generator(device=device).manual_seed(seed)
121
+
122
+ # Important: Pass target image (body) first, then reference face
123
+ # This matches the prompt structure: Picture 1 = target, Picture 2 = reference
124
+ image_list = [target_image, reference_face]
125
+
126
+ progress(0.2, desc="Swapping face...")
127
+
128
+ image = pipe(
129
+ prompt=FACE_SWAP_PROMPT,
130
+ image=image_list,
131
+ height=height,
132
+ width=width,
133
+ num_inference_steps=num_inference_steps,
134
+ guidance_scale=guidance_scale,
135
+ generator=generator,
136
+ ).images[0]
137
+
138
+ # Return slider comparison (before, after) and seed
139
+ return (target_image, image), seed
140
+
141
+
142
+ css = """
143
+ #col-container {
144
+ margin: 0 auto;
145
+ max-width: 1200px;
146
+ }
147
+ .image-container img {
148
+ object-fit: contain;
149
+ }
150
+ """
151
+
152
+ with gr.Blocks(css=css) as demo:
153
+
154
+ with gr.Column(elem_id="col-container"):
155
+ gr.Markdown("""# Face Swap with FLUX.2 Klein 9B
156
+
157
+ Swap faces using Flux.2 Klein 9B [Alissonerdx/BFS-Best-Face-Swap](https://huggingface.co/Alissonerdx/BFS-Best-Face-Swap) LoRA
158
+ """)
159
+
160
+ with gr.Row():
161
+ with gr.Column():
162
+ with gr.Row():
163
+ reference_face = gr.Image(
164
+ label="Reference Face",
165
+ type="pil",
166
+ sources=["upload"],
167
+ elem_classes="image-container"
168
+ )
169
+
170
+ target_image = gr.Image(
171
+ label="Target Image (Body/Scene)",
172
+ type="pil",
173
+ sources=["upload"],
174
+ elem_classes="image-container"
175
+ )
176
+ run_button = gr.Button("Swap Face", visible=False)
177
+ with gr.Accordion("Advanced Settings", open=False):
178
+ seed = gr.Slider(
179
+ label="Seed",
180
+ minimum=0,
181
+ maximum=MAX_SEED,
182
+ step=1,
183
+ value=0,
184
+ )
185
+
186
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
187
+
188
+ with gr.Row():
189
+ width = gr.Slider(
190
+ label="Width",
191
+ minimum=256,
192
+ maximum=1024,
193
+ step=8,
194
+ value=1024,
195
+ )
196
+
197
+ height = gr.Slider(
198
+ label="Height",
199
+ minimum=256,
200
+ maximum=1024,
201
+ step=8,
202
+ value=1024,
203
+ )
204
+
205
+ with gr.Row():
206
+ num_inference_steps = gr.Slider(
207
+ label="Inference Steps",
208
+ minimum=1,
209
+ maximum=20,
210
+ step=1,
211
+ value=4,
212
+ info="Number of denoising steps (4 is optimal for distilled model)"
213
+ )
214
+
215
+ guidance_scale = gr.Slider(
216
+ label="Guidance Scale",
217
+ minimum=0.0,
218
+ maximum=5.0,
219
+ step=0.1,
220
+ value=1.0,
221
+ info="How closely to follow the prompt (1.0 is optimal for distilled model)"
222
+ )
223
+
224
+
225
+ comparison_slider = gr.ImageSlider(
226
+ label="Before / After",
227
+ type="pil"
228
+ )
229
+
230
+
231
+
232
+
233
+ seed_output = gr.Number(label="Seed Used", visible=False)
234
+
235
+ # Auto-update dimensions when target image is uploaded
236
+ target_image.upload(
237
+ fn=update_dimensions_from_image,
238
+ inputs=[target_image],
239
+ outputs=[width, height]
240
+ )
241
+
242
+ # Create a shared input/output configuration
243
+ swap_inputs = [
244
+ reference_face,
245
+ target_image,
246
+ seed,
247
+ randomize_seed,
248
+ width,
249
+ height,
250
+ num_inference_steps,
251
+ guidance_scale
252
+ ]
253
+ swap_outputs = [comparison_slider, seed_output]
254
+
255
+ # Manual trigger via button
256
+ run_button.click(
257
+ fn=face_swap,
258
+ inputs=swap_inputs,
259
+ outputs=swap_outputs,
260
+ )
261
+
262
+ # Auto-trigger when both images are uploaded
263
+ def auto_swap_wrapper(ref_face, target_img, s, rand_s, w, h, steps, cfg):
264
+ """Only run face swap if both images are provided"""
265
+ if ref_face is not None and target_img is not None:
266
+ result = face_swap(ref_face, target_img, s, rand_s, w, h, steps, cfg)
267
+ # Show the button after first generation
268
+ return result[0], result[1], gr.update(visible=True)
269
+ return None, s, gr.update(visible=False)
270
+
271
+ # Trigger on reference face upload/change
272
+ reference_face.change(
273
+ fn=auto_swap_wrapper,
274
+ inputs=swap_inputs,
275
+ outputs=[comparison_slider, seed_output, run_button],
276
+ )
277
+
278
+ # Trigger on target image upload/change
279
+ target_image.change(
280
+ fn=auto_swap_wrapper,
281
+ inputs=swap_inputs,
282
+ outputs=[comparison_slider, seed_output, run_button],
283
+ )
284
+
285
+ if __name__ == "__main__":
286
+ demo.launch(share=True, theme=gr.themes.Citrus())
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ git+https://github.com/huggingface/diffusers.git
2
+
3
+ peft
4
+ transformers
5
+ Pillow
6
+ numpy
7
+ peft