images/ ディレクトリ内の画像から、入力テキストに意味的に近い画像を検索するプログラムです。
Qwen3-VL-Embedding-2B を使って、画像とテキストを同じ埋め込み空間のベクトルに変換し、FAISS で類似検索します。
.
├── images/
│ ├── sample001.jpg
│ ├── sample002.jpg
│ └── ...
├── data/
│ ├── images.faiss
│ ├── image_paths.json
│ └── image_dates.json
├── results/
│ └── 20260424_153012/
│ ├── query.txt
│ ├── 01_0.8123_sample001.jpg
│ └── ...
├── scripts/
│ ├── build_index.py
│ ├── generate_image_dates.py
│ └── search.py
├── requirements.txt
└── README.md
仮想環境を作成します。
python -m venv .venv
source .venv/bin/activateWindows の場合は以下です。
.venv\Scripts\activate依存ライブラリをインストールします。
pip install -U pip
pip install -r requirements.txt検索対象の画像を images/ ディレクトリに入れます。
対応拡張子:
.jpg
.jpeg
.png
例:
images/
├── car_001.jpg
├── dog_002.jpg
└── snow_003.jpg
最初に、画像をベクトル化して検索用インデックスを作成します。
python scripts/build_index.py成功すると、以下のファイルが作成されます。
data/images.faiss
data/image_paths.json
images.faiss: 画像ベクトルの検索インデックスimage_paths.json: ベクトルIDと元画像ファイルパスの対応表
画像に実際の日付メタデータがない場合は、検索実験用にランダムな日付を割り当てられます。
python scripts/generate_image_dates.pyデフォルトでは data/image_paths.json の各画像に対して、2023-01-01 から 2025-12-31 までの1096日を割り当て、data/image_dates.json に保存します。
10万件の場合は、各日に91枚または92枚が対応するように日付ラベルを作ってからシャッフルします。
期間や乱数シードを変更する場合は、以下のように指定します。
python scripts/generate_image_dates.py --start-date 2023-01-01 --end-date 2025-12-31 --seed 42image_dates.json は以下のような形式です。
{
"schema_version": 1,
"start_date": "2023-01-01",
"end_date": "2025-12-31",
"dates_by_path": {
"images/sample001.jpg": "2023-02-11"
}
}scripts/search.py は data/image_dates.json が存在する場合だけ自動で読み込み、検索結果の表示とLLMへ渡す画像文脈に日付を含めます。
画像を追加・削除・変更した場合は、インデックス作成と日付メタデータ作成を再実行してください。
以下のように検索します。
python scripts/search.py "赤い車が雪道を走っている"デフォルトでは上位10件を検索します。
検索結果は標準出力に表示されるだけでなく、results/ ディレクトリにもコピーされます。
例:
results/
└── 20260424_153012/
├── query.txt
├── raw_query.txt
├── llm_response.txt
├── ollama_thinking.txt
├── ollama_thinking.json
├── 01_0.8123_car_001.jpg
├── 02_0.7991_snow_045.jpg
├── 03_0.7814_vehicle_120.jpg
└── ...
検索を実行するたびに、results/実行時刻/ というディレクトリが作成されます。
ディレクトリ名の形式:
YYYYMMDD_HHMMSS
例:
20260424_153012
各画像ファイル名は以下の形式で保存されます。
順位_スコア_元ファイル名
例:
01_0.8123_car_001.jpg
02_0.7991_snow_045.jpg
また、検索に使ったクエリは以下に保存されます。
query.txt
例:
赤い車が雪道を走っている
query.txt には実際に検索へ使ったクエリが保存されます。ユーザが入力した元の文は raw_query.txt に保存されます。クエリ変換を使わない通常検索では、両方とも同じ内容になります。
LLM の最終回答は llm_response.txt に保存されます。
Ollama の thinking は ollama_thinking.txt に保存され、画像検索要否判定、クエリ変換、回答生成の各ステップごとに確認できます。
推論中も ollama_thinking.txt へ逐次追記され、処理完了後に同じファイルが整形済みの内容で上書きされます。
同じ内容を機械的に扱いやすい形式で確認したい場合は ollama_thinking.json を参照してください。
画像検索が不要と判定された場合も results/実行時刻/ は作成され、raw_query.txt、query.txt、llm_response.txt、ollama_thinking.txt、ollama_thinking.json が保存されます。
検索時は、まず Ollama の qwen3.5:9b が回答に過去画像データベースの参照が必要かを Yes / No で判定します。
Yes の場合は画像検索し、最上位の検索結果画像を LLM に渡して回答を生成します。
No の場合は画像検索を行わず、LLM が通常のテキスト質問として回答します。
検索結果画像を使って回答させるには、--ollama-model に画像入力へ対応した Ollama モデルを指定してください。
--interactive では LLM 回答がストリーミング表示されます。
画像検索が必要な場合に、Ollama で入力を画像検索向けの短い視覚クエリに変換してから検索したい場合は、--query-rewrite を指定します。
python scripts/search.py "赤い車が雪道を走っている" --query-rewriteOllama の接続先やモデルを変える場合は、以下のオプションを使います。
python scripts/search.py "赤い車が雪道を走っている" --query-rewrite --ollama-url http://localhost:11434 --ollama-model qwen3.5:9bthinking を有効にしているため、初回ロードや長い推論で時間がかかる場合があります。タイムアウトする場合は --ollama-timeout を大きくしてください。
python scripts/search.py "以前雪道を走ったよね?" --ollama-timeout 600thinking が長くなりすぎる場合は、ステップごとに概算トークン上限を変更できます。
上限を超えた場合は、その時点までの thinking を直前の句点または改行までで区切り、think=False の新しい Ollama リクエストで最終出力だけを生成します。
デフォルト:
--thinking-budget-decision 500
--thinking-budget-rewrite 500
--thinking-budget-answer 500
例:
python scripts/search.py "以前雪道を走ったよね?" --thinking-budget-answer 8000 以下を指定すると、そのステップの thinking 上限を無効化します。
--top-k を指定すると、保存する検索結果の数を変更できます。
python scripts/search.py "夕焼けの海辺を走る犬" --top-k 20この場合、上位20件の画像が results/実行時刻/ にコピーされます。
images/ の中身を変更した場合は、検索前に以下を再実行してください。
python scripts/build_index.pyインデックス作成:
python scripts/build_index.py検索:
python scripts/search.py "赤い車が雪道を走っている"上位20件を検索:
python scripts/search.py "赤い車が雪道を走っている" --top-k 20python scripts/search.py "赤い車が雪道を走っている"出力例:
raw query: 赤い車が雪道を走っている
needs image search: Yes
search query: 赤い車が雪道を走っている
results: results/20260424_153012
01 score=0.8123 images/car_001.jpg -> results/20260424_153012/01_0.8123_car_001.jpg
02 score=0.7991 images/snow_045.jpg -> results/20260424_153012/02_0.7991_snow_045.jpg
03 score=0.7814 images/vehicle_120.jpg -> results/20260424_153012/03_0.7814_vehicle_120.jpg
scripts/build_index.pyで画像をベクトル化する- ベクトルを
data/images.faissに保存する - 画像パスを
data/image_paths.jsonに保存する scripts/search.pyでユーザ入力を Ollama に渡し、回答に過去画像データベースの参照が必要かを判定する- 画像検索が不要な場合は、検索せずに Ollama で通常回答する
- 画像検索が必要で
--query-rewrite指定時は、Ollama で画像検索向けクエリへ変換する - 実際に検索へ使うクエリをベクトル化する
- FAISS で近い画像ベクトルを検索する
- 上位画像を
results/実行時刻/にコピーする - 最上位の検索結果画像を Ollama に渡して回答を生成し、
llm_response.txtに保存する - Ollama の thinking を
ollama_thinking.txtとollama_thinking.jsonに保存する - 元の入力を
raw_query.txt、検索に使ったクエリをquery.txtに保存する