
餃子の秤量画像をVLMで認識してみた
はじめに
こんにちは!クラスメソッド製造ビジネステクノロジー部のtanaka-takeruです。
以前の記事では、餃子作りを題材にNVIDIA SOP Monitoring Servicesで作業工程を認識しました。
餃子を作った際に、家庭用の電子天秤で秤量して画像データを撮っておいたので、今回はそのデータを使って、重さなどの情報を自動で抽出することを試します。
活用シーンとして、工場にある既存設備の表示(7セグ表示器や目盛など)を読み取って、現状把握や異常検知の根拠の一つとして活用することを想定しています。
もちろん設備にAPIがあったり電気的に信号を取り出せたりする場合であれば、より確実なデータ化が期待できますが、既存工程・設備にできるだけ手を入れないことを重視したいと思います。また、VLMならではの画像情報を解釈して自然言語で表現する強みを検証したいと思います。
この記事では次の流れで3つの実験について説明します。
- タスク説明
- 実験1. 正常画像50枚の認識精度:VLMでどれくらい認識できるのか?固定ROIを用いた古典的な手法と比較
- 実験2. 位置ずれ画像の認識精度:VLMによって認識対象の位置が変わってもどこまで対応可能か?
- 実験3. 異常画像の認識:VLMによって予期せぬデータをどう扱えるか?
タスク説明
作業者A・Bが作った合計50個の餃子の画像を使います。
1枚の画像から読み取りたい情報は次の通りです。
| 対象 | 取得する値 | 表示の特徴 |
|---|---|---|
| スマートフォン | 作業者、連番(その作業者の何個目の餃子か?) | 日本語と数字 |
| 秤 | 重量(g) | 七セグ表示 |
| 皿の上 | 餃子のROI | 文字ではない物体位置 |
画像の一例を示します。
- 正常な画像

