Yankkee commited on
Commit
45702fb
·
verified ·
1 Parent(s): b2cea58

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +55 -5
  2. app.py +268 -0
  3. requirements.txt +10 -0
README.md CHANGED
@@ -1,13 +1,63 @@
1
  ---
2
- title: CodeMonsterControlNet
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: QR Code Monster
3
+ emoji: 🧟
4
  colorFrom: purple
5
  colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 4.44.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: openrail++
11
+ short_description: Künstlerische, scanbare QR-Codes mit ControlNet
12
  ---
13
 
14
+ # QR Code Monster Space
15
+
16
+ Erzeugt künstlerische, weiterhin scanbare QR-Codes mit
17
+ [`monster-labs/control_v1p_sd15_qrcode_monster`](https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster) (v2)
18
+ auf Basis von Stable Diffusion 1.5.
19
+
20
+ ## Space anlegen
21
+
22
+ 1. Auf huggingface.co → **New Space** → SDK **Gradio**, Hardware **ZeroGPU** (gratis für Pro)
23
+ oder **T4 small** (ca. 0,40 $/h).
24
+ 2. `app.py`, `requirements.txt` und `README.md` ins Repo pushen:
25
+
26
+ ```bash
27
+ git clone https://huggingface.co/spaces/DEIN_NAME/qr-code-monster
28
+ cd qr-code-monster
29
+ cp /pfad/zu/{app.py,requirements.txt,README.md} .
30
+ git add . && git commit -m "init" && git push
31
+ ```
32
+
33
+ 3. Der Build dauert einige Minuten; die Modelle (~4 GB) werden beim ersten Start geladen.
34
+
35
+ ## Anderes Basismodell
36
+
37
+ SD-1.5-Checkpoints lassen sich per Environment-Variable tauschen — in den Space-Settings
38
+ unter *Variables* z. B.:
39
+
40
+ ```
41
+ BASE_MODEL=SG161222/Realistic_Vision_V5.1_noVAE
42
+ ```
43
+
44
+ Wichtig: Es muss ein **SD 1.5**-Modell sein, SDXL ist mit diesem ControlNet nicht kompatibel.
45
+
46
+ ## Parameter-Faustregeln
47
+
48
+ | Ziel | Einstellung |
49
+ |---|---|
50
+ | Code scannt nicht | ControlNet-Stärke ↑ (1.5–2.0), Denoising ↓ (0.75–0.85) |
51
+ | Zu offensichtlich als QR | ControlNet-Stärke ↓ (1.0–1.2), Denoising ↑ (0.95–1.0) |
52
+ | Rettung eines Bildes | Ergebnis als Start-Bild hochladen, Stärke max, Denoising minimal, dann langsam erhöhen |
53
+
54
+ Fehlerkorrektur **H** und kurze URLs (weniger Module) erhöhen die Scanbarkeit deutlich.
55
+
56
+ ## Lokal ausführen
57
+
58
+ ```bash
59
+ pip install -r requirements.txt
60
+ python app.py
61
+ ```
62
+
63
+ Braucht ca. 6 GB VRAM bei 768×768.
app.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ QR Code Monster — ControlNet Space
3
+ Modell: monster-labs/control_v1p_sd15_qrcode_monster (v2)
4
+ """
5
+
6
+ import os
7
+ import random
8
+
9
+ import gradio as gr
10
+ import qrcode
11
+ import torch
12
+ from PIL import Image
13
+ from qrcode.constants import (
14
+ ERROR_CORRECT_L,
15
+ ERROR_CORRECT_M,
16
+ ERROR_CORRECT_Q,
17
+ ERROR_CORRECT_H,
18
+ )
19
+ from diffusers import (
20
+ ControlNetModel,
21
+ StableDiffusionControlNetImg2ImgPipeline,
22
+ DPMSolverMultistepScheduler,
23
+ EulerAncestralDiscreteScheduler,
24
+ )
25
+
26
+ # --------------------------------------------------------------------------
27
+ # ZeroGPU-Support (funktioniert auch lokal ohne das "spaces"-Paket)
28
+ # --------------------------------------------------------------------------
29
+ try:
30
+ import spaces
31
+
32
+ gpu_decorator = spaces.GPU(duration=90)
33
+ except Exception: # lokal / eigene GPU
34
+
35
+ def gpu_decorator(fn):
36
+ return fn
37
+
38
+
39
+ # --------------------------------------------------------------------------
40
+ # Konfiguration
41
+ # --------------------------------------------------------------------------
42
+ BASE_MODEL = os.environ.get("BASE_MODEL", "stable-diffusion-v1-5/stable-diffusion-v1-5")
43
+ CONTROLNET_REPO = "monster-labs/control_v1p_sd15_qrcode_monster"
44
+ CONTROLNET_SUBFOLDER = "v2" # v2 ist deutlich besser als v1
45
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
46
+ DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
47
+ GRAY = (128, 128, 128) # #808080 – laut Model Card ideal zum "Verschmelzen"
48
+ MAX_SEED = 2**31 - 1
49
+
50
+ ERROR_LEVELS = {
51
+ "L (7 %)": ERROR_CORRECT_L,
52
+ "M (15 %)": ERROR_CORRECT_M,
53
+ "Q (25 %)": ERROR_CORRECT_Q,
54
+ "H (30 %) – empfohlen": ERROR_CORRECT_H,
55
+ }
56
+
57
+ SCHEDULERS = {
58
+ "DPM++ 2M Karras": lambda cfg: DPMSolverMultistepScheduler.from_config(
59
+ cfg, use_karras_sigmas=True, algorithm_type="dpmsolver++"
60
+ ),
61
+ "Euler a": lambda cfg: EulerAncestralDiscreteScheduler.from_config(cfg),
62
+ }
63
+
64
+ # --------------------------------------------------------------------------
65
+ # Pipeline laden (einmalig beim Start)
66
+ # --------------------------------------------------------------------------
67
+ controlnet = ControlNetModel.from_pretrained(
68
+ CONTROLNET_REPO,
69
+ subfolder=CONTROLNET_SUBFOLDER,
70
+ torch_dtype=DTYPE,
71
+ )
72
+
73
+ pipe = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
74
+ BASE_MODEL,
75
+ controlnet=controlnet,
76
+ torch_dtype=DTYPE,
77
+ safety_checker=None,
78
+ requires_safety_checker=False,
79
+ )
80
+ pipe.scheduler = DPMSolverMultistepScheduler.from_config(
81
+ pipe.scheduler.config, use_karras_sigmas=True, algorithm_type="dpmsolver++"
82
+ )
83
+ pipe.to(DEVICE)
84
+ if DEVICE == "cuda":
85
+ pipe.enable_vae_tiling()
86
+ pipe.enable_attention_slicing()
87
+
88
+
89
+ # --------------------------------------------------------------------------
90
+ # Hilfsfunktionen
91
+ # --------------------------------------------------------------------------
92
+ def make_qr_image(content: str, size: int, error_level: int, quiet_zone: int = 4) -> Image.Image:
93
+ """Erzeugt ein QR-Bild mit Modulgröße ~16 px auf grauem Hintergrund."""
94
+ qr = qrcode.QRCode(
95
+ version=None,
96
+ error_correction=error_level,
97
+ box_size=16, # Model Card: module size 16px
98
+ border=quiet_zone,
99
+ )
100
+ qr.add_data(content)
101
+ qr.make(fit=True)
102
+ img = qr.make_image(fill_color="black", back_color="white").convert("RGB")
103
+
104
+ # QR mittig auf graue Leinwand legen, ohne Kanten zu verwaschen (NEAREST!)
105
+ inner = int(size * 0.9)
106
+ img = img.resize((inner, inner), Image.NEAREST)
107
+ canvas = Image.new("RGB", (size, size), GRAY)
108
+ offset = (size - inner) // 2
109
+ canvas.paste(img, (offset, offset))
110
+ return canvas
111
+
112
+
113
+ def prepare_init_image(image: Image.Image | None, size: int) -> Image.Image:
114
+ if image is None:
115
+ return Image.new("RGB", (size, size), GRAY)
116
+ return image.convert("RGB").resize((size, size), Image.LANCZOS)
117
+
118
+
119
+ # --------------------------------------------------------------------------
120
+ # Generierung
121
+ # --------------------------------------------------------------------------
122
+ @gpu_decorator
123
+ def generate(
124
+ qr_content,
125
+ prompt,
126
+ negative_prompt,
127
+ controlnet_scale,
128
+ guidance_scale,
129
+ steps,
130
+ strength,
131
+ seed,
132
+ randomize_seed,
133
+ size,
134
+ error_level_name,
135
+ scheduler_name,
136
+ init_image,
137
+ num_images,
138
+ progress=gr.Progress(track_tqdm=True),
139
+ ):
140
+ if not qr_content or not qr_content.strip():
141
+ raise gr.Error("Bitte Text oder URL für den QR-Code eingeben.")
142
+ if not prompt or not prompt.strip():
143
+ raise gr.Error("Bitte einen Prompt eingeben.")
144
+
145
+ if randomize_seed:
146
+ seed = random.randint(0, MAX_SEED)
147
+ seed = int(seed)
148
+
149
+ pipe.scheduler = SCHEDULERS[scheduler_name](pipe.scheduler.config)
150
+
151
+ size = int(size)
152
+ control_image = make_qr_image(qr_content, size, ERROR_LEVELS[error_level_name])
153
+ init = prepare_init_image(init_image, size)
154
+
155
+ generator = torch.Generator(device=DEVICE).manual_seed(seed)
156
+
157
+ result = pipe(
158
+ prompt=prompt,
159
+ negative_prompt=negative_prompt or None,
160
+ image=init,
161
+ control_image=control_image,
162
+ width=size,
163
+ height=size,
164
+ num_inference_steps=int(steps),
165
+ guidance_scale=float(guidance_scale),
166
+ controlnet_conditioning_scale=float(controlnet_scale),
167
+ strength=float(strength),
168
+ num_images_per_prompt=int(num_images),
169
+ generator=generator,
170
+ )
171
+
172
+ return result.images, control_image, seed
173
+
174
+
175
+ # --------------------------------------------------------------------------
176
+ # UI
177
+ # --------------------------------------------------------------------------
178
+ DEFAULT_NEGATIVE = (
179
+ "ugly, disfigured, low quality, blurry, jpeg artifacts, watermark, text, "
180
+ "worst quality, lowres, deformed"
181
+ )
182
+
183
+ EXAMPLES = [
184
+ ["https://qrcode.monster", "a japanese zen garden with raked sand, moss, soft morning light, 8k photo"],
185
+ ["https://huggingface.co", "an ancient stone mosaic floor in a roman villa, intricate, weathered"],
186
+ ["https://example.com", "aerial view of a snowy forest, winding paths, cinematic, highly detailed"],
187
+ ]
188
+
189
+ with gr.Blocks(title="QR Code Monster", theme=gr.themes.Soft()) as demo:
190
+ gr.Markdown(
191
+ """
192
+ # 🧟 QR Code Monster
193
+ Künstlerische, **scanbare** QR-Codes mit
194
+ [`control_v1p_sd15_qrcode_monster`](https://huggingface.co/monster-labs/control_v1p_sd15_qrcode_monster) (v2).
195
+
196
+ **Tipp:** Nicht jeder Code scannt beim ersten Versuch. Mehrere Seeds generieren,
197
+ oder ControlNet-Stärke hoch + Denoising runter drehen.
198
+ """
199
+ )
200
+
201
+ with gr.Row():
202
+ with gr.Column(scale=1):
203
+ qr_content = gr.Textbox(
204
+ label="QR-Inhalt (URL oder Text)",
205
+ value="https://qrcode.monster",
206
+ placeholder="https://…",
207
+ )
208
+ prompt = gr.Textbox(
209
+ label="Prompt",
210
+ lines=3,
211
+ placeholder="z. B. a lush jungle with ancient ruins, cinematic lighting",
212
+ )
213
+ negative_prompt = gr.Textbox(
214
+ label="Negativer Prompt", value=DEFAULT_NEGATIVE, lines=2
215
+ )
216
+
217
+ with gr.Row():
218
+ controlnet_scale = gr.Slider(
219
+ 0.5, 2.5, value=1.4, step=0.05,
220
+ label="ControlNet-Stärke (hoch = besser scanbar)",
221
+ )
222
+ strength = gr.Slider(
223
+ 0.5, 1.0, value=0.9, step=0.01,
224
+ label="Denoising-Stärke",
225
+ )
226
+
227
+ with gr.Accordion("Erweiterte Einstellungen", open=False):
228
+ with gr.Row():
229
+ guidance_scale = gr.Slider(1, 20, value=7.5, step=0.5, label="CFG Guidance")
230
+ steps = gr.Slider(10, 60, value=30, step=1, label="Steps")
231
+ with gr.Row():
232
+ seed = gr.Number(value=0, label="Seed", precision=0)
233
+ randomize_seed = gr.Checkbox(value=True, label="Zufälliger Seed")
234
+ with gr.Row():
235
+ size = gr.Radio([512, 640, 768], value=768, label="Auflösung")
236
+ num_images = gr.Slider(1, 4, value=1, step=1, label="Anzahl Bilder")
237
+ error_level_name = gr.Dropdown(
238
+ list(ERROR_LEVELS), value="H (30 %) – empfohlen",
239
+ label="Fehlerkorrektur",
240
+ )
241
+ scheduler_name = gr.Dropdown(
242
+ list(SCHEDULERS), value="DPM++ 2M Karras", label="Sampler"
243
+ )
244
+ init_image = gr.Image(
245
+ label="Optionales Start-/Referenzbild (img2img)", type="pil"
246
+ )
247
+
248
+ run = gr.Button("QR-Code generieren", variant="primary")
249
+
250
+ with gr.Column(scale=1):
251
+ gallery = gr.Gallery(label="Ergebnisse", columns=2, height=520)
252
+ control_preview = gr.Image(label="Verwendeter QR-Code (Condition)")
253
+ used_seed = gr.Number(label="Verwendeter Seed", interactive=False)
254
+
255
+ gr.Examples(examples=EXAMPLES, inputs=[qr_content, prompt])
256
+
257
+ run.click(
258
+ fn=generate,
259
+ inputs=[
260
+ qr_content, prompt, negative_prompt, controlnet_scale, guidance_scale,
261
+ steps, strength, seed, randomize_seed, size, error_level_name,
262
+ scheduler_name, init_image, num_images,
263
+ ],
264
+ outputs=[gallery, control_preview, used_seed],
265
+ )
266
+
267
+ if __name__ == "__main__":
268
+ demo.queue(max_size=20).launch()
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ diffusers>=0.31.0
3
+ transformers>=4.44.0
4
+ accelerate>=0.34.0
5
+ safetensors
6
+ qrcode[pil]>=7.4.2
7
+ Pillow
8
+ gradio>=4.44.0
9
+ # nur für ZeroGPU-Spaces nötig, lokal einfach weglassen:
10
+ spaces