2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Real-ESRGANで完全無料・登録不要・制限なしの画像拡大サイトを作ったので構成を書いておく

2
Posted at

作ったもの

Real-ESRGANを使って画像を最大4倍に拡大できるWebサイトを作りました。

登録不要・無料で、画像をアップロードするだけです。大したものではないですが、構成をメモがてら書いておきます。

構成

項目 技術
フロントエンド HTML / CSS / JS(素のやつ)
バックエンド Python / FastAPI
AI処理 Real-ESRGAN(VPS上でCPU推論)
サーバー VPS + Nginx
SSL Cloudflare

ReactもNext.jsも使ってません。ページ数が少ないので素のHTMLで十分でした。

バックエンド

FastAPIでAPIを1本生やして、Real-ESRGANで画像を処理して返すだけです。

モデルの読み込み

_upsampler = None

def get_upsampler():
    global _upsampler
    if _upsampler is None:
        from realesrgan import RealESRGANer
        from basicsr.archs.rrdbnet_arch import RRDBNet

        model = RRDBNet(
            num_in_ch=3, num_out_ch=3, num_feat=64,
            num_block=23, num_grow_ch=32, scale=4
        )
        _upsampler = RealESRGANer(
            scale=4,
            model_path="/path/to/weights/RealESRGAN_x4plus.pth",
            model=model,
            tile=256,
            tile_pad=10,
            pre_pad=0,
            half=False,
            device='cpu'
        )
    return _upsampler

初回リクエスト時にモデルをロードしています。起動時にやるとsystemdのタイムアウトに引っかかることがあったので。

tile=256はVPSだとメモリが足りなくなるので必須。half=FalseはCPU推論なのでFP16が使えないため。

画像処理

def _process_upscale(contents, scale):
    nparr = np.frombuffer(contents, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    if img is None:
        return None
    try:
        upsampler = get_upsampler()
        output, _ = upsampler.enhance(img, outscale=scale)
    except Exception:
        return None
    _, buffer = cv2.imencode('.png', output)
    return buffer.tobytes()


@app.post("/upscale")
async def upscale_image(
    file: UploadFile = File(...),
    scale: int = 4
):
    contents = await file.read()
    if len(contents) > 10 * 1024 * 1024:
        raise HTTPException(status_code=400, detail="10MB以下にしてください")

    png_bytes = await asyncio.to_thread(_process_upscale, contents, scale)

    if png_bytes is None:
        raise HTTPException(status_code=503, detail="処理に失敗しました")

    return Response(content=png_bytes, media_type="image/png")

Real-ESRGANの処理はCPUバウンドで1枚数十秒かかるので、asyncio.to_threadでスレッドに逃がしています。これをやらないとuvicornのイベントループが止まって、処理中にヘルスチェックすら返せなくなります。

レスポンスはバイナリPNGをそのまま返しています。最初はbase64で返していたんですが、サイズが33%増えるだけで何のメリットもなかったのでやめました。

フロントエンド

バイナリPNGを受け取ってBlob URLで表示するだけです。

const response = await fetch(`${API_BASE_URL}/upscale?scale=${scale}`, {
    method: 'POST',
    body: formData
});
const blob = await response.blob();

// 前回のBlob URLを解放してから新しいのを作る
if (currentBlobUrl) URL.revokeObjectURL(currentBlobUrl);
currentBlobUrl = URL.createObjectURL(blob);
resultImage.src = currentBlobUrl;
downloadBtn.href = currentBlobUrl;

revokeObjectURLしないとメモリリークするので注意。

あとAI処理は時間がかかってタイムアウトしやすいので、失敗したら2秒待って最大3回リトライするようにしています。

let resultBlob = null;
for (let i = 0; i < 3; i++) {
    try {
        resultBlob = await doUpscale();
        break;
    } catch (err) {
        if (i < 2) await new Promise(r => setTimeout(r, 2000));
    }
}

Nginx

静的ファイルの配信とAPIへのリバースプロキシです。

server {
    listen 80;
    server_name ai-kakudai.com;

    root /home/user/ai-kakudai/frontend;
    index index.html;

    location /api/ {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 300s;
    }
}

proxy_read_timeoutはデフォルト60秒だとAI処理がタイムアウトするので長めにしています。

ハマったところ

Cloudflare Flexible SSLのリダイレクトループ

Cloudflareの「Flexible SSL」はCloudflare↔サーバー間がHTTPです。Nginx側でHTTP→HTTPSリダイレクトを書いていると、Cloudflareからの通信が常にHTTPなので無限ループします。

Nginx側のリダイレクトを消して解決。

CPU推論が遅い

GPUなら数秒のところ、CPUだと数十秒〜数分。対策はフロントのリトライとNginxのタイムアウト延長くらいしかやってません。GPUサーバーを借りればいいんですが、コストが跳ね上がるので今のところCPUで我慢しています。

tileパラメータ

tileを設定しないと大きい画像でメモリ不足になります。最初知らなくてOOMで落ちました。

コスト

外部のAI APIは使っていないので、VPSのサーバー代とドメイン代だけで動いています。リクエスト数が増えてもコストは変わりません。逆に言うとCPU推論なので処理速度は出ませんが、個人サイトなのでそこまで同時アクセスもないので問題なし。

おわりに

やっていることはReal-ESRGANをFastAPIで包んでNginxで配信しているだけなので、技術的には特に目新しいことはしていません。似たようなことをやろうとしている人のメモになれば。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?