[15 Apr 2026] [English Version]
與 Gemini 實戰 Google AI Edge Gallery邊緣運算實驗室
(幕後花絮:西芹「答」煎雞扒)

最近在朋友介紹下,我開始試玩 Google AI Edge Gallery 。這是一個非常吸引我的領域,因為它主打 Local LLM(本地大語言模型),直接利用手機或電腦的 GPU 運算
。這意味著加載完成後,不用上網也能運行,既能保障隱私,反應也極快。
📍 第一站:手機版的初步體驗
我先從手機版入手,嘗試了「Ask Image」模型 。
- 在 Best overall 模型中選擇 Try it 。

- 上傳圖片,輸入指令(Prompt)或留空 。

- 稍等片刻,結果便在本地端產生了 。

📸 減肥中,與 AI 的第一場靈魂對話
最近為了配合營養師減肥,我的每一餐都必須嚴格執行「低卡路里」原則。今晚的晚餐(如圖),我堅持沒有用一滴油去煎這塊雞扒,但因為實在太懶,連西芹都懶得炒,直接切段擺盤。
看著這盤有點「生硬」的組合,我順手丟給 AI 分析,沒想到它竟然回報:「西芹答煎雞扒」。
我隨即糾正了它:「那個『答』是『問答』的答,『搭』是『配搭』的搭。」當時心想,難道 AI 比我更懶?它是在「答」我這盤生菜雞扒的違和感嗎?😂
🛠️ 第二站:從基礎出發(MVP 階段)
註: 頁尾附有 HTML 及 JavaScript 範例。
這場體驗開啟了我的好奇心:如果我能用代碼建立自己的實驗室呢 ?我先與 Gemini 合作寫了一個最簡單、沒有訂製的版本,在 Codepen 上運行 。
初版技術架構:Image Classifier
這個版本使用了 ImageClassifier API,並加載了 efficientnet_lite0 模型 。
[ 🧪 意外的 UAT 結果 ]
我上傳了一張包含枝豆、煎三文魚與白飯的照片,結果 AI 竟信誓旦旦地回報:“guacamole” (牛油果醬) !
🔍 深度點評:
邊緣運算的真實感:雖然認錯了,但我反而想「大力讚」。這證明模型真的是在本地跑,沒有連線到雲端「作弊」,這種「青澀」的結果正是 Offline-First AI 的真實寫照 。
特徵提取錯誤:模型顯然被那一盤綠色的枝豆吸引了,在輕量化模型的邏輯裡,一盤「綠色顆粒狀」的食物,最高機率是牛油果醬 。
🎀 第三站:進化!Annie’s Pink AI Lab
註: 頁尾附有 HTML 及 JavaScript 範例。
我不滿足於基礎版,我想要一套具備《賭神》中「出千系統」掃描感,卻又充滿粉紅的療癒風格的介面 。
龜兔賽跑:與錯誤拼搏的過程
這次開發就像一場龜兔賽跑 :
- 小兔子 (AI 模型):天賦極高(EfficientNet 引擎),但加載時容易因為環境限制而「冬眠」。
- 烏龜 (開發者 Annie):憑藉韌性,不斷優化路徑與更換 CDN 。
✨ Gemini Pro 的神救援
當遇到環境兼容性問題卡關時,我將 Gemini 從 “Fast mode” 切換到 “Pro mode” 。神奇的事發生了!Pro 模式馬上診斷出根源:一個微小的 efficientdet 拼寫錯誤 。解謎後,小兔子終於跑到了終點 !
【最終成果展示】
我們最終轉戰 Visual Studio Code,並利用 Live Server 成功運行了這套客製化的系統。
[ 📸 爆笑的 UAT 現場 ]
在使用者驗收測試(UAT)環節,我拿出了一盒從 Donki 購買的方型「即食飯」進行掃描。結果 AI 認真地回報它是「三文治」。這種由模型偏差帶來的驚喜,正是開發過程中不可多得的趣味環節。
📋 驗收報告:Google AI Edge Gallery
以下是本次 Google AI Edge Gallery 實驗的驗收結果與關鍵技術點:
| 測試維度 | 驗收結果 | 技術關鍵點 |
| 傳輸效率 | ✅ 極速響應 | 250 Mbps 頻寬讓 3.1MB 模型瞬間部署。 |
| 運算邏輯 | ✅ 邊緣運算 | 利用 WASM 在瀏覽器本地執行,斷網亦可運作。 |
| 故障排除 | ✅ 完美修復 | 解決了 Net (分類) 與 Det (偵測) 的命名歧義。 |
| UI 體驗 | ✅ 療癒粉紅 | 符合 Baby Pink 美學,且具備動態按鈕回饋。 |
| 模型表現 | ⚠️ 尚待調優 | 將「即食飯」認作「三文治」,反映了通用模型的偏差。 |
🌸 後記:從「問答」到「配搭」的進化
回到開頭那個「西芹答雞扒」的故事。在 2026 年 4 月 15 日測試時,它還在忙著玩「問答」,而我也在那時主動教導了它正確的用字。
沒想到科技的進化快得驚人。到了 4 月 16 日,系統竟然自動更新了!現在模型不僅修正了字眼,更能詳細地列出盤中的每一項食材。有趣的是,英文版一直以來都能詳細列出,沒有中文版的同音詞問題。
這場「一日進化」讓我看見了 Google 在多語言本地化上的追趕速度。從此,西芹再也不會忙著「答」煎雞扒,而是學會了如何優雅地與食材「搭」配。 🐰✨
🐰 結語
這次是我首次接觸 Google AI Edge Gallery,Gemini 陪著我一起排查問題,它是一個非常可靠的隊友,填補了我需要的技術缺口 。能找到一個隨時補位、與妳一起進步的伙伴,才是最珍貴的事 。
範例 1:基礎代碼 (Standard)
HTML
https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs<style> body { background-color: #fff1f2; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #9f1239; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } #container { background-color: #ffffff; padding: 40px; border-radius: 25px; box-shadow: 0 10px 25px rgba(159, 18, 57, 0.08); text-align: center; border: 2px solid #fecdd3; max-width: 400px; } h1 { color: #be123c; font-size: 24px; margin-bottom: 25px; letter-spacing: 1px; } /* 自定義粉色按鈕 */ .custom-file-upload { display: inline-block; padding: 12px 24px; cursor: pointer; background-color: #fda4af; /* Baby Pink 按鈕 */ color: #ffffff; border-radius: 50px; font-weight: bold; text-transform: uppercase; font-size: 14px; transition: all 0.3s ease; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(253, 164, 175, 0.3); } .custom-file-upload:hover { background-color: #f43f5e; transform: translateY(-2px); } #fileInput { display: none; } #result { margin-top: 20px; font-size: 18px; font-weight: 600; color: #9f1239; background-color: #ffe4e6; padding: 15px; border-radius: 15px; border: 1px solid #fecdd3; } #imagePreview { margin-top: 20px; border-radius: 20px; border: 5px solid #ffffff; box-shadow: 0 5px 15px rgba(0,0,0,0.08); }</style><div id="container"> <h1>My Edge AI Laboratory 🐰</h1> <label for="fileInput" class="custom-file-upload"> Upload photo </label> <input type="file" id="fileInput" accept="image/*"> <img id="imagePreview" style="max-width: 100%; display: none;"> <div id="result">Ready...</div></div>
JS
import { ImageClassifier, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs";async function runAI(imageElement) { const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"); const classifier = await ImageClassifier.createFromOptions(vision, { baseOptions: { modelAssetPath: `https://storage.googleapis.com/mediapipe-models/image_classifier/efficientnet_lite0/float32/1/efficientnet_lite0.tflite` }, maxResults: 3 }); const results = classifier.classify(imageElement); document.getElementById('result').innerText = "Result:" + results.classifications[0].categories[0].categoryName;}document.getElementById('fileInput').addEventListener('change', (e) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (f) => { const img = document.getElementById('imagePreview'); img.src = f.target.result; img.style.display = 'block'; img.onload = () => runAI(img); }; reader.readAsDataURL(file);});

