ControlNet in 🧨 Diffusori
ControlNet in Diffusori
Dal momento in cui Stable Diffusion ha spazzato il mondo, le persone hanno cercato modi per avere un maggiore controllo sui risultati del processo di generazione. ControlNet fornisce un’interfaccia minimale che consente agli utenti di personalizzare il processo di generazione in modo considerevole. Con ControlNet, gli utenti possono facilmente condizionare la generazione con contesti spaziali diversi come una mappa di profondità, una mappa di segmentazione, uno schizzo, punti chiave e così via!
Possiamo trasformare un disegno a cartoni animati in una foto realistica con una coerenza incredibile.
Ragazza Lofi realistica |
---|
![]() |
O addirittura usarlo come tuo designer d’interni.
- Utilizzare l’Apprendimento Automatico per Aiutare i Sopravvissuti e Gareggiare nel Tempo
- Previsione multivariata delle serie temporali probabilistiche con Informer
- Ethics and Society Newsletter #3 Apertura Etica da Hugging Face
Prima | Dopo |
---|---|
![]() |
![]() |
Puoi trasformare il tuo schizzo in un disegno artistico.
Prima | Dopo |
---|---|
![]() |
![]() |
Inoltre, rendi alcuni dei famosi loghi che prendono vita.
Prima | Dopo |
---|---|
![]() |
![]() |
Con ControlNet, il cielo è il limite 🌠
In questo post del blog, presentiamo prima il StableDiffusionControlNetPipeline
e poi mostriamo come può essere applicato per varie condizioni di controllo. Prendiamo il controllo!
ControlNet: TL;DR
ControlNet è stato introdotto in “Adding Conditional Control to Text-to-Image Diffusion Models” di Lvmin Zhang e Maneesh Agrawala. Introduce un framework che consente di supportare vari contesti spaziali che possono fungere da condizioni aggiuntive per modelli di diffusione come Stable Diffusion. L’implementazione dei diffusori è adattata dal codice sorgente originale .
Addestrare ControlNet si compone dei seguenti passaggi:
- Clonare i parametri preaddestrati di un modello di diffusione, come il latent UNet di Stable Diffusion, (chiamato “copia addestrabile”) mantenendo anche i parametri preaddestrati separatamente (“copia bloccata”). Questo viene fatto in modo che la copia dei parametri bloccati possa conservare la vasta conoscenza appresa da un grande dataset, mentre la copia addestrabile viene utilizzata per apprendere gli aspetti specifici del compito.
- Le copie addestrabili e bloccate dei parametri sono collegate tramite livelli di “convoluzione a zero” (vedi qui per ulteriori informazioni) che vengono ottimizzati come parte del framework ControlNet. Questo è un trucco di addestramento per preservare la semantica già appresa dal modello congelato mentre vengono addestrate nuove condizioni.
Pittoricamente, addestrare un ControlNet appare così:
Il diagramma è preso da qui .
Un esempio dall’insieme di addestramento per l’addestramento simile a ControlNet appare così (la condizione aggiuntiva avviene tramite mappe di contorno):
Prompt | Immagine Originale | Condizionamento |
---|---|---|
“uccello” | ![]() |
![]() |
Allo stesso modo, se volessimo condizionare ControlNet con mappe di segmentazione semantica, un esempio di allenamento sarebbe il seguente:
Prompt | Immagine Originale | Condizionamento |
---|---|---|
“grande casa” | ![]() |
![]() |
Ogni nuovo tipo di condizionamento richiede l’allenamento di una nuova copia dei pesi di ControlNet. Nel paper sono stati proposti 8 diversi modelli di condizionamento, tutti supportati in Diffusers!
Per l’inferenza, sono necessari sia i pesi dei modelli di diffusione pre-addestrati che i pesi di ControlNet addestrati. Ad esempio, utilizzare Stable Diffusion v1-5 con un checkpoint di ControlNet richiede circa 700 milioni di parametri in più rispetto all’utilizzo del solo modello di Stable Diffusion originale, il che rende ControlNet un po’ più costoso in termini di memoria per l’inferenza.
Poiché i modelli di diffusione pre-addestrati sono bloccati durante l’addestramento, è sufficiente sostituire i parametri di ControlNet quando si utilizza un diverso condizionamento. Questo rende piuttosto semplice distribuire più pesi di ControlNet in un’applicazione, come vedremo di seguito.
Il StableDiffusionControlNetPipeline
Prima di iniziare, vogliamo ringraziare enormemente il contributore della community Takuma Mori per aver guidato l’integrazione di ControlNet in Diffusers ❤️.
Per sperimentare con ControlNet, Diffusers espone la StableDiffusionControlNetPipeline
simile alle altre pipeline di Diffusers. Al centro della StableDiffusionControlNetPipeline
c’è l’argomento controlnet
che ci permette di fornire un’istanza specifica del modello addestrato di ControlNetModel
mantenendo gli stessi pesi del modello di diffusione pre-addestrato.
Esploreremo diversi casi d’uso con la StableDiffusionControlNetPipeline
in questo post del blog. Il primo modello di ControlNet che esamineremo è il modello Canny – questo è uno dei modelli più popolari che ha generato alcune delle incredibili immagini che probabilmente state vedendo su internet.
Vi invitiamo ad eseguire i frammenti di codice mostrati nelle sezioni seguenti con questo Notebook di Colab.
Prima di iniziare, assicuriamoci di avere tutte le librerie necessarie installate:
pip install diffusers==0.14.0 transformers xformers git+https://github.com/huggingface/accelerate.git
Per elaborare diversi condizionamenti a seconda del ControlNet scelto, è necessario installare anche alcune dipendenze aggiuntive:
- OpenCV
- controlnet-aux: una semplice raccolta di modelli di pre-elaborazione per ControlNet
pip install opencv-contrib-python
pip install controlnet_aux
Useremo il famoso dipinto “La ragazza con l’orecchino di perla” per questo esempio. Quindi, scarichiamo l’immagine e diamo un’occhiata:
from diffusers.utils import load_image
image = load_image(
"https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png"
)
image
Successivamente, passeremo l’immagine attraverso il pre-processore canny:
import cv2
from PIL import Image
import numpy as np
image = np.array(image)
low_threshold = 100
high_threshold = 200
image = cv2.Canny(image, low_threshold, high_threshold)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
canny_image
Come possiamo vedere, si tratta essenzialmente di rilevamento dei bordi:
Ora, carichiamo anche il modello runwaylml/stable-diffusion-v1-5 e il modello ControlNet per i bordi di canny. I modelli vengono caricati in mezza precisione (torch.dtype
) per consentire un’inferenza rapida ed efficiente in termini di memoria.
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
import torch
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
)
Invece di utilizzare il programma di avvio predefinito di Stable Diffusion, utilizziamo uno dei programmi di diffusione più veloci attualmente disponibili, chiamato UniPCMultistepScheduler. La scelta di un programma migliorato può ridurre drasticamente il tempo di inferenza – nel nostro caso siamo in grado di ridurre il numero di passaggi di inferenza da 50 a 20 mantenendo più o meno la stessa qualità di generazione dell’immagine. Ulteriori informazioni sui programmi possono essere trovate qui .
from diffusers import UniPCMultistepScheduler
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
Invece di caricare direttamente la nostra pipeline sulla GPU, abilitiamo invece il trasferimento intelligente della CPU che può essere ottenuto con la funzione enable_model_cpu_offload
.
Ricordate che durante l’inferenza dei modelli di diffusione, come Stable Diffusion, non è necessario solo un componente del modello ma più componenti del modello che vengono eseguiti in sequenza. Nel caso di Stable Diffusion con ControlNet, utilizziamo prima l’encoder di testo CLIP, quindi il modello di diffusione unet e il control net, quindi il decoder VAE e infine eseguiamo un controllo di sicurezza. La maggior parte dei componenti viene eseguita solo una volta durante il processo di diffusione e quindi non è necessario occupare la memoria della GPU tutto il tempo. Abilitando il trasferimento intelligente del modello, ci assicuriamo che ogni componente venga caricato solo nella GPU quando è necessario in modo da poter risparmiare significativamente il consumo di memoria senza rallentare significativamente l’inferenza.
Nota: Quando si esegue enable_model_cpu_offload
, non spostare manualmente la pipeline sulla GPU con .to("cuda")
– una volta abilitato il trasferimento della CPU, la pipeline si occupa automaticamente della gestione della memoria della GPU.
pipe.enable_model_cpu_offload()
Infine, vogliamo sfruttare appieno l’incredibile accelerazione del layer di attenzione FlashAttention/xformers, quindi abilitiamo questa funzionalità! Se questo comando non funziona per voi, potrebbe essere che xformers
non sia installato correttamente. In questo caso, potete semplicemente saltare la riga di codice successiva.
pipe.enable_xformers_memory_efficient_attention()
Ora siamo pronti per eseguire la pipeline ControlNet!
Forniamo ancora un prompt per guidare il processo di generazione dell’immagine, proprio come faremmo normalmente con una pipeline di immagini di Stable Diffusion. Tuttavia, ControlNet permetterà un controllo molto maggiore sull’immagine generata perché saremo in grado di controllare la composizione esatta nell’immagine generata con l’immagine dei bordi di canny appena creata.
Sarà divertente vedere alcune immagini in cui celebrità contemporanee posano per lo stesso dipinto del XVII secolo. Ed è davvero facile farlo con ControlNet, tutto quello che dobbiamo fare è includere i nomi di queste celebrità nel prompt!
Creiamo prima una semplice funzione di aiuto per visualizzare le immagini come griglia.
def image_grid(imgs, rows, cols):
assert len(imgs) == rows * cols
w, h = imgs[0].size
grid = Image.new("RGB", size=(cols * w, rows * h))
grid_w, grid_h = grid.size
for i, img in enumerate(imgs):
grid.paste(img, box=(i % cols * w, i // cols * h))
return grid
Successivamente, definiamo le prompt di input e impostiamo un seed per la riproducibilità.
prompt = ", migliore qualità, estremamente dettagliato"
prompt = [t + prompt for t in ["Sandra Oh", "Kim Kardashian", "rihanna", "taylor swift"]]
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(len(prompt))]
Infine, possiamo eseguire il pipeline e visualizzare l’immagine!
output = pipe(
prompt,
canny_image,
negative_prompt=["monocromatico, bassa risoluzione, anatomia errata, peggiore qualità, bassa qualità"] * 4,
num_inference_steps=20,
generator=generator,
)
image_grid(output.images, 2, 2)
Possiamo combinare senza sforzo ControlNet con il fine-tuning! Ad esempio, possiamo eseguire il fine-tuning di un modello con DreamBooth e usarlo per renderizzare noi stessi in scene diverse.
In questo post, useremo il nostro amato Mr Potato Head come esempio per mostrare come utilizzare ControlNet con DreamBooth.
Possiamo utilizzare lo stesso ControlNet. Tuttavia, anziché utilizzare Stable Diffusion 1.5, caricheremo il modello di Mr Potato Head nel nostro pipeline: Mr Potato Head è un modello di Stable Diffusion sottoposto a un fine-tuning con il concetto di Mr Potato Head utilizzando Dreambooth 🥔
Eseguiamo di nuovo i comandi sopra, mantenendo però lo stesso controlnet!
model_id = "sd-dreambooth-library/mr-potato-head"
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
controlnet=controlnet,
torch_dtype=torch.float16,
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
pipe.enable_xformers_memory_efficient_attention()
Adesso facciamo posare Mr Potato per Johannes Vermeer !
generator = torch.manual_seed(2)
prompt = "una foto di Mr Potato Head sks, migliore qualità, estremamente dettagliato"
output = pipe(
prompt,
canny_image,
negative_prompt="monocromatico, bassa risoluzione, anatomia errata, peggiore qualità, bassa qualità",
num_inference_steps=20,
generator=generator,
)
output.images[0]
Si nota che Mr Potato Head non è il candidato migliore, ma ha fatto del suo meglio e ha fatto un buon lavoro nel catturare parte dell’essenza 🍟
Un’altra applicazione esclusiva di ControlNet è che possiamo prendere una posa da un’immagine e riutilizzarla per generare un’immagine diversa con la stessa posa esatta. Quindi in questo prossimo esempio, insegneremo ai supereroi come fare yoga usando Open Pose ControlNet !
Per prima cosa, avremo bisogno di ottenere alcune immagini di persone che praticano yoga:
urls = "yoga1.jpeg", "yoga2.jpeg", "yoga3.jpeg", "yoga4.jpeg"
imgs = [
load_image("https://huggingface.co/datasets/YiYiXu/controlnet-testing/resolve/main/" + url)
for url in urls
]
image_grid(imgs, 2, 2)
Adesso estraiamo le pose di yoga utilizzando i pre-processori di OpenPose che sono comodamente disponibili tramite controlnet_aux
.
from controlnet_aux import OpenposeDetector
model = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
poses = [model(img) for img in imgs]
image_grid(poses, 2, 2)
Per utilizzare queste pose di yoga per generare nuove immagini, creiamo un Open Pose ControlNet . Genereremo alcune immagini di supereroi, ma nelle pose di yoga mostrate in precedenza. Andiamo 🚀
controlnet = ControlNetModel.from_pretrained(
"fusing/stable-diffusion-v1-5-controlnet-openpose", torch_dtype=torch.float16
)
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
controlnet=controlnet,
torch_dtype=torch.float16,
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
È ora di fare yoga!
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(4)]
prompt = "personaggio supereroe, migliore qualità, estremamente dettagliato"
output = pipe(
[prompt] * 4,
poses,
negative_prompt=["monocromatico, bassa risoluzione, anatomia scarsa, peggiore qualità, bassa qualità"] * 4,
generator=generator,
num_inference_steps=20,
)
image_grid(output.images, 2, 2)
Combining multiple conditionings
È possibile combinare più condizionamenti di ControlNet per una singola generazione di immagini. Passare una lista di ControlNet al costruttore della pipeline e una lista corrispondente di condizionamenti a __call__
.
Quando si combinano i condizionamenti, è utile mascherare i condizionamenti in modo che non si sovrappongano. Nell’esempio, mascheriamo la parte centrale della mappa canny in cui si trova il condizionamento della posa.
Può essere utile variare anche le scale di condizionamento controlnet_conditioning_scale
per enfatizzare un condizionamento rispetto all’altro.
Condizionamento Canny
L’immagine originale
Preparare il condizionamento
from diffusers.utils import load_image
from PIL import Image
import cv2
import numpy as np
from diffusers.utils import load_image
canny_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/landscape.png"
)
canny_image = np.array(canny_image)
low_threshold = 100
high_threshold = 200
canny_image = cv2.Canny(canny_image, low_threshold, high_threshold)
# azzerare le colonne centrali dell'immagine in cui verrà sovrapposta la posa
zero_start = canny_image.shape[1] // 4
zero_end = zero_start + canny_image.shape[1] // 2
canny_image[:, zero_start:zero_end] = 0
canny_image = canny_image[:, :, None]
canny_image = np.concatenate([canny_image, canny_image, canny_image], axis=2)
canny_image = Image.fromarray(canny_image)
Condizionamento Openpose
L’immagine originale
Preparare il condizionamento
from controlnet_aux import OpenposeDetector
from diffusers.utils import load_image
openpose = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
openpose_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/person.png"
)
openpose_image = openpose(openpose_image)
Esecuzione di ControlNet con più condizionamenti
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
import torch
controlnet = [
ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-openpose", torch_dtype=torch.float16),
ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16),
]
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_xformers_memory_efficient_attention()
pipe.enable_model_cpu_offload()
prompt = "un gigante in un paesaggio fantasy, migliore qualità"
negative_prompt = "monocromatico, bassa risoluzione, anatomia scarsa, peggiore qualità, bassa qualità"
generator = torch.Generator(device="cpu").manual_seed(1)
images = [openpose_image, canny_image]
image = pipe(
prompt,
images,
num_inference_steps=20,
generator=generator,
negative_prompt=negative_prompt,
controlnet_conditioning_scale=[1.0, 0.8],
).images[0]
image.save("./multi_controlnet_output.png")
Durante gli esempi, abbiamo esplorato molteplici aspetti del StableDiffusionControlNetPipeline
per mostrare quanto sia facile e intuitivo giocare con ControlNet tramite Diffusers. Tuttavia, non abbiamo coperto tutti i tipi di condizionamento supportati da ControlNet. Per saperne di più su questi, ti incoraggiamo a consultare le rispettive pagine di documentazione del modello:
- lllyasviel/sd-controlnet-depth
- lllyasviel/sd-controlnet-hed
- lllyasviel/sd-controlnet-normal
- lllyasviel/sd-controlnet-scribble
- lllyasviel/sd-controlnet-seg
- lllyasviel/sd-controlnet-openpose
- lllyasviel/sd-controlnet-mlsd
- lllyasviel/sd-controlnet-canny
Ti invitiamo a combinare questi diversi elementi e condividere i tuoi risultati con @diffuserslib . Assicurati di dare un’occhiata al Notebook di Colab per provare alcuni degli esempi sopra menzionati!
Abbiamo anche mostrato alcune tecniche per rendere il processo di generazione più veloce e che richiede meno memoria, utilizzando uno scheduler rapido, un modello intelligente di offloading e xformers
. Con queste tecniche combinate, il processo di generazione richiede solo ~3 secondi su una GPU V100 e consuma solo ~4 GB di VRAM per un’immagine singola ⚡️ Nei servizi gratuiti come Google Colab, la generazione richiede circa 5 secondi sulla GPU predefinita (T4), mentre l’implementazione originale richiede 17 secondi per creare lo stesso risultato! Combinare tutti gli elementi nella cassetta degli attrezzi diffusers
è un vero superpotere 💪
Conclusion
Abbiamo giocato molto con StableDiffusionControlNetPipeline
, e la nostra esperienza finora è stata divertente! Siamo entusiasti di vedere cosa la comunità costruirà su questa pipeline. Se vuoi scoprire altre pipeline e tecniche supportate in Diffusers che consentono la generazione controllata, consulta la nostra documentazione ufficiale .
Se non puoi aspettare di provare ControlNet direttamente, siamo qui per te! Basta fare clic su uno dei seguenti spazi per giocare con ControlNet: