[15 Apr 2026] [English Version]

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

最近在朋友介紹下,我開始試玩 Google AI Edge Gallery 。這是一個非常吸引我的領域,因為它主打 Local LLM(本地大語言模型),直接利用手機或電腦的 GPU 運算
。這意味著加載完成後,不用上網也能運行,既能保障隱私,反應也極快。

📍 第一站:手機版的初步體驗

我先從手機版入手,嘗試了「Ask Image」模型 。

  1. Best overall 模型中選擇 Try it
  2. 上傳圖片,輸入指令(Prompt)或留空 。
  3. 稍等片刻,結果便在本地端產生了 。

📸 減肥中,與 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

<!DOCTYPE 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>