Views
No views yet

runwayml/stable-diffusion-v1-5)step 1, step 4), you can generate:1import torch
2import cv2
3import numpy as np
4import ipywidgets as widgets
5import io
6import gc
7from PIL import Image
8from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
9from transformers import BlipProcessor, BlipForConditionalGeneration
10from IPython.display import display, clear_output
11import matplotlib.pyplot as plt
12import os
13
14# ==========================================
15# 1. LOAD MODELS (Singleton Check)
16# ==========================================
17def load_models():
18 global pipe, blip_processor, blip_model
19
20 # Only load pipeline if not already loaded
21 if 'pipe' not in globals() or not isinstance(pipe.controlnet, torch.nn.ModuleList):
22 print("⏳ Loading ControlNets... (Wait ~1 min)")
23 cn_loomis = ControlNetModel.from_pretrained(
24 "HudTariq/loomis-model-output-v4-high-quality-7steps",
25 subfolder="checkpoint-3000/controlnet",
26 torch_dtype=torch.float16
27 )
28 cn_canny = ControlNetModel.from_pretrained(
29 "lllyasviel/sd-controlnet-canny",
30 torch_dtype=torch.float16
31 )
32
33 pipe = StableDiffusionControlNetPipeline.from_pretrained(
34 "runwayml/stable-diffusion-v1-5",
35 controlnet=[cn_loomis, cn_canny],
36 torch_dtype=torch.float16,
37 safety_checker=None
38 ).to("cuda")
39 pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
40 pipe.enable_model_cpu_offload()
41 print("✅ Pipeline Loaded!")
42
43 # Only load captioner if not already loaded
44 if 'blip_model' not in globals():
45 print("⏳ Loading Captioner...")
46 blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
47 blip_model = BlipForConditionalGeneration.from_pretrained(
48 "Salesforce/blip-image-captioning-base",
49 torch_dtype=torch.float16
50 ).to("cuda")
51 print("✅ Captioner Loaded!")
52
53load_models()
54
55# ==========================================
56# 2. ROBUST FACE CROPPER (OpenCV Version)
57# ==========================================
58def smart_crop(image):
59 """Detects face using OpenCV and crops to portrait."""
60 # Download the Face Cascade XML if missing (Standard OpenCV model)
61 cascade_path = "haarcascade_frontalface_default.xml"
62 if not os.path.exists(cascade_path):
63 os.system(f"wget -q https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/{cascade_path}")
64
65 # Convert PIL -> OpenCV
66 img_np = np.array(image)
67 gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
68
69 # Detect Faces
70 face_cascade = cv2.CascadeClassifier(cascade_path)
71 faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
72
73 if len(faces) == 0:
74 print("⚠️ No face detected (or face too small). Using Center Crop.")
75 width, height = image.size
76 short_dim = min(width, height)
77 # Fallback: simple square crop
78 return image.crop(((width-short_dim)//2, (height-short_dim)//2, (width+short_dim)//2, (height+short_dim)//2)).resize((512,512))
79
80 # Pick the largest face found
81 x, y, w, h = max(faces, key=lambda b: b[2] * b[3])
82
83 # === THE MAGIC PADDING MATH ===
84 # Loomis portraits need the head to take up about 50-60% of vertical space
85 # We expand the crop box by 2.2x the face size
86 crop_size = int(max(w, h) * 2.2)
87
88 center_x = x + w // 2
89 center_y = y + h // 2
90
91 img_h, img_w = img_np.shape[:2]
92
93 x1 = max(0, center_x - crop_size // 2)
94 y1 = max(0, center_y - crop_size // 2)
95 x2 = min(img_w, center_x + crop_size // 2)
96 y2 = min(img_h, center_y + crop_size // 2)
97
98 # Crop and Resize
99 cropped = image.crop((x1, y1, x2, y2))
100 return cropped.resize((512, 512), Image.LANCZOS)
101
102# ==========================================
103# 3. HELPERS
104# ==========================================
105def get_canny_image(image, low_threshold=100, high_threshold=200):
106 image = np.array(image)
107 image = cv2.Canny(image, low_threshold, high_threshold)
108 image = image[:, :, None]
109 image = np.concatenate([image, image, image], axis=2)
110 return Image.fromarray(image)
111
112def auto_detect_subject(image):
113 inputs = blip_processor(image, "a close up photo of a", return_tensors="pt").to("cuda", torch.float16)
114 out = blip_model.generate(**inputs, max_new_tokens=20)
115 caption = blip_processor.decode(out[0], skip_special_tokens=True)
116 return caption.replace("a close up photo of a", "").strip()
117
118# ==========================================
119# 4. EXECUTION LOGIC
120# ==========================================
121STEP_PROMPTS = {
122 1: "step 1, basic cranial circle, jawline stroke, ear axis line, raw construction, minimal lines",
123 2: "step 2, profile contour, nose silhouette, lip profile, chin outline, wireframe",
124 3: "step 3, ear shape definition, eye socket placement, nostril marking, feature blocking",
125 4: "step 4, side plane shading, cheekbone shadow, temple value, planar masses",
126 5: "step 5, hair mass blocking, mid-tone shading, ear detailing, form definition",
127 6: "step 6, hair strand texture, stubble detail, skin texture, eye shading, contrast",
128 7: "step 7, final graphite render, deep shadows, hyper-realistic detailed portrait, masterpiece"
129}
130
131STRENGTH_SCHEDULE = {1: 0.30, 2: 0.40, 3: 0.55, 4: 0.65, 5: 0.80, 6: 0.90, 7: 1.00}
132
133uploader = widgets.FileUpload(accept='image/*', multiple=False, description='Upload')
134subject_display = widgets.Text(placeholder='Detected subject...', description='Subject:', disabled=True)
135btn_gen = widgets.Button(description='Smart Crop & Generate', button_style='primary', icon='crop', layout=widgets.Layout(width='100%'))
136out = widgets.Output()
137
138def run_generation(b):
139 with out:
140 clear_output()
141 if not uploader.value:
142 print("⚠️ Upload a photo!")
143 return
144
145 try:
146 val = uploader.value[0] if isinstance(uploader.value, tuple) else list(uploader.value.values())[0]
147 content = val['content']
148 except:
149 print("⚠️ Upload Error. Try again.")
150 return
151
152 print("🔍 Detecting Face (OpenCV) & Cropping...")
153 raw_img = Image.open(io.BytesIO(content)).convert("RGB")
154 input_image = smart_crop(raw_img)
155
156 display(input_image.resize((150,150))) # Show crop preview
157
158 print("👁️ Captioning...")
159 subject = auto_detect_subject(input_image)
160 subject_display.value = subject
161 print(f"✅ Subject: {subject}")
162
163 canny_image = get_canny_image(input_image)
164 images = [input_image]
165 titles = ["Original Crop"]
166
167 print("🎨 Sketching...")
168 for i in range(1, 8):
169 prompt = f"a Loomis construction sketch of a {subject}, {STEP_PROMPTS[i]}"
170 strength = STRENGTH_SCHEDULE[i]
171 generator = torch.Generator(device="cuda").manual_seed(42)
172
173 with torch.no_grad():
174 res = pipe(
175 prompt,
176 image=[input_image, canny_image],
177 num_inference_steps=25,
178 guidance_scale=7.5,
179 controlnet_conditioning_scale=[1.0, strength],
180 generator=generator
181 ).images[0]
182 images.append(res)
183 titles.append(f"Step {i}")
184 torch.cuda.empty_cache()
185
186 fig, axes = plt.subplots(1, 8, figsize=(24, 4))
187 for ax, img, title in zip(axes, images, titles):
188 ax.imshow(img)
189 ax.set_title(title, fontsize=10)
190 ax.axis('off')
191 plt.tight_layout()
192 plt.show()
193 gc.collect()
194
195btn_gen.on_click(run_generation)
196display(widgets.VBox([widgets.Label("Loomis Portrait Generator (Auto-Crop)"), uploader, subject_display, btn_gen, out])basic cranial circle (Step 1)wireframe, chin outline (Step 2)planar masses, cheekbone shadow (Step 4)graphite render, masterpiece (Step 7)