Qwen3-VL による Visual Question Answering(自由入力質問対応・ソースコードと実行結果)

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

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

Python 3.12 のインストール

Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。

[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]

Windows での Python 3.12 のインストール

以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。

方法 1:winget によるインストール

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。

REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""

REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; $c=[Environment]::GetEnvironmentVariable('Path','Machine'); if((Test-Path $p) -and (';'+$c+';' -notlike \"*;$p;*\") -and (';'+$c+';' -notlike \"*;$s;*\")){[Environment]::SetEnvironmentVariable('Path',\"$p;$s;$c\",'Machine')}"

方法 2:インストーラーによるインストール

  1. Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
  2. ダウンロードしたインストーラーを実行する。
  3. 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから python コマンドを実行できない。
  4. 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。

インストールの確認

コマンドプロンプトで以下を実行する。

python --version

バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。

Python の開発環境 Visual Studio Code のインストールと Python 用の設定

Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。

[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]

Windows での Visual Studio Code のインストールと Python 用の設定手順

1. VS Code と拡張機能のインストール

以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。

インストールコマンド


REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
winget uninstall -e --id Microsoft.VisualStudioCode --silent --disable-interactivity --accept-source-agreements
rmdir /s /q C:\ProgramData\vscode-extensions 2>nul
rmdir /s /q "%APPDATA%\Code" 2>nul
rmdir /s /q "%USERPROFILE%\.vscode" 2>nul
rmdir /s /q "%LOCALAPPDATA%\Microsoft\vscode-update" 2>nul

REM VS Code をシステム領域に新規インストール
winget install --scope machine --id Microsoft.VisualStudioCode -e --silent --accept-source-agreements --accept-package-agreements

REM 全ユーザー共有の拡張機能フォルダ
mkdir C:\ProgramData\vscode-extensions 2>nul
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T

REM スタートメニューのショートカットを --extensions-dir 付きで再作成
rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" 2>nul
del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" 2>nul
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"

REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f

REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"

REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
echo === セットアップ完了 ===

2. Python インタプリタの選択

同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.

  1. コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(Ctrl+Shift+P
  2. Python: Select Interpreter と入力する
  3. 表示される一覧から,使用する Python(例:C:\Program Files\Python312\python.exe)を選択する.

必要なライブラリをシステム領域にインストール

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

REM PyTorch をインストール(GPU対応版)
set "CUDA_TAG=cu128"
set "PYTHON_PATH=C:\Program Files\Python312"
"%PYTHON_PATH%\Scripts\pip" install --no-user -U numpy torch torchvision torchaudio --index-url https://download.pytorch.org/whl/%CUDA_TAG%
pip install --no-user -U transformers pillow opencv-python accelerate

Qwen3-VL による Visual Question Answering プログラム

概要

このプログラムは、画像の内容を理解し、自然言語による質問に対して回答する能力を示す。画像中の物体、場面、関係性を認識し、質問の意図を理解した上で、適切な回答を生成する。

主要技術

従来モデルからの変更点

本ページは、従来使用していたBLIP-2(Salesforce/blip2-opt-2.7b等)をQwen3-VL-4B-Instructに置き換えたものである。BLIP-2は言語モデル部分が2.7Bパラメータで、FP16精度でのVRAM使用量が約7.2GBであるのに対し、Qwen3-VL-4B-Instructは4Bパラメータで、FP16精度でのVRAM使用量は約9.7GBである。Qwen3-VLへの変更により、画像認識と言語理解の精度が向上し、日本語を含む多言語での質問応答が可能になる利点があるが、パラメータ数とVRAM使用量が増加するため、より多くのGPUメモリを必要とする点に注意が必要である。

参考文献

ソースコード

# Visual Question Answering with Qwen3-VL
# 特徴技術名: Qwen3-VL(Dense アーキテクチャ)
# 出典: Qwen Team. (2025). Qwen3-VL Technical Report. arXiv preprint arXiv:2511.21631.
# 特徴機能: 画像とテキストを統合的に理解する視覚言語モデルによる高精度な視覚的質問応答。Interleaved-MRoPEとDeepStackにより、画像とテキストの整合性が高い回答生成が可能。
# 学習済みモデル: Qwen/Qwen3-VL-4B-Instruct - 4Bパラメータの指示追従型視覚言語モデル、単一GPUでの動作に適する、https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct
# 方式設計:
#   関連利用技術: transformers(HuggingFace提供のTransformerモデルライブラリ、Qwen3-VLの利用にはバージョン4.57.0以上が必要)、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()による処理結果表示,プログラム終了時にresult.txtファイルに保存
#   処理手順: 1.学習済みQwen3-VLモデルとプロセッサの読み込み、2.入力画像の前処理、3.ユーザ入力質問でVQA推論実行、4.チャットテンプレートを用いた画像特徴量抽出と言語モデルによるテキスト生成、5.生成されたテキスト回答の後処理と表示
#   前処理、後処理: 前処理: 画像のRGB変換とプロセッサによる正規化・テンソル変換、後処理: 生成トークンのデコード、特殊トークン除去
#   追加処理: 画像品質チェック(入力画像の解像度とアスペクト比確認による推論精度向上)
#   調整を必要とする設定値: MAX_NEW_TOKENS(生成する最大トークン数、デフォルト256)
# 将来方策: MAX_NEW_TOKENSの動的調整機能(質問タイプに応じて、Yes/No質問は50、詳細説明質問は256など自動設定)
# その他の重要事項: GPU利用時のVRAM使用量監視が推奨、学習済みモデルの初回ダウンロード時間への配慮が必要
# 前準備: pip install -U --no-user numpy torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128
# pip install --no-user -U transformers pillow opencv-python accelerate

import cv2
import tkinter as tk
from tkinter import filedialog
import urllib.request
import os
from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
from PIL import Image
import torch

# 設定値
MODEL_NAME = 'Qwen/Qwen3-VL-4B-Instruct'  # 使用する学習済みモデル
MAX_NEW_TOKENS = 256  # 生成する最大トークン数(質問タイプに応じて調整)
FONT_SIZE = 0.7  # OpenCV表示のフォントサイズ
LINE_HEIGHT = 30  # テキスト表示の行間隔


def load_model():
    try:
        print('Qwen3-VLモデルを読み込み中...')
        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        print(f'使用デバイス: {device}')

        proc = AutoProcessor.from_pretrained(MODEL_NAME)
        mdl = Qwen3VLForConditionalGeneration.from_pretrained(
            MODEL_NAME,
            dtype=torch.float16 if device.type == 'cuda' else torch.float32,
            device_map='auto' if device.type == 'cuda' else None
        )
        if device.type == 'cpu':
            mdl = mdl.to(device)
        mdl.eval()

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


def process_vqa(img, proc, mdl, device, res):
    if img is None:
        print('画像の読み込みに失敗しました')
        return img

    disp = img.copy()
    y_pos = 30

    # 最初に画像を表示
    cv2.imshow('Image', disp)
    cv2.waitKey(1)

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

        try:
            pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

            # Qwen3-VLの公式使用方法(チャットテンプレート)
            messages = [
                {
                    'role': 'user',
                    'content': [
                        {'type': 'image', 'image': pil},
                        {'type': 'text', 'text': q}
                    ]
                }
            ]
            inputs = proc.apply_chat_template(
                messages,
                tokenize=True,
                add_generation_prompt=True,
                return_dict=True,
                return_tensors='pt'
            ).to(device)

            with torch.no_grad():
                generated_ids = mdl.generate(**inputs, max_new_tokens=MAX_NEW_TOKENS)

            generated_ids_trimmed = [
                out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs['input_ids'], generated_ids)
            ]
            ans = proc.batch_decode(generated_ids_trimmed, skip_special_tokens=True)[0].strip()

            res.append(f'Q: {q} A: {ans}')
            print(f'回答: {ans}')

            # 全ての質問と回答を表示
            cv2.putText(disp, f'Q: {q}', (10, y_pos), cv2.FONT_HERSHEY_SIMPLEX,
                       FONT_SIZE, (255, 255, 0), 2)
            # 長い回答の場合は省略表示
            display_ans = ans if len(ans) <= 50 else ans[:47] + '...'
            cv2.putText(disp, f'A: {display_ans}', (10, y_pos + LINE_HEIGHT), cv2.FONT_HERSHEY_SIMPLEX,
                       FONT_SIZE, (0, 255, 0), 2)
            y_pos += LINE_HEIGHT * 2

            # 更新した画像を表示
            cv2.imshow('Image', disp)
            cv2.waitKey(1)

        except Exception as e:
            print(f'VQA処理でエラーが発生しました: {e}')

    return disp


def show_img(img, win, proc, mdl, device, res):
    if img is None:
        print('画像の読み込みに失敗しました')
        return
    process_vqa(img, proc, mdl, device, res)
    cv2.waitKey(0)
    cv2.destroyWindow(win)


print('Qwen3-VL Visual Question Answering プログラム')
print('概要: 画像に関する質問に対してAIが回答します')
print('操作方法:')
print('  - 画像選択後、英語で質問を入力してください')
print('  - 複数の質問が可能です(quitで次の画像へ)')
print('  - カメラモード: スペースキーで撮影、qキーで終了')

proc, mdl, device = load_model()
res = []

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:
        show_img(cv2.imread(path), 'Image', proc, mdl, device, res)
elif choice == '1':
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    try:
        while True:
            cap.grab()
            ret, frame = cap.retrieve()
            if not ret:
                break
            cv2.imshow('Camera', frame)
            key = cv2.waitKey(1) & 0xFF
            if key == ord(' '):
                show_img(frame, 'Image', proc, mdl, device, res)
            elif key == ord('q'):
                break
    finally:
        cap.release()
elif choice == '2':
    urls = [
        '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'
    ]
    files = []
    for i, url in enumerate(urls):
        fname = f'sample_{i}.jpg'
        try:
            urllib.request.urlretrieve(url, fname)
            files.append(fname)
            show_img(cv2.imread(fname), 'Sample Image', proc, mdl, device, res)
        except Exception as e:
            print(f'画像のダウンロードに失敗しました: {url}')
            print(f'エラー: {e}')
            continue
    for fname in files:
        try:
            os.remove(fname)
        except OSError:
            pass

cv2.destroyAllWindows()

if res:
    with open('result.txt', 'w', encoding='utf-8') as f:
        for i, r in enumerate(res):
            f.write(f'{i+1}: {r}\n')
    print('result.txtに保存')