Thomas Piacentini
← Atelier

Fiche

ControlNet, Inpainting and Image Editing

Texte original · anglais

Diffusers calls for text to image, img2img, inpainting, LoRA and ControlNet. Knobs, defaults and fixes.

Control signals into the U-Net

featuresadded to decodertext tokensimage tokenslow-rank deltaCondition imagedepth, pose, edgesclientControlNetcloned encoder, trainablemodelZero conv1×1, starts at zeroprocessPromptclientText encoderCLIP-L or T5modelFrozen SD U-Netcross-attention to textgpuReference imageclientIP-AdapterCLIP image tokensmodelLoRAW + BA in attentionstore
Nodes and connections
NodeRoleKind
Condition imagedepth, pose, edgesclient
ControlNetcloned encoder, trainablemodel
Zero conv1×1, starts at zeroprocess
Promptclient
Text encoderCLIP-L or T5model
Frozen SD U-Netcross-attention to textgpu
Reference imageclient
IP-AdapterCLIP image tokensmodel
LoRAW + BA in attentionstore
FromToConnection
Condition imageControlNet
ControlNetZero convfeatures
Zero convFrozen SD U-Netadded to decoder
PromptText encoder
Text encoderFrozen SD U-Nettext tokens
Reference imageIP-Adapter
IP-AdapterFrozen SD U-Netimage tokens
LoRAFrozen SD U-Netlow-rank delta
Sources

Text to image

pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,halves VRAM
).to("cuda")
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
image = pipe(
prompt="<prompt>",
guidance_scale=7.5,CFG scale w, SD default
num_inference_steps=25,matches 50 steps of DDIM
generator=torch.Generator("cuda").manual_seed(42),
).images[0]
image.save("dog.png")
Sources

Img2img, inpaint and LoRA

out = img2img(prompt="<prompt>", image=init_image,
strength=0.6, guidance_scale=7.5).images[0]0.0 unchanged, 1.0 new
mask = Image.open("dog_mask.png").convert("L").resize((512, 512))
out = inpaint(prompt="<prompt>", image=image,
mask_image=mask, guidance_scale=7.5).images[0]white pixels regenerate
pipe.load_lora_weights("<repo>", weight_name="<file>")
pipe.fuse_lora(lora_scale=0.8)bakes in, no swap
pipe.unfuse_lora()before another adapter
pipe.set_adapters([...], adapter_weights=[...])per-request LoRA mix
Sources

Tasks in code

TaskCode
Install the stackpip install diffusers transformers accelerate
Image to imageStableDiffusionImg2ImgPipeline
Inpaint with SDStableDiffusionInpaintPipeline
Inpaint with FluxFluxFillPipeline
Stochastic samplerEulerAncestralDiscreteScheduler
Encode to latentspipe.image_processor.preprocess(img)
Reuse old latentspass latents=... and skip one VAE encode
Scale VAE latentslatent_dist.sample() * 0.18215
Sources

Mask-aware reverse loop

  1. Start draw x from N(0, I) for every dimension.
  2. Reinject noise the clean source to step t. Write it into each unmasked dimension.
  3. Predict the denoiser gives eps_hat for x at step t.
  4. Reverse compute the DDPM mean. Add noise when t > 0.
  5. Repeat go from t = T - 1 down to 0.
  6. Pin copy the clean values into the unmasked dimensions at the end.
  7. Outpaint invert the mask. The training objective stays the same.
Sources

Formulas and numbers

CFG
\epsilon = \epsilon_{\text{uncond}} + w \cdot (\epsilon_{\text{cond}} - \epsilon_{\text{uncond}})
LoRA
W' = W + \alpha \cdot B A with r \ll d. New parameters: 2 \cdot d \cdot r instead of d^2.
LoRA size
d=640, r=16 gives 20k parameters instead of 410k.
ControlNet mix
features += weight_a * control_a(depth) + weight_b * control_b(pose)
Latent diffusion
3x512x512 to 4x64x64 cuts compute by 48x.
Inpaint U-Net
9 input channels. Noisy latent 4, encoded image 4, mask 1.
SDEdit t/T
0.3 nearly the source. 0.6 keeps coarse structure. 0.9 near noise.
Zero conv
ControlNet starts as identity. It does no harm before training.
Sources

Editing methods side by side

