Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
79e7646
0
Parent(s):
Update background_edit.py
Browse files- .gitattributes +35 -0
- README.md +13 -0
- app.py +101 -0
- app_old.py +52 -0
- background_edit.py +77 -0
- inference.py +44 -0
- requirements.txt +9 -0
- utils.py +9 -0
.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Professional Head
|
3 |
+
emoji: 🔥
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: red
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 5.29.0
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
short_description: create a professional headshot from your images
|
11 |
+
---
|
12 |
+
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from inference import generate_with_lora
|
3 |
+
from background_edit import run_background_removal_and_inpaint
|
4 |
+
import traceback, torch, gc
|
5 |
+
|
6 |
+
# ───────────────────── Helpers ─────────────────────
|
7 |
+
def _print_trace(): traceback.print_exc()
|
8 |
+
|
9 |
+
def safe_generate_with_lora(*a, **kw):
|
10 |
+
try: return generate_with_lora(*a, **kw)
|
11 |
+
except gr.Error: _print_trace(); raise
|
12 |
+
except Exception as e:
|
13 |
+
_print_trace(); raise gr.Error(f"Image generation failed: {e}")
|
14 |
+
|
15 |
+
def unload_models(): torch.cuda.empty_cache(); gc.collect()
|
16 |
+
|
17 |
+
def safe_run_background(*args, **kwargs):
|
18 |
+
try:
|
19 |
+
unload_models() # free VRAM before running background edit
|
20 |
+
return run_background_removal_and_inpaint(*args, **kwargs)
|
21 |
+
except Exception as e:
|
22 |
+
_print_trace()
|
23 |
+
raise gr.Error(f"[Step 2] Background replacement failed: {type(e).__name__} - {e}")
|
24 |
+
|
25 |
+
|
26 |
+
# ───────────────────── UI ─────────────────────
|
27 |
+
shared_output = gr.State()
|
28 |
+
original_input = gr.State()
|
29 |
+
|
30 |
+
with gr.Blocks() as demo:
|
31 |
+
demo.queue()
|
32 |
+
|
33 |
+
# ─────────── STEP 1: Headshot Refinement ───────────
|
34 |
+
with gr.Tab("Step 1: Headshot Refinement"):
|
35 |
+
with gr.Row():
|
36 |
+
input_image = gr.Image(type="pil", label="Upload Headshot")
|
37 |
+
output_image = gr.Image(type="pil", label="Refined Output")
|
38 |
+
|
39 |
+
with gr.Row():
|
40 |
+
prompt = gr.Textbox(
|
41 |
+
label="Prompt",
|
42 |
+
value="a professional corporate headshot of a confident woman in her 30s with blonde hair"
|
43 |
+
)
|
44 |
+
negative_prompt = gr.Textbox(
|
45 |
+
label="Negative Prompt",
|
46 |
+
value="deformed, cartoon, anime, illustration, painting, drawing, sketch, low resolution, blurry, out of focus, pixelated"
|
47 |
+
)
|
48 |
+
|
49 |
+
with gr.Row():
|
50 |
+
strength = gr.Slider(0.1, 1.0, value=0.20, step=0.05, label="Strength")
|
51 |
+
guidance = gr.Slider(1, 20, value=17.0, step=0.5, label="Guidance Scale")
|
52 |
+
|
53 |
+
run_btn = gr.Button("Generate")
|
54 |
+
|
55 |
+
def _save_to_state(img):
|
56 |
+
return {"step1": img} if img is not None else gr.skip()
|
57 |
+
|
58 |
+
# Build the *single* click chain and keep the handle in `event`
|
59 |
+
event = (
|
60 |
+
run_btn.click(
|
61 |
+
fn=safe_generate_with_lora,
|
62 |
+
inputs=[input_image, prompt, negative_prompt, strength, guidance],
|
63 |
+
outputs=output_image,
|
64 |
+
)
|
65 |
+
.then(_save_to_state, None, shared_output)
|
66 |
+
.then(lambda x: x, input_image, original_input)
|
67 |
+
)
|
68 |
+
|
69 |
+
# ─────────── STEP 2: Background Replacement ───────────
|
70 |
+
with gr.Tab("Step 2: Replace Background"):
|
71 |
+
with gr.Row():
|
72 |
+
inpaint_prompt = gr.Textbox(
|
73 |
+
label="New Background Prompt",
|
74 |
+
value="modern open‑plan startup office background, natural lighting, glass walls, clean design, minimalistic decor"
|
75 |
+
)
|
76 |
+
inpaint_negative = gr.Textbox(
|
77 |
+
label="Negative Prompt",
|
78 |
+
value="dark lighting, cluttered background, fantasy elements, cartoon, anime, painting, low quality, distorted shapes"
|
79 |
+
)
|
80 |
+
|
81 |
+
with gr.Row():
|
82 |
+
inpaint_result = gr.Image(type="pil", label="Inpainted Image")
|
83 |
+
|
84 |
+
with gr.Row():
|
85 |
+
inpaint_btn = gr.Button("Remove Background & Inpaint", interactive=False)
|
86 |
+
|
87 |
+
def guarded_inpaint(img, prompt_bg, neg_bg):
|
88 |
+
if img is None:
|
89 |
+
raise gr.Error("[Step 2] Error: No image to inpaint. Please run Step 1 first.")
|
90 |
+
return safe_run_background(img, prompt_bg, neg_bg)
|
91 |
+
|
92 |
+
inpaint_btn.click(
|
93 |
+
fn=guarded_inpaint,
|
94 |
+
inputs=[shared_output, inpaint_prompt, inpaint_negative],
|
95 |
+
outputs=inpaint_result,
|
96 |
+
)
|
97 |
+
|
98 |
+
# Enable the Step 2 button once Step 1 finishes
|
99 |
+
event.then(lambda: gr.update(interactive=True), None, inpaint_btn)
|
100 |
+
|
101 |
+
demo.launch(debug=True)
|
app_old.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import torch
|
3 |
+
from diffusers import AutoPipelineForImage2Image
|
4 |
+
from diffusers.utils import load_image
|
5 |
+
|
6 |
+
# === Load SDXL and LoRA from Hugging Face ===
|
7 |
+
pipe = AutoPipelineForImage2Image.from_pretrained(
|
8 |
+
"stabilityai/stable-diffusion-xl-base-1.0",
|
9 |
+
torch_dtype=torch.float16,
|
10 |
+
variant="fp16",
|
11 |
+
use_safetensors=True
|
12 |
+
).to("cuda")
|
13 |
+
|
14 |
+
pipe.load_lora_weights("theoracle/sdxl-lora-headshot")
|
15 |
+
|
16 |
+
# === Inference function ===
|
17 |
+
def generate_image(image, prompt, negative_prompt, strength, guidance_scale):
|
18 |
+
image = image.resize((1024, 1024))
|
19 |
+
result = pipe(
|
20 |
+
prompt=prompt,
|
21 |
+
negative_prompt=negative_prompt,
|
22 |
+
image=image,
|
23 |
+
strength=strength,
|
24 |
+
guidance_scale=guidance_scale,
|
25 |
+
num_inference_steps=50
|
26 |
+
).images[0]
|
27 |
+
return result
|
28 |
+
|
29 |
+
# === Gradio UI ===
|
30 |
+
demo = gr.Interface(
|
31 |
+
fn=generate_image,
|
32 |
+
inputs=[
|
33 |
+
gr.Image(type="pil", label="Upload Headshot"),
|
34 |
+
gr.Textbox(
|
35 |
+
label="Prompt",
|
36 |
+
lines=4,
|
37 |
+
value="a photo of a professional corporate TOK headshot with nice hair and heavy make up, featuring a confident female with blonde hair in formal business attire, clean background, soft natural lighting, clear facial features, natural makeup or well-groomed face, direct or angled eye contact, neutral or friendly expression, wearing a blazer or business suit, realistic skin texture, high-resolution detail, styled hair e.g., parted, neat, or voluminous"
|
38 |
+
),
|
39 |
+
gr.Textbox(
|
40 |
+
label="Negative Prompt",
|
41 |
+
lines=4,
|
42 |
+
value="brown hair, cartoon, anime, painting, drawing, low resolution, blurry, deformed face, extra limbs, unrealistic skin, overly saturated colors, harsh lighting, exaggerated makeup, fantasy, hat, helmet, sunglasses, messy background, extreme poses, profile-only shots, old-fashioned clothing, distorted proportions, unnatural expressions, glitch, watermark, text, duplicate face"
|
43 |
+
),
|
44 |
+
gr.Slider(minimum=0.1, maximum=1.0, step=0.05, value=0.20, label="Strength"),
|
45 |
+
gr.Slider(minimum=1.0, maximum=20.0, step=0.5, value=17.0, label="Guidance Scale")
|
46 |
+
],
|
47 |
+
outputs=gr.Image(type="pil", label="Generated Image"),
|
48 |
+
title="SDXL LoRA - Corporate Headshot Generator",
|
49 |
+
description="Upload a headshot and customize prompts to generate a refined corporate-style portrait using SDXL and a LoRA adapter."
|
50 |
+
)
|
51 |
+
|
52 |
+
demo.launch()
|
background_edit.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
# ── before you set the env var ──
|
4 |
+
hf_home = "/data/.cache/huggingface"
|
5 |
+
yolo_cfg = "/data/ultralytics"
|
6 |
+
|
7 |
+
# create the folders (and any parents) if they don’t already exist
|
8 |
+
os.makedirs(hf_home, exist_ok=True)
|
9 |
+
os.makedirs(yolo_cfg, exist_ok=True)
|
10 |
+
|
11 |
+
# now point HF and YOLO at them
|
12 |
+
os.environ["HF_HOME"] = hf_home
|
13 |
+
os.environ["YOLO_CONFIG_DIR"] = yolo_cfg
|
14 |
+
|
15 |
+
from ultralytics import YOLO
|
16 |
+
import numpy as np
|
17 |
+
import torch
|
18 |
+
from PIL import Image
|
19 |
+
import cv2
|
20 |
+
from diffusers import StableDiffusionXLInpaintPipeline
|
21 |
+
from utils import pil_to_cv2, cv2_to_pil
|
22 |
+
import gradio as gr # ✅ Needed for error handling
|
23 |
+
|
24 |
+
|
25 |
+
|
26 |
+
# ✅ Load models once
|
27 |
+
yolo = YOLO("yolov8x-seg.pt")
|
28 |
+
|
29 |
+
inpaint_pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
|
30 |
+
"diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
|
31 |
+
torch_dtype=torch.float16,
|
32 |
+
use_safetensors=True,
|
33 |
+
use_auth_token=os.getenv("HF_TOKEN")
|
34 |
+
).to("cuda")
|
35 |
+
|
36 |
+
def run_background_removal_and_inpaint(shared_output, prompt, negative_prompt):
|
37 |
+
|
38 |
+
# Get image from shared_output
|
39 |
+
if isinstance(shared_output, dict):
|
40 |
+
image = shared_output.get("step1")
|
41 |
+
else:
|
42 |
+
image = None
|
43 |
+
|
44 |
+
if image is None:
|
45 |
+
raise gr.Error("Run Step 1 first to get a base image.")
|
46 |
+
|
47 |
+
img_cv = pil_to_cv2(image)
|
48 |
+
results = yolo(img_cv)
|
49 |
+
|
50 |
+
# ✅ Validate YOLO detection result
|
51 |
+
if not results or not results[0].masks or len(results[0].masks.data) == 0:
|
52 |
+
raise gr.Error("No subject detected in the image. Please upload a clearer photo.")
|
53 |
+
|
54 |
+
mask = results[0].masks.data[0].cpu().numpy()
|
55 |
+
|
56 |
+
# Create inpainting mask
|
57 |
+
binary = (mask > 0.5).astype(np.uint8)
|
58 |
+
background_mask = 1 - binary
|
59 |
+
kernel = np.ones((15, 15), np.uint8)
|
60 |
+
dilated = cv2.dilate(background_mask, kernel, iterations=1)
|
61 |
+
inpaint_mask = (dilated * 255).astype(np.uint8)
|
62 |
+
|
63 |
+
# Resize and prepare images
|
64 |
+
mask_pil = cv2_to_pil(inpaint_mask).resize((1024, 1024)).convert("L")
|
65 |
+
img_pil = image.resize((1024, 1024)).convert("RGB")
|
66 |
+
|
67 |
+
# Inpaint
|
68 |
+
result = inpaint_pipe(
|
69 |
+
prompt=prompt,
|
70 |
+
negative_prompt=negative_prompt or "",
|
71 |
+
image=img_pil,
|
72 |
+
mask_image=mask_pil,
|
73 |
+
guidance_scale=10,
|
74 |
+
num_inference_steps=40
|
75 |
+
).images[0]
|
76 |
+
|
77 |
+
return result
|
inference.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torch
|
3 |
+
import traceback
|
4 |
+
import gradio as gr # ✅ Needed for gr.Error
|
5 |
+
from diffusers import AutoPipelineForImage2Image
|
6 |
+
|
7 |
+
# ✅ Cache models and tokenizers inside persistent storage
|
8 |
+
os.environ["HF_HOME"] = "/data/.cache/huggingface"
|
9 |
+
|
10 |
+
# Load SDXL pipeline with LoRA
|
11 |
+
pipe = AutoPipelineForImage2Image.from_pretrained(
|
12 |
+
"stabilityai/stable-diffusion-xl-base-1.0",
|
13 |
+
torch_dtype=torch.float16,
|
14 |
+
variant="fp16",
|
15 |
+
use_safetensors=True,
|
16 |
+
token=os.getenv("HF_TOKEN") # ✅ Your token from Space secrets
|
17 |
+
).to("cuda")
|
18 |
+
|
19 |
+
pipe.load_lora_weights("theoracle/sdxl-lora-headshot")
|
20 |
+
|
21 |
+
def generate_with_lora(image, prompt, negative_prompt, strength, guidance_scale):
|
22 |
+
try:
|
23 |
+
if image is None:
|
24 |
+
raise ValueError("Uploaded image is None. Please upload a valid image.")
|
25 |
+
|
26 |
+
print("[INFO] Received image size:", image.size)
|
27 |
+
image = image.convert("RGB").resize((1024, 1024)) # ✅ Safer with convert("RGB")
|
28 |
+
print("[INFO] Starting pipeline with prompt:", prompt)
|
29 |
+
|
30 |
+
result = pipe(
|
31 |
+
prompt=prompt,
|
32 |
+
negative_prompt=negative_prompt or "",
|
33 |
+
image=image,
|
34 |
+
strength=strength,
|
35 |
+
guidance_scale=guidance_scale,
|
36 |
+
num_inference_steps=50
|
37 |
+
).images[0]
|
38 |
+
|
39 |
+
print("[INFO] Generation successful.")
|
40 |
+
return result
|
41 |
+
|
42 |
+
except Exception as e:
|
43 |
+
print("[ERROR] Exception in generate_with_lora:\n", traceback.format_exc())
|
44 |
+
raise gr.Error(f"Image generation failed: {str(e)}")
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
diffusers
|
2 |
+
transformers
|
3 |
+
safetensors
|
4 |
+
accelerate
|
5 |
+
torch
|
6 |
+
peft
|
7 |
+
gradio
|
8 |
+
ultralytics
|
9 |
+
opencv-python
|
utils.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
def pil_to_cv2(pil_img):
|
6 |
+
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
7 |
+
|
8 |
+
def cv2_to_pil(cv_img):
|
9 |
+
return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
|