【Real-ESRGAN】Stable Diffusionで4K画像を生成する方法【Python】

【Python】Stable Diffusion4K画像を生成

本記事はアフィリエイト広告(PR)を含みます

Stable Diffusionはデフォルト設定で4K画像を出力することを想定していません。
SD1.5で512×512、SDXLでも1024×1024pxが基準の解像度です。

直接4K(3840×2160)を指定することもできますがVRAMの消費が非常に大きいのでGPUのスペックによっては「CUDA out of memory」になってしまうかもしれません。

VRAMが少ないGPUで4K画像を生成するには少し工夫が必要になります。

そこで今回は3種類の4K画像を生成する方法についてまとめました。

実行環境

  • OS : Windows 11 Home
  • GPU : NVIDIA GeForce RTX 5070 Ti
  • Python:3.12 (venv)
  • 実行方式:Diffusers

 

1. 直接指定で生成してみる

まずは直接4Kを指定して生成できるか試してみます。
ちなみに今回は「夜の街並みを写真のように描く」というニュアンスで

cinematic photo of a futuristic tokyo street at night, neon lights, ultra detailed

というプロンプトを指定しました。

1.1. サンプルコード

ソースコードはこちらです。
seedを固定し処理時間の計測も入れました。

from diffusers import StableDiffusionXLPipeline
import torch
import time

model_id = "stabilityai/stable-diffusion-xl-base-1.0"

pipe = StableDiffusionXLPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
).to("cuda")

pipe.enable_attention_slicing()
pipe.enable_vae_slicing()

prompt = "cinematic photo of a futuristic tokyo street at night, neon lights, ultra detailed"

#解像度指定(4K)
width, height = 3840, 2160

#seed固定
seed = 1234
generator = torch.Generator(device="cuda").manual_seed(seed)

#処理時間計測開始
start_time = time.time()

image = pipe(
    prompt,
    num_inference_steps=30,
    guidance_scale=6.5,
    width=width,
    height=height,
    generator=generator,
).images[0]

#処理時間計測終了
end_time = time.time()
elapsed = end_time - start_time

image.save("sdxl_4k.png")

print("saved: sdxl_4k.png")
print(f"seed: {seed}")
print(f"time: {elapsed:.2f} sec")

① ライブラリ読み込み

from diffusers import StableDiffusionXLPipeline
import torch
import time
ライブラリ意味
diffusers画像生成用(Stable Diffusion)
torchPyTorch
time処理時間計測用

② モデルID指定

SDXL 1.0を指定しています。

model_id = "stabilityai/stable-diffusion-xl-base-1.0"

③ モデル読み込み

pipe = StableDiffusionXLPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
).to("cuda")

④ VRAM節約設定

pipe.enable_attention_slicing()
pipe.enable_vae_slicing()

enable_attention_slicing()はVRAMが節約できる代わりに10%処理速度が下がります。
enable_vae_slicing()はVAEの処理を分割して実行することで、VRAM使用量を抑えるための設定です。

⑤ プロンプト指定

prompt = "cinematic photo of a futuristic tokyo street at night, neon lights, ultra detailed"

⑥ 解像度指定(4K)

width, height = 3840, 2160

⑦ seed固定

seed = 1234
generator = torch.Generator(device="cuda").manual_seed(seed)

⑧ 処理時間計測開始

start_time = time.time()

⑨ 画像生成

image = pipe(
    prompt,
    num_inference_steps=30,
    guidance_scale=6.5,
    width=width,
    height=height,
    generator=generator,
).images[0]
引数意味
num_inference_stepsノイズ除去回数。
SDXLでは30回が精度と速度のバランスがいいとされている。
guidance_scaleプロンプト忠実度。
6~8がよくつかわれるらしいので今回は6.5あたりを設定する。
generatorseed固定を適用

⑩ 処理時間計測終了

end_time = time.time()
elapsed = end_time - start_time

⑪ 画像保存

image.save("sdxl_4k.png")

⑫ ログ出力

print("saved: sdxl_4k.png")
print(f"seed: {seed}")
print(f"time: {elapsed:.2f} sec")