FactorInpaint modelSDEdit or img2imgInstructPix2PixRePaint
Inputimage, mask, promptimage, noise level, promptimage and instructionimage and mask
Training9-channel U-Netnonefine-tune on triplesnone, unconditional model
Keepspixels outside the maskcoarse structurestructure of the inputpixels outside the mask
Main knobmask and CFGt/T or strengthimage scale and text scaleresample steps
Use forremove object, replace skystyle of one region"make it sunset"no inpaint model at hand
Sources

Conditioning tools side by side

FactorControlNetLoRAIP-AdapterDreamBooth
Controlsspatial structurestyle, subject, conceptlook of a reference imageone subject
Size70-360MB20-200MB20MB2-5GB
Mechanismcloned encoder, zero-conv skipslow-rank delta on attentionCLIP image tokens in cross-attentionfull fine-tune
Use whenexact layout or compositionpersonalization or styleno text describes the lookstrong identity, high compute
Sources

Defaults

SettingDefaultNote
guidance_scale7.5SD default CFG scale
num_inference_steps25With DPM-Solver++
img2img strength0.60.5-0.7 for style transfer
Prompt drop in CFG training10%Gives the unconditional branch
VAE scale factor0.18215SDXL uses 0.13025
LoRA rank r4-1664-128 for heavy fine-tunes
Sum of ControlNet weights1.0Safe default
Mask dilation in pixels8-16Hides seams
Sources

Troubleshooting

SymptomCauseFix
Over-stylized or broken outputLoRA α at 2 or 3Keep α at 1.5 or below
Pose plus depth overshootsTwo ControlNets at weight 1.0Make the weights sum to 1.0
LoRA has no effectSDXL LoRA on SD 1.5Match the base model
Cannot load another LoRAfuse_lora baked it inCall pipe.unfuse_lora() first
Visible seams at the maskNaive reinjectionDilate the mask or use a 9-channel model
Saturated patchHigh CFG on a small maskReduce CFG for small edits
Subject loses identitySDEdit t/T from 0.5 to 0.6Sweep and checkpoint
Fill ignores the scenePrompt names only the new contentDescribe the whole image
Sources

Use X when

  • ControlNet-Openpose + SDXL + text a specific pose with a prompt.
  • ControlNet-Scribble or ControlNet-Canny an exact layout.
  • LoRA on ~30 curated images at rank 32 a brand art style.
  • SD-Inpaint or Flux-Fill remove an object under a small mask.
  • SAM mask, then SD-Inpaint background replacement.
  • InstructPix2Pix or Flux-Kontext an instruction edit with no mask.
  • LCM-LoRA latency under 1s.
Sources

Questions

Why init the ControlNet gate at zero?
At step 0 the output is identical to the base.
Merge LoRAs or hot-swap them?
Hot-swap. A merge is 3-5% faster but freezes α and the base.
Why does strength 1.0 ignore the input?
It adds full noise. The result is a full regeneration.
Why use a 9-channel model, not reinjection?
The model sees the context around the mask.
Can I swap the scheduler after training?
Yes. Scheduler state is decoupled from U-Net weights.
What does an extra ControlNet cost?
Budget about 1.5× step cost for each active ControlNet.
Sources
Original source
---
id: image-editing-control
title: ControlNet, Inpainting and Image Editing
code: CI
subtitle: Diffusers calls for text to image, img2img, inpainting, LoRA and ControlNet. Knobs, defaults and fixes.
hue: 265
sources: phases/08-generative-ai/08-controlnet-lora-conditioning, phases/08-generative-ai/09-inpainting-outpainting-editing, phases/04-computer-vision/11-stable-diffusion
source_hash: a2512a00213a927e
---

## Control signals into the U-Net
type: architecture
from: phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/04-computer-vision/11-stable-diffusion/docs/en.md

```arch
node | cond | 1,1 | Condition image | depth, pose, edges | client
node | cn | 2,1 | ControlNet | cloned encoder, trainable | model
node | zc | 3,1 | Zero conv | 1×1, starts at zero | process
node | txt | 1,2 | Prompt | | client
node | te | 2,2 | Text encoder | CLIP-L or T5 | model
node | unet | 3,2 | Frozen SD U-Net | cross-attention to text | gpu
node | ref | 1,3 | Reference image | | client
node | ip | 2,3 | IP-Adapter | CLIP image tokens | model
node | lora | 3,3 | LoRA | W + BA in attention | store
edge | cond | cn |
edge | cn | zc | features
edge | zc | unet | added to decoder
edge | txt | te |
edge | te | unet | text tokens
edge | ref | ip |
edge | ip | unet | image tokens
edge | lora | unet | low-rank delta
```

