AIコーディング 2026.07.13

ChatGPT APIで画像をコンテキストとして送信する方法|GPT-4 Vision API完全実装ガイド【2026年版】

タグ:ChatGPT API / GPT-4 Vision / 画像処理 / OpenAI / Python

はじめに:ChatGPT APIで画像が使える理由

ChatGPT APIのVision機能(GPT-4 Vision)を使うと、テキストだけでなく画像をコンテキストとして同時に送信できるマルチモーダルモデルです。この機能により、開発者は以下のような用途を実装できます。

  • 商品画像の自動分析・タグ付け
  • スクリーンショットやドキュメント画像の解析
  • チャートやグラフの読み取り
  • 建築図面や設計図の検証
  • ユーザーが投稿した画像コンテンツのモデレーション
  • コード画像の解析:ホワイトボードに書かれたコードやアルゴリズムをデジタル化
  • UIデザインの品質チェック:デザイン、レイアウト、UI/UXの問題点を指摘

月1,000回以上検索されるほど需要の高い機能であるにもかかわらず、実装の詳細がわかりにくいため、このガイドではコードレベルで完全に説明します。

対応モデルとバージョン情報

GPT-4 Vision APIは複数のモデルで利用可能です。2026年8月現在、OpenAI公式ドキュメントで確認できる対応モデルは以下の通りです:

  • gpt-4o(推奨・高速・低価格)
  • gpt-4-turbo
  • gpt-4 (vision)(従来版)

画像処理が目的であれば、gpt-4oを最初の選択肢とすることが推奨されています。本ガイドではgpt-4oを使用したコード例を提供します。

必須の準備物

項目要件備考
OpenAI APIキー有効なAPI キーhttps://platform.openai.com で取得
Pythonバージョン3.8以上3.10以上を推奨
openai-pythonライブラリ1.3.0以上pip install openaiでインストール
追加ライブラリrequests, python-dotenv(オプション)画像URL指定や環境変数管理に便利

重要な制限事項

  • 対応画像形式: JPEG、PNG、GIF、WebP
  • 最大画像サイズ: 20MB(単一画像)
  • 複数画像の上限: 1リクエストあたり最大10枚
  • サポート言語: 英語が最適だが、日本語でも動作します

方法1:Base64エンコードで画像を埋め込む

ローカルファイルの画像をAPIに送信する最もシンプルな方法です。外部サービスに依存しないため、ファイアウォール下での使用にも適しています。

ステップ1:環境準備

pip install openai python-dotenv requests --upgrade

openaiライブラリを最新版に更新してください。APIキーの設定は、環境変数で行うのが安全です。プロジェクトのルートディレクトリに.envファイルを作成し、APIキーを記述します。

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxx

本番環境では、.envファイルを.gitignoreに追加して、GitHubなどにアップロードされないようにしてください。

export OPENAI_API_KEY="your-api-key-here"

ステップ2:画像ファイルをBase64エンコード

import base64
from pathlib import Path

def encode_image_to_base64(image_path: str) -> str:
    """
    ローカル画像ファイルをBase64文字列に変換
    対応形式: jpg, jpeg, png, gif, webp
    """
    with open(image_path, "rb") as image_file:
        return base64.standard_b64encode(image_file.read()).decode("utf-8")

# 使用例
image_base64 = encode_image_to_base64("product_photo.jpg")
print(f"エンコード完了。長さ: {len(image_base64)} 文字")

ステップ3:APIリクエストで画像を含める

import os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# ステップ2で取得したBase64文字列
image_base64 = encode_image_to_base64("product_photo.jpg")

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "この商品画像を分析してください。色、形、素材、用途を説明してください。"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_base64}"
                    },
                },
            ],
        }
    ],
)

print(response.choices[0].message.content)

メディアタイプの指定: 画像形式に合わせてURLスキームのdata:部分を設定してください。MIME タイプの指定は必須です。JPG画像を「image/png」と指定するとエラーになります。

  • JPEG: data:image/jpeg;base64,...(注意: image/jpg ではない)
  • PNG: data:image/png;base64,...
  • GIF: data:image/gif;base64,...
  • WebP: data:image/webp;base64,...