正解の基準については
- 作業者・連番・重量:3項目がすべて正しい画像だけを完全一致として正解としました。
- 餃子ROI:正解と推論結果のpolygon IoUが0.8以上なら正解としました。
なお、画像データは一般的な産業用カメラやWEBカメラのボリュームゾーンをイメージして177万画素に落としました。1152×1536pxにリサイズの上、JPEG圧縮しており、1枚あたり約490KBの容量です。
7セグ表示領域に手の影が映り込んでいてコントラストが弱いところが難しいポイントになりそうです。
実験環境
- PC:MacBook Pro Apple M5、10コアCPU、メモリ16GB
- OS:macOS 26.5.2
- VLM推論:Ollama 0.31.1
実験1. 正常画像50枚の認識精度
VLMの設定とプロンプト
VLMには固定ROIを与えず、縮小した画像全体を1枚ずつ入力しました。主な生成設定を比較ごとに整理します。
| 項目 | 内容 |
|---|---|
| モデル | qwen3-vl:4b-instruct |
| temperature / seed | 0 / 42 |
| コンテキスト長 | 8192 |
| 出力制約 | JSON Schema |
| その他 | stream=false、keep_alive=10m |
推論プロンプトは次のとおりです。
You are drafting annotations for a gyoza weighing image.
Read only visible evidence. Do not guess unreadable values.
For a normal image:
- worker_id is the A or B value shown on the smartphone.
- sequence_no is the integer shown below the worker on the smartphone.
- weight_g is the integer grams shown on the digital scale.
- gyoza_bbox_1000 is the smallest axis-aligned rectangle around visible gyoza pixels,
excluding the plate, cast shadow, and thread-like foreign matter.
Coordinates use the full displayed image: left=0, top=0, right=1000, bottom=1000.
For an occluded, empty, or otherwise unintended image, explain the anomaly in Japanese.
Set human_review_required=true for anomalies. Use null for values that cannot be confirmed.
Return only data conforming to the supplied JSON schema.
プロンプトの日本語訳
餃子の秤量画像に対するアノテーション案を作成してください。画像上で確認できる情報だけを読み取り、読めない値を推測しないでください。
正常画像では、次のように読み取ります。
worker_idは、スマートフォンに表示されたAまたはBsequence_noは、スマートフォンで作業者の下に表示された整数weight_gは、デジタル秤に表示されたグラム単位の整数gyoza_bbox_1000は、皿、投影された影、糸状の異物を除き、見えている餃子の画素を囲む最小の軸平行矩形
座標は表示画像全体を基準とし、左端0、上端0、右端1000、下端1000とします。遮蔽、対象の欠落、その他の意図しない画像では、異常内容を日本語で説明してください。異常時はhuman_review_required=trueとし、確認できない値にはnullを使用してください。指定されたJSON Schemaに適合するデータだけを返してください。
プロンプトで与えたのは、各値の意味と、餃子ROIを「どの画素まで含めるか」という定義です。スマートフォンや秤の位置、固定ROIの座標は与えていません。出力は、次のSchemaで型と値域も制限しました。
| フィールド | 制約 |
|---|---|
classification |
normal、scene_occluded、empty_scene、other_anomalyのいずれか |
worker_id |
A、B、nullのいずれか |
sequence_no |
1〜999の整数、またはnull |
weight_g |
0〜1000の整数、またはnull |
gyoza_bbox_1000 |
0〜1000で表したx_min、y_min、x_max、y_max、またはnull |
| 異常・確認情報 | 日本語の異常説明、human_review_required、確認理由、最大6件の目視根拠 |
すべてのフィールドを必須とし、Schemaにないプロパティは禁止しました。
以降の実験ではこちらのVLMパイプラインを使用します。
固定ROIパイプラインの設定
VLMの評価の参考のために、固定ROI OCRによって作業者・連番・重量の読み取りも試しました。
Tesseract 5.5.2とテンプレートマッチングを使っています。
詳細はこちら
| 対象 | 固定ROI(左、上、右、下) | ROI決定後の処理 |
|---|---|---|
| スマートフォン | (0.820, 0.680, 1.000, 0.880) |
グレースケール、オートコントラスト、二値化後にTesseract |
| 重量表示 | (0.430, 0.790, 0.560, 0.890) |
暗画素の形を、重量クラスごとのテンプレートと照合 |
スマートフォンのOCR設定は次のとおりです。PSMはページ分割モード、OEMはOCRエンジンモードを表します。
| 項目 | 設定値 |
|---|---|
| エンジン | Tesseract 5.5.2 |
| OCRモデル | --oem 1(LSTM) |
| ページ分割 | --psm 11(疎なテキスト) |
| 言語 | -l jpn+eng |
| 辞書 | load_system_dawg=0、load_freq_dawg=0 |
| 前処理 | グレースケール → オートコントラスト(cutoff 1%)→ コントラスト2倍 → 閾値140で二値化 → LANCZOSで3倍拡大 → 白枠20px |
| 文字列の解釈 | 空白を除去し、作業者([AB])([0-9]{1,3})で抽出。連続しない場合は作業者の後に現れる1〜3桁を採用 |
実際のTesseract呼び出しは、次のパラメータです。
tesseract phone.png stdout \
--oem 1 \
--psm 11 \
-l jpn+eng \
--tessdata-dir <TESSDATA_DIR> \
-c load_system_dawg=0 \
-c load_freq_dawg=0
重量の7セグ表示はTesseractでは難しかったので、次のように簡易的なテンプレートマッチングを使っています。
- 重量ROIをグレースケール化し、160×96pxへ双線形補間でリサイズ
- 画素値の35パーセンタイルを閾値に、暗画素を1とする二値特徴へ変換
- 正常50枚から17〜24gの重量クラスごとに、特徴画像の中央値を作る
- 入力特徴との平均絶対差が最小の重量クラスを返す
結果
| 指標 | VLMパイプライン | 固定ROIパイプライン |
|---|---|---|
| 作業者一致 | 50/50 | 49/50 |
| 連番一致 | 50/50 | 40/50 |
| 重量一致 | 50/50 | 33/50 |
| 文字3項目完全一致 | 50/50 | 28/50 |
| 餃子ROI:IoU 0.8以上 | 50/50 | - |
- 固定ROIパイプラインでも、作業者は49/50まで認識することができました。一方で、連番と7セグ重量を含む全体の精度である完全一致では28/50に下がりました。パイプラインの作り込みが必要です。
- VLMパイプラインの結果は50/50でした。複数の文字認識に加えて、餃子の場所を切り出す物体検出タスクまでまとめて実行できる点は非常に大きなメリットであると感じました。
実験2. 位置ずれ画像の認識精度
先の実験では、撮影条件を固定してVLMパイプラインと固定ROIパイプラインを比較しました。しかし、実際の現場では撮影条件を固定することが難しい場合も多々あります。改めて述べる必要性は低いかもしれませんが、VLMは画像データの全体および局所の特徴量を抽出しており、この課題に適応できる仮説があります。
そこで、正常画像50枚に並行移動・回転を加えた300枚の画像に対して、VLMパイプラインで認識を試しました。
- 並行移動は、左右上下に各辺の5%のピクセル距離を移動(4パターン) ※空いた領域はエッジ画素で補完
- 回転は、±5度回転(2パターン)
→1枚の正常画像に対して6枚の位置ずれ画像を生成し、50 * 6=300枚の画像を評価します。
変換した画像の例を示します。
| 元画像 | 左へ移動(短辺長の5%のピクセル距離) | 反時計回りへ5度 |
|---|---|---|
![]() |
![]() |
![]() |
結果
| 指標 | VLM 4B |
|---|---|
| 作業者 | 300/300 |
| 連番 | 300/300 |
| 重量 | 298/300 |
| 文字3項目完全一致 | 298/300(99.3%) |
| 餃子ROI:polygon IoU 0.8以上 | 300/300(100%) |
文字3項目完全一致の精度は99.3%となりました。VLMの強みが頑健性として現れ、高い精度で位置ズレにも対応したといえると考えます。
重量で間違った画像については、いずれも左移動させた2枚で18gを8gと読みました。
認識結果の一部を示します。

