図は VLM に読ませるべきか ― ローカル VLM で図を Mermaid 化する試験と、文字だけで図が再構成できた話

図は VLM に読ませるべきか ― ローカル VLM で図を Mermaid 化する試験と、文字だけで図が再構成できた話 AI

前回は、8GB の GPU で日本語の縦書き資料を OCR し、AWS の Knowledge Base に載せるところまで書いた。今回はその続きで、図と表をどう扱うかである。結論を先に書くと、図を VLM に読ませるのはやめた。代わりに「図のキャプションと図中の文字を本文に埋める」だけにして、図の意味は Claude 側で本文から再構成させる。その判断に至るまでの試験と、途中で見つけたバグの話をする。

問題: OCR は本文しか取らない

OCR エンジン(YomiToku)は段落のほかに図の領域と表の構造も検出しているのだが、パイプラインは段落しか使っていなかった。しかも完了時に原本のページ画像を削除する設計なので、図は文字どおり消えていた。

横書きの技術資料を登録してみると、これが痛い。通信プロトコルの資料はシーケンス図と状態遷移図が本体で、本文はその説明である。「閉塞信号を送って確認信号が返らなければどうなるか」という質問に答えるには、図の中の矢印の向きが要る、と思っていた。

選択肢は 3 つあった。

案 やること 検索 解釈 保持する著作物
A. 表だけ 表を Markdown 表に変換して本文へ ○ ○ 増えない
B. 図を切り出して保持 図の画像を残し、検索時に MCP で Claude に見せる キャプション経由 ◎ 図の画像が残る
C. 図を文章化 切り出した図をローカル VLM で説明文にする ○ △ 増えない

A は迷う余地がない。B と C の分かれ目は「図の画像をサーバーに残すか」で、これは「原本は残さない」という方針とぶつかる。そこで出てきたのが「図を AA(テキストの図)にすれば、文字しか残さない方針を崩さずに済む」という発想だった。AA と言っても罫線の AA ではなく、シーケンス図なら Mermaid の sequenceDiagram を狙う。VLM が出しやすく、Claude Code がそのまま描画できる。

試験: Qwen3-VL 4B / 8B に 7 枚の図を渡す

試験は次のように組んだ。

  • 資料からシーケンス図を中心に 7 図を選ぶ(2 本の矢印しかない易しい図から、20 本以上の矢印に番号が付いた難しい図まで)
  • YomiToku の図検出で枠を取り、切り抜く
  • Ollama の Qwen3-VL 4B-instruct と 8B-instruct に、Mermaid を出すプロンプトと罫線 AA を出すプロンプトの 2 種類を投げる
  • 出力を原図と目視で突き合わせ、矢印の向きと構造を採点する
  • 打ち切り条件を先に決める: 矢印の向きが 9 割を超えなければ見送り

8GB の GPU では、OCR ワーカーが VRAM を 3.7GB 押さえていると 8B(5.8GB)は載らず、73% が CPU に落ちて事実上止まった。4B で先に結果を出し、8B はコンテナを再起動して VRAM を空けてから GPU 100% で流した。

結果はこうなった。

図 難易度 4B 8B 8B の内容
衝突時の初期設定シーケンス(矢印 4 本) 易 × ◎ 向きまで完全に一致。唯一の正解
閉塞シーケンス(矢印 2 本 × 2 図) 易 × × 信号名を「参加者」にしてしまう。失敗側の図が丸ごと欠落
4 局を経由する呼接続(矢印 24 本) 中 △ △ 6 種の信号連鎖のうち 4 つは正しい。導通試験音と呼出音の向きが逆
導通試験の 4 場面(小図 4 つ) 中 × × 最初の 1 つだけ出して残りを落とす
誤り制御手順(番号付き矢印 20 本超) 難 × × 注釈文を矢印に読み替える。8B は同じ行を出力上限まで繰り返した
通知シーケンス(概念図と併記) 中 × × 参加者は合うが矢印はでたらめ
回線構成図 対象外 × × シーケンス図ではない

8B は 4B より速く(1 図 3〜25 秒)、構造もそれらしいのだが、矢印 4 本の図を完璧に描いた同じモデルが、矢印 2 本の図を落とす。全体の正解率が低いだけでなく、どの出力を信じてよいか事後に判別できないのが致命的だった。罫線 AA のプロンプトはさらに悪く、出力上限まで空行や罫線を吐き続ける暴走が 2 回あった。

打ち切り条件に照らして、図の Mermaid 化は見送りにした。事前に条件を決めておいたおかげで、「もう少しプロンプトを工夫すれば」という誘惑を切れた。

表はそのまま使える

