スーパーのチラシをOCRで読み取らせてAIに献立を提案してもらう

2024年09月24日 火曜日


【この記事を書いた人】
立木 也真人

クラウド本部 クラウドソリューション部に所属(2024年入社) 主に社内向けのクラウド記事を書いてます。でかい鯉を見るのが好き。

「スーパーのチラシをOCRで読み取らせてAIに献立を提案してもらう」のイメージ

はじめに

どうもこんにちは、好きな料理漫画は「ミスター味っ子」、クラウドソリューション部の立木です。

ここ2か月間、おいしいので毎晩カレーを食べていましたが、流石に嫌気が刺してきたのでAIに献立を考えてもらうことにしました。

ただ、それだけだと面白味がないので、近所のスーパーのチラシを用いてOCRを行い、読み取った情報を元に献立を考えてもらいます。

それでは実際にやっていきましょう。

※今回、たまたまいなげや様のチラシを使用していますが、本件はいなげや様とは無関係なのでお問い合わせされないようにお願いします。あくまで私個人が本ツールの検証として使用しただけです。

構成

今回の構成です。

まず、チラシをWebサイトから入手し、そのときの形式がPDFなのでjpegに変換します。
次に、jpegをVision APIでOCRを行い、そこで読み取ったデータをBedrockを用いて献立を考えてもらいます

Vision APIとは

Google Cloud Vision APIのことで、Google Cloud Platformが提供する機械学習サービスの1つです。
このサービスを利用すると、Googleの所有する機械学習モデルを使用して、対象の画像から情報を取得することが可能です。

例えば、以下の例があげられます。

  • 乗り物や動物など、画像に写っているさまざまなカテゴリの物体を検出(ラベル検出)
  • 画像内のテキストに対して光学式文字認識(OCR)を実施してテキストを検出、抽出
  • 画像内の顔検出

今回はこちらを使ってチラシから情報を読み取ります。

Bedrockとは

Amazon Bedrockのことで、様々な基盤モデルを単一のAPIから使用できるサービスです。
注意したいのが、Bedrock自体がGPTのような基盤モデルを指すわけではありません。

例えば、BedrockではAnthropic社が提供しているClaude(クロード)や、Stability AI社のSDXLなどの基盤モデルを使用することが出来ます。

また、Agents やKnowledge Baseの作成、ファインチューニングを行ってモデルを学習、セーフガードを定義してAIの答えを制御、などの機能が提供されています。

つまり、Bedrockは生成AIアプリケーションを開発するために必要な基盤モデル+各種機能を提供するサービスです。

今回使用するチラシ

今回入手したチラシは8/17 ~ 18日のものです。

実装

PDFからjpegに変換する

WebサイトからチラシをPDFで取得しました。

PDFのままOCR出来れば良かったのですが、実際にそのままVision APIでOCRを実行してみたら、あまり精度が良くありませんでした。

そこで「チラシのようなはっきりとした色合いのものなら、PDFよりも鮮明に色を表現できるjpegの方がいいのでは?」と思い、一旦PDFをjpegに変換してOCRを行うことにしました。

from pdf2image import convert_from_path


def pdf_to_jpeg(path):
  # poppler/binを環境変数PATHに追加する
  poppler_dir = 'poppler/bin'
  os.environ['PATH'] += os.pathsep + str(poppler_dir)


  image_paths = []


  pdf_path = Path(path)
  # PDF -> Image に変換(150dpi)
  pages = convert_from_path(str(pdf_path), 150)


  # 画像ファイルを1ページずつ保存
  image_dir = Path('./jpg')
  for i, page in enumerate(pages):
    file_name = pdf_path.stem + '_{:02d}'.format(i + 1) + '.jpeg'
    image_path = image_dir / file_name
    page.save(str(image_path), 'JPEG')
    image_paths.append(image_path)


  return image_paths

Vision APIでチラシの情報を読み取る

Vision APIの処理です。

from google.cloud import vision


def detect_text(image_path):
  client = vision.ImageAnnotatorClient()
 # 読み取った文字を結合させる
  all_text = ''
  
  # Vision APIで読み取り
  with io.open(image_path, 'rb') as image_file:
    content = image_file.read()
  image = vision.Image(content=content)
  response = client.text_detection(image=image)
  texts = response.text_annotations
  for text in texts:
    all_text += str(text.description)
  if response.error.message:
    raise Exception(
      '{}\nFor more info on error messages, check: '
      'https://cloud.google.com/apis/design/errors'.format(
        response.error.message))
  
  return all_text

そうして実行した結果がこちら

画像は全体の20分の1ぐらいなんですが、何が何だかよくわからないですね。
ここでBedrockを用いて、食材と値段を抽出して紐づけてもらいましょう。

読み取った内容をBedrockで整理する

ここからBedrockの処理です。モデルは「Claude 3.5 Sonnet」を使用します。

from boto3.session import Session


def split_text(text, session):
  bedrock_runtime = session.client(
    service_name='bedrock-runtime',
    region_name='us-east-1',
  )


  modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"
  
  prompt = f"以下の記載から、食材と値段を紐づけてリストにしてください /n {text}"


  messages = [
    {
      "role": "user",
      "content": [{"text": prompt}]
    }
  ]


  response = bedrock_runtime.converse(
    modelId=modelId ,
    messages=messages
  )


  split = response["output"]["message"]["content"][0]["text"]
  print(split)


  return split

結果がこちら。

以下、食材と値段を紐づけてリストにしました:
1. きゅうり: 99円
2. まぐろたっぷり: 500円
3. くりーむパン (カスタード、マンゴー): 価格不明
4. かぼちゃ: 69円
5. なす: 1個 税込259円
6. 牛豚焼肉盛り合わせ: 1パック320g入 999円
7. 国産豚肉生姜焼き用: 100g当たり199円
8. 国産豚肉うす切り(バラ): 100g当たり199円
9. 冷凍牛肉味付ハラミ焼肉用: 1パック220g入 699円
10. 豚ロース酢豚: 1パック 369円
11. パリッと中華春巻: 5本+1本 699円
12. こだわりポテトサラダ(小): 1パック 198円
13. ミニパン: よりどり2袋 299円
14. にぎり寿司: 2人前 1,290円
15. にんにく醤油の若鶏ジューシー竜田揚: 1パック 699円
16. 大海老天重: 1パック 679円
17. キャベツ: 179円
18. ブロッコリー: 179円
19. シャインマスカット: 1パック 1,290円
20. トマト小箱: 1箱 399円
21. 牛肉ステーキ用: 100g当たり350円
22. 国産豚肉小間切れ: 100g当たり159円
23. 銀鮭西京漬: 2切入 459円
24. お刺身用かつお平盛: 200g入 500円
25. お刺身用本まぐろ赤身平盛: 130g入 999円
26. スモークサーモン切落し: 1パック 499円

以下のリストは、食材とその価格を紐づけたものです:
- 国産白菜: 180円
- ゴーヤ(にがうり) 1本: 100円
- とうもろこし 1本: 100円
- おくら: 100円
- きゃべつ 1ネット: 100円
- 国産豆腐(絹・木綿) 350g: 85円
- ミニトマト 1パック: 232円
- お刺身用真だこ(ボイル) 100g: 399円
- 釜揚げしらす 80g: 399円
- 豚肉しゃぶしゃぶ用(ロース) 100g: 139円
- 小松菜 1袋: 98円
- みょうが 1パック: 98円
- キウイ(中玉) 1個: 98円
- 大隅半島の洗いごぼう(カット): 98円
- 根生姜: 98円
- にんにく: 98円
- かいわれ大根 2パック: 98円
- 玉ねぎ 2個: 98円
- 株どりなめこパック: 98円
- 生しいたけ少量パック: 98円
- 霜降りひらたけ 1パック: 98円
- 生きくらげ 1パック: 98円
- 豚肉生姜焼き用(ロース) 100g: 298円
- 豚肉小間切れ 100g: 159円
- 国産若鶏(ささみ) 100g: 98円

同じような文言が並んでいるのは、チラシが2ページあったのを分割して実行したからです。
微妙にチラシと値段が異なるものも存在しますが、おおむね良い感じで紐づけ出来ています。

整理した食材データから献立を考えてもらう

では、取得した食材から献立を考えてもらいましょう。
「予算は1000円以内です」と入れている理由は、テスト実行したときに5000円のコース料理を提案されたからです。

textの中には先ほど抽出したレシピが入ります。

from boto3.session import Session


def suggestion(text, session): 
  bedrock_runtime = session.client(
    service_name='bedrock-runtime',
    region_name='us-east-1',
  )


  modelId = "anthropic.claude-3-5-sonnet-20240620-v1:0"
  
  prompt = f"以下の食材から、今日の夜ご飯を提案してください。予算は1000円程度です。 /n {text}"


  messages = [
    {
      "role": "user",
      "content": [{"text": prompt}]
    }
  ]


  response = bedrock_runtime.converse(
    modelId=modelId ,
    messages=messages
  )


  suggestion = response["output"]["message"]["content"][0]["text"]
  print(suggestion)

実行した結果はこちら。

予算1000円程度で、栄養バランスの良い夜ご飯を提案します:

1. メイン料理:豚肉生姜焼き
   - 国産豚肉生姜焼き用(200g): 398円
   - 玉ねぎ(2個): 98円

2. 副菜:
   - きゅうり: 99円
   - 小松菜 1袋: 98円

3. サラダ:
   - ミニトマト 1パック: 232円

4. 汁物:
   - 国産豆腐(絹・木綿)350g: 85円
   - 株どりなめこパック: 98円

合計:1,108円

調理方法:
1. 豚肉生姜焼きを作ります。玉ねぎをスライスし、豚肉と一緒に炒めます。
2. きゅうりを薄切りにし、塩もみしてから水気を絞り、ごま油と塩で和えます。
3. 小松菜を茹でて、醤油とごまで和えます。
4. ミニトマトをサラダとして提供します。
5. 豆腐となめこを使って味噌汁を作ります。

この組み合わせで、タンパク質、野菜、汁物がバランス良く摂取できます。
予算を少し超過していますが、量を調整すれば1000円以内に収めることができます。

ふむ、普通ですね。
味噌を材料に入れてないのに味噌汁を作っていることに若干違和感を感じますが、おおむね問題ないです。

ちょっと面白味に欠けるのでプロンプトを少しいじりましょう。
「独身男性が普段食べないような独創的な料理を詳しく教えてください」という文言を入れてみました。

以下の食材から、今日の夜ご飯を提案してください。
独身男性が普段食べないような独創的な料理を詳しく教えてください。
米に合うようなおかずを教えてください。
オーブンやミキサーの類は家にありません。予算は1000円程度です。

実行した結果はこちら。

独創的で米に合う料理として、「なすとまぐろの和風カルパッチョ」を提案します。
これは通常の独身男性が普段作らないような料理ですが、簡単で美味しく、予算内で作れます。

材料(2人分):
- なす(1個): 259円
- まぐろたっぷり: 500円
- きゅうり(1本): 99円
- みょうが(1パック): 98円
- 小松菜(1袋): 98円

調理方法:
1. なすを薄くスライスし、水にさらして灰汁を抜きます。
2. きゅうりを薄くスライスし、塩少々(材料外)でもみ、水気を絞ります。
3. みょうがを薄くスライスします。
4. 小松菜を3cm程度に切り、さっと湯通しして冷水にさらします。
5. まぐろを1cm角に切ります。
6. 大皿になすを敷き詰め、その上にまぐろ、きゅうり、みょうが、小松菜を彩りよく盛り付けます。

タレ:
- 醤油大さじ2
- 酢大さじ1
- みりん大さじ1
- すりおろし生姜小さじ1(材料外)

タレの材料を混ぜ合わせ、盛り付けた具材にかけます。
このメニューは見た目も美しく、さっぱりとした味わいで夏にぴったりです。
なすとまぐろの組み合わせは珍しく、独創的な一品となります。
また、冷たいままで食べられるので、暑い日でも食欲が出ます。
米との相性も抜群で、さっぱりとした味付けのおかずは白米を引き立てます。

合計金額:954円(タレの材料を除く)で、予算内に収まっています。

確かになすとまぐろの組み合わせは珍しいですね。これは興味を惹かれるので、作ってみましょう。

 

なすを弱火で焼くというアレンジを加えています。
それ以外はレシピ通りですが、誰かに振る舞っても問題ないぐらい美味しかったです。

では2品目いってみましょう。

独創的な料理として、「かぼちゃと豚肉の生姜焼きカレー風味」を提案します。
この料理は、普段の生姜焼きとは一味違う、独特な味わいが楽しめます。

材料(2人分):
1. かぼちゃ: 69円
2. 国産豚肉生姜焼き用: 400g (798円)
3. 玉ねぎ: 1個 (49円)
4. にんにく: 1片 (約20円)
5. 根生姜: 少々 (約30円)

合計: 約966円
 
調理方法:
1. かぼちゃは皮をむき、1cm角に切ります。
2. 玉ねぎはスライス、にんにくと生姜はみじん切りにします。
3. フライパンに油をひき、にんにくと生姜を炒めて香りを出します。
4. 豚肉を加えて炒め、色が変わったら玉ねぎを加えます。
5. かぼちゃを加え、蓋をして中火で7-8分蒸し煮します。
6. かぼちゃが柔らかくなったら、醤油大さじ2、みりん大さじ1、カレー粉小さじ1を加えて味付けします。
7. 全体に味がなじむまで炒め合わせ、水分を飛ばします。
 
このレシピは、甘みのあるかぼちゃと生姜焼きの相性が良く、カレー粉を加えることで独特の風味が生まれます。
かぼちゃの自然な甘みと豚肉の旨味、カレーのスパイシーさが絶妙なハーモニーを奏でます。
rice.txた独身男性にとっては新鮮な味わいになるでしょう。
また、調理も比較的簡単で、オーブンやミキサーも必要ありません。
米との相性も良く、ボリュームも十分なので満足度の高い一品になると思います。

ふむ、生姜焼きにカレー粉は試したことがないですね。またかぼちゃも珍しい。
「rice.txた独身男性」とちょいとバグってますが、どういう言葉が入る想定だったんだろう…。米た独身男性?

まぁ気を取り直して作ってみましょう。

 

この味は…!

普通のカレーですね。生姜焼きは消滅しました。美味しかったです。かぼちゃもおいしい。

余談

スーパーに行ったときに、「きゅうり」はセール外、「まぐろたっぷり」が存在しないことに気づきました。
改めてチラシを見てみると…

あっ

セール商品じゃなかったです。

まとめ

Vision APIの精度が思っていたよりも高くて驚きです。

OCRしたデータをAIを使って特定の形式にまとめる、というのは今後何かに使えないかなと思いました。

今回の一連のサービスを応用して、料理名と味、感想をチェーンさせて、自分好みの料理を提案してくれる「献立提案ボット」とか出来れば面白そうです。

立木 也真人

2024年09月24日 火曜日

クラウド本部 クラウドソリューション部に所属(2024年入社) 主に社内向けのクラウド記事を書いてます。でかい鯉を見るのが好き。

Related
関連記事