方法2:URLで画像を参照する

すでにウェブサーバーでホストされている画像の場合は、URLを直接指定できます。ファイルアップロードが不要になるため、レスポンスが高速です。

import os
from openai import OpenAI

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "このスクリーンショットに表示されているエラーメッセージを読み取り、原因を推測してください。"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/screenshots/error_2026_07_13.png",
                    },
                },
            ],
        }
    ],
)

print(response.choices[0].message.content)

URLを使う際の注意点

利点注意点
エンコード処理が不要URLにアクセスできない環境では使用不可
大容量ファイルに対応URLの有効期限切れでエラーになる可能性
複数画像の処理が簡単外部サイトの画像URL変更に影響される
  • URLは公開アクセス可能である必要があります
  • プライベートな画像はBase64エンコード方式で送信してください
  • HTTPSの使用が強く推奨されます

方法3:複数画像を同時に分析

1つのAPIリクエストで複数枚の画像を送信し、全体を比較分析させることもできます。複数の画像を個別にリクエストするより、1つのリクエストにまとめる方が効率的です。

import os
import base64
from openai import OpenAI

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def encode_image(image_path: str) -> str:
    with open(image_path, "rb") as img:
        return base64.standard_b64encode(img.read()).decode("utf-8")

# 複数画像をBase64に変換
image1_base64 = encode_image("before.jpg")
image2_base64 = encode_image("after.jpg")

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "この2枚の白背景商品画像を比較してください。写真品質、背景処理、照明の違いを指摘してください。"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image1_base64}"
                    },
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image2_base64}"
                    },
                },
            ],
        }
    ],
)

print(response.choices[0].message.content)

複数画像での分析は、以下のようなユースケースに有効です:

  • 写真品質の比較検証
  • 画像編集前後の確認
  • 類似製品の識別
  • デザイン案の比較評価

料金体系と費用最適化

GPT-4o の料金(2026年8月参考値)は以下の通りです:

  • 入力: テキスト100万トークン あたり $5、画像1,000トークン あたり $1.25
  • 出力: テキスト100万トークン あたり $15、画像1,000トークン あたり $5.00

画像トークン計算方法

画像は解像度に基づいてトークン化されます。

  • 小画像(512×512px以下): 85トークン
  • 標準画像: 約170トークン(512px × 多重スケーリング)
  • 高解像度画像(2048×768px以上): 最大2,550トークン

コスト削減テクニック

1. 画像解像度を最適化

GPT-4 Visionは高解像度画像でも分析できますが、実用的な用途ではVGA解像度(640×480)で十分なことが多いです。

from PIL import Image

def resize_image_for_api(image_path: str, max_width=1024) -> str:
    """
    APIコスト削減のため、大きな画像をリサイズ
    """
    img = Image.open(image_path)
    if img.width > max_width:
        ratio = max_width / img.width
        new_height = int(img.height * ratio)
        img = img.resize((max_width, new_height), Image.LANCZOS)
    
    import io
    import base64
    
    buffer = io.BytesIO()
    img.save(buffer, format="JPEG", quality=85)
    return base64.standard_b64encode(buffer.getvalue()).decode("utf-8")

# 使用例
optimized_base64 = resize_image_for_api("large_product_photo.jpg")

2. 必要な部分だけトリミング

大きな画像の中で分析対象が限定されている場合、トリミングしてからAPIに送信すればトークン数が削減されます。

from PIL import Image

def crop_image_for_analysis(image_path: str, crop_box: tuple) -> str:
    """
    crop_box: (left, top, right, bottom) ピクセル座標
    """
    img = Image.open(image_path)
    cropped = img.crop(crop_box)
    
    import io
    import base64
    
    buffer = io.BytesIO()
    cropped.save(buffer, format="JPEG")
    return base64.standard_b64encode(buffer.getvalue()).decode("utf-8")

