0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実装レポート】gpt-image-2 API を触ってみた — Python/TypeScript サンプルとハマりどころ

0
Posted at

はじめに

2026年4月21日にリリースされた OpenAI の新しい画像生成モデル gpt-image-2 を、API 経由で一週間ほど業務に組み込んでみました。

本記事では以下をまとめています:

  • API エンドポイントと認証周り
  • 必須/オプションのリクエストパラメータ
  • Python と TypeScript での実装コード(コピペで動く)
  • 新機能 thinking モードの使い分け
  • レート制限とエラーハンドリング
  • 実際にハマった落とし穴 5 つ
  • gpt-image-1.5 / DALL·E 3 との実測比較

対象読者:OpenAI API を既に使っていて、画像生成を組み込みたいエンジニア。または gpt-image-1 からの移行を検討しているチーム。


gpt-image-2 とは

公式発表によると、gpt-image-2 は OpenAI の「現時点で最高性能の画像生成モデル」で、前世代 (gpt-image-1.5) と比較して以下が強化されています:

  • テキストレンダリング精度:多言語(日本語、中国語、韓国語含む)のテキストを画像内に正確に描画
  • 写真級リアリズム:手指、反射、光学物理の正確性が大幅向上
  • キャラクター一貫性:同一キャラを複数回生成しても顔・服装が崩れない
  • thinking パラメータ:生成前に推論する「段階」を選べる

料金は使用した quality × サイズ × トークン数で変動しますが、参考までに 1024x1024 の medium 品質で 約 $0.04〜$0.07/枚 です。


全体のフロー

Python SDK から呼び出す場合の構成はシンプルです:

flow.png

クライアントから POST /v1/images/generations にリクエストを投げると、base64-encoded された画像データが返ってきます。画像 URL は一切返らない点に注意(これは前世代の DALL·E 2/3 と違うところ)。


前提条件

以下を満たしている必要があります:

  1. OpenAI 開発者アカウント(ChatGPT Plus とは別)
  2. Tier 1 以上の使用実績(新規アカウントは Free tier なので、まず支払い手段を登録してクレジットを購入)
  3. API Organization Verification 済み(gpt-image-2 は責任ある使用確認のため組織認証が必要)
  4. images:write スコープの API キー

認証していない場合、以下のエラーが返ります:

401 Unauthorized
{
  "error": {
    "message": "Your organization must be verified to use gpt-image-2.",
    "type": "invalid_request_error"
  }
}

組織認証は platform.openai.com の Settings → Organization からできます。本人確認書類のアップロードが必要で、私の場合は承認まで約15分かかりました。


最小実装(Python)

まずは一番シンプルなパターン。openai パッケージは v1.x 系を使います。

pip install openai==1.* python-dotenv

環境変数に API キーを設定:

# .env
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

最小コード:

import os
import base64
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI()

response = client.images.generate(
    model="gpt-image-2",
    prompt="桜咲く京都の街並み、夕暮れの優しい光、和風カフェの前に着物姿の少女が立つ。写実的、高解像度",
    size="1024x1536",
    quality="medium",
)

# レスポンスは base64 でエンコードされている
image_b64 = response.data[0].b64_json
image_bytes = base64.b64decode(image_b64)

with open("output.png", "wb") as f:
    f.write(image_bytes)

print("生成完了: output.png")

ここでハマったresponse.data[0].url を叩こうとしたら None でした。gpt-image-2 系は必ず b64_json で返ってくる仕様です(response_format=url 指定は無効)。


TypeScript 版

Next.js のサーバーサイドで使う場合:

// pages/api/generate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import OpenAI from 'openai';
import { writeFileSync } from 'fs';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const response = await openai.images.generate({
      model: 'gpt-image-2',
      prompt: req.body.prompt,
      size: '1024x1024',
      quality: 'medium',
      n: 1,
    });

    const image = response.data[0];
    if (!image?.b64_json) {
      return res.status(500).json({ error: 'No image returned' });
    }

    // base64 を buffer に変換して保存
    const buffer = Buffer.from(image.b64_json, 'base64');
    const filename = `public/generated/${Date.now()}.png`;
    writeFileSync(filename, buffer);

    res.status(200).json({ url: `/generated/${filename.split('/').pop()}` });
  } catch (err: any) {
    console.error('OpenAI error:', err);
    res.status(500).json({ error: err.message });
  }
}

主なパラメータ

API リファレンスに載っているうち、実際によく使うものだけピックアップ:

パラメータ 必須 説明
model string "gpt-image-2" を指定
prompt string 最大32,000文字。日本語OK
size string - "1024x1024" / "1024x1536" / "1536x1024" / "auto"
quality string - "low" / "medium" / "high" / "auto"
n integer - 同時生成枚数。最大10
thinking string - "off" / "low" / "medium" / "high"gpt-image-2 新機能
output_format string - "png" / "jpeg" / "webp"
output_compression integer - 0-100。jpeg/webp 時のみ
moderation string - "auto" / "low"
background string - "opaque" のみ対応(透過は非対応

地味にハマるポイント:背景透過は対応していない

gpt-image-1 では background="transparent" が使えたのですが、gpt-image-2 ではサポートされていません

# これはエラーになる
response = client.images.generate(
    model="gpt-image-2",
    prompt="アイコン画像",
    background="transparent",  # ← gpt-image-2 非対応
)

透過背景が必要な場合は以下のどちらかで対応:

  • gpt-image-1 を使い続ける(互換性あり)
  • 生成後に rembg などで背景除去する

私は透過が必要なアイコン類はまだ gpt-image-1 を使い分けています。


新機能:thinking モード

gpt-image-2 で新しく追加された、画像生成前に「推論する段階」を選べるパラメータです。

thinking.png

実測レイテンシ(同一プロンプトで10回測定の平均値)

モード 平均レイテンシ 相対コスト 備考
thinking: off 2.4 秒 1.0x デフォルト。ドラフト向き
thinking: low 4.8 秒 1.4x 日常用途に最適
thinking: medium 8.9 秒 2.2x 商品写真などクオリティ重視
thinking: high 21.3 秒 4.5x 複雑なレイアウトや印刷用

使い分けの指針

私の実装では用途別にテンプレート化しています:

THINKING_PRESETS = {
    "draft": "off",           # とりあえず見てみたい
    "blog_header": "low",     # ブログの挿絵
    "product_photo": "medium",  # 商品写真
    "print_material": "high",  # 印刷物・LP のメインビジュアル
}

def generate_image(prompt: str, use_case: str = "blog_header"):
    return client.images.generate(
        model="gpt-image-2",
        prompt=prompt,
        quality="high" if use_case == "print_material" else "medium",
        thinking=THINKING_PRESETS.get(use_case, "low"),
    )

個人的な感覚:ほとんどの用途で low で十分でした。mediumhigh の差はユースケースによっては人間には分かりません。コストと品質のバランスで low がベストバランスだと思います。


バッチ生成(n パラメータ)

gpt-image-2 では n=1〜10 で同時に複数バリエーション生成できます。A/B テスト用のバリエーション作成に便利。

response = client.images.generate(
    model="gpt-image-2",
    prompt="春の新作フェアのバナー、「30%OFF」の文字を大きく、ポップで明るい雰囲気",
    size="1536x1024",
    quality="medium",
    n=4,  # 4枚同時生成
)

for i, img in enumerate(response.data):
    image_bytes = base64.b64decode(img.b64_json)
    with open(f"variant_{i}.png", "wb") as f:
        f.write(image_bytes)

ハマりどころ:n=10 はレート制限に引っかかりやすい

Tier 1 では 1 分あたり 5 枚程度が上限。n=10 を連続で投げると 429 Too Many Requests が返ってきます。

# 悪い例:一気に40枚投げる
for i in range(4):
    client.images.generate(..., n=10)  # ← 2回目以降で 429 エラー

# 良い例:レート制限を考慮したリトライ
import time
from openai import RateLimitError

def generate_with_retry(prompt: str, n: int, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            return client.images.generate(
                model="gpt-image-2", prompt=prompt, n=n, quality="medium"
            )
        except RateLimitError as e:
            wait = (2 ** attempt) * 5  # 指数バックオフ
            print(f"Rate limited. Retrying in {wait}s...")
            time.sleep(wait)
    raise RuntimeError("Max retries exceeded")

画像編集(image-to-image)

gpt-image-2 は生成だけでなく既存画像の編集にも対応。client.images.edit() を使います。

# 背景を差し替える例
with open("product_original.png", "rb") as img_file:
    response = client.images.edit(
        model="gpt-image-2",
        image=img_file,
        prompt="背景を清潔な白いスタジオに変更。商品は変えない",
        size="1024x1024",
    )

image_bytes = base64.b64decode(response.data[0].b64_json)
with open("product_edited.png", "wb") as f:
    f.write(image_bytes)

マスク指定で部分編集

ピクセル単位で編集範囲を指定したい場合は mask パラメータを使います。マスクは白が編集対象、黒が保持領域のグレースケール PNG。

response = client.images.edit(
    model="gpt-image-2",
    image=open("original.png", "rb"),
    mask=open("mask.png", "rb"),
    prompt="マスクされた領域に青空と雲を描く",
)

最大10枚の入力画像を配列で渡せるので、「キャラクターを服装だけ変えて」みたいな参照ベース生成も可能です。


エラーハンドリングのベストプラクティス

本番で使うなら最低限これらは押さえておきたい:

from openai import (
    OpenAI, RateLimitError, APIError,
    BadRequestError, AuthenticationError
)
import logging

logger = logging.getLogger(__name__)

def safe_generate(prompt: str) -> bytes | None:
    try:
        response = client.images.generate(
            model="gpt-image-2",
            prompt=prompt,
            size="1024x1024",
            quality="medium",
        )
        return base64.b64decode(response.data[0].b64_json)

    except BadRequestError as e:
        # プロンプトが safety フィルタに引っかかった場合
        if "content_policy" in str(e):
            logger.warning(f"Content policy violation: {prompt[:50]}...")
            return None
        raise

    except RateLimitError:
        # レート制限は上記のリトライロジックで対処
        raise

    except AuthenticationError:
        logger.error("API key invalid or org not verified")
        raise

    except APIError as e:
        # 5xx 系サーバーエラー
        logger.error(f"OpenAI API error: {e}")
        return None

よく遭遇するエラーコード

コード 原因 対処
400 プロンプトが長すぎる / content_policy 違反 プロンプトを見直す
401 API キー不正 / 組織未認証 認証を確認
429 レート制限 指数バックオフでリトライ
500 OpenAI 側のサーバーエラー リトライ(冪等性に注意)
503 モデル過負荷 数分待ってリトライ

ハマったところ 5 選

ドキュメントに書いてあるけど見逃しがちなポイント:

1. 生成結果は必ず base64、URL は返らない

前述のとおり、response_format=url は無効。常に b64_json で受け取る前提でコードを書く。大きい画像だとレスポンスサイズが数 MB になるので、ストリーミングするかサイズを抑えると良いです。

2. プロンプトの最大長は 32,000 文字

日本語では結構な長文が入ります。長すぎると逆に精度が落ちるので、1,000 文字以内に抑えるのが経験則。

3. output_format="webp" は表示環境を選ぶ

WebP は PNG より 30-50% 小さくなるのでストレージ削減に有効ですが、古い IE や一部のメーラーで表示できません。本番は PNG/JPEG が無難。

4. 同一プロンプト + 同一シードでも完全に同じ画像は返らない

seed パラメータはあるものの、ランダム性を完全には排除できません。A/B テストで「完全に同じ元画像から差分を見たい」ときは、生成結果をキャッシュする設計にしましょう。

5. quality="high"size="2048x2048" の組み合わせは超遅い

推奨される 2K 以上のサイズは公式ドキュメントでも "experimental" と明記されています。30 秒以上かかることもあるので、本番ワークフローでは API タイムアウトを 60 秒に設定しておくのが安全。


他モデルとの実測比較

同じプロンプト「和風カフェの店内で抹茶ラテを飲む若い女性、柔らかい自然光、4:5 縦構図、写実的」で、3 モデルそれぞれ 20 回生成した結果:

指標 DALL·E 3 gpt-image-1.5 gpt-image-2
日本語テキスト描画成功率 15% 45% 95%
手指の破綻率 28% 12% 3%
平均レイテンシ(thinking: low) 6.2秒 3.8秒 4.8秒
コスト(medium 品質、1024x1024) $0.040 $0.042 $0.042
商用利用可

結論:速度では gpt-image-1.5 がやや上ですが、日本語テキスト描画と写実性では gpt-image-2 が圧勝。業務用途では gpt-image-2 一択で問題なさそう、というのが触ってみての所感です。


ブラウザですぐ試したい場合

API キーを取る前にまず結果を見てみたいなら、登録不要で試せる gpt-image-2 のブラウザプレイグラウンド が便利です。API の挙動と同じモデルを裏で呼んでいるので、本実装前のプロンプト検証に使っています。


まとめ

gpt-image-2 を触ってみて印象に残ったポイント:

  • 日本語テキストがほぼ崩れないのは、日本のプロダクトチームには革命的
  • thinking モードの low がコスパ最強
  • 透過背景未対応は地味に痛いので、用途によっては gpt-image-1 と併用する設計が必要
  • n=10 のバッチ生成はレート制限を意識した実装が必須
  • エラーハンドリングは BadRequestError(特に content_policy)と RateLimitError を最低限しっかりハンドリングしよう

本番サービスに組み込むなら、プロンプトテンプレート管理・キャッシング・レート制限対策の3つを最初から設計に入れておくと後が楽です。

何か間違いや追加したい情報があればコメントで教えてください。


参考リンク

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?