![[Amazon Bedrock] 洗濯ばさみで外観検査AIを試してみました 〜つまずいたのは「正常の定義」でした〜](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/v1788046759/user-gen-eyecatch/tzq56vughsqznj8zultm.png)
[Amazon Bedrock] 洗濯ばさみで外観検査AIを試してみました 〜つまずいたのは「正常の定義」でした〜
1 はじめに
製造ビジネステクノロジー部の平内(SIN)です。
前回、異常画像と正常画像を渡すと検査プロンプトを VLM 自身に生成させる仕組みを試しました。
ただ、あのときの検査対象は NEU-DET という金属表面欠陥の公開データセットでした。ベンチマークとして整備されたデータなので、「うまくいって当たり前なのでは」という疑問が少し残ります。
そこで今回は、自分にとっても未知の対象で同じ仕組みを動かしてみました。100円ショップで買ってきた洗濯ばさみです。白い樹脂の射出成形品なので、実際の製造業の検査対象からそう遠くはないと思います。
先に結論を述べると、検出率は 2/4、過検出率は 0/5 でした。そして、うまくいかなかった部分から出てきた知見のほうが多い結果になりました。本稿はあくまで特定環境での一例として捉えてください。
なお、コードは前回のものをほぼそのまま使っています。変更したのは 2 箇所だけです。
撮影した画像・生成された検査仕様・判定結果の JSON を含めて、以下にまとめました。
2 検証の準備
(1) 撮影セット
Web カメラを三脚アームで真上に固定し、黒い画用紙を背景に敷きました。被写体だけを入れ替える方式です。


手持ちで撮ると条件が揃わないため、カメラは動かさないようにしています。
(2) 検査対象
同じ製品を 10 個購入し、正常 5 個と異常 4 個を用意しました。異常品は自分で欠陥を作っています。

欠陥は 4 種類です。性質がそれぞれ違うものを選びました。
| 欠陥 | 性質 |
|---|---|
| 変形 | 形状異常(全体) |
| 壊れている | 形状異常(局所・破断面あり) |
| 曲がり | 形状異常(局所・破断面なし) |
| 汚れ | 表面欠陥 |


前回の金属表面と違い、表面の欠陥だけでなく形の異常が混ざっています。ここが今回の難しさになりました。
傷は「地肌と違うもの」として単体で判定できます。一方、変形は「正しい形」を知らないと判定できません。
(3) 正常品はわざと傾けて撮る
正常品 5 個のうち 3 個は、意図的に傾けて撮影しました。1 個は約 30 度傾けています。

狙いは 2 つあります。
1 つは、置き方のずれを「変形」と誤判定しないかを確認するためです。もう 1 つは、この 3 個を検査仕様の生成には使わないことです。
検査仕様を生成するときに渡した画像は、異常 4 枚と正常 2 枚(正立のもの)だけにしました。傾けた 3 枚は評価にだけ使います。自分で作った基準で自分を採点する形を避けたかったためです。
3 検査仕様の生成
(1) 実行前に 2 点つまずいたので補足します
最初の実行は 60 秒でタイムアウトしました。
botocore.exceptions.ReadTimeoutError: Read timeout on endpoint URL:
"https://bedrock-runtime.ap-northeast-1.amazonaws.com/model/global.anthropic.claude-opus-5/converse"
原因は 2 つ重なっていました。
1 つ目は、botocore の既定の read_timeout が 60 秒だった点です。検査仕様の生成は Claude Opus 5 に画像を複数枚渡すため、前回の記事でも 47〜111 秒かかっていました。
Github scripts/bedrock.py
# 要点抜粋
from botocore.config import Config
_BOTO_CONFIG = Config(connect_timeout=10, read_timeout=600,
retries={"max_attempts": 1, "mode": "standard"})
_client = boto3.client("bedrock-runtime", region_name=REGION, config=_BOTO_CONFIG)
リトライを 1 回に絞っているのは、タイムアウト時に裏で再実行されて課金が増えるのを避けるためです。
2 つ目は画像のサイズです。Web カメラのキャプチャをそのまま保存していたため、PNG・RGBA・1 枚 1.4MB でした。6 枚渡すので送信ペイロードが約 8MB になっていました。
写真に PNG は不向きですし、アルファチャンネルにも意味がありません。JPEG に変換しました。
| 変換前 | 変換後 | |
|---|---|---|
| 形式 | PNG / RGBA | JPEG(q92) / RGB |
| 1 枚あたり | 約 1.4MB | 約 137KB |
| 画素数 | 1392×832 | 1392×832(変更なし) |
画素数は下げていません。 Claude は長辺 1568px までしか使わないため、1392px のままで情報の損失がなく、形式を変えるだけで約 1/10 になりました。なお前回の記事では PNG → JPEG でエンコード時間が短縮しましたが、今回は同じ効果が送信ペイロードの側で出た形です。
(2) 可動品には「正常な変動」がある
ここが今回いちばん重要だった点です。
撮影した正常品を並べて気づいたのですが、洗濯ばさみはバネで開閉する可動品です。つまり、2 本の脚の開き具合は正常品でも変わります。

4 枚とも欠陥のない正常品です。それでも、置いた向きも脚の開き具合も揃っていません。n4 は他の 3 枚より脚が広く開いていますが、これは不良ではなく可動範囲内の姿勢です。
これを検査仕様に書かないと、次のどちらかが起きます。
- 正常品を「変形」と誤判定する
- 本物の変形を「開き具合の違い」として見逃す
そこで META_PROMPT に、この製品固有の注意点を追記しました。
Github scripts/generate_spec.py
## この製品について(重要)
検査対象は白い樹脂製の洗濯ばさみ(射出成形品)です。次の 2 点に注意してください。
1. 表面の欠陥(汚れ・傷)だけでなく、形の異常(変形・曲がり・破損)も含みます。
形の異常を判定するには「正常な形がどういうものか」の記述が不可欠です。
2. この製品はバネで開閉する可動品です。
したがって 2 本の脚の開き具合は、正常品でも大きく変わります。
開きが広い/狭いことは欠陥ではありません。
判定すべきは「開き具合」ではなく、
樹脂部品そのものが壊れている・歪んでいる・欠けているかです。
人が検査要件を書こうとすると「変形を検出したい」で止まりがちです。ただ実際には、何が正常な変動で、何が欠陥かを切り分けないと成立しません。
(3) 生成された仕様
$ python3 generate_spec.py \
--defect ../images/defect/d1_stain.jpg ../images/defect/d2_broken.jpg \
../images/defect/d3_deformed.jpg ../images/defect/d4_bent.jpg \
--normal ../images/normal/n1.jpg ../images/normal/n5.jpg \
--cols 4 --rows 4 --out spec_clothespin_4x4.json
# => model : global.anthropic.claude-opus-5
# elapsed : 87.02s
# tokens : in=10611 out=5187
87.02 秒で完了しました。生成された欠陥は 5 種類で、いずれも分類の専門用語ではなく見た目を表す語になっています。
1. 先端のつまみ部が折れて欠けている
2. 脚(つまみ部)の途中が欠けて切り欠きができている
3. 本体が曲がって左右非対称になっている(ねじれ・変形)
4. 脚の面にできた黒い細長い穴・裂け
5. 先端のかみ合わせ部にある白い異物・盛り上がり
狙いどおり、除外条件にも可動部の記述が入りました。
・2 本の脚の開き具合の違い。バネで開閉する可動品なので、下端が広く開いている/狭い、
上端の先端が接している/わずかに離れているのはすべて正常。
生成された仕様は全文を置いてあります。normal_characteristics(正常品の形の記述)と inspection_prompt(検査プロンプト)、output_schema が入っています。
Github results/spec_clothespin_4x4.json
(4) 指示していない除外項目が追加されていた
こちらが指示していない項目も入っていました。
・成形由来の浅い縦溝、面取り、パーティングライン、支点部の正常な三角形開口、
挟み面の規則的なギザギザ。
・背景フェルトの繊維ムラ。
射出成形品には金型の合わせ目(パーティングライン)が線状に残ります。これを傷と判定されると全数 NG になります。支点部の穴も、「穴」というだけで欠陥に見えますが、製品の正常な構造です。
いずれも現場では当たり前すぎて人が書き忘れやすい項目だと思います。
正常品の画像を見せるという手順そのものが、この漏れを防いでいるように見えました。
抜粋した箇所は、生成された仕様の inspection_prompt に入っています。
Github results/spec_clothespin_4x4.json
(5) 「汚れ」が「穴」と解釈された
一方で、誤りもありました。defect_types の 4 番目です。
Github results/spec_clothespin_4x4.json
4. 脚の面にできた黒い細長い穴・裂け:
白い面の途中に、内部が均一に暗く輪郭のはっきりした細長いスリット状の穴。
これは黒い汚れを付けた個体を指しているはずですが、「汚れ」ではなく「穴」と解釈されています。白い面に付いた黒い領域は、見た目だけでは汚れと穴の区別がつきません。
実務ではこの差は小さくありません。汚れなら拭けば直りますが、穴なら不良品です。検査仕様は生成させたうえで、人がレビューして直す前提にしておくのが良さそうです。
なお判定の段階でも、この解釈がそのまま出ています。汚れを付けた個体は「脚の途中が欠けて切り欠きができている」として C3 に検出されました。位置は合っていますが、欠陥の種類は取り違えたままです。

4 判定結果
生成した仕様で、9 枚(異常 4・正常 5)を判定しました。検査モデルは Claude Haiku 4.5、4×4 グリッド、3 回多数決です。
(1) 結果
| 個体 | 欠陥 | 性質 | 判定 | セル |
|---|---|---|---|---|
| d1 | 汚れ | 表面 | REVIEW | C3 |
| d2 | 壊れている | 形状・局所 | REVIEW | C1 |
| d3 | 変形 | 形状・全体 | OK | — |
| d4 | 曲がり | 形状・局所 | OK | — |
| n1〜n5 | 正常 | — | すべて OK | — |
数値にすると次のとおりです。
過検出率 = 0 / 5 = 0%
検出率(REVIEW以上) = 2 / 4 = 50%
見逃し = 2 / 4
処理時間 = 5.8〜8.7 秒/枚
JSONパース失敗 = 0 / 27 回
3 回とも同じ判定で、ばらつきはありませんでした。
(2) 検出できたもの
破損は先端の位置(C1)を正しく指しました。

汚れも位置(C3)は合っています。ただし欠陥の名前は「脚の途中が欠けて切り欠きができている」で、種類は取り違えていました。仕様生成の段階で汚れを「穴」と解釈していたので、一貫はしています。
位置は当てられるので一次スクリーニングとしては機能しますが、欠陥の種類までは信用できないという結果でした。
(3) 検出できなかったもの
変形と曲がりは、どちらも OK 判定でした。

(4) 傾き 30 度でも過検出は出なかった
正常品は 5 個すべて OK でした。約 30 度傾けた個体も含みます。

この 3 個は検査仕様の生成に使っていないので、仕様側から見れば未知の画像です。可動部の記述を入れた効果が出たのだと思います。
実務では、見逃しは人が拾えますが、正常品を不良と判定すると歩留まりが直接落ちます。そのため、過検出が出なかったことは検出率より重視して良い結果だと感じました。
(5) 境界線は「形状異常かどうか」ではなかった
検証前は「表面欠陥は検出できて、形状異常は難しい」と予想していました。実測の線は少し違いました。
| 結果 | |
|---|---|
| 明確な破断面・高コントラストの付着物 | 検出できる |
| 破断面のない微妙な形状変化 | 検出できない |
d2(先端の破損)と d4(脚先端の曲がり)は、どちらも形状の異常です。それでも前者は捉え、後者は逃しました。差は破断面という局所的な手がかりが画像に出ているかでした。
「形状異常だから難しい」ではなく、「手がかりが局所的に出ているかどうか」が実際の分かれ目です。この線引きは、動かしてみなければ出せなかったと思います。
5 プロンプトを変えて 2 回試した
形状異常が見逃されたので、検査プロンプトを変えて再挑戦しました。結果としては、2 回とも悪化しました。
(1) 案B:手順を指示する
「左右対称性を評価する」「輪郭の連続性を確認する」といった手順を書き、出力もグリッドのセル名から部位名(先端/つまみ部/バネ部/脚)に変えました。
結果は 9 枚すべて OK です。案 A で検出できていた d1・d2 も落ちました。出力を見ると、対称性の確認自体は実行されています。
"symmetry_check": "左右の脚を中心線で比較した結果、両脚の太さ、長さ、曲がり方はほぼ対称である。
…左右対称性は正常である。"
"verdict": "OK"
原因は分かりやすいところにありました。案 B を作るとき、手順を足す代わりに案 A にあった「探すべき欠陥」の具体的な記述を落としていたためです。
(案A にあった記述)
1. 先端のつまみ部が折れて欠けている:
片側の先端だけが短く断ち切られ、ガタガタした白い破断面が見える。
左右の先端長が明らかに不揃い。
「輪郭の連続性を評価する」という抽象的な手順は、「ガタガタした白い破断面が見える」という具体的な記述の代わりにはなりませんでした。
(2) 案C:記述を残して手順を足す
そこで案 A の記述をすべて残したまま、手順だけを追加しました。これなら悪くなる要素はないはずでした。
結果は、こちらも 9 枚すべて OK です。案 A で検出できていた d1・d2 が落ちました。

この画像は 005.png と同じ個体・同じモデルです。プロンプトだけが違います。
出力を並べると、違いがはっきりしました。どちらも C1 の白い部分は見えています。
Github results/haiku/res_d2_broken.json
// 案A(verdict: REVIEW)
"findings": [{
"defect_type": "先端のつまみ部が折れて欠けている",
"cells": ["C1"], "confidence": 0.65,
"evidence": "C1 セルの上端付近に、左側の先端よりも右側の先端が短く見える可能性がある。
…ただし、被写体の傾きや照明による白飛びハイライトの影響も否定できない"
}],
"normal_observations": "C1 セルの上部に白い球状の光沢(ハイライト)が見えるが、
これは照明由来の正常な反射。…"
Github results/partC/res_d2_broken.json
// 案C(verdict: OK)
"findings": [],
"normal_observations": "C1セル上部に白い球状の物体が見えるが、これは照明由来の反射または
カメラレンズの前方にある外部物体(ほこり、水滴など)と判断される。
本体の一部ではなく、背景領域に属する。"
案 A は、同じ C1 を findings(欠陥かもしれない)と normal_observations(正常な反射)の両方に書いています。根拠も「短く見える可能性がある」「ただし白飛びの影響も否定できない」と、判断を保留した書き方です。その結果が REVIEW でした。
一方の案 C は、normal_observations だけに書き、「外部物体と判断される」と断定して findings を空にしています。
なお normal_observations overall_notes は、こちらが後から足したものではありません。最初の検査仕様を生成した時点で output_schema に含まれていた項目です。案 A の出力にも同じ項目があります。
つまり手順を足したことで変わったのは、項目の有無ではなく 両論併記をやめて断定するようになったことでした。
(3) 3 案の比較
| 案 | 具体的な欠陥記述 | 手順の指示 | 検出 | 過検出 | 判定の傾向 |
|---|---|---|---|---|---|
| A | あり | なし | 2 / 4 | 0 / 5 | REVIEW |
| B | なし | あり | 0 / 4 | 0 / 5 | OK |
| C | あり | あり | 0 / 4 | 0 / 5 | OK |
いちばん素直な案 A が最良でした。3 案の検査仕様は、それぞれ以下に置いてあります。
| 案 | ファイル |
|---|---|
| A | Github results/spec_clothespin_4x4.json |
| B | Github results/spec_clothespin_partB.json |
| C | Github results/spec_clothespin_partC.json |
判定結果の JSON も、モデル別・案別に results/ 配下へ入れてあります。
なお、先に案 C を作っていたら「両方入れた結果」しか分からず、記述と手順のどちらが効いているのか切り分けられなかったと思います。案 B で記述を落としたことで、検出を担っているのが記述の側だと分かりました。
(4) REVIEW は不完全さではなかった
3 案を並べて気づいたのは、検出率よりも判定の傾向の変化です。
案 A の 2 件は、どちらも確信度 0.65 の REVIEW でした。当初これは「NG を出し切れていない、不完全な結果」だと考えていました。ただ、案 B と案 C では迷いが消えて OK と判定するようになり、その判断は誤りでした。
案 A の REVIEW は、確信が持てないものを人手確認に回している状態だったことになります。今回の仕組みには「迷ったら NG ではなく REVIEW にする」という指示を入れていますが、それが意図どおりに働いていたわけです。
品質検査では、間違えること以上に、間違いに自信を持つことのほうが扱いにくいと思います。その意味で、REVIEW が出ること自体は悪い状態ではないと感じました。
6 安いモデルに変えてみる
前回は Nova 2 Lite で金属表面の傷を検出できていました。安いモデルで足りるならそのほうが良いので、検査仕様はそのままでモデルだけ変えてみました。
$ python3 inspect_image.py --spec spec_clothespin_4x4.json \
--model jp.amazon.nova-2-lite-v1:0 \
--image ../images/defect/d2_broken.jpg --runs 3
結果は 9 枚すべて OK でした。異常 4 個も含めて、何も検出されませんでした。
| Claude Haiku 4.5 | Nova 2 Lite | |
|---|---|---|
| 検出(REVIEW以上) | 2 / 4 | 0 / 4 |
| 過検出 | 0 / 5 | 0 / 5 |
| 入力トークン(1 枚・3 回計) | 20,766 | 7,536 |
| 出力トークン(1 枚・3 回計) | 1,848 | 1,406 |
| 処理時間 | 5.8〜8.7 秒/枚 | 2.3〜6.4 秒/枚 |
| JSON パース失敗 | 0 / 27 | 1 / 27 |
過検出 0% だけを見ると良さそうに見えますが、何も検出していないので意味がありません。
気になったのは、入力トークンが Haiku の 1/3 以下である点です。同じ画像を渡しているので、Nova 2 Lite は画像をより粗く扱っているのだろうと思います。金属表面のように画面全体を占める傷なら拾えても、画面の一部にある小さな欠けや汚れには解像度が足りていないのかもしれません。
コストは次のとおりでした(9 枚・3 回多数決)。
入力 67,824 tok / 出力 12,657 tok => $0.06877 = 10.31 円
1 枚あたり $0.00764 = 1.146 円 / 月 1 万枚なら約 11,500 円
単価は AWS Pricing API の実値(jp.amazon.nova-2-lite-v1:0 / ap-northeast-1)を使用しています。Claude Haiku 4.5 は 2026 年 8 月時点で同 API の ap-northeast-1 に登録がないため、推測値でのコスト算出は行っていません。なお前回は 1 枚 0.34〜0.54 円でしたが、今回は参照画像として正常品を毎回同梱しているぶん高くなっています。
ここから言えるのは、検査対象によって必要なモデルのグレードが変わるということです。そしてそれは、そのままランニングコストの差になります。
7 できたことと、できていないこと
できたこと
- 公開データセットではない未知の対象でも、検査仕様の自動生成から判定まで一通り動いた
- 検出できた 2 件は、位置(グリッドのセル名)を正しく指した
- 過検出は 0 件。約 30 度傾けた個体を含めても、正常品を不良と判定しなかった
- 「脚の開き具合」のような可動部の正常な変動を、除外条件として仕様に落とし込めた
- 指示していない除外項目(パーティングライン等)が正常品画像から補われた
できていないこと
- 破断面のない形状変化(変形・曲がり)は検出できなかった
- 欠陥の種類を取り違えた(汚れ → 穴・裂け)
- NG 判定は 1 件も出ず、すべて REVIEW か OK だった
- 安価なモデル(Nova 2 Lite)では何も検出できなかった
次は、正常品との差分比較を明示的に取り入れたときに形状変化を捉えられるか、また撮像条件を変えたときに検出できる欠陥が増えるかを試してみたいと思います。
8 最後に
今回の検証で分かったことを 3 点にまとめます。
1 点目は、可動品には「正常な変動」があるということです。洗濯ばさみの脚の開き具合のように、正常品でも変わる要素を先に定義しておかないと、過検出か見逃しのどちらかが起きます。人が検査要件を書くと「変形を検出したい」で止まりがちですが、実際には何が正常な変動なのかまで書かないと成立しませんでした。
2 点目は、検出を担っているのは「何を探すか」の具体的な記述だったということです。「輪郭の連続性を評価する」という手順ではなく、「ガタガタした白い破断面が見える」という記述が結果を分けていました。手順を足したときは、確認したという記述が増えるだけで、検出は落ちました。
3 点目は、適用できる範囲を実測で切り分けられたことです。事前の予想は「表面欠陥は可、形状異常は不可」でしたが、実際の境界は「破断面のような局所的な手がかりが画像に出ているか」でした。予想が外れたぶん、より使える線が引けたと思います。
全部の欠陥を検出できたわけではありませんが、「この欠陥なら見られる」「この欠陥は今の方式では難しい」を欠陥の種類ごとに言えるようになりました。外観検査に AI を使えるかどうかを判断する場面では、できることの一覧と同じくらい、できないことの一覧が役に立つのではないかと思います。
同じ仕組みを別の製品で試すときのたたき台になれば嬉しいです。
この記事で使用したコードは、以下に置いてあります。今回変更したのは bedrock.py のタイムアウト設定と、generate_spec.py の META_PROMPT の 2 箇所だけです。
- furuya02/bedrock-vlm-visual-inspection-clothespin(本記事)
- furuya02/bedrock-vlm-visual-inspection-quickstart(前回)