1.2. 実行結果

実行結果はこちらです。

5070 tiをもってしても4K直接出力はメモリが足りませんでした…

スポンサーリンク

2. Resize(補完拡大)

4K画像を直接出力できない場合、小さいサイズで出力してからResizeするという方法があります。
これなら画像生成自体のVRAM消費は抑えて4K出力が可能になります。

ただし画質は落ちます。

2.1. サンプルコード

from diffusers import StableDiffusionXLPipeline
import torch
import time
from PIL import Image

model_id = "stabilityai/stable-diffusion-xl-base-1.0"

pipe = StableDiffusionXLPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
).to("cuda")

pipe.enable_attention_slicing()
pipe.enable_vae_slicing()

prompt = "cinematic photo of a futuristic tokyo street at night, neon lights, ultra detailed"

#解像度指定(1024pxに近い解像度)
width, height = 1536, 864

#Resize後の解像度(4K)
width_4K, height_4K = 3840, 2160

#seed固定
seed = 1234
generator = torch.Generator(device="cuda").manual_seed(seed)

#処理時間計測開始
start_time = time.time()

image = pipe(
    prompt,
    num_inference_steps=30,
    guidance_scale=6.5,
    width=width,
    height=height,
    generator=generator,
).images[0]

#Resize
image_4K = image.resize(
    (width_4K, height_4K),
    resample=Image.LANCZOS  # 高品質リサイズ(LANCZOS補間)
)

#処理時間計測終了
end_time = time.time()
elapsed = end_time - start_time

image_4K.save("sdxl_4K_resize.png")

print("saved: sdxl_4K_resize.png")
print(f"seed: {seed}")
print(f"time: {elapsed:.2f} sec")

① Pillowインポート

from PIL import Image

Resizeを使用するためにライブラリを追加します。

インストールされていない場合はこちらのコマンドでインストールします。

pip install pillow

② 解像度指定

width, height = 1536, 864

今回は標準1024pxの拡張サイズである1536pxを指定しました。

Stable Diffusionには生成が安定しやすい解像度があります。
これはモデルが特定の解像度で学習されているためです。
SDXLの基準解像度は1024pxなので、その倍数に近いサイズは比較的安定します。
1536×864は4K(3840×2160)と同じ16:9なので4Kへ変換する用途にも適していると考えました。

③ Resize処理

image_4K = image.resize(
    (width_4K, height_4K),
    resample=Image.LANCZOS  # 高品質リサイズ(LANCZOS補間)
)

LANCZOS補間はリサイズアルゴリズムの一つで、エッジを保ちながら高品質なリサイズが可能です。

2.2. 実行結果

今回は上手く生成できました。
ぱっと見画質は気になりませんね。

ログはこちら。
生成時間は約13秒でした。

saved: sdxl_4K_resize.png
seed: 1234
time: 13.26 sec
スポンサーリンク

3. AIアップスケール

AIアップスケールは画像生成した後、AIによって4Kに拡大する手法です。
通常のリサイズは単純なピクセル拡大なので画質が下がったり、ぼやけた画像になってしまいます。

一方AIによる拡大では、

  • AIがディテールを補完
  • シャープ&高精細
  • 質感などを再現

つまり、単純な補間ではなくAIでディテールを補間しながら拡大するためResizeと比較して高解像度な画像が出力されるということです。

AIアップスケールにもいくつか種類がありますが、今回はReal-ESRGANを使用しました。
Real-ESRGANの環境構築についてはこちらの記事でまとめています。

3.1. サンプルコード

このコードはStable Diffusionで画像生成をした後、Real-ESRGANでアップスケールし、4Kへ縮小して保存します。
Real-ESRGANは4倍など固定倍率のモデルが多いため、今回はなるべく大きめに生成してから拡大・縮小することでなるべく高画質になるようにしています。

import os
import time
from urllib.request import urlretrieve

import cv2
import torch
from PIL import Image
from diffusers import StableDiffusionXLPipeline
from realesrgan import RealESRGANer
from basicsr.archs.rrdbnet_arch import RRDBNet