# 使用例:画像の左上から640×480ピクセルの領域を抽出
crop_base64 = crop_image_for_analysis("screenshot.png", (0, 0, 640, 480))

レート制限と並行処理

ChatGPT APIにはレート制限が設定されています。多数の画像を処理する場合、エラーを回避するために適切な待機処理が必要です。

レート制限への対応

import time
import os
from openai import OpenAI, RateLimitError

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def call_api_with_retry(messages, model="gpt-4o", max_retries=3):
    """
    レート制限エラーを検出し、自動的に再試行
    """
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model=model,
                max_tokens=1024,
                messages=messages,
            )
            return response
        except RateLimitError:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # 指数バックオフ: 1秒, 2秒, 4秒
                print(f"レート制限に達しました。{wait_time}秒待機します...")
                time.sleep(wait_time)
            else:
                raise

# 使用例
messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "この画像を分析してください"},
            {
                "type": "image_url",
                "image_url": {
                    "url": f"data:image/jpeg;base64,{image_base64}"
                },
            },
        ],
    }
]

response = call_api_with_retry(messages)
print(response.choices[0].message.content)

複数画像のバッチ処理

大量の画像を処理する場合、キューを使って並行リクエストを制御します。

import asyncio
import os
import base64
from openai import AsyncOpenAI
from pathlib import Path

async def process_images_in_batch(image_paths: list, max_concurrent=3):
    """
    複数の画像ファイルを順序を保ちながら処理
    """
    client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    async def process_single_image(image_path: str):
        with open(image_path, "rb") as img:
            image_base64 = base64.standard_b64encode(img.read()).decode("utf-8")
        
        response = await client.chat.completions.create(
            model="gpt-4o",
            max_tokens=512,
            messages=[
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": "この画像を1文で説明してください"},
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": f"data:image/jpeg;base64,{image_base64}"
                            },
                        },
                    ],
                }
            ],
        )
        return image_path, response.choices[0].message.content
    
    # セマフォで並行リクエスト数を制限
    semaphore = asyncio.Semaphore(max_concurrent)
    
    async def bounded_task(image_path):
        async with semaphore:
            return await process_single_image(image_path)
    
    results = await asyncio.gather(
        *[bounded_task(path) for path in image_paths]
    )
    return results

# 使用例
image_files = [
    "product_1.jpg",
    "product_2.jpg",
    "product_3.jpg",
    "product_4.jpg",
    "product_5.jpg",
]

results = asyncio.run(process_images_in_batch(image_files, max_concurrent=2))
for image_path, analysis in results:
    print(f"{image_path}: {analysis}")

よくあるエラーと対処法

エラー1:「Invalid API Key」/「Invalid base64 string」

「Invalid API Key」の原因: APIキーが正しく設定されていない、または無効になっている

対処法:

import os
from dotenv import load_dotenv

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
    print("エラー: OPENAI_API_KEYが設定されていません")
elif not api_key.startswith("sk-"):
    print("エラー: APIキーの形式が正しくありません")
else:
    print(f"APIキーの先頭4文字: {api_key[:4]}...")
  • .envファイルが実際に存在しているか確認
  • APIキーの有効期限を確認(OpenAIダッシュボードで確認可能)
  • キーの前後に空白がないか確認

「Invalid base64 string」の原因: Base64エンコードが不完全または不正な形式

対処法:

import base64
from pathlib import Path

def validate_and_encode_image(image_path: str) -> tuple:
    """
    ファイルの存在・形式を確認してからBase64エンコードする
    """
    if not Path(image_path).exists():
        raise FileNotFoundError(f"ファイルが見つかりません: {image_path}")
    
    supported_formats = [".jpg", ".jpeg", ".png", ".gif", ".webp"]
    file_ext = Path(image_path).suffix.lower()
    if file_ext not in supported_formats:
        raise ValueError(f"サポートされていない形式です: {file_ext}")
    
    mime_map = {
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".png": "image/png",
        ".gif": "image/gif",
        ".webp": "image/webp"
    }
    mime_type = mime_map[file_ext]
    
    with open(image_path, "rb") as f:
        base64_string = base64.b64encode(f.read()).decode("utf-8")
    
    return f"data:{mime_type};base64,{base64_string}", mime_type

