0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Geminiで食品メニューの多様なPDF表の栄養情報を抽出してみた

Posted at

はじめに

外食チェーンのメニューには、カロリーや栄養成分などの栄養情報が公開されています。

この情報は多くの場合、PDF 形式で提供されています。

私はその栄養情報を自動で取得して活用したいと考えました。

しかし、実際に見てみると、いくつかの課題があることに気づきました。

  • PDF ごとに表の形式が異なる
  • セル結合が複雑で、商品名や栄養成分が複数行にまたがることがある

そこで今回は、Gemini を使い、PDF に含まれる表から栄養情報をどのくらい自動で抽出できるかを確認してみました。

使用した生成AI モデル

今回のPDF 抽出に用いたのは、Gemini 2.5 Flash です。

理由は、無料で使え、かつファイルのアップロード制限が他よりも緩いことです。

また、Gemini はPDF を単なるテキストではなく、画像として認識しているようです。

ChatGPT やClaude でも試したところ、おそらくPDF のテキスト情報抽出を試みるため、セル結合や複雑なレイアウトの表データが大きく崩れてしまいました。

(画像として渡せば問題なく認識されました。)

対象にしたPDF

さまざまな形式のものを用意しました。

  • ミスタードーナツ:シンプルな表だが途中に見出しあり
  • すき家:行名にセル結合あり
  • 吉野家:行名にセル結合あり(すき家よりも少し複雑)
  • KFC:列名にセル結合あり
  • ゆで太郎:温/冷のバリエーションあり
  • ピザハット:縦横に複数の表あり

プロンプト

栄養成分の列名は店舗ごとに異なるため、具体的な列指定ではなく、
「商品名と栄養情報」という形で指定しました。

以下のPDFに含まれる表を読み取り、商品名と栄養情報をJSON形式で出力してください。
JSONの形式は { "name": "商品名", "列名1": 値, "列名2": 値, ... } の配列としてください。

結果まとめ

結果は以下のようになりました。

検証のためにさまざまなバリエーションを持った表を選びましたが、難しい部分を除けばだいぶ読み取れている印象でした。

店名 結果 備考
ミスタードーナツ 問題なし
すき家 問題なし
吉野家 行名セル結合が複雑なところでミス
KFC 列名セル結合が複雑なところでミス
ゆで太郎 温/冷やアレルゲンでミス
ピザハット 複数表を認識

各PDFごとの詳細

ミスタードーナツ

ミスタードーナツ.png

  • 出力例
{
  "name": "ブラックサンダーチョコレート",
  "熱量 kcal": 385,
  "たんぱく質 g": 4.3,
  "脂質 g": 24.2,
  "炭水化物 g": 36.9,
  "食塩相当量 g": 0.6
}

→ 見出し行を無視して正しく抽出できました


すき家

すき家.png

  • 出力例
{
  "name": "牛皿",
  "サイズ": "ミニ",
  "カロリー(kcal)": 219,
  "たんぱく質(g)": 11.1,
  "脂質(g)": 16.2,
  "炭水化物(g)": 7.3,
  "食塩相当量(g)": 1.7
}

→ 商品名とサイズを正しく認識できました


吉野家

吉野家.png

  • 出力例
{
  "name": "並盛",  // 「から揚げ丼 並盛」が正しい
  "エネルギー(kcal)": 943,
  "たんぱく質(g)": 31.9,
  "脂質(g)": 44.0,
  "炭水化物(g)": 104.5,
  "食塩相当量(g)": 2.2
}
...
{
  "name": "スパイシーカレー",  // 「スパイシーカレー 並盛」が正しい
  "エネルギー(kcal)": 460,
  "たんぱく質(g)": 7.1,
  "脂質(g)": 6.6,
  "炭水化物(g)": 96.0,
  "食塩相当量(g)": 1.5
}
//  「スパイシーカレー ご飯増量」が抜けている
{
  "name": "牛スパイシーカレー 並盛",
  "エネルギー(kcal)": 585,  // 「602」が正しい
  "たんぱく質(g)": 14.0,
  "脂質(g)": 16.8,         //  「18.1」が正しい
  "炭水化物(g)": 98.8,
  "食塩相当量(g)": 3.2     // 「4.2」が正しい
}

→ 行のセル結合が多い「から揚げ」では、商品名とサイズの両方を認識できませんでした。
→ 「カレー」の部分では「牛スパイシーカレー 並盛」と商品名とサイズを認識できましたが、「スパイシーカレー」ではミスが起こり、以降では栄養情報がずれました。


KFC

