AIコーディング 2026.07.13

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

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

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

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

  • 商品画像の自動分析・タグ付け
  • スクリーンショットやドキュメント画像の解析
  • チャートやグラフの読み取り
  • 建築図面や設計図の検証
  • ユーザーが投稿した画像コンテンツのモデレーション

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

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

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

  • gpt-4o(推奨・高速・低価格)
  • gpt-4-turbo
  • gpt-4-vision-preview(レガシー)

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

重要な制限事項

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

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

ローカルファイルの画像をAPIに送信する最もシンプルな方法です。

ステップ1:環境準備

pip install openai

openaiライブラリを最新版に更新してください。

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

import base64
from pathlib import Path

def encode_image_to_base64(image_path: str) -> str:
    """
    ローカル画像ファイルをBase64文字列に変換
    """
    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リクエストで画像を含める

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

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

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

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

メディアタイプの指定: 画像形式に合わせてmedia_typeを設定してください。

  • JPEG: image/jpeg
  • PNG: image/png
  • GIF: image/gif
  • WebP: image/webp

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

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

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

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

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

URLを使う際の注意点

  • URLは公開アクセス可能である必要があります
  • プライベートな画像はBase64エンコード方式で送信してください
  • HTTPSの使用が強く推奨されます

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

1つのAPIリクエストで複数枚の画像を送信し、全体を比較分析させることもできます。

from openai import OpenAI
import base64

client = OpenAI(api_key="your-api-key-here")

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.messages.create(
    model="gpt-4o",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "この2枚の白背景商品画像を比較してください。写真品質、背景処理、照明の違いを指摘してください。"
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": image1_base64,
                    },
                },
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": image2_base64,
                    },
                },
            ],
        }
    ],
)

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

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

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

料金体系と費用最適化

GPT-4 Vision APIの料金は、テキストトークンと画像トークンの両方で課金されます。

GPT-4oの料金(2026年参考値)

  • 入力: テキスト1M トークン あたり $2.50、画像1,000トークン あたり $1.25
  • 出力: テキスト1M トークン あたり $10.00、画像1,000トークン あたり $5.00

画像トークン計算方法

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

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

コスト削減テクニック

1. 画像解像度を最適化

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
from openai import OpenAI, RateLimitError

client = OpenAI(api_key="your-api-key-here")

def call_api_with_retry(messages, model="gpt-4o", max_retries=3):
    """
    レート制限エラーを検出し、自動的に再試行
    """
    for attempt in range(max_retries):
        try:
            response = client.messages.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",
                "source": {
                    "type": "base64",
                    "media_type": "image/jpeg",
                    "data": image_base64,
                },
            },
        ],
    }
]

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

複数画像のバッチ処理

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

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

async def process_images_in_batch(image_paths: list, max_concurrent=3):
    """
    複数の画像ファイルを順序を保ちながら処理
    """
    client = AsyncOpenAI(api_key="your-api-key-here")
    
    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.messages.create(
            model="gpt-4o",
            max_tokens=512,
            messages=[
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": "この画像を1文で説明してください"},
                        {
                            "type": "image",
                            "source": {
                                "type": "base64",
                                "media_type": "image/jpeg",
                                "data": 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 base64 string」

原因: Base64エンコードが不完全または不正な形式

対処法:

import base64

def validate_base64(data: str) -> bool:
    """
    Base64文字列が有効か確認
    """
    try:
        return base64.b64encode(base64.b64decode(data)) == data.encode()
    except Exception:
        return False

# 正しいエンコード方法
with open("image.jpg", "rb") as f:
    valid_base64 = base64.b64encode(f.read()).decode("utf-8")
    print(f"有効: {validate_base64(valid_base64)}")

エラー2:「Invalid image format」

原因: 指定したmedia_typeが実際の画像形式と一致していない、または非対応形式

対処法:

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}")

エラー3:「Rate limit exceeded」

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

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

エラー4:「Image too large」

原因: 画像サイズが20MBを超えている

対処法:

from pathlib import Path

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

# 大きなファイルはリサイズして対応
if not check_image_size("large_image.jpg"):
    from PIL import Image
    img = Image.open("large_image.jpg")
    img.thumbnail((2048, 2048))
    img.save("large_image_resized.jpg", quality=85)

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

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

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

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

class ImageAnalyzer:
    def __init__(self, api_key: str, model: str = "gpt-4o"):
        self.client = OpenAI(api_key=api_key)
        self.model = model
    
    def encode_image(self, image_path: str) -> str:
        """画像をBase64エンコード"""
        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:
        """
        単一の画像を分析し、結果を返す
        """
        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.messages.create(
                    model=self.model,
                    max_tokens=1024,
                    messages=[
                        {
                            "role": "user",
                            "content": [
                                {"type": "text", "text": prompt},
                                {
                                    "type": "image",
                                    "source": {
                                        "type": "base64",
                                        "media_type": media_type,
                                        "data": image_base64,
                                    },
                                },
                            ],
                        }
                    ],
                )
                
                processing_time = time.time() - start_time
                
                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 = 2 ** attempt
                    print(f"レート制限。{wait_time}秒待機...")
                    time.sleep(wait_time)
                else:
                    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="your-api-key-here")
    
    # 単一画像の分析
    result = analyzer.analyze_image(
        image_path="product.jpg",
        prompt="この商品の色、素材、サイズを説明してください。",
    )
    
    print(f"画像: {result.image_path}")
    print(f"分析結果:\n{result.analysis}")
    print(f"トークン数: {result.token_count}")
    print(f"処理時間: {result.processing_time_seconds:.2f}\n")
    
    # 複数画像の分析
    images = ["product_1.jpg", "product_2.jpg", "product_3.jpg"]
    results = analyzer.analyze_multiple_images(
        image_paths=images,
        prompt="この画像の商品をECサイト用に1文で説明してください。",
    )
    
    for result in results:
        print(f"{result.image_path}: {result.analysis}")

Vision APIの現在の制限と回避方法

問題1:複雑な画像認識の精度

Vision APIは一般的な物体認識は得意ですが、以下のような場合は精度が低下します。

  • 手書き文字の認識(OCR)
  • テーブルやチャートの数値精密読み取り
  • 医療画像の専門的な診断

このような場合は、OpenAIの画像生成API(DALL-E)ではなく、専門的なOCRサービスやドメイン特化型モデルと組み合わせることが推奨されます。

問題2:画像内テキストの多言語対応

日本語テキストを含む画像の分析は動作しますが、英語よりも精度が低下する傾向があります。重要なテキスト抽出が必要な場合は、Google Cloud Vision APIなどの専門ツールの使用を検討してください。

応用例:実務で使えるユースケース

ユースケース1:ECサイト商品画像の自動カテゴリ分類

CATEGORIES = ["衣料品", "電子機器", "家具", "食品", "その他"]

def categorize_product_image(image_path: str, analyzer: ImageAnalyzer) -> str:
    """
    商品画像を自動的にカテゴリに分類
    """
    prompt = f"""
    この商品画像をECサイト用に分類してください。
    選択肢: {', '.join(CATEGORIES)}
    
    JSONフォーマットで返してください:
    {{"category": "カテゴリ名", "confidence": 0.95, "reason": "理由"}}
    """
    result = analyzer.analyze_image(image_path, prompt)
    
    import json
    try:
        return json.loads(result.analysis)
    except json.JSONDecodeError:
        return {"category": "未分類", "confidence": 0.0}

ユースケース2:ユーザー投稿画像のモデレーション

def check_image_safety(image_path: str, analyzer: ImageAnalyzer) -> dict:
    """
    投稿画像が利用規約に適合しているかチェック
    """
    prompt = """
    この画像の内容を評価してください。以下の観点で判定してください:
    
    1. 不適切なコンテンツが含まれているか
    2. テキストベースのスパムが含まれているか
    3. 非常に低い品質(ぼやけ、ノイズが大きい)か
    
    JSONで返してください:
    {"is_safe": true/false, "violations": [], "quality_score": 0-100}
    """
    result = analyzer.analyze_image(image_path, prompt)
    
    import json
    try:
        return json.loads(result.analysis)
    except json.JSONDecodeError:
        return {"is_safe": True, "violations": [], "quality_score": 50}

まとめ

ChatGPT APIで画像をコンテキストとして送信する方法は、実装の難易度が低いわりに活用範囲が広く、実務での価値が高い機能です。

本ガイドのポイント:

  1. Base64エンコード方式は確実だが、大容量ファイルはコスト増
  2. URL参照方式は高速だが、プライベート画像には不適切
  3. 複数画像の比較分析で複雑な判断ロジックが実装できる
  4. レート制限対策(指数バックオフ、並行制御)は本番環境で必須
  5. 画像解像度の最適化でコストを30~50%削減可能

実装時は、対応モデル(gpt-4o推奨)と料金体系を確認し、レート制限エラーへの耐性を組み込むことで、信頼性の高いシステムが構築できます。


あわせて読みたい

参考ソース