おすすめメニューを提案！TeachableMachineで画像判断して今日の献立を決めよう！

皆さん、こんにちは。Naoki-su と申します。
小売業に勤め、デジタルの勉強中です。
今回は TeachableMachine (ML) を使ってメニュー提案ツールを作成します。


# 今日の晩ご飯は何にしよう…
私の今日の晩ご飯は、**さつまいもご飯**でした。
皆さん、さつまいもご飯好きですか？
以前は何の興味もありませんでしたが、食べてみてその美味しさに驚きました。
ナメてましたね。

<img width="500" alt="さつまいもご飯" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/7e276ce3-12e8-d223-1d74-618a0ed8b40f.jpeg">


さつまいもには「**十三里**」という別名があります。まあ、言葉遊びです。
江戸の焼き芋屋さんが「**栗（九里）より（四里）うまい十三里**」と謳って販売したのが由来と言われているそうです。
納得の美味しさです。栗ご飯の手間暇や値段を考えると**相当なコスパ**ですね。


**私自身も料理をする**ので、お気に入りのレシピがいくつかあります。
皆さんも今日の晩ご飯でお悩みではないですか？
そんな時に従業員イチオシの美味しい料理レシピを、お客様に**おすすめのメニューとして簡単に提案出来る**様にならないかと、**TeachableMachine を使用したツール**を作成しました。
私はもちろん、他の従業員が使っても良いですし、お客様が直に使用しても良いかと思います。

# 完成品

https://x.com/naokisu379274/status/1857827246339403853

# 使用ツール
・ [TeachableMachine](https://teachablemachine.withgoogle.com/)
・ [codepen](https://codepen.io/)
・ [ChatGPT](https://chatgpt.com/)
・ [GoogleSpreadSheet](https://docs.google.com/spreadsheets/create?hl=ja)


# 製作過程

### ①TeachableMachineで画像を機械学習

https://teachablemachine.withgoogle.com/

 [TeachableMachine](https://teachablemachine.withgoogle.com/)を開いて、「使ってみる」をクリック。

 
![スクリーンショット (91).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/ba1c9142-d75b-14d6-e61c-03b4051d10b7.png)

<br>
新しいプロジェクトの「画像プロジェクト」をクリック。

![スクリーンショット (92).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/d6dbb741-4f9c-c34c-53b1-fe2ec05129b4.png)

<br>
新しいイメージプロジェクトの「標準の画像モデル」をクリック。

![スクリーンショット (93).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/3e94a0c0-43be-0701-9bbf-8dcd09005162.png)

<br>
<br>

Classに項目を入力していきます。Classが足りなければ「クラスを追加」をクリックして必要な量を足します。
![スクリーンショット (94).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/a50b1f48-f7c3-a686-0f2c-4270daedfe68.png)
今回は
・さつまいも
・ごぼう
・たまねぎ
・じゃがいも
・対象無し
の５つです。

各項目の「ウェブカメラ」をクリックするとカメラが起動します。
「**長押し**して録画」を押して対象のサンプル写真を連続撮影します。

![スクリーンショット (83).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/e79ede19-ebd9-73b3-f5a4-1eaa1aeecc65.png)

:::note info
何も写っていなくても分類が行われてしまうため、
対象無し（背景・手のみ）のクラスを設定すると画像判断の信頼度が上がります。

![スクリーンショット (95).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/5fbcd411-e9ef-f259-978c-574696a1e180.png)

:::

<br>
<br>
すべてのサンプル収集が完了したら
真ん中のトレーニングの「モデルをトレーニングする」をクリックします。

![スクリーンショット (96).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/d72fa8bd-fc9f-6649-a266-8cc6abf89b66.png)

:::note warn
しばらく時間がかかる場合があります。
:::

<br>
<br>
トレーニングが終わると右のプレビューに完成品が映し出されます。
何も写ってなければ「対象無し」、さつまいもを写すと「さつまいも」のパーセンテージが
ちゃんと上がる様になりました。

![スクリーンショット (99).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/28ee9540-eac6-c4db-5a0b-5aaa9ca290be.png)

<br>
<br>
<br>
<br>
プレビューの「モデルをエクスポートする」をクリックし、
出てきたウィンドウの「モデルをアップロード」を選択します。
完了するとリンクが出来るのでコピーしておきます。

![スクリーンショット (102).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/d8f038b4-b2ce-fe1a-8197-b2e5d6850290.png)

<br>
<br>
<br>
画面左のメニューから「ドライブにプロジェクトを保存」もしておきましょう。


![スクリーンショット (103).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/b3c1621a-0622-e9a7-7cef-71cc7efdc84c.png)

<br>
<br>

### ②GoogleSpreadSheetに必要項目を入力

GoogleSpreadSheetを開いて、野菜名・おすすめメニュー名・レシピURLを入力します。
おすすめメニュー名にはリンクを設定し、クリックすると該当のサイトに移動できるようにします。

![スクリーンショット (104).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/a6fd0303-edae-3105-9a74-385e6e126201.png)

:::note info
TeachableMachineの時と同様に対象無しを作成し、
該当時には「野菜を映してください。」と表示されるようにします。
:::

<br>
<br>
<br>
<br>










### ③ChatGPT でコードを作成

https://chatgpt.com/

私が最初に入力したプロンプトが以下です。

![スクリーンショット (105).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/2674fe9a-3463-ff9f-c9f6-d3e5539ad602.png)
![スクリーンショット (106).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/0b2e93a7-ed88-2d87-a434-f55a1e2992b5.png)
その後、試行錯誤を続け、なんとか完成しました。

#### ChatGPT作成のソースコードはこちら

<details><summary>Click!!（ソースコードが開きます）</summary>


```rb
<!DOCTYPE html> 
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>おすすめメニュー表示</title>
    <style>
        body {
            background-color: #e0f7df; /* 淡い緑色の背景 */
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        h1 {
            color: red;
            background-color: white; /* 白い背景 */
            border: 2px solid white;
            display: inline-block;
            padding: 10px;
            margin-bottom: 20px;
            font-size: 24px; /* スマホに合わせてサイズを調整 */
        }
        #menu-name {
            color: yellow;
            background-color: #8b0000;
            padding: 10px;
            border: 2px solid black;
            display: inline-block;
            text-decoration: none;
         　 font-size: 16px; /* メニューのリンク文字サイズ */
            max-width: 90%; /* 画面幅に合わせてリンクサイズ調整 */
        }
        /* カメラ映像の反転設定を制御 */
        .mirror {
            transform: scaleX(-1); /* PC用の反転設定 */
        }
    </style>
    <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
</head>
<body>
    <h1>おすすめメニュー提案</h1>
    <video id="webcam" autoplay playsinline width="430" height="320"></video>
    <p><a id="menu-name" href="#" target="_blank"></a></p>

    <script>
        const modelURL = 'https://teachablemachine.withgoogle.com/models/lXk8mCHOJ/'; // Model URL from Teachable Machine

        let classifier;
        let video;
        let modelLoaded = false;

        async function init() {
            classifier = await ml5.imageClassifier(modelURL + 'model.json', () => {
                document.getElementById('menu-name').innerText = 'Model loaded';
                modelLoaded = true;
            });
            video = document.getElementById('webcam');
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ 
                    video: { facingMode: 'environment' }  // 背面カメラを指定
                });
                video.srcObject = stream;

                // スマホまたはPCを判定し、反転を適用
                if (window.innerWidth < 768) {
                    // スマートフォンなどの場合は反転しない
                    video.classList.remove('mirror');
                } else {
                    // PCの場合は反転
                    video.classList.add('mirror');
                }
            } catch (err) {
                console.error('Webcam access error:', err);
            }
            classifyVideo();
        }

        async function classifyVideo() {
            if (modelLoaded) {
                const results = await classifier.classify(video);
                const recognizedItem = results[0].label;
                fetchMenu(recognizedItem);
            }
    setTimeout(classifyVideo, 500);  // 500msごとに認識
        }

        function fetchMenu(vegetable) {
            const sheetId = "1astJRs2hnAvAKB0_3f1Kf_dOUbejoYPzCw22iA-X4KY"; // Replace with your actual Sheet ID
            const sheetName = "シート1";
            const url = `https://docs.google.com/spreadsheets/d/${sheetId}/gviz/tq?tqx=out:json&sheet=${sheetName}`;

            fetch(url)
                .then(response => response.text())
                .then(data => {
                    const json = JSON.parse(data.substr(47).slice(0, -2));
                    const rows = json.table.rows;
                    let menuFound = false;

                    rows.forEach(row => {
                        if (row.c[0].v === vegetable) {
                            const menuName = row.c[1].v;
                            const recipeUrl = row.c[3] ? row.c[3].v : "#";
                            
                            document.getElementById("menu-name").textContent = menuName;
                            if (vegetable === "対象無し") {
                                document.getElementById("menu-name").href = "#"; // No link for "対象無し"
                            } else {
                                document.getElementById("menu-name").href = recipeUrl;
                            }
                            menuFound = true;
                        }
                    });
                    if (!menuFound) {
                        document.getElementById("menu-name").textContent = "該当するメニューが見つかりません";
                        document.getElementById("menu-name").href = "#";
                    }
                })
                .catch(error => console.error("Error:", error));
        }

        init();
    </script>
</body>
</html>
```
</details>


<br>
<br>
<br>
<br>


### ④CodePenに作成したコードを貼付

https://codepen.io/

CodePenを開き、ChatGPTで作成したコードを左側のHTML部分に貼り付けます。

![スクリーンショット (108).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/3913564a-288b-ec70-23f4-f14f5df6f5f4.png)



野菜が映されていない為、ちゃんと「野菜を映してください」が表示されています。

<br>

### スマホでの操作
スマホでCodePenを開いてログインし、作成したコードを開きます。
カメラへのアクセスを求められるので許可します。
画面上のメニューをタップし、`Debug mode`をタップします。
全画面表示されるようになりました。

![IMG_0417.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/ae30a595-1cc4-dfcb-ea22-aeefe1760b1a.jpeg)

しっかりと背面カメラで起動し、各々の野菜もちゃんと認識し、リンクも出来ています。

![IMG_0418-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/0c52977b-15c2-c07b-2514-68dec2d65b6e.png)




:::note alert
本当は野菜を認識したら**料理の写真**も出てくるようにしたかったのですが、GoogleSpreadSheetに`https://drive.google.com/uc?export=view&id=○○○○○○○○○○○○○○`
に修正したコードを入れましたが、私の力不足で表示されるに至りませんでした。

<img width="100" alt="さつまいもご飯" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885070/7e276ce3-12e8-d223-1d74-618a0ed8b40f.jpeg">

:::


# さいごに
TeachableMachineを使用することにより、画像認識を容易に使用することが出来ました。
ポーズ認識など他の機能も活用すれば、売場案内やレジ待ちの監視ツールなど幅広い活用が考えられます。

今回はよりアプリらしく仕上げる事が出来ましたが、料理画像を表示する所は改善点かと思います。対応出来る野菜の品目増加や複数の選べるおすすめレシピなど、他にもまだ改善の余地はあります。今後もアップデートを続けて行きたいと思います。

最後まで読んで頂きありがとうございます！