3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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

今日の晩ご飯は何にしよう…

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

さつまいもご飯

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

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

完成品

使用ツール

TeachableMachine
codepen
ChatGPT
GoogleSpreadSheet

製作過程

①TeachableMachineで画像を機械学習

TeachableMachineを開いて、「使ってみる」をクリック。

スクリーンショット (91).png


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

スクリーンショット (92).png


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

スクリーンショット (93).png



Classに項目を入力していきます。Classが足りなければ「クラスを追加」をクリックして必要な量を足します。
スクリーンショット (94).png
今回は
・さつまいも
・ごぼう
・たまねぎ
・じゃがいも
・対象無し
の5つです。

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

スクリーンショット (83).png

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

スクリーンショット (95).png



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

スクリーンショット (96).png

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



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

スクリーンショット (99).png





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

スクリーンショット (102).png




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

スクリーンショット (103).png



②GoogleSpreadSheetに必要項目を入力

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

スクリーンショット (104).png

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





③ChatGPT でコードを作成

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

スクリーンショット (105).png
スクリーンショット (106).png
その後、試行錯誤を続け、なんとか完成しました。

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

Click!!(ソースコードが開きます)
<!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>




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

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

スクリーンショット (108).png

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


スマホでの操作

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

IMG_0417.jpg

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

IMG_0418-1.png

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

さつまいもご飯

さいごに

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

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

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

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?