# =========================================
# 設定
# =========================================
sd_model_id = "stabilityai/stable-diffusion-xl-base-1.0"

prompt = "cinematic photo of a futuristic tokyo street at night, neon lights, ultra detailed"

# SDXL生成サイズ(最初に大きめで生成)
width, height = 1536, 864

# 最終出力サイズ(4K)
final_width, final_height = 3840, 2160

# seed固定
seed = 1234

# Real-ESRGAN設定
realesrgan_model_name = "RealESRGAN_x4plus.pth"
realesrgan_model_url = "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth"
realesrgan_scale = 4

# ファイル名
sd_output_path = "sdxl.png"
esrgan_output_path = "sdxl_x4.png"
final_output_path = "sdxl_4k_realesrgan.png"


# =========================================
# デバイス設定
# =========================================
device = "cuda" if torch.cuda.is_available() else "cpu"
print("device:", device)

# =========================================
# Stable Diffusion XL パイプライン
# =========================================
print("loading SDXL pipeline...")
pipe = StableDiffusionXLPipeline.from_pretrained(
    sd_model_id,
    torch_dtype=torch.float16 if device == "cuda" else torch.float32,
)
pipe = pipe.to(device)

# メモリ節約
pipe.enable_attention_slicing()
pipe.enable_vae_slicing()

generator = torch.Generator(device=device).manual_seed(seed)


# =========================================
# Real-ESRGAN モデル準備
# =========================================
if not os.path.exists(realesrgan_model_name):
    print("downloading Real-ESRGAN model...")
    urlretrieve(realesrgan_model_url, realesrgan_model_name)
    print("downloaded:", realesrgan_model_name)

print("building Real-ESRGAN model...")
esrgan_model = RRDBNet(
    num_in_ch=3,
    num_out_ch=3,
    num_feat=64,
    num_block=23,
    num_grow_ch=32,
    scale=realesrgan_scale
)

upsampler = RealESRGANer(
    scale=realesrgan_scale,
    model_path=realesrgan_model_name,
    model=esrgan_model,
    tile=0,
    tile_pad=10,
    pre_pad=0,
    half=(device == "cuda"),
    device=device,
)


# =========================================
# 1. SDXLで画像生成
# =========================================
print("generating image with SDXL...")
start_total = time.time()
start_sd = time.time()

image = pipe(
    prompt=prompt,
    num_inference_steps=30,
    guidance_scale=6.5,
    width=width,
    height=height,
    generator=generator,
).images[0]

if device == "cuda":
    torch.cuda.synchronize()

sd_elapsed = time.time() - start_sd

image.save(sd_output_path)
print("saved:", sd_output_path)
print("SDXL output size:", image.size)


# =========================================
# 2. Real-ESRGANでアップスケール
# =========================================
print("upscaling with Real-ESRGAN...")
img = cv2.imread(sd_output_path, cv2.IMREAD_COLOR)
if img is None:
    raise ValueError(f"failed to read image: {sd_output_path}")

start_esrgan = time.time()

output, _ = upsampler.enhance(img, outscale=realesrgan_scale)

if device == "cuda":
    torch.cuda.synchronize()

esrgan_elapsed = time.time() - start_esrgan

cv2.imwrite(esrgan_output_path, output)
print("saved:", esrgan_output_path)
print("Real-ESRGAN output size:", output.shape[1], "x", output.shape[0])


# =========================================
# 3. 4Kへ高品質縮小
# =========================================
print("resizing to exact 4K...")
upscaled_pil = Image.open(esrgan_output_path).convert("RGB")

final_image = upscaled_pil.resize(
    (final_width, final_height),
    resample=Image.LANCZOS
)

final_image.save(final_output_path)

total_elapsed = time.time() - start_total

print("saved:", final_output_path)
print(f"seed: {seed}")
print(f"SDXL time: {sd_elapsed:.2f} sec")
print(f"Real-ESRGAN time: {esrgan_elapsed:.2f} sec")
print(f"total time: {total_elapsed:.2f} sec")

