[Amazon Bedrock] Nova 2 Lite で金属部品の欠陥検出(外観検査)を試してみました
1 はじめに
製造ビジネステクノロジー部の平内(SIN)です。
製造業の品質検査(外観検査)を自動化する場合、本来はコンピュータビジョンの専用モデルを作成するのが一般的です。しかし、欠陥画像の収集・アノテーション・学習・再学習が必要で、対象部品や欠陥の種類が変わるたびにやり直しになるため、コストは、比較的高いものとなります。
一方、LLM(マルチモーダルな基盤モデル)による外観検査は、モデルの学習が不要で、検出したい欠陥を自然言語で指定するだけと手軽です。ただし、後述するように検出精度には限界があります。
そこで今回は、「学習を一切せず、LLM だけでどこまでの外観検査ができるのか」を試してみました。
先に結論を述べると、欠陥の有無やおおまかな位置は捉えられるものの、バウンディングボックスの精度には限界があり、本番ラインへの適用には、位置精度の向上やバウンディングボックスの後処理が必要と考えられます。
一方で、学習が不要で導入が非常に軽く、コストもわずかなため、プロトタイプの検証や、検査システムのサンプル作成といった用途であれば十分に使える、というのが今回の手応えです。
参考にさせて頂いたのは、AWS のブログで紹介されていた Amazon Nova 2 Lite による物体検出(Object Detection)です。
このブログ記事では、モデルの学習を一切行わず、「scratch(傷)」「dent(へこみ)」「rust(錆)」のような検出対象を自然言語(プロンプト)で指定するだけで、バウンディングボックス付きの検出結果が得られる方法が解説されています。ユースケースの 1 つとして、金属加工工場での品質検査の自動化が挙げられていたので、今回は、これを題材に「金属部品の欠陥検出」を色々試してみました。
作成したコードは、下記のリポジトリで公開しています。
2 Amazon Nova 2 Lite による物体検出
(1) 仕組み
Nova 2 Lite はマルチモーダルな基盤モデルであり、画像とテキストのプロンプトを同時に入力できます。物体検出は、次の要領で行います。
- 画像と「検出対象のリスト」「出力してほしい JSON スキーマ」をプロンプトとして Amazon Bedrock の Converse API に送信する
- モデルが、検出した物体ごとに 0〜1000 スケールの正規化座標 [x_min, y_min, x_max, y_max] を JSON で返す
- クライアント側で画像サイズに応じてピクセル座標へ変換し、バウンディングボックスを描画する
座標が 0〜1000 の正規化値で返るため、元画像の解像度に依存せず利用できます。
(2) 推論プロファイル
東京リージョン(ap-northeast-1)で list-foundation-models を確認すると、amazon.nova-2-lite-v1:0 の inferenceTypesSupported は INFERENCE_PROFILE のみとなっており、オンデマンドでの直接呼び出しはできません。呼び出しには推論プロファイルを使用します。
$ aws bedrock list-inference-profiles --region ap-northeast-1 \
--query "inferenceProfileSummaries[?contains(inferenceProfileId,'nova-2')].{id:inferenceProfileId,status:status}" --output table
| global.amazon.nova-2-lite-v1:0 | ACTIVE |
| jp.amazon.nova-2-lite-v1:0 | ACTIVE |
今回は、国内リージョン内でルーティングされる jp.amazon.nova-2-lite-v1:0 を使用しました。
3 構成
作成したサンプルの構成です。

- CloudFront + S3 (OAC) … 検査用の Web UI(静的ページ)を配信します。S3 バケットはプライベートのままです
- API Gateway + Lambda … 画像(Base64)と欠陥リストを受け取り、Converse API で Nova 2 Lite を呼び出します
- Amazon Bedrock (Nova 2 Lite) … 欠陥の検出を行い、正規化座標の JSON を返します
参考にした AWS ブログの公式サンプル(aws-samples/sample-object-detection-nova-2-lite)では、Lambda に Pillow のレイヤーを追加して描画済みの画像を S3 に保存し、署名付き URL を返す構成になっています。本稿では、Lambda は正規化座標の JSON を返すだけとし、描画はブラウザの Canvas(またはローカルスクリプトの Pillow)で行う構成にしました。これにより、Lambda の依存ライブラリがゼロになり、レイヤーや結果保存用のバケットが不要になっています。
4 Lambda(Nova 2 Lite の呼び出し)
Github index.py
プロンプトでは、検出対象(elements)と出力スキーマ(schema)を動的に埋め込みます。要点のみ抜粋します。
PROMPT_TEMPLATE = """Your task is to detect and localize defects on the metal part in the image with high precision and recall.
The defects to be detected are: {elements}
Output Requirements:
1. Provide coordinates of the top-left corner and bottom-right corner of each bounding box
2. Use [x_min, y_min, x_max, y_max] format with values from 0-1000
...
Return ONLY valid JSON in this exact format:
{schema}
"""
def handler(event, context):
body = json.loads(event["body"])
image_bytes = base64.b64decode(body["image"])
objects = body.get("objects", ["scratch", "dent", "rust"])
schema = json.dumps({name: [{"bbox": ["x_min", "y_min", "x_max", "y_max"]}] for name in objects})
prompt = PROMPT_TEMPLATE.format(elements=", ".join(objects), schema=schema)
bedrock = boto3.client("bedrock-runtime")
response = bedrock.converse(
modelId=MODEL_ID, # jp.amazon.nova-2-lite-v1:0
messages=[{"role": "user", "content": [
{"image": {"format": image_format, "source": {"bytes": image_bytes}}},
{"text": prompt},
]}],
inferenceConfig={"temperature": 0, "maxTokens": 2048},
)
注意が必要なのは、モデルの応答が常に正しい JSON とは限らないことです。実際に試していると、temperature=0 でも次のような「引用符が壊れた JSON」が返ることがあり、json.loads がそのままでは失敗しました。
"rust": [
["109", 208", "153", 263"],
公式サンプルの Lambda にも、json.loads → クォート置換 → ast.literal_eval → 正規表現抽出、という多段のフォールバックが実装されており、既知の挙動のようです。今回のサンプルでも、JSON のパースに失敗した場合は、ラベルごとに座標 4 つ組を正規表現で抽出するフォールバックを実装しています。
def parse_detections(text):
match = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
raw = (match.group(1) if match else text).strip()
try:
return json.loads(raw)
except json.JSONDecodeError:
# 引用符が壊れた JSON が返ることがあるため、ラベルごとに座標 4 つ組を正規表現で抽出する
...
LLM に構造化出力をさせる場合、「正しい JSON が返らない前提」でパーサーを書く必要があるようです。
5 CDK
Github stack.ts
CDK(TypeScript)で、Lambda・API Gateway・S3・CloudFront を定義しています。Bedrock の呼び出し権限は、推論プロファイルと基盤モデルの両方に必要です。
detectFn.addToRolePolicy(
new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: [
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${MODEL_ID}`,
'arn:aws:bedrock:*::foundation-model/amazon.nova-2-lite-v1:0',
],
}),
);
また、フロントエンドのデプロイでは、BucketDeployment の Source.data() を使用して、API Gateway の URL を config.js として S3 に書き込んでいます。デプロイするだけで Web UI から API に接続できます。
new s3deploy.BucketDeployment(this, 'DeployFrontend', {
sources: [
s3deploy.Source.asset('frontend'),
s3deploy.Source.data('config.js', `window.API_URL = '${api.url}detect';`),
],
destinationBucket: frontendBucket,
distribution,
});
デプロイは、以下のとおりです。
git clone https://github.com/furuya02/bedrock-nova2-metal-defect-inspection.git
cd bedrock-nova2-metal-defect-inspection/cdk
pnpm install
pnpm cdk deploy
6 Web UI
Github index.html
CloudFront の URL を開くと、検査用の Web UI が表示されます。画像を選択し、検出したい欠陥(デフォルトは scratch, dent, rust)を指定して「検査する」を押すだけです。

API から返却された 0〜1000 の正規化座標は、Canvas 上でピクセル座標に変換して描画しています。
// 0-1000 の正規化座標をピクセルに変換
const x1 = box[0] / 1000 * image.width;
const y1 = box[1] / 1000 * image.height;
const x2 = box[2] / 1000 * image.width;
const y2 = box[3] / 1000 * image.height;
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
7 動作確認
いくつかの画像で、試してみました。テスト画像には、Wikimedia Commons で公開されている CC0 ライセンスの画像と、鋼板表面欠陥のデータセットである NEU-DET(後述)を使用しました。
検出は、Web UI のほか、API に直接画像を POST して結果画像を生成するスクリプトでも確認できます。
Github detect.py
cd scripts
python detect.py <ApiUrl>detect images/sample_part_rust.jpg "rust"
# -> result_sample_part_rust.png が生成されます
(1) 金属部品の錆
左側のレール部分に錆(腐食)が発生している金属部品の画像です。rust を指定すると、錆の発生している左側のレール部分が検出されています。

(2) 錆びた鉄板
画像の大部分が錆びている鉄板です。rust と scratch が大きなボックスとして検出されました。欠陥が画像全体に広がっている場合は、領域を細かく分割するのではなく、大きな領域として返る傾向があるようです。

(3) 公開データセット(NEU-DET)での検証
より外観検査らしい画像でも試してみました。使用したのは、東北大学(Northeastern University)が公開している鋼板表面欠陥のデータセット NEU-DET です(Roboflow で再配布されている CC BY 4.0 版)。ここでは、傷(scratches)2 枚・斑点(patches)1 枚・介在物(inclusion)1 枚の計 4 枚を選びました。元画像は 200×200 のグレースケールで小さいため、3 倍(600×600)に拡大して使用しています。
まず、傷(scratches)の 2 枚です。scratch を指定すると、横方向の傷は、その付近を囲むバウンディングボックスで検出できました。

縦方向の傷も scratch で検出できました。傷のおおまかな縦のラインは捉えているものの、水平位置はやや左に寄っています。

次に、斑点(patches)と介在物(inclusion)です。斑点は patch という指定では 1 件も検出されませんでしたが、見た目を表す dark spot(暗い斑点)に変えると、2 件の暗い領域が検出されました。

介在物も同様に、inclusion では検出されず、dark streak(暗い筋)に変えると、暗い筋を検出できました。ただし非常に低コントラストなため、検出結果は呼び出しによってばらつき、暗い筋の一部だけを細く囲んだり、複数の領域に分割されたりしました。

この 4 枚を試して、以下のようなことが分かりました。
1 つめは、検出対象を指定する語の選び方です。 上で試した指定語と結果をまとめると、次のようになります。
| 画像(欠陥の種類) | 指定した欠陥語 | 検出結果 |
|---|---|---|
| scratches(傷・横方向) | scratch |
1 件(傷の付近を検出) |
| scratches(傷・縦方向) | scratch |
1 件(位置はやや左寄り) |
| patches(斑点) | patch |
0 件 |
| patches(斑点) | dark spot, stain |
2 件(暗い領域を検出) |
| inclusion(介在物) | inclusion |
0 件 |
| inclusion(介在物) | dark streak |
1 件(暗い筋を検出、結果は不安定) |
patch や inclusion のような分類名では検出されず、dark spot や dark streak のように見た目を表す語に変えると検出されました。専門用語よりも、欠陥の見た目を表す平易な英語の方が検出されやすい傾向があるようで、検出対象の指定は、いくつかの表現を試してみる必要がありそうです。
2 つめは、バウンディングボックスの位置精度です。 これらの画像では、欠陥の有無やおおまかな領域は捉えられるものの、位置精度はあまり高くありませんでした。モデルが返す座標(0〜1000 に正規化した [x_min, y_min, x_max, y_max])と、画像から求めた実際の欠陥領域を比べると、次のようなズレが見られました。
| 画像 | 実際の欠陥領域(概算) | モデルが返した座標 |
|---|---|---|
| scratches(横方向) | 縦位置 y≈336〜498 の横帯 | [0, 298, 998, 370](やや上寄り) |
| scratches(縦方向) | 主要な傷は x≈130〜220 | [0, 0, 56, 796](左端に寄る) |
| patches(右の暗い塊) | x≈580〜780 | [550, 0, 800, 1000](ほぼ一致) |
横方向の傷が横帯として、右側の暗い塊が正しい水平位置として返っていることから、座標の変換自体は正しく機能しています。位置がずれるのは、元が 200×200 と小さく低コントラストな画像に対する、モデル側の位置精度の限界かもしれません。また、temperature=0 を指定していても、同じ画像で呼び出すたびに座標が多少変動しました(上記の縦方向の傷も、呼び出しによって [0, 0, 62, 1000] や [0, 0, 56, 796] のように変わりました)。厳密な位置の再現性が必要な用途では、この点も考慮が必要です。
(4) 高解像度な実写画像での例
位置精度が画像の質に依存することを確かめるため、より高解像度で欠陥がはっきり写った実写画像でも試しました。車体(ホイールアーチ周辺)に錆が生じている画像です。rust を指定した検出結果です。

小さく低コントラストな NEU-DET の画像と異なり、錆の領域を的確に囲むバウンディングボックスが得られました。前述のとおり、入力画像の解像度とコントラストが高いほど、位置精度も向上する傾向がはっきりと表れています。学習なしでプロンプトだけで欠陥を検出するアプローチでは、撮影条件を整えて質の良い画像を入力することが、精度を引き出す近道になりそうです。
(5) 金属以外への応用
LLM を使うこの方式は、対象ごとの学習が不要なぶん、指定を変えるだけで別の対象にも試せます。汎用性を確かめる一例として、画面が割れたスマートフォンに対して crack(ひび)を指定してみました。

プロンプトの検出対象を crack にしただけで、画面のひびの領域を検出できました。
ここまで「金属部品の欠陥検出」を中心に進めてきましたが、このように、プロンプトを工夫するだけで対象を簡単に広げられます。製造ラインの多品種化や、検査項目の追加・変更が多い場合、この柔軟性は特に活きてくると思います。
8 コスト
Amazon Nova 2 Lite はトークン単位の課金です。金属部品の検出では、消費トークンは入力 549 / 出力 34 でした。Amazon Bedrock の料金から計算すると、1 画像あたりのコストは $0.001 未満です。
参考にした AWS ブログでも、部品 1 つにつき 5 枚の画像を分析し、月 1 万個の部品を検査する場合の費用は約 8 ドル/月と試算されています。
学習用の GPU インスタンスなどを常時確保する必要もないため、スモールスタートに向いていると思います。
9 使ってみて気がついたこと
実際に動かしてみて、いくつか気がついた点がありました。
- 不正な JSON が返ることがある … 引用符が壊れた JSON が返ることがあります。パーサーのフォールバックは必須と考えたほうが良さそうです
- 連続した欠陥領域が複数ボックスに分割されることがある … 縦に連続した錆の領域に対して、同じ幅の小さなボックスがタイル状に並んで返ってくるケースがありました。検出位置としては誤りではありませんが、後処理でマージするなどの工夫の余地があります
- コントラストの低い欠陥は検出が難しい … 暗い金属面のうっすらとした傷は検出されませんでした。撮影条件(照明・角度)を整えることは、従来のコンピュータビジョンと同様に重要です
- 検出対象の指定は、見た目を表す平易な語が効きやすい …
patchやinclusionのような分類名では検出されず、dark spotやdark streakのように見た目を表す語に変えると検出されました。指定する語は複数試してみるのが良さそうです - バウンディングボックスの位置精度は画像に依存する … 解像度が高くコントラストのはっきりした画像ではボックスがよく一致しましたが、小さく低コントラストな画像では、欠陥の有無は当てられても位置がずれやすくなりました。また
temperature=0でも呼び出しごとに座標が多少変動します。位置の厳密さが要求される用途では注意が必要です
10 まとめ
今回は、Amazon Nova 2 Lite を使用して、金属部品の欠陥検出(外観検査)を試してみました。
- モデルの学習なしで、「scratch」「dent」「rust」のような自然言語の指定だけで欠陥検出ができます
- 座標は 0〜1000 の正規化値で返るため、クライアント側の描画も簡単です
- 東京リージョンでは、推論プロファイル
jp.amazon.nova-2-lite-v1:0で利用できます - 学習不要で、導入・実行コストも低く抑えられます
- 学習不要のため、金属以外の対象や別の欠陥にも、プロンプトの変更だけで応用できます
はじめに述べたとおり、精度には限界があると思います。しかし、検査対象や欠陥の種類が変わってもプロンプトの変更だけで追従でき、学習なしで軽く始められる点は、従来のモデル学習ベースのアプローチにはない魅力です。プロトタイプの検証やサンプル作成の用途で、まずは、手軽に試してみるのが良いのでは、と感じました。
11 参考リンク
- Object detection with Amazon Nova 2 Lite
- aws-samples/sample-object-detection-nova-2-lite
- Amazon Bedrock の料金
- Supported Regions and models for inference profiles
- NEU-DET(Roboflow Universe, CC BY 4.0) … K. Song and Y. Yan, Applied Surface Science, 2013 に基づく鋼板表面欠陥データセット
- 車体の錆の画像: Car bodywork rusted through in BMW 318i E46 (c) Marek Ślusarczyk (Tupungato), CC BY 3.0
- スマートフォンの画像: MyPhone my27 front panel (c) JGBlue1509, CC BY 4.0
- furuya02/bedrock-nova2-metal-defect-inspection










