はじめに
ローカル LLM を使い、画像のタイトルを生成してみる。
プライベートな書類や写真のような、あまりインターネットに送信したくはないものを処理するにはローカル LLM がうってつけ。
私はこれを画像の整理に使っていて、子供が学校から持ち帰ったプリントをスキャンして、プリントの内容から自動的にファイル名を決めるようにしている。
構成
- macOS
- Linux でも動くが Homebrew を前提とした説明になっている
- 必要空きメモリ
- 8 GiB 弱 (条件による)
- Homebrew
- Ollama
- Qwen3-VL 2b
手順
ollama のインストール
ollama をインストールする。LM Studio でも構わないが ollama の方がとりあえず動かすには楽なので今回はこちらを使用する。また JSON パーサーの jq も使う。
デフォルトでは localhost からの接続しか受け付けないため、環境変数 OLLAMA_HOST を設定して LAN 内からの接続を受け付けるようにする。今回は localhost で完結しているため本当は設定不要なのだが、ハマりがちなので入れておいた。
OLLAMA_ORIGINS はリクエストを受け付ける範囲を限定するためのものなので無くても動く。
他にも設定しておくといいものはあるがパフォーマンス周りなので割愛。
## インストール
% brew install ollama jq
## homebrew.mxcl.ollama.plist に OLLAMA_HOST を追加
% vim "$(brew ls ollama | grep -F .ollama.plist)"
...
<key>EnvironmentVariables</key>
<dict>
<key>OLLAMA_HOST</key>
<string>0.0.0.0</string>
<key>OLLAMA_ORIGINS</key>
<string>192.168.0.*</string>
</dict>
...
## 再起動
% brew service restart ollama
モデルのダウンロード
モデルは大きなものもあるので十分にストレージの空きがあることを確認すること。
ここでは Qwen3-VL 2b Instruct を使用する。
% ollama pull qwen3-vl:2b-instruct
不要になった場合は ollama rm qwen3-vl:2b-instruct でストレージから削除できる。
スクリプト
PNG ファイルからタイトルを生成するスクリプト。
OpenAI API 形式を使うと画像はバイナリを base64 にして添付することができる。
#!/usr/bin/env bash
## PNG ファイルからタイトルを生成する
## 使用例: gen-image-title test.png
set -eu -o pipefail
IMAGE_FILE=$1
if base64 --version 2>&1 | grep -qF GNU; then
BASE64_OPT="--wrap=0"
else
BASE64_OPT="-i"
fi
printf '
{
"model": "qwen3-vl:2b-instruct",
"stream": false,
"messages": [
{
"role": "system",
"content": "Response title only, never explanation."
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "画像の題名を考えてください。"
},
{
"type": "image_url",
"image_url": "data:image/png;base64,%s"
}
]
}
]
}
' "$(base64 "${BASE64_OPT}" "${IMAGE_FILE}")" |
curl -s -f \
-X POST \
-H "content-type: application/json" \
-d @- \
http://localhost:11434/v1/chat/completions |
jq -r '.choices[0].message.content'
動作確認
本当はスキャンデータで試したいところだが、簡単のために千代田区の広報誌をお借りした:
2025年11月5日号の表紙をスクリーンショットで取得して test.png として保存。
% ./gen-image-title test.png
未来の創り出す人とデジタル
タイトルが生成できてる。
おまけ (スキャン時のファイル名)
冒頭に紹介したスキャン時に自動的にファイル名をつける方法を紹介する。
プロンプトは前述の通り最小限であるし、スクリプトも最小限になっているため参考程度であることに注意。
スキャナーは ScanSnap iX100 を USB 接続し、スキャンボタンを押した際にスクリプトが動作するように Ubuntu 24.04 で scanbd (Scanner Button Daemon) を使用することを仮定している。
scanbd 用の設定ファイルを用意する。ここではボタンが押された際に scan.script を実行するようにする:
global {
user = saned
group = scanner
saned = "/usr/sbin/saned"
saned_opt = {}
saned_env = { "SANE_CONFIG_DIR=/etc/scanbd" }
scriptdir = /etc/scanbd/scripts
timeout = 500
pidfile = "/var/run/scanbd.pid"
environment {
device = "SCANBD_DEVICE"
action = "SCANBD_ACTION"
}
function function_knob {
filter = "^message.*"
desc = "The value of the function knob / wheel / selector"
env = "SCANBD_FUNCTION"
}
## スキャナーのスキャンボタンを押したら scan.script を実行する
action scan {
filter = "^scan.*"
numerical-trigger {
from-value = 1
to-value = 0
}
desc = "Scan to file"
script = "scan.script"
}
}
## ScanSnap を仮定
include(scanner.d/fujitsu.conf)
ボタンが押された際に実行するスクリプトを用意する。
スキャナーでスキャンを行い、前述の gen-image-title でタイトルを決めてファイル名の変更をする:
#!/bin/bash
## スキャンボタンを押した際に実行されるスクリプト
## /scan ディレクトリに保存される
set -eu -o pipefail
BASEDIR=/scan
NAME="$(date +"%F_%H%M%S")"
TODAY="${NAME%%_*}"
OUT_PATH="${BASEDIR}/${NAME}.jpg"
## scanimage コマンドで実際にスキャンを行う
sudo -u saned SANE_CONFIG_DIR=/etc/scanbd scanimage --format=jpeg --mode=Color --resolution 200 --device="${SCANBD_DEVICE}" -o "${OUT_PATH}"
## 画像のタイトル
TITLE="$(sudo -u saned /usr/local/bin/gen-image-title "${OUT_PATH}")"
if [[ -n $TITLE ]]; then
## タイトルが得られたらファイル名を変更する
sudo -u saned mv "${OUT_PATH}" "${BASEDIR}/${TODAY}_${TITLE}.jpg"
fi
scanbd のインストールとファイルの配置:
sudo apt install -y scanbd
sudo mkdir -p /usr/local/bin
sudo mkdir -p /etc/scanbd/scripts
sudo mkdir /scan
sudo chown saned:saned /scan
## https://wiki.archlinux.jp/index.php/Scanner_Button_Daemon
echo net > /etc/sane.d/dll.conf
echo fujitsu /etc/scanbd/dll.conf ## ScanSnap を仮定
sudo mv scan.script /etc/scanbd/scripts/
chmod 755 gen-image-title
sudo mv gen-image-title /usr/local/bin
sudo systemctl restart scanbd
実際にスキャナーのスキャンボタンを押してファイルが保存されれば成功。
おわりに
今回は Qwen3-VL 2b Instruct を使用した。この他にも Qwen3 にはテキストのみを扱う Qwen3 や、コーディングに特化した Qwen3-Coder といったものがあるが、Qwen3-VL は画像や動画の認識に特化したモデル。しかし 32b くらいになるとテキストの扱いも十分な能力を持っているため、通常は Qwen3-VL だけでよくなってくる。
Instruct は思考しない高速なモデルだが、思考する reasoning model が欲しい場合は qwen3-vl:2b のように後ろの -instruct を外す。qwen3-vl:2b-thinking もエイリアスで同一。
Qwen3 シリーズの他には Gemma 3 がお勧め。Gemma 3 は画像の細かい説明はあまり得意ではないが、比較的高速で概要の説明は十分にこなせる。
スクリプトは最小構成になっているのでプロンプトを変えたり、大きすぎる画像は事前に縮小したり工夫してみてほしい。