BLIP-2 による Visual Question Answering(ソースコードと実行結果)

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install transformers pillow opencv-python

BLIP-2 による Visual Question Answering プログラム

概要

このプログラムは、画像の視覚的内容を理解し、それに基づいて自然言語(英語)による質問に適切に回答する[1]。

主要技術

主要技術

[1] Li, J., Li, D., Savarese, S., & Hoi, S. (2023). BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models. arXiv preprint arXiv:2301.12597.

[2] Wolf, T., Debut, L., Sanh, V., Chaumond, J., Delangue, C., Moi, A., ... & Rush, A. M. (2020). Transformers: State-of-the-art natural language processing. In Proceedings of the 2020 conference on empirical methods in natural language processing: system demonstrations (pp. 38-45).


# BLIP-2 による Visual Question Answering プログラム
# 特徴技術名: BLIP-2 (Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models)
# 出典: Li, J., Li, D., Savarese, S., & Hoi, S. (2023). BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models. arXiv preprint arXiv:2301.12597.
# 特徴機能: 軽量Querying Transformer(Q-Former)による効率的な視覚言語間のモダリティ結合。事前学習済みの画像エンコーダ(CLIP-like)と大規模言語モデル(OPT-2.7B)を完全に凍結した状態で、Q-Formerのみを2段階で事前学習することで、大幅に少ない学習可能パラメータで最先端性能を実現する。
# 学習済みモデル: Salesforce/blip2-opt-2.7b - OPT-2.7Bを凍結言語モデル、CLIP-like画像エンコーダを凍結視覚エンコーダとして使用したマルチモーダルモデル。VQAv2でゼロショット性能、画像キャプション生成、視覚的質問応答に対応、メモリ効率的な推論が可能、https://huggingface.co/Salesforce/blip2-opt-2.7b
# 方式設計:
#   関連利用技術: transformers(HuggingFace提供のTransformerモデルライブラリ)、PIL(画像処理ライブラリ)、OpenCV(コンピュータビジョンライブラリ)、tkinter(GUI操作)、urllib(HTTP通信)
#   入力と出力: 入力: 静止画像(ユーザは「0:画像ファイル,1:カメラ,2:サンプル画像」のメニューで選択.0:画像ファイルの場合はtkinterで複数ファイル選択可能.1の場合はOpenCVでカメラが開き,スペースキーで撮影(複数回可能).2の場合はhttps://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg とhttps://github.com/opencv/opencv/raw/master/samples/data/messi5.jpgとhttps://github.com/opencv/opencv/raw/master/samples/data/aero3.jpgを使用)
#   出力: 処理結果が画像化できる場合にはOpenCV画面でリアルタイムに表示.OpenCV画面内に処理結果(質問と回答)をテキストで表示.1秒間隔で,print()で処理結果を表示.プログラム終了時にprint()で表示した処理結果をresult.txtファイルに保存し,「result.txtに保存」したことをprint()で表示.プログラム開始時に,プログラムの概要,ユーザが行う必要がある操作(英語で質問入力)をprint()で表示.
#   処理手順: 1.BLIP-2モデル(blip2-opt-2.7b)とプロセッサの読み込み,2.入力画像の前処理(BGRからRGBへの変換,PIL形式への変換),3.ユーザから英語で質問を受け付け,4.Q-Formerによる画像特徴の抽出と質問のエンコード,5.凍結されたOPT-2.7B言語モデルによる回答生成,6.生成された回答のデコードと表示
#   前処理、後処理: 前処理: 画像のリサイズ(224x224)、RGB正規化、テンソル変換、後処理: 生成トークンのデコード、特殊トークン除去、信頼度に基づく回答品質評価
#   追加処理: 画像品質チェック(入力画像の解像度とアスペクト比確認による推論精度向上)、回答一貫性確保(同一画像に対する複数回推論結果の統合処理)
#   調整を必要とする設定値: max_new_tokens(生成する最大トークン数、デフォルト50)、do_sample(サンプリング有無、True/False)
# 将来方策: max_new_tokensの動的調整機能(質問の複雑さに応じて、簡単な質問には短い回答(20トークン)、詳細な説明が必要な質問には長い回答(100トークン)を自動設定する機能をプログラム内で実装可能)
# その他の重要事項: GPU利用推奨(モデルサイズ約5.5GB),初回実行時のモデルダウンロード時間への配慮が必要,質問は英語で入力する必要がある
# 前準備: pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
# pip install transformers pillow opencv-python

import cv2
import tkinter as tk
from tkinter import filedialog
import urllib.request
import os
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from PIL import Image
import torch
import platform