# 使用例
try:
    image_url, mime = validate_and_encode_image("product.png")
    print(f"エンコード成功。MIMEタイプ: {mime}")
except Exception as e:
    print(f"エラー: {e}")

エラー2:「Invalid image format」

原因: 指定したメディアタイプが実際の画像形式と一致していない、または非対応形式(TIFF、BMP、SVGなど)

対処法:

from PIL import Image

def get_media_type_from_file(image_path: str) -> str:
    """
    ファイルから正しいメディアタイプを検出
    """
    img = Image.open(image_path)
    format_map = {
        "JPEG": "image/jpeg",
        "PNG": "image/png",
        "GIF": "image/gif",
        "WEBP": "image/webp",
    }
    return format_map.get(img.format, "image/jpeg")

media_type = get_media_type_from_file("product.png")
print(f"検出されたメディアタイプ: {media_type}")

非対応形式の場合は変換が必要です:

# TIFFやBMPをPNGに変換
img = Image.open("image.tiff")
img.save("image.png")

エラー3:「401 Unauthorized」/「Rate limit exceeded」

「401 Unauthorized」の原因: APIキーが無効、または有効期限が切れている

対処法:

from openai import OpenAI, AuthenticationError

def verify_api_key():
    """APIキーの有効性を確認"""
    try:
        client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
        client.models.list()
        print("✓ APIキーは有効です")
        return True
    except AuthenticationError:
        print("✗ APIキーが無効です。新しいキーを取得してください")
        return False

verify_api_key()

「Rate limit exceeded」の原因: APIのレート制限に達した。通常、APIキーあたり1分間のリクエスト数制限がある

対処法: 前述の「レート制限と並行処理」を参照してください。

エラー4:「Image too large」 / 「Request too large」

原因: 画像サイズが20MBを超えている、または複数の大きな画像を同時に送信しようとしている

対処法:

from pathlib import Path
from PIL import Image
import base64, os

def check_image_size(image_path: str, max_size_mb=20) -> bool:
    """
    APIの最大サイズ制限をチェック
    """
    file_size_mb = Path(image_path).stat().st_size / (1024 * 1024)
    if file_size_mb > max_size_mb:
        print(f"ファイルサイズが大きすぎます: {file_size_mb:.2f}MB")
        return False
    return True

def compress_image(image_path: str, max_width=1024, quality=85) -> str:
    """画像を圧縮してBase64エンコードする"""
    img = Image.open(image_path)
    if img.width > max_width:
        ratio = max_width / img.width
        new_height = int(img.height * ratio)
        img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
    
    import io
    buffer = io.BytesIO()
    img.save(buffer, format="JPEG", quality=quality, optimize=True)
    return base64.b64encode(buffer.getvalue()).decode("utf-8")

# 大きなファイルはリサイズして対応
if not check_image_size("large_image.jpg"):
    compressed_base64 = compress_image("large_image.jpg")

エラー5:「Model not found」

原因: モデル名が正しくない、または使用APIキーがVision対応モデルへのアクセス権を持っていない

対処法: 利用可能なモデルを確認します。

# 利用可能なモデルを確認
models = client.models.list()
vision_models = [m.id for m in models.data if "vision" in m.id or "gpt-4" in m.id]
print("利用可能なVisionモデル:", vision_models)