長いので主要な部分だけ解説します。

① Stable Diffusion XLのパイプライン読み込み

pipe = StableDiffusionXLPipeline.from_pretrained(
    sd_model_id,
    torch_dtype=torch.float16 if device == "cuda" else torch.float32,
).to(device)

まずはdiffusersライブラリのSDXLパイプラインを読み込みます。
ここで

torch_dtype=torch.float16

を指定するとVRAMの消費を減らすことができます。

② 画像生成

image = pipe(
    prompt=prompt,
    num_inference_steps=30,
    guidance_scale=6.5,
    width=width,
    height=height,
    generator=generator,
).images[0]

ここでは1536×864の画像を生成しています。
主なパラメータはこちら

パラメータ意味
prompt生成する画像の内容
num_inference_steps生成ステップ数
guidance_scaleプロンプトへの忠実度
width画像の幅
height画像の高さ

③ Real-ESRGANのネットワーク定義

esrgan_model = RRDBNet(
    num_in_ch=3,
    num_out_ch=3,
    num_feat=64,
    num_block=23,
    num_grow_ch=32,
    scale=realesrgan_scale
)

次に、Real-ESRGANのネットワーク構造を定義します。
realesrgan_scale=4なので生成された画像を4倍(6144×3456)に拡大します。

④ 画像アップスケール

output, _ = upsampler.enhance(img, outscale=realesrgan_scale)

ここで実際にアップスケール処理を行います。
リサイズ処理とは違い、AIでディテールを補完しながら拡大します。

⑤ 4Kサイズへ縮小

final_image = upscaled_pil.resize(
    (final_width, final_height),
    resample=Image.LANCZOS
)

Real-ESRGANでアップスケールされた画像は6144×3456なので、4Kサイズ(3840×2160)へ縮小します。
ここはリサイズ処理と同じLANCZOS補間を使用しています。

3.2. 動作確認

出力結果はこちら。

リサイズは13秒なのに対し、Real-ESRGANによりアップスケールは33秒かかりました。
やはりAIを使う分処理時間は長くなります。

saved: sdxl.png
SDXL output size: (1536, 864)
upscaling with Real-ESRGAN...
saved: sdxl_x4.png
Real-ESRGAN output size: 6144 x 3456
resizing to exact 4K...
saved: sdxl_4k_realesrgan.png
seed: 1234
SDXL time: 13.58 sec
Real-ESRGAN time: 18.50 sec
total time: 33.77 sec

800倍に拡大してリサイズと比較してみます。

リサイズ
Real-ESRGAN

違いは一目瞭然ですね!

リサイズは拡大するとぼやけて見えるのに対し、Real-ESRGANでアップスケールしたほうは鮮明に描画されています。

スポンサーリンク

4. まとめ

今回はStable Diffusionで4K画像を生成する方法を3種類紹介しました。
直接指定する方法はVRAM消費が大きいため現実的ではありませんが、リサイズやReal-ESRGANを使用した方法であれば実用的なのではないでしょうか。

手軽さを重視するならリサイズ、多少処理時間がかかっても画質を重視するならReal-ESRGANを使用する方法がおすすめです。

今回は以上です。

スポンサーリンク

5. 参考サイト

・v0.3.0: 新しいAPI、安定した拡散パイプライン、低メモリ推論、MPSバックエンド、ONNX
https://github.com/huggingface/diffusers/releases/tag/v0.3.0

・SDXLで画質爆上げ!初心者でもわかるHires.fix完全ガイド
https://unikoukokun.jp/n/neb83efd801ba

・Real-ESRGAN:AIで画像を高品質にアップスケールする方法
https://www.toolify.ai/ja/ai-news-jp/realesrganai%E7%94%BB%E5%83%8F%E9%AB%98%E5%93%81%E8%B3%AA%E6%96%B9%E6%B3%95-3464438

・画像のリサイズを実装する(Lanczos編)
https://hexadrive.jp/hexablog/program/28091/

6. 関連書籍

スポンサーリンク

コメント

タイトルとURLをコピーしました