重量を8gと認識した画像を示します。

実験3. 異常画像の認識
最後に、異常画像として、手で撮影対象を隠した画像と餃子・スマートフォンが写っていない画像に対するVLMの出力結果を示します。
結果
餃子・スマートフォンが写っていない
-
入力

-
出力
{
"status": "review_required",
"readings": {
"worker_id": {"value": null, "confidence": null},
"sequence_no": {"value": null, "confidence": null},
"weight_g": {"value": 0, "confidence": null}
},
"gyoza_bbox": null,
"anomaly": {
"detected": true,
"code": "empty_scene",
"message": "画像には餃子が写っていません。空の皿と電子レンジが写っています。"
},
"human_review": {
"required": true,
"state": "pending",
"reason": "画像に餃子が存在しないため、重量や位置情報は取得できません。異常な状況です。人間によるレビューが必要です。"
}
}
- "anomaly"の"detected"が
trueであることから、異常を認識していることがわかります。 - "message"に異常の内容を出力していますが、電子レンジが写っていると、誤った情報を出力しています。
- "human_review"の"required"が
trueであることから、人による確認の必要性を認識していることがわかります。 - "reason"を見てみると異常の理由について適切に出力しているといえます。
手による遮蔽
-
入力

-
出力
{
"status": "review_required",
"readings": {
"worker_id": {"value": null, "confidence": null},
"sequence_no": {"value": null, "confidence": null},
"weight_g": {"value": null, "confidence": null}
},
"anomaly": {
"detected": true,
"code": "empty_scene",
"message": "画像には餃子が写っていません。手と金属の皿しか見えません。"
},
"human_review": {
"required": true,
"state": "pending",
"reason": "撮影ミスまたは意図しない画像と判断します。人間のレビューが必要です。"
}
}
- "anomaly"の"detected"が
trueであることから、異常を認識していることがわかります。 - "message"に異常の内容を出力していますが、適切な情報を出力しています。
- "human_review"の"required"が
trueであることから、人による確認の必要性を認識していることがわかります。 - "reason"を見てみると異常の理由について適切に出力しているといえます。
最後に
VLMの強みをまとめます。
・OCRと物体検出、解釈の複数タスクの統合
・撮影対象の位置ずれ対応
・異常時の人へのエスカレーション、および異常内容の解釈可能性
VLMによって動画情報の圧縮が、非常に効率的かつ柔軟に(自然言語的に)行えることが確認できました。
これは、従来のルールベース処理に比べて立ち上げコストの低さが期待できるほか、工程の変更(例で言うと天秤を買い替えたとか、餃子ではなくシュウマイにしたとか)に対してプログラムを変えることなく対応できる可能性もあると考えられます。
VLM一辺倒で検討するのはなく、
- リアルタイム性を求める部分は固定ROIパイプラインを使い分ける
- 利用頻度(毎日なのかカイゼン検討の際にのみ使うのか)によってローカル/クラウドを使い分ける
などの工夫によって多くの情報を適切に抽出することができると思います。
VLMのモデル違いや計算リソースの変更、現場運用を想定したアーキテクチャの検討など試していきたいと思います。