OpenAI公式ドキュメント(https://platform.openai.com/docs/models)で最新の対応モデル名を確認してください。現在は`gpt-4o`が推奨されています。

実装例:完全な画像分析パイプライン

ここまでの内容を組み合わせた、本番環境で使用できるサンプルコードを示します。

import os
import time
import base64
import logging
from pathlib import Path
from typing import Optional
from dataclasses import dataclass
from openai import OpenAI, RateLimitError, APIError

# ロギング設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class ImageAnalysisResult:
    image_path: str
    analysis: str
    token_count: int
    processing_time_seconds: float

class ImageAnalyzer:
    def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o"):
        self.client = OpenAI(api_key=api_key or os.environ.get("OPENAI_API_KEY"))
        self.model = model
        self.max_retries = 3
        self.retry_delay = 2
    
    def encode_image(self, image_path: str) -> str:
        """画像をBase64エンコード"""
        ext = Path(image_path).suffix.lower().lstrip(".")
        supported = ["jpg", "jpeg", "png", "gif", "webp"]
        if ext not in supported:
            raise ValueError(f"非対応形式: {ext}. 対応: {supported}")
        with open(image_path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")
    
    def get_media_type(self, image_path: str) -> str:
        """ファイル拡張子からメディアタイプを推定"""
        ext = Path(image_path).suffix.lower()
        media_types = {
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".png": "image/png",
            ".gif": "image/gif",
            ".webp": "image/webp",
        }
        return media_types.get(ext, "image/jpeg")
    
    def analyze_image(
        self,
        image_path: str,
        prompt: str,
        max_retries: int = 3,
    ) -> ImageAnalysisResult:
        """
        単一の画像を分析し、結果を返す
        """
        if not os.path.exists(image_path):
            raise FileNotFoundError(f"ファイルが見つかりません: {image_path}")
        
        file_size_mb = os.path.getsize(image_path) / (1024 ** 2)
        if file_size_mb > 20:
            logger.warning(f"ファイルサイズが大きい: {file_size_mb:.2f}MB")
        
        start_time = time.time()
        image_base64 = self.encode_image(image_path)
        media_type = self.get_media_type(image_path)
        
        for attempt in range(max_retries):
            try:
                response = self.client.chat.completions.create(
                    model=self.model,
                    max_tokens=1024,
                    messages=[
                        {
                            "role": "user",
                            "content": [
                                {"type": "text", "text": prompt},
                                {
                                    "type": "image_url",
                                    "image_url": {
                                        "url": f"data:{media_type};base64,{image_base64}"
                                    },
                                },
                            ],
                        }
                    ],
                )
                
                processing_time = time.time() - start_time
                logger.info(f"API呼び出し成功。トークン使用: {response.usage.total_tokens}")
                
                return ImageAnalysisResult(
                    image_path=image_path,
                    analysis=response.choices[0].message.content,
                    token_count=response.usage.total_tokens,
                    processing_time_seconds=processing_time,
                )
            
            except RateLimitError:
                if attempt < max_retries - 1:
                    wait_time = self.retry_delay * (2 ** attempt)
                    logger.warning(f"レート制限。{wait_time}秒待機...")
                    time.sleep(wait_time)
                else:
                    raise
            except APIError as e:
                logger.error(f"APIエラー: {e}")
                raise
    
    def analyze_multiple_images(
        self,
        image_paths: list,
        prompt: str,
    ) -> list:
        """複数の画像を順序を保ちながら分析"""
        results = []
        for i, image_path in enumerate(image_paths, 1):
            print(f"処理中: {i}/{len(image_paths)} - {image_path}")
            result = self.analyze_image(image_path, prompt)
            results.append(result)
            
            # リクエスト間に短い待機を入れてレート制限を回避
            if i < len(image_paths):
                time.sleep(1)
        
        return results

# 使用例
if __name__ == "__main__":
    analyzer = ImageAnalyzer(api_key=os.getenv("OPENAI_API_KEY"))
    
    # 単一画像の分析
    result = analyzer.analyze_image(
        "sample.png",
        "この画像に何が写っていますか?詳

---

## あわせて読みたい

- [AIから正確な回答をもらう7つのプロンプト術【Claude・ChatGPT実例付き】](/code/ai-7-2/)
- [OpusがSonnetを指揮する2モデル協調パターン|コスト30%削減のPython実装](/code/two-models-collaborate-single-api-call/)
- [Claude Code Context Window完全ガイド【2026年版200K活用とContext超過エラーの解決法](/code/claude-code-context-window/)

参考ソース