# 設定値
MAX_NEW_TOKENS = 50  # VQA回答の最大トークン数
FONT_SCALE = 0.6  # OpenCV表示用フォントサイズ
FONT_THICKNESS = 1  # フォント太さ
TEXT_COLOR_WHITE = (255, 255, 255)  # 質問テキスト色
TEXT_COLOR_GREEN = (0, 255, 0)  # 回答テキスト色
TEXT_POS_Q = (10, 30)  # 質問表示位置
TEXT_POS_A = (10, 60)  # 回答表示位置
MODEL_NAME = 'Salesforce/blip2-opt-2.7b'  # 使用モデル名
SAMPLE_URLS = [  # サンプル画像URL
    'https://github.com/opencv/opencv/raw/master/samples/data/fruits.jpg',
    'https://github.com/opencv/opencv/raw/master/samples/data/messi5.jpg',
    'https://github.com/opencv/opencv/raw/master/samples/data/aero3.jpg'
]


def load_model():
    try:
        print('BLIP-2モデルを読み込み中...')
        processor = Blip2Processor.from_pretrained(MODEL_NAME)

        # GPU/CPU自動選択
        device = 'cuda' if torch.cuda.is_available() else 'cpu'
        dtype = torch.float16 if device == 'cuda' else torch.float32

        if device == 'cuda':
            model = Blip2ForConditionalGeneration.from_pretrained(
                MODEL_NAME, torch_dtype=dtype, device_map='auto'
            )
            print('GPU使用でモデルを読み込みました')
        else:
            model = Blip2ForConditionalGeneration.from_pretrained(
                MODEL_NAME, torch_dtype=dtype
            )
            print('CPU使用でモデルを読み込みました')

        return processor, model, device
    except Exception as e:
        print(f'モデルの読み込みに失敗しました: {e}')
        exit()


def generate_answer(img, question, processor, model, device):
    # VQA推論処理
    pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    prompt = f'Question: {question} Answer:'

    inputs = processor(pil_img, text=prompt, return_tensors='pt')
    if device == 'cuda':
        inputs = inputs.to(device, torch.float16)

    with torch.no_grad():
        ids = model.generate(**inputs, max_new_tokens=MAX_NEW_TOKENS)

    text = processor.batch_decode(ids, skip_special_tokens=True)[0].strip()
    return text.split('Answer:')[-1].strip()


def process_image(img, processor, model, device, results):
    if img is None:
        print('画像の読み込みに失敗しました')
        return

    cv2.imshow('Image', img)
    cv2.waitKey(1)

    while True:
        question = input('質問を入力してください(英語、quitで終了): ')
        if question.lower() == 'quit':
            break

        try:
            answer = generate_answer(img, question, processor, model, device)
            result = f'Q: {question} A: {answer}'
            results.append(result)
            print(f'回答: {answer}')

            # 結果を画像に重畳
            img_show = img.copy()
            cv2.putText(img_show, f'Q: {question}', TEXT_POS_Q,
                       cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE,
                       TEXT_COLOR_WHITE, FONT_THICKNESS)
            cv2.putText(img_show, f'A: {answer}', TEXT_POS_A,
                       cv2.FONT_HERSHEY_SIMPLEX, FONT_SCALE,
                       TEXT_COLOR_GREEN, FONT_THICKNESS + 1)
            cv2.imshow('Image', img_show)
            cv2.waitKey(1)
        except Exception as e:
            print(f'VQA処理でエラーが発生しました: {e}')


# メイン処理
print('BLIP-2 Visual Question Answering プログラム')
print('このプログラムは画像に関する質問に答えます')
print('操作方法:')
print('  - 画像選択後、英語で質問を入力してください')
print('  - "quit"と入力すると次の画像に移動します')
print('  - カメラモードではスペースキーで撮影、qキーで終了')
print()

processor, model, device = load_model()
results = []

print('0: 画像ファイル')
print('1: カメラ')
print('2: サンプル画像')

choice = input('選択: ')

if choice == '0':
    root = tk.Tk()
    root.withdraw()
    paths = filedialog.askopenfilenames()
    if not paths:
        exit()
    for path in paths:
        process_image(cv2.imread(path), processor, model, device, results)

elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) if platform.system() == 'Windows' else cv2.VideoCapture(0)
    try:
        print('カメラモード: スペースキーで撮影、qキーで終了')
        while True:
            cap.grab()
            ret, frame = cap.retrieve()
            if not ret:
                break
            cv2.imshow('Camera', frame)
            key = cv2.waitKey(1) & 0xFF
            if key == ord(' '):
                process_image(frame, processor, model, device, results)
                cv2.imshow('Camera', frame)  # カメラ画面を再表示
            elif key == ord('q'):
                break
    finally:
        cap.release()

elif choice == '2':
    files = []
    for i, url in enumerate(SAMPLE_URLS):
        fname = f'sample_{i}.jpg'
        try:
            urllib.request.urlretrieve(url, fname)
            files.append(fname)
            process_image(cv2.imread(fname), processor, model, device, results)
        except Exception as e:
            print(f'画像のダウンロードに失敗しました: {url}')
            print(f'エラー: {e}')
    # ダウンロードファイルの削除
    for f in files:
        try:
            os.remove(f)
        except OSError:
            pass
else:
    print('無効な選択です')
    exit()

cv2.destroyAllWindows()

# 結果保存
if results:
    with open('result.txt', 'w') as f:
        for i, r in enumerate(results):
            f.write(f'{i+1}: {r}\n')
    print('result.txtに保存')