theoracle commited on
Commit
79e7646
·
0 Parent(s):

Update background_edit.py

Browse files
Files changed (8) hide show
  1. .gitattributes +35 -0
  2. README.md +13 -0
  3. app.py +101 -0
  4. app_old.py +52 -0
  5. background_edit.py +77 -0
  6. inference.py +44 -0
  7. requirements.txt +9 -0
  8. 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))