Stable Diffusion: Tutorial Komprehensif
Daftar Isi
Pendahuluan
Stable Diffusion adalah model difusi laten yang menghasilkan gambar berkualitas tinggi dari deskripsi teks. Berbeda dengan model difusi sebelumnya yang beroperasi langsung di ruang piksel, Stable Diffusion bekerja di ruang laten yang terkompresi, sehingga jauh lebih cepat dan hemat memori. Tutorial ini membahas segala hal mulai dari generasi teks-ke-gambar dasar hingga teknik lanjutan seperti ControlNet dan fine-tuning DreamBooth.
Library Diffusers dari Hugging Face menyediakan antarmuka yang paling mudah untuk bekerja dengan model Stable Diffusion, dan tutorial ini menggunakannya secara ekstensif.
Prasyarat
# Instal paket yang diperlukan
pip install diffusers transformers accelerate torch torchvision
pip install safetensors xformers
pip install Pillow numpy
pip install peft # Untuk fine-tuning dengan LoRA/DreamBooth
Persyaratan sistem:
- Python 3.9 atau lebih tinggi
- GPU NVIDIA dengan minimal 8 GB VRAM (16 GB direkomendasikan untuk fine-tuning)
- CUDA 11.8 atau lebih tinggi
- Minimal 16 GB RAM sistem
Verifikasi setup Anda:
import torch
import diffusers
print(f"Versi PyTorch: {torch.version}")
print(f"CUDA tersedia: {torch.cuda.isavailable()}")
print(f"GPU: {torch.cuda.getdevicename(0) if torch.cuda.isavailable() else 'Tidak ada'}")
print(f"Versi Diffusers: {diffusers.version}")
Memahami Arsitektur Stable Diffusion
Stable Diffusion terdiri dari tiga komponen utama:
Proses generasi bekerja sebagai berikut: dimulai dengan noise acak di ruang laten, kemudian secara iteratif menghilangkan noise menggunakan U-Net, dipandu oleh conditioning teks dari CLIP.
Generasi Gambar dari Teks
Generasi Dasar
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
import torch
def buatpipeline(modelid="stabilityai/stable-diffusion-2-1"):
"""Inisialisasi pipeline Stable Diffusion."""
pipe = StableDiffusionPipeline.frompretrained(
modelid,
torchdtype=torch.float16,
safetychecker=None,
)
pipe.scheduler = DPMSolverMultistepScheduler.fromconfig(pipe.scheduler.config)
pipe = pipe.to("cuda")
# Aktifkan optimasi memori
pipe.enableattentionslicing()
return pipe
pipe = buatpipeline()
Hasilkan gambar
prompt = "Pemandangan gunung yang tenang saat matahari terbenam, fotorealistis, resolusi 8k"
negativeprompt = "buram, kualitas rendah, terdistorsi, cacat"
gambar = pipe(
prompt=prompt,
negativeprompt=negativeprompt,
numinferencesteps=30,
guidancescale=7.5,
width=768,
height=768,
).images[0]
gambar.save("gunungsunset.png")
Generasi Batch dengan Seed Berbeda
import torch
def hasilkanvariasi(pipe, prompt, negativeprompt="", jumlahgambar=4, seedawal=42):
"""Menghasilkan beberapa variasi dari prompt yang sama dengan seed berbeda."""
gambarlist = []
for i in range(jumlahgambar):
generator = torch.Generator(device="cuda").manualseed(seedawal + i)
gambar = pipe(
prompt=prompt,
negativeprompt=negativeprompt,
numinferencesteps=30,
guidancescale=7.5,
generator=generator,
).images[0]
gambarlist.append(gambar)
gambar.save(f"variasi{seedawal + i}.png")
return gambarlist
gambarlist = hasilkanvariasi(
pipe,
prompt="Kota futuristik dengan mobil terbang, gaya cyberpunk",
negativeprompt="jelek, buram, kualitas rendah",
jumlahgambar=4
)
Menggunakan SDXL untuk Kualitas Lebih Tinggi
from diffusers import StableDiffusionXLPipeline, StableDiffusionXLRefinerPipeline
def buatpipelinesdxl():
"""Membuat pipeline SDXL dengan refiner opsional."""
base = StableDiffusionXLPipeline.frompretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torchdtype=torch.float16,
variant="fp16",
usesafetensors=True,
).to("cuda")
refiner = StableDiffusionXLRefinerPipeline.frompretrained(
"stabilityai/stable-diffusion-xl-refiner-1.0",
torchdtype=torch.float16,
variant="fp16",
usesafetensors=True,
).to("cuda")
return base, refiner
def hasilkandenganrefiner(base, refiner, prompt, negativeprompt=""):
"""Generasi dua tahap: model base + refiner untuk kualitas optimal."""
# Generasi base (jalankan untuk 80% langkah)
gambar = base(
prompt=prompt,
negativeprompt=negativeprompt,
numinferencesteps=40,
denoisingend=0.8,
outputtype="latent",
).images
# Refiner (jalankan untuk 20% langkah sisanya)
hasilrefined = refiner(
prompt=prompt,
negativeprompt=negativeprompt,
numinferencesteps=40,
denoisingstart=0.8,
image=gambar,
).images[0]
return hasilrefined
Teknik Prompt Engineering untuk Gambar
Prompt yang efektif sangat penting untuk menghasilkan gambar berkualitas tinggi. Berikut adalah strategi-strategi kunci yang perlu dikuasai:
Struktur Prompt
# Template: [Subjek] [Gaya] [Detail] [Modifier kualitas]
prompts = {
"potret": (
"Potret seorang wanita muda dengan rambut cokelat kemerahan, "
"pencahayaan alami yang lembut, depth of field dangkal, "
"Canon EOS R5, lensa 85mm, f/1.4, "
"fotorealistis, sangat detail, 8k"
),
"lanskap": (
"Padang rumput alpine yang luas dengan bunga liar di latar depan, "
"gunung bersalju di latar belakang, pencahayaan golden hour, "
"awan dramatis, fotografi lanskap, "
"gaya National Geographic, sangat tajam, 4k"
),
"conceptart": (
"Kuil kuno yang melayang di atas awan, "
"ukiran batu yang rumit, atmosfer mistis, "
"pencahayaan volumetrik, concept art, "
"trending di ArtStation, lukisan digital"
),
}
Template negative prompt
negativeprompts = {
"umum": (
"jelek, cacat, berisik, buram, kontras rendah, "
"watermark, teks, tanda tangan, keluar bingkai, "
"kualitas terburuk, kualitas rendah, artefak jpeg"
),
"potret": (
"jelek, cacat, berisik, buram, anatomi buruk, "
"anggota tubuh tambahan, jari tambahan, tangan yang digambar buruk, "
"wajah yang digambar buruk, mutasi, terdistorsi"
),
}
Penekanan pada Prompt (Prompt Weighting)
from diffusers import StableDiffusionPipeline
from compel import Compel
pipe = StableDiffusionPipeline.frompretrained(
"stabilityai/stable-diffusion-2-1",
torchdtype=torch.float16
).to("cuda")
compel = Compel(tokenizer=pipe.tokenizer, textencoder=pipe.textencoder)
Gunakan ++ untuk penekanan lebih kuat, -- untuk lebih lemah
prompt = "kucing merah++ duduk di kursi biru--, fotorealistis"
conditioning = compel(prompt)
gambar = pipe(
promptembeds=conditioning,
numinferencesteps=30,
).images[0]
Generasi Image-to-Image
Transformasi gambar yang sudah ada menggunakan panduan teks:
from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image
def transformasiimg2img(inputpath, prompt, strength=0.75):
"""
Mentransformasi gambar yang ada berdasarkan prompt teks.
strength: Seberapa banyak transformasi (0.0 = tanpa perubahan, 1.0 = regenerasi penuh)
"""
pipe = StableDiffusionImg2ImgPipeline.frompretrained(
"stabilityai/stable-diffusion-2-1",
torchdtype=torch.float16,
).to("cuda")
gambarawal = Image.open(inputpath).convert("RGB")
gambarawal = gambarawal.resize((768, 768))
hasil = pipe(
prompt=prompt,
image=gambarawal,
strength=strength,
guidancescale=7.5,
numinferencesteps=30,
).images[0]
return hasil
Contoh: Ubah sketsa menjadi lukisan
hasil = transformasiimg2img(
"sketsa.png",
prompt="Lukisan minyak hutan yang indah, mahakarya, sapuan kuas detail",
strength=0.6
)
hasil.save("lukisandarisketsa.png")
Inpainting
Mengganti atau memodifikasi area tertentu dari sebuah gambar:
from diffusers import StableDiffusionInpaintPipeline
from PIL import Image
import numpy as np
def inpaintarea(imagepath, maskpath, prompt):
"""
Inpaint area yang di-mask pada sebuah gambar.
Piksel putih pada mask menandakan area yang akan diregenerasi.
"""
pipe = StableDiffusionInpaintPipeline.frompretrained(
"stabilityai/stable-diffusion-2-inpainting",
torchdtype=torch.float16,
).to("cuda")
gambar = Image.open(imagepath).convert("RGB").resize((512, 512))
mask = Image.open(maskpath).convert("RGB").resize((512, 512))
hasil = pipe(
prompt=prompt,
image=gambar,
maskimage=mask,
guidancescale=7.5,
numinferencesteps=30,
).images[0]
return hasil
def buatmaskprogramatis(ukurangambar, arealist):
"""Membuat mask inpainting dari area persegi panjang."""
mask = np.zeros((ukurangambar, 3), dtype=np.uint8)
for x1, y1, x2, y2 in arealist:
mask[y1:y2, x1:x2] = 255
return Image.fromarray(mask)
Contoh penggunaan
mask = buatmaskprogramatis((512, 512), [(100, 100, 300, 300)])
mask.save("mask.png")
hasil = inpaintarea("foto.png", "mask.png", "Anjing golden retriever duduk di sini")
hasil.save("hasilinpaint.png")
ControlNet untuk Generasi Terkontrol
ControlNet menambahkan conditioning spasial ke Stable Diffusion, memungkinkan kontrol yang presisi terhadap struktur output yang dihasilkan.
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
import cv2
import numpy as np
from PIL import Image
def buatpipelinecannycontrolnet():
"""Membuat pipeline ControlNet yang dikondisikan oleh tepi Canny."""
controlnet = ControlNetModel.frompretrained(
"lllyasviel/sd-controlnet-canny",
torchdtype=torch.float16,
)
pipe = StableDiffusionControlNetPipeline.frompretrained(
"runwayml/stable-diffusion-v1-5",
controlnet=controlnet,
torchdtype=torch.float16,
).to("cuda")
pipe.enableattentionslicing()
return pipe
def hasilkandaritepi(pipe, imagepath, prompt):
"""Menghasilkan gambar yang dipandu oleh tepi Canny dari input."""
# Ekstrak tepi Canny
gambar = cv2.imread(imagepath)
tepi = cv2.Canny(gambar, 100, 200)
tepi = np.stack([tepi] 3, axis=-1)
gambarkontrol = Image.fromarray(tepi)
hasil = pipe(
prompt=prompt,
image=gambarkontrol,
numinferencesteps=30,
guidancescale=7.5,
).images[0]
return hasil
Penggunaan
pipe = buatpipelinecannycontrolnet()
hasil = hasilkandaritepi(
pipe,
"arsitektur.jpg",
"Rumah modern yang indah, fotorealistis, pencahayaan golden hour"
)
hasil.save("outputterkontrol.png")
Menggunakan Beberapa ControlNet Sekaligus
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
def pipelinemulticontrolnet():
"""Menggabungkan ControlNet tepi Canny dan kedalaman."""
controlnets = [
ControlNetModel.frompretrained(
"lllyasviel/sd-controlnet-canny", torchdtype=torch.float16
),
ControlNetModel.frompretrained(
"lllyasviel/sd-controlnet-depth", torchdtype=torch.float16
),
]
pipe = StableDiffusionControlNetPipeline.frompretrained(
"runwayml/stable-diffusion-v1-5",
controlnet=controlnets,
torchdtype=torch.float16,
).to("cuda")
return pipe
Fine-tuning dengan DreamBooth
DreamBooth memungkinkan Anda mempersonalisasi Stable Diffusion dengan mengajarkan konsep baru hanya dari beberapa gambar. Ini sangat berguna untuk membuat model yang mengenali subjek spesifik.
# Skrip pelatihan DreamBooth (jalankan dari command line)
"""
accelerate launch traindreambooth.py \
--pretrainedmodelnameorpath="stabilityai/stable-diffusion-2-1" \
--instancedatadir="./fotoanjingsaya" \
--outputdir="./modeldreambooth" \
--instanceprompt="foto dari anjing sks" \
--resolution=512 \
--trainbatchsize=1 \
--gradientaccumulationsteps=1 \
--learningrate=5e-6 \
--lrscheduler="constant" \
--lrwarmupsteps=0 \
--maxtrainsteps=800 \
--use8bitadam \
--mixedprecision="fp16"
"""
Menggunakan DreamBooth dengan LoRA untuk fine-tuning yang efisien
"""
accelerate launch traindreamboothlora.py \
--pretrainedmodelnameorpath="stabilityai/stable-diffusion-2-1" \
--instancedatadir="./fotoanjingsaya" \
--outputdir="./dreamboothlora" \
--instanceprompt="foto dari anjing sks" \
--resolution=512 \
--trainbatchsize=1 \
--gradientaccumulationsteps=1 \
--learningrate=1e-4 \
--lrscheduler="constant" \
--lrwarmupsteps=0 \
--maxtrainsteps=500 \
--rank=4
"""
Memuat dan menggunakan model yang sudah di-fine-tune
from diffusers import StableDiffusionPipeline
def muatmodeldreambooth(modelpath):
"""Memuat model yang sudah di-fine-tune dengan DreamBooth."""
pipe = StableDiffusionPipeline.frompretrained(
modelpath,
torchdtype=torch.float16,
).to("cuda")
return pipe
def muatmodellora(basemodelid, lorapath):
"""Memuat model yang sudah di-fine-tune dengan LoRA."""
pipe = StableDiffusionPipeline.frompretrained(
basemodelid,
torchdtype=torch.float16,
).to("cuda")
pipe.loadloraweights(lorapath)
return pipe
Hasilkan gambar dengan model fine-tuned
pipe = muatmodellora(
"stabilityai/stable-diffusion-2-1",
"./dreamboothlora"
)
gambar = pipe("foto dari anjing sks memakai topi ulang tahun").images[0]
gambar.save("anjingsayaultah.png")
Strategi Deployment
Server FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from diffusers import StableDiffusionPipeline
import torch
import io
import base64
app = FastAPI()
Muat model saat startup
pipe = StableDiffusionPipeline.frompretrained(
"stabilityai/stable-diffusion-2-1",
torchdtype=torch.float16,
).to("cuda")
class PermintaanGenerasi(BaseModel):
prompt: str
negativeprompt: str = ""
numinferencesteps: int = 30
guidancescale: float = 7.5
width: int = 512
height: int = 512
seed: int = -1
@app.post("/generate")
async def hasilkangambar(request: PermintaanGenerasi):
try:
generator = None
if request.seed >= 0:
generator = torch.Generator(device="cuda").manualseed(request.seed)
gambar = pipe(
prompt=request.prompt,
negativeprompt=request.negativeprompt,
numinferencesteps=request.numinferencesteps,
guidancescale=request.guidancescale,
width=request.width,
height=request.height,
generator=generator,
).images[0]
# Konversi ke base64
buffer = io.BytesIO()
gambar.save(buffer, format="PNG")
imgstr = base64.b64encode(buffer.getvalue()).decode()
return {"image": imgstr, "seed": request.seed}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Praktik Terbaik
torch.float16 untuk inferensi guna mengurangi penggunaan memori setengahnya dan meningkatkan kecepatan tanpa penurunan kualitas yang terlihat.Kesimpulan
Stable Diffusion adalah alat yang serbaguna dan powerful untuk generasi gambar, dengan aplikasi mulai dari pembuatan konten kreatif hingga desain industri dan prototyping. Tutorial ini membahas teknik-teknik inti: generasi dasar, prompt engineering, img2img, inpainting, ControlNet, dan fine-tuning DreamBooth. Dengan menggabungkan teknik-teknik ini dan mengikuti strategi deployment yang diuraikan, Anda dapat membangun sistem generasi gambar yang siap untuk produksi.
Bidang ini berkembang dengan sangat cepat. Pantau terus arsitektur model yang lebih baru seperti Stable Diffusion 3, FLUX, dan mekanisme kontrol yang terus bermunculan untuk hasil yang lebih baik lagi.