KFC.png

  • 出力例
{
  "name": "オリジナルチキン",
  "可食部 平均 重量 (g)": 87,
  "エネルギー (kcal)": 218,
  "たんぱく質 (g)": 16.5,
  "脂質 (g)": 12.8,
  "炭水化物 (g)": 9.1,
  "灰分 (g)": 2.3,
  "ナトリウム (mg)": 585,
  "カリウム (mg)": 318,
  "カルシウム (mg)": 15,
  "リン (mg)": 193,
  "鉄 (mg)": 0.6,
  "レチノール (µg)": 44,
  // 「β-カロテン当量 (µg)」が抜けている
  "A (µg)": 48,  //「レチノール活性当量 (µg)」が正しい
  "B1 (mg)": 0.09,
  "B2 (mg)": 0.48,
  "ナイアシン (mg)": 5.7,
  "C (mg)": 4,
  "食物繊維 総量 (g)": 0.4,
  "食塩相当量 (g)": 1.5
}

→ 列のセル結合が多いビタミンAで一部ミスはありました


ゆで太郎

ゆで太郎.png

  • 出力例
{
  "name": "もり",
  "売価": "430円",
  "熱量(kcal)": "373",
  "アレルゲン (特定品目)": "乳, 小麦, そば"  // 「小麦, そば」が正しい
}

→ 熱量(kcal)の温/冷の認識はできておらず、アレルゲンも異なっていました


ピザハット

ピザハット.png

  • 出力例
{
  "name": "マッシュルームソースのアヒージョ風",
  "種類": "ハンドトス",
  "エネルギー(kcal)": 155
}
...
{
  "name": "地雷系イカ Yummy堕ちピザ",
  "エネルギー(kcal)": 125
}
// 同じものが記載された
{
  "name": "地雷系イカ墨Yummy堕ちピザ",
  "エネルギー(kcal)": 125
}
{
  "name": "マルゲリータ",
  "種類": "Handy Melts",
  "エネルギー(kcal)": 252
}
{
  "name": "辛ペーニョ",
  "種類": "Handy Melts",
  "エネルギー(kcal)": 267
}
{
  "name": "テリマヨチキン",
  "種類": "Handy Melts",
  "エネルギー(kcal)": 277
}
{
  "name": "ミートラバー",
  "種類": "Hut Melts",
  "エネルギー(kcal)": 555
}

→ 一部の商品が2回記載されることもありましたが、複数表の処理自体は可能でした

より詳細なプロンプトでの再挑戦

ゆで太郎の場合で、読み取りが難しかったところをもっと特化して具体的なプロンプトにしてみました。

以下のPDFに含まれる表を読み取り、商品名と栄養情報をJSON形式で出力してください。
JSONの形式は { "name": "商品名", "列名1": 値, "列名2": 値, ... } の配列としてください。 
ただし、熱量(kcal)は次の3種類のキーを用意してください。
- "calories_hot"
    - 表に「温」の数値がある場合に使用します。
    - 数値がなく「―」と記載されている場合は null を格納してください。
- "calories_cold"
    - 表に「冷」の数値がある場合に使用します。
    - 数値がなく「―」と記載されている場合は null を格納してください。
- "calories_general"
    - 「温」「冷」の区別がなく、セルが結合されて単一の数値が記載されている場合に使用します。
    - 「温」「冷」の値が存在する場合は null を格納してください。

結果は以下のようになり、うまくいきませんでした。

特化しても難しいものは難しいようです。

{
  "name": "もり",
  "売価": "430円",
  "calories_hot": null,  // ここで「373」が正しい
  "calories_cold": null,
  "calories_general": 373,
}
...
{
  "name": "かけそば",
  "売価": "430円",
  "calories_hot": null,
  "calories_cold": null,  // ここで「386」が正しい
  "calories_general": 386,
}

感想

  • シンプルな表であれば十分な精度で抽出可能であることがわかりました
  • セル結合が2層などになっている場合、完璧には読み取れないことがわかりました
  • 難しい表はそれほど多くないので、大幅に手動での作業は減らせると思いました

参考資料:検証に使用したPDFファイル

ミスタードーナツ:https://www.misterdonut.jp/m_menu/eiyou/eiyou.pdf
すき家:https://images.zensho.co.jp/materials/sukiya/allergen/nutrition.pdf
吉野家:https://www.yoshinoya.com/pdf/allergy/
KFC:https://assets.ctfassets.net/jax7ylg56usf/4zZVNBldIn7NcECl8hJf1i/1349af6ab2940e2ef618abe04eca810e/240717KFC________________.pdf
ゆで太郎:https://yudetaro.jp/wp-content/uploads/2024/10/yude_calorie10.1_2.pdf#page=1
ピザハット:https://static.pizzahut.jp/jp/tenant/content/upload_5e71b0a2-38c6-4398-8e26-8a8c16e6fdf5.pdf#page=5

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?