範例 2:客製化代碼 (Customized)
HTML
<html lang="zh-Hant"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Annie's Pink AI Lab 🐰</title> <style> body { background-color: #fff1f2; /* Very light Baby Pink background */ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #9f1239; /* dark pink text */ display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } #container { background-color: #ffffff; padding: 40px; border-radius: 25px; box-shadow: 0 10px 25px rgba(159, 18, 57, 0.08); /* light pink shade */ text-align: center; border: 2px solid #fecdd3; /* Soft pink border */ max-width: 400px; width: 90%; } h1 { color: #be123c; /* medium pink title */ font-size: 24px; margin-bottom: 25px; letter-spacing: 1px; } /* Custom pink button */ .custom-file-upload, .analyze-btn { display: inline-block; padding: 12px 24px; cursor: pointer; background-color: #fda4af; /* Baby Pink Button */ color: #ffffff; border-radius: 50px; font-weight: bold; text-transform: uppercase; font-size: 14px; transition: all 0.3s ease; margin-bottom: 10px; box-shadow: 0 4px 6px rgba(253, 164, 175, 0.3); border: none; width: 100%; box-sizing: border-box; } .custom-file-upload:hover, .analyze-btn:hover { background-color: #f43f5e; /* Deepen on hover */ transform: translateY(-2px); } .analyze-btn:disabled { background-color: #fbcfe8; cursor: not-allowed; transform: none; } #fileInput { display: none; } #result { margin-top: 20px; font-size: 16px; font-weight: 600; color: #9f1239; background-color: #ffe4e6; /* Result area background */ padding: 15px; border-radius: 15px; border: 1px solid #fecdd3; min-height: 60px; white-space: pre-wrap; text-align: left; } #imagePreview { margin-top: 20px; border-radius: 20px; border: 5px solid #ffffff; box-shadow: 0 5px 15px rgba(0,0,0,0.08); max-width: 100%; display: none; } .status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: #fda4af; margin-right: 5px; } </style></head><body><div id="container"> <h1>Annie's AI Lab 🐰</h1> <input type="file" id="fileInput" accept="image/*"> <label for="fileInput" class="custom-file-upload"> Step 1: Please upload a photo <br>步驟一: 請上傳照片 📸 </label> <button id="submitBtn" class="analyze-btn" disabled> Loading (充能中)... 🍵 </button> <div id="result">System is starting (系統啟動中)... 🐰✨</div> <img id="imagePreview" alt="Preview"></div><script type="module"> import { ObjectDetector, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14"; let objectDetector; const resultDiv = document.getElementById('result'); const submitBtn = document.getElementById('submitBtn'); const imgPreview = document.getElementById('imagePreview'); const fileInput = document.getElementById('fileInput'); // Init AI async function initAI() { try { console.log("🚀 Engine Start..."); const vision = await FilesetResolver.forVisionTasks( "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm" ); // 💡 efficientDET objectDetector = await ObjectDetector.createFromOptions(vision, { baseOptions: { modelAssetPath: "https://storage.googleapis.com/mediapipe-models/object_detector/efficientdet_lite0/float32/1/efficientdet_lite0.tflite", delegate: "CPU" }, scoreThreshold: 0.3, runningMode: "IMAGE" }); console.log("✅ Loading completed (順利加載)!"); resultDiv.innerText = "Please upload a photo to start analysis\n請上傳照片開始分析 🐰🍵"; submitBtn.innerText = "Step 2: Reveal results\n步驟二: 掃描 🚀"; submitBtn.disabled = false; } catch (err) { console.error(err); resultDiv.innerText = "初始化失敗 ❌\n請檢查網路連線"; } } initAI(); // Upload fileInput.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (f) => { imgPreview.src = f.target.result; imgPreview.style.display = 'block'; resultDiv.innerText = "Image is ready,Please click [Step 2: Scan]\n圖片已就緒,請按[步驟二: 掃描]!📸"; }; reader.readAsDataURL(file); } }; // Perform analysis submitBtn.onclick = async () => { if (!objectDetector) return; resultDiv.innerText = "Initializing (初結化中)... 🐰🧠"; // Slight delay for UI updates setTimeout(() => { const detections = objectDetector.detect(imgPreview); displayResults(detections); }, 100); }; function displayResults(results) { if (results.detections.length > 0) { const topResults = results.detections .sort((a, b) => b.categories[0].score - a.categories[0].score) .slice(0, 3); let report = "【Scan Report 掃描報告】🐰✨\n"; topResults.forEach((d, i) => { const name = d.categories[0].categoryName; const score = Math.round(d.categories[0].score * 100); report += `${i + 1}. ${name.toUpperCase()} (${score}%)\n`; }); resultDiv.innerText = report + "\nRecognition Completed (辨識完成) 🐰 "; } else { resultDiv.innerText = "Recognition failed (辨識失敗) 🤔 \n"; } }</script></body></html>