一方で表は拍子抜けするほど素直だった。YomiToku はセルの行・列・結合情報を返すので、結合セルを左上に寄せたグリッドにして Markdown 表にするだけである。技術資料の 64 表が、2〜6 列・最大 15 行まで、列も行も原本どおりになった。長い文がセルの途中で切れる(セル検出側の問題)のと、図と表が隣接すると一つの図領域にまとめられて表にならない、という限界は残ったが、頻度は低い。

図については、VLM を使わない確実な部分だけ実装した。図の枠に一番近い「図 N」で始まる段落をキャプションとみなし、図中の文字(これは YomiToku が漏れなく拾う)と一緒に > 図: 図2.45 閉塞シーケンス > 図中の文字: ¥MKB / BLO / BLA / … という形で本文の後ろに置く。これで「閉塞の確認信号は何か」のような質問は該当ページに当たる。図の意味は引けないが、場所と語彙は確実に引ける。

転機: 文字だけから図が描けた

ここで予想外のことが起きた。別の Claude Code セッションで、この索引を MCP 経由で検索し、「呼接続の基本シーケンスを図で描いて」と頼んだところ、4 局を経由する 24 本の矢印のシーケンス図が、ほぼ原図どおりに出てきたのである。画像は一切渡していない。材料は図中の文字(参加者 4 局と信号名の並び)と、隣のページの本文(「アナログ回線に対して呼設定の前に導通試験を行う」「COT の送出条件は前位局からの COT と後位局からの試験音の AND」)だけだ。

8B の VLM が原図の画像を見て 6 割しか当てられなかった矢印を、Claude は画像なしで復元した。しかも「導通試験音はアナログ回線区間だけにあり、ディジタル区間では省略」という、図中の文字には書かれていない構造を本文から推論していた。

念のため、Claude Code(Anthropic API)ではなく Bedrock 上の Claude Sonnet 4.6 でも同じことを試した。Knowledge Base の検索結果 4 件をそのままプロンプトに入れて「抜粋だけを根拠に Mermaid で再構成せよ」と頼むと、10 秒、約 $0.03 で同じ図が出た。判断に迷った箇所はコメントで理由を書いてくる。

Bedrock 上の Claude Sonnet 4.6 が、Knowledge Base の検索結果(図中の文字と本文)だけから描いた呼接続シーケンス

Bedrock 上の Claude Sonnet 4.6 が、Knowledge Base の検索結果(図中の文字と本文)だけから描いた呼接続シーケンス

気づいたのは単純なことで、技術資料の図は本文が説明している。図と本文は対になっていて、図の意味は本文が持っている。だから索引に要るのは「どの図がどこにあり、何が書かれているか」までで、描くのは Claude 側の仕事でよい。VLM に図を読ませようとしていたのは、この構造を見落としていたからだった。

逆に言えば、本文に説明のない図(フォーマット図や仕様書の構成図)は今の方法では復元できない。そこは「図だけが情報源」の資料が出てきたときに、切り抜きを MCP で見せる案 B を検討すればよい。

落とし穴: 図だけのページ

実装を配置して資料を登録し直したところ、OCR が 222 ページ終わった直後に Markdown の組み立てで落ちた。

IndexError: list index out of range
  File "/app/core/ocr_worker.py", line 324, in build_markdown
    if is_continuation(paras[0]):

原因はこうだ。パイプラインには「前のページが文の途中で終わっていたら、次のページの最初の段落と連結する」という処理がある。以前は本文のないページを丸ごと飛ばしていたので、連結相手の段落は必ず存在した。今回「図・表だけのページも通す」ようにしたことで、「文の途中で終わるページ」の次に「図だけのページ」が来る組み合わせが生まれ、空のリストの先頭を参照した。テストには図だけのページを入れていたが、その直前が文の途中で終わる組み合わせを見落としていた。

修正は 1 行で、先頭段落があるときだけ連結し、なければ保留していた段落をそのまま出す。再現テストを足して、OCR 済みの結果を再利用して組み立てだけやり直したところ、図 112・表 64 が入った Markdown が生成され、Knowledge Base に索引された。

まとめ

  • ローカル VLM(Qwen3-VL 4B / 8B)で図を Mermaid 化する試験は、矢印の向きの正解率が打ち切り条件に届かず見送った。試験の前に打ち切り条件を決めておくと判断が早い
  • 表は YomiToku の構造情報から Markdown 表にするだけで実用になる。図はキャプションと図中の文字を本文に埋め、場所と語彙を引けるようにする
  • 図の意味は本文が持っている。Claude は文字だけから原図を再構成でき、それは Bedrock 上の Claude でも同じだった
  • 「図・表だけのページ」を通すようにしたら、ページをまたぐ文の連結が空のリストを踏んだ。前提を変えたら、その前提に依存していた処理を疑う

次回は、この構成を Claude Code なしで Bedrock だけで回す「閉域構成」の話を書く。

コメント

タイトルとURLをコピーしました