## Text to image
type: annotated-code
lang: python
from: phases/04-computer-vision/11-stable-diffusion/docs/en.md, phases/04-computer-vision/11-stable-diffusion/code/main.py

```python
pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16,            # halves VRAM
).to("cuda")
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
image = pipe(
    prompt="<prompt>",
    guidance_scale=7.5,                   # CFG scale w, SD default
    num_inference_steps=25,               # matches 50 steps of DDIM
    generator=torch.Generator("cuda").manual_seed(42),
).images[0]
image.save("dog.png")
```

## Img2img, inpaint and LoRA
type: annotated-code
lang: python
from: phases/04-computer-vision/11-stable-diffusion/docs/en.md, phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md

```python
out = img2img(prompt="<prompt>", image=init_image,
              strength=0.6, guidance_scale=7.5).images[0]     # 0.0 unchanged, 1.0 new
mask = Image.open("dog_mask.png").convert("L").resize((512, 512))
out = inpaint(prompt="<prompt>", image=image,
              mask_image=mask, guidance_scale=7.5).images[0]  # white pixels regenerate
pipe.load_lora_weights("<repo>", weight_name="<file>")
pipe.fuse_lora(lora_scale=0.8)                                # bakes in, no swap
pipe.unfuse_lora()                                            # before another adapter
pipe.set_adapters([...], adapter_weights=[...])               # per-request LoRA mix
```

## Tasks in code
type: task-table
from: phases/04-computer-vision/11-stable-diffusion/docs/en.md, phases/04-computer-vision/11-stable-diffusion/code/main.py, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md

| Task | Code |
|---|---|
| Install the stack | `pip install diffusers transformers accelerate` |
| Image to image | `StableDiffusionImg2ImgPipeline` |
| Inpaint with SD | `StableDiffusionInpaintPipeline` |
| Inpaint with Flux | `FluxFillPipeline` |
| Stochastic sampler | `EulerAncestralDiscreteScheduler` |
| Encode to latents | `pipe.image_processor.preprocess(img)` |
| Reuse old latents | pass `latents=...` and skip one VAE encode |
| Scale VAE latents | `latent_dist.sample() * 0.18215` |

## Mask-aware reverse loop
type: flow
from: phases/08-generative-ai/09-inpainting-outpainting-editing/code/main.py, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md

1. **Start**: draw `x` from N(0, I) for every dimension.
2. **Reinject**: noise the clean source to step t. Write it into each unmasked dimension.
3. **Predict**: the denoiser gives `eps_hat` for `x` at step t.
4. **Reverse**: compute the DDPM mean. Add noise when t > 0.
5. **Repeat**: go from t = T - 1 down to 0.
6. **Pin**: copy the clean values into the unmasked dimensions at the end.
7. **Outpaint**: invert the mask. The training objective stays the same.

## Formulas and numbers
type: concepts
from: phases/04-computer-vision/11-stable-diffusion/docs/en.md, phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md

- **CFG**: $\epsilon = \epsilon_{\text{uncond}} + w \cdot (\epsilon_{\text{cond}} - \epsilon_{\text{uncond}})$
- **LoRA**: $W' = W + \alpha \cdot B A$ with $r \ll d$. New parameters: $2 \cdot d \cdot r$ instead of $d^2$.
- **LoRA size**: $d=640$, $r=16$ gives 20k parameters instead of 410k.
- **ControlNet mix**: `features += weight_a * control_a(depth) + weight_b * control_b(pose)`
- **Latent diffusion**: 3x512x512 to 4x64x64 cuts compute by 48x.
- **Inpaint U-Net**: 9 input channels. Noisy latent 4, encoded image 4, mask 1.
- **SDEdit t/T**: 0.3 nearly the source. 0.6 keeps coarse structure. 0.9 near noise.
- **Zero conv**: ControlNet starts as identity. It does no harm before training.

## Editing methods side by side
type: comparison
from: phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md, phases/04-computer-vision/11-stable-diffusion/docs/en.md
span: 2

| Factor | Inpaint model | SDEdit or img2img | InstructPix2Pix | RePaint |
|---|---|---|---|---|
| Input | image, mask, prompt | image, noise level, prompt | image and instruction | image and mask |
| Training | 9-channel U-Net | none | fine-tune on triples | none, unconditional model |
| Keeps | pixels outside the mask | coarse structure | structure of the input | pixels outside the mask |
| Main knob | mask and CFG | `t/T` or `strength` | image scale and text scale | resample steps |
| Use for | remove object, replace sky | style of one region | "make it sunset" | no inpaint model at hand |

## Conditioning tools side by side
type: comparison
from: phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md
span: 2

| Factor | ControlNet | LoRA | IP-Adapter | DreamBooth |
|---|---|---|---|---|
| Controls | spatial structure | style, subject, concept | look of a reference image | one subject |
| Size | 70-360MB | 20-200MB | 20MB | 2-5GB |
| Mechanism | cloned encoder, zero-conv skips | low-rank delta on attention | CLIP image tokens in cross-attention | full fine-tune |
| Use when | exact layout or composition | personalization or style | no text describes the look | strong identity, high compute |

## Defaults
type: defaults
from: phases/04-computer-vision/11-stable-diffusion/docs/en.md, phases/04-computer-vision/11-stable-diffusion/code/main.py, phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md

| Setting | Default | Note |
|---|---|---|
| `guidance_scale` | 7.5 | SD default CFG scale |
| `num_inference_steps` | 25 | With DPM-Solver++ |
| img2img `strength` | 0.6 | 0.5-0.7 for style transfer |
| Prompt drop in CFG training | 10% | Gives the unconditional branch |
| VAE scale factor | 0.18215 | SDXL uses 0.13025 |
| LoRA rank `r` | 4-16 | 64-128 for heavy fine-tunes |
| Sum of ControlNet weights | 1.0 | Safe default |
| Mask dilation in pixels | 8-16 | Hides seams |

## Troubleshooting
type: troubleshooting
from: phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md, phases/04-computer-vision/11-stable-diffusion/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Over-stylized or broken output | LoRA α at 2 or 3 | Keep α at 1.5 or below |
| Pose plus depth overshoots | Two ControlNets at weight 1.0 | Make the weights sum to 1.0 |
| LoRA has no effect | SDXL LoRA on SD 1.5 | Match the base model |
| Cannot load another LoRA | `fuse_lora` baked it in | Call `pipe.unfuse_lora()` first |
| Visible seams at the mask | Naive reinjection | Dilate the mask or use a 9-channel model |
| Saturated patch | High CFG on a small mask | Reduce CFG for small edits |
| Subject loses identity | SDEdit `t/T` from 0.5 to 0.6 | Sweep and checkpoint |
| Fill ignores the scene | Prompt names only the new content | Describe the whole image |

## Use X when
type: tips
from: phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md, phases/04-computer-vision/11-stable-diffusion/docs/en.md

- **ControlNet-Openpose + SDXL + text**: a specific pose with a prompt.
- **ControlNet-Scribble or ControlNet-Canny**: an exact layout.
- **LoRA on ~30 curated images at rank 32**: a brand art style.
- **SD-Inpaint or Flux-Fill**: remove an object under a small mask.
- **SAM mask, then SD-Inpaint**: background replacement.
- **InstructPix2Pix or Flux-Kontext**: an instruction edit with no mask.
- **LCM-LoRA**: latency under 1s.

## Questions
type: qa
from: phases/08-generative-ai/08-controlnet-lora-conditioning/docs/en.md, phases/08-generative-ai/09-inpainting-outpainting-editing/docs/en.md, phases/04-computer-vision/11-stable-diffusion/docs/en.md

- **Why init the ControlNet gate at zero?** At step 0 the output is identical to the base.
- **Merge LoRAs or hot-swap them?** Hot-swap. A merge is 3-5% faster but freezes α and the base.
- **Why does strength 1.0 ignore the input?** It adds full noise. The result is a full regeneration.
- **Why use a 9-channel model, not reinjection?** The model sees the context around the mask.
- **Can I swap the scheduler after training?** Yes. Scheduler state is decoupled from U-Net weights.
- **What does an extra ControlNet cost?** Budget about 1.5× step cost for each active ControlNet.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