お疲れ様です。
以前の記事でプロジェクトのセットアップを行っていた、VLMを使ったバウンディングボックス(BBox)のアノテーション補助ツールを実際に作ってみました。
ソースコード
ソースコードはこちらのGitHubにあります。以前の記事でプロジェクトの初期化まで行ったリポジトリですね。
uvの環境設定を行っているため、uv環境さえあれば動作自体はできると思います。
実装
ソースコードの中身はプロジェクト本体を見ていただくとして、こちらには概要を載せておきます。 動作フローは以下のようになっています。
flowchart TD
Start([開始: main.py 実行]) --> ParseArgs[CLI引数の解析\n-d/--data, -v/--visualize]
ParseArgs --> LoadConfig[classes_info.json 読み込み\n対象クラス・説明の取得]
LoadConfig --> InitAnnotator[VLMAnnotateAssistant 初期化\n- プロンプト生成\n- LLMモデルの取得]
InitAnnotator --> LoopImages[画像ディレクトリ走査\n.jpg, .png, .jpeg, .bmp]
subgraph ImageProcess [画像ごとの処理ループ]
direction TB
LoadImg[画像読み込み\nPIL.Image.open] --> ResizeBase64[前処理\n- 1000x1000にリサイズ\n- Base64エンコード]
ResizeBase64 --> CallVLM[VLM呼び出し\nLangChain invoke\n構造化出力: ResponseFormat]
CallVLM --> CheckVisual{可視化フラグ\n-v / --visualize?}
CheckVisual -- True --> Vis[draw_detection_results\nMatplotlibでBBoxを描画・表示]
CheckVisual -- False --> SaveXML
Vis --> SaveXML[create_voc_xml\n- 正規化座標をピクセル座標へ変換\n- Pascal VOC XMLファイルを書き出し]
end
LoopImages --> ImageProcess
SaveXML --> NextImage{次の画像あり?}
NextImage -- Yes --> LoopImages
NextImage -- No --> End([終了])
使用可能なLLMは今のところ、Gemini APIで使用可能なモデルかOllamaで使用可能なモデルです。 VLMや画像入力が可能なマルチモーダルLLMを使用することができます。 LangChainを使用しているため、他のサービスを利用したい場合も設定を追加すれば対応可能かと思います。
画像の入力方法については、過去にお試しで実装したやり方を使いました。 LangChainの「Chat***」クラスにBase64エンコードした画像を渡すという方法です。
一応アノテーションの補助ツールという立ち位置なので、最終的にはユーザー自身がチェックして修正する想定です。 最終的な出力はPascal VOC形式のアノテーションファイルになっており、LabelImg等のアノテーションツールで読み込むことができます。 MS COCO形式は現在未対応ですが、必要があれば対応するかもしれません。 VOC形式をCOCO形式に変換するスクリプトなどは多く公開されているため、それらを使って変換するのも手です。
実行方法
設定の変更と実行方法について説明します。
まず、モデルは .env ファイルで指定します。
使用するサービスとモデル名、Geminiを使う場合はAPIキーを指定します。

プロジェクト内の classes_info.json に検出したいクラスを設定します。
下記のように クラス名: 物体の説明 の形式で記載します。
これが、LLMに画像とともに入力されるプロンプトに埋め込まれます。
{ "human": "画像内の人、人間", "car": "画像内の自動車", "bicycle": "画像内の自転車" }
Pythonコマンドで実行します。 実行時にコマンドライン引数(argparse)で画像が入ったフォルダを指定し、その中の画像に対して物体検出を適用します。 出力されるアノテーションファイルも同じフォルダに格納されます。 また、検出結果の可視化の有効/無効もコマンドライン引数で設定します。
# コマンドライン引数の設定 parser = ArgumentParser() parser.add_argument("-d", "--data", type=str, default="./data") parser.add_argument("-v", "--visualize", action="store_true") args = parser.parse_args()
python main.py -d ./data -v
出力結果
実際に動作させてみます。 使用するモデルにはGemini APIからgemini-3.5-flash-liteを選択しました。(VLMではないですが…。) また、検出クラスは人(human)、車(car)、自転車(bicycle)の3クラスとしています。
出力結果は以下のようになりました。 プロンプトの内容によるとは思いますが、Geminiだとかなり良い感じに出力してくれました。 今回のクラス指定がシンプルだった影響もあると思いますが、アノテーション補助ツールのつもりがそのまま使えそうなレベルの精度です。

出力したPascal VOC形式のアノテーションファイルもLabelImgで問題なく表示されました。

所感
今回、VLMの勉強も兼ねてこのようなツールを作成してみました。 実際に試してみた感想ですが、プロンプトでかなり詳細に指示しないと意図した出力が得られないという印象を受けました。 このあたりは、もっとより良いやり方やプロンプトの工夫があるのかもしれません。
物体検出(BBox)のアノテーションだったため、難易度が高かったという側面もあるかもしれません。 今後は異常検知やOCRなども試してみたいところです。 業務内でVLMを活用したいという話も耳にするため、そのあたりの知見もより深めていければと思います。
今回はGeminiを使用しましたが、OllamaによるローカルVLMもいくつか試しています。 そちらについても、また別の機会に記事にしようと思います。





























