การสร้างเว็บแอปตรวจจับวัตถุแบบเรียลไทม์ผ่านกล้องของอุปกรณ์ โดยใช้ TensorFlow.js และ coco-ssd (COCO-SSD Model) มีฟังก์ชันหลักดังนี้:
- เริ่มต้นกล้องและโหลดโมเดล สำหรับตรวจจับวัตถุ
- แสดงผลวัตถุที่ตรวจพบและนับจำนวน ในแต่ละประเภท
1. โครงสร้างของไฟล์ HTML
ไฟล์นี้ประกอบไปด้วย:
- ส่วนหัว (
<head>
)- โหลด Google JSAPI (ไม่จำเป็นในโค้ดนี้)
- โหลด Meta Tags เพื่อกำหนดรูปแบบของหน้าเว็บ
- โหลด CSS สำหรับตกแต่ง UI
- โหลด TensorFlow.js และ coco-ssd model สำหรับการตรวจจับวัตถุ
- ส่วนเนื้อหา (
<body>
)- แสดง กล้องวิดีโอ (
<video>
) - แสดงผลการนับวัตถุที่ตรวจพบ (
<p id="count-result">
) - ปุ่มบันทึกข้อมูล (
<button id="saveCount">
) - แสดงข้อความแจ้งสถานะการบันทึก (
<p id="save-result">
)
- แสดง กล้องวิดีโอ (
2. การเริ่มต้นกล้องและโหลดโมเดล
📌 startCamera() - เปิดกล้อง
async function startCamera() { try { const constraints = { video: { facingMode: "environment" } // ใช้กล้องหลัง }; const stream = await navigator.mediaDevices.getUserMedia(constraints); video.srcObject = stream; video.onloadedmetadata = () => { video.play(); detectObjects(); // เริ่มตรวจจับวัตถุเมื่อกล้องพร้อม }; } catch (error) { countResult.innerHTML = "❌ ไม่สามารถเข้าถึงกล้องได้"; countResult.style.color = "red"; } }
📌 อธิบาย
- ใช้
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
facingMode: "environment"
ใช้กล้องหลังของอุปกรณ์- หากเปิดกล้องไม่สำเร็จ จะแสดงข้อความแจ้งข้อผิดพลาด
📌 loadModel() - โหลดโมเดล COCO-SSD
async function loadModel() { try { model = await cocoSsd.load(); countResult.innerHTML = "โมเดลโหลดแล้ว กำลังเริ่มตรวจจับวัตถุ..."; } catch (error) { countResult.innerHTML = "❌ ไม่สามารถโหลดโมเดลได้"; countResult.style.color = "red"; } }
📌 อธิบาย
- โหลดโมเดล COCO-SSD ผ่าน
cocoSsd.load()
- หากโหลดโมเดลสำเร็จ ระบบจะแสดงข้อความว่าโมเดลพร้อมใช้งานแล้ว
3. ตรวจจับวัตถุและแสดงผล
📌 detectObjects() - ตรวจจับวัตถุ
async function detectObjects() { try { const predictions = await model.detect(video); updateObjectCounts(predictions); renderPredictions(predictions); requestAnimationFrame(detectObjects); } catch (error) { console.error("เกิดข้อผิดพลาดในการตรวจจับวัตถุ:", error); } }
📌 อธิบาย
model.detect(video)
ตรวจจับวัตถุจากกล้องและคืนค่าผลลัพธ์เป็นอาร์เรย์ของวัตถุที่พบupdateObjectCounts(predictions)
นับจำนวนวัตถุแต่ละประเภทrenderPredictions(predictions)
วาดกรอบรอบวัตถุที่ตรวจพบ- ใช้
requestAnimationFrame(detectObjects)
เพื่อให้ทำงานแบบเรียลไทม์
📌 updateObjectCounts() - นับจำนวนวัตถุ
function updateObjectCounts(predictions) { objectCounts = {}; predictions.forEach(prediction => { const className = translateClass(prediction.class); if (!objectCounts[className]) { objectCounts[className] = 0; } objectCounts[className]++; }); updateCountDisplay(); }
📌 อธิบาย
- รีเซ็ต
objectCounts
ทุกครั้งก่อนคำนวณใหม่ - วนลูปผ่าน
predictions
และนับจำนวนวัตถุที่พบ - ใช้
translateClass()
แปลงชื่อวัตถุจากภาษาอังกฤษเป็นภาษาไทย
📌 translateClass() - แปลงชื่อวัตถุ
function translateClass(className) { const translations = { "person": "คน", "chair": "เก้าอี้", "cup": "แก้ว", "book": "หนังสือ", "laptop": "แล็ปท็อป", "bottle": "ขวด", }; return translations[className] || className; }
📌 อธิบาย
- เปลี่ยนชื่อวัตถุจากภาษาอังกฤษเป็นภาษาไทย
- หากไม่พบชื่อในพจนานุกรม จะใช้ชื่อเดิมที่โมเดลให้มา
📌 renderPredictions() - วาดกรอบรอบวัตถุ
function renderPredictions(predictions) { const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext("2d"); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); predictions.forEach(prediction => { const [x, y, width, height] = prediction.bbox; ctx.strokeStyle = "#00FF00"; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); ctx.fillStyle = "#00FF00"; ctx.font = "16px Arial"; ctx.fillText( `${translateClass(prediction.class)} (${Math.round(prediction.score * 100)}%)`, x, y > 10 ? y - 5 : 10 ); }); }
📌 อธิบาย
- ใช้
<canvas>
วาดกรอบสีเขียวรอบวัตถุที่ตรวจพบ - แสดงเปอร์เซ็นต์ความมั่นใจของโมเดล
4. เรียกใช้ฟังก์ชันเริ่มต้น
async function init() { await loadModel(); await startCamera(); } init();
📌 อธิบาย
- โหลดโมเดล coco-ssd
- เปิดกล้องและเริ่มตรวจจับวัตถุ
📌 สรุป
- ตรวจจับวัตถุแบบเรียลไทม์ ด้วย TensorFlow.js
- แสดงกรอบรอบวัตถุและนับจำนวน แบบเรียลไทม์
- บันทึกผลการตรวจจับไปยัง Google Sheets ด้วย Google Apps Script
- รองรับหลายภาษา (มีแปลไทย)
🎯 ระบบนี้สามารถนำไปพัฒนาต่อยอด เช่น เพิ่มประเภทวัตถุ หรือนำไปใช้กับ AI อื่น ๆ ได้ ✅
5. วิธีเพิ่มประเภทวัตถุ
เพิ่มชื่อวัตถุในฟังก์ชัน translateClass
ฟังก์ชัน translateClass
มีโครงสร้างดังนี้:
function translateClass(className) { const translations = { "person": "คน", "chair": "เก้าอี้", "cup": "แก้ว", "book": "หนังสือ", "laptop": "แล็ปท็อป", "bottle": "ขวด", "car": "รถยนต์", // เพิ่ม รถยนต์ "cell phone": "โทรศัพท์มือถือ", // เพิ่ม โทรศัพท์มือถือ "handbag": "กระเป๋า", // เพิ่ม กระเป๋า }; return translations[className] || className; }
6. ตัวอย่างการใช้งาน
<html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>สแกนวัตถุแบบเรียลไทม์</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; } body { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; background: #f0f0f0; padding: 20px; } #camera-feed { width: 100%; max-width: 640px; border: 2px solid #ccc; border-radius: 10px; } #count-result { font-size: 1.5rem; font-weight: bold; color: green; margin-top: 20px; } #save-result { font-size: 1.2rem; color: #28a745; margin-top: 10px; text-align: center; } .btn { margin-top: 20px; padding: 10px 20px; font-size: 1rem; border: none; border-radius: 5px; background: #28a745; color: white; cursor: pointer; } .btn:disabled { background: #ccc; cursor: not-allowed; } </style> </head> <body> <h1>📷 ระบบนับวัตถุแบบเรียลไทม์</h1> <video id="camera-feed" autoplay="" playsinline=""></video> <p id="count-result">คน: 1<br></p> <p id="save-result"></p> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd"></script> <script> const video = document.getElementById("camera-feed"); const countResult = document.getElementById("count-result"); const saveResult = document.getElementById("save-result"); const saveCountBtn = document.getElementById("saveCount"); let model; let objectCounts = {}; async function startCamera() { try { const constraints = { video: { facingMode: "environment" } }; const stream = await navigator.mediaDevices.getUserMedia(constraints); video.srcObject = stream; video.onloadedmetadata = () => { video.play(); detectObjects(); }; } catch (error) { countResult.innerHTML = "❌ ไม่สามารถเข้าถึงกล้องได้"; countResult.style.color = "red"; } } async function loadModel() { try { model = await cocoSsd.load(); countResult.innerHTML = "โมเดลโหลดแล้ว กำลังเริ่มตรวจจับวัตถุ..."; } catch (error) { countResult.innerHTML = "❌ ไม่สามารถโหลดโมเดลได้"; countResult.style.color = "red"; } } async function detectObjects() { try { const predictions = await model.detect(video); updateObjectCounts(predictions); renderPredictions(predictions); requestAnimationFrame(detectObjects); } catch (error) { console.error("เกิดข้อผิดพลาดในการตรวจจับวัตถุ:", error); } } function updateObjectCounts(predictions) { objectCounts = {}; predictions.forEach(prediction => { const className = translateClass(prediction.class); if (!objectCounts[className]) { objectCounts[className] = 0; } objectCounts[className]++; }); updateCountDisplay(); } function translateClass(className) { const translations = { "person": "คน", "chair": "เก้าอี้", "cup": "แก้ว", "book": "หนังสือ", "laptop": "แล็ปท็อป", "bottle": "ขวด", "car": "รถยนต์", "cell phone": "โทรศัพท์มือถือ", "handbag": "กระเป๋า", "keyboard": "แป้นพิมพ์", }; return translations[className] || className; } function renderPredictions(predictions) { const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); predictions.forEach(prediction => { const [x, y, width, height] = prediction.bbox; ctx.strokeStyle = "#00FF00"; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); ctx.fillStyle = "#00FF00"; ctx.font = "16px Arial"; ctx.fillText( `${translateClass(prediction.class)} (${Math.round(prediction.score * 100)}%)`, x, y > 10 ? y - 5 : 10 ); }); video.srcObject.getVideoTracks().forEach(track => { const imageCapture = new ImageCapture(track); imageCapture.grabFrame().then(frame => { ctx.drawImage(frame, 0, 0, canvas.width, canvas.height); }); }); } function updateCountDisplay() { let resultText = ""; for (let className in objectCounts) { resultText += `${className}: ${objectCounts[className]}<br>`; } countResult.innerHTML = resultText || "ไม่พบวัตถุ"; saveCountBtn.disabled = Object.keys(objectCounts).length === 0; } async function init() { await loadModel(); await startCamera(); } init(); </script> </body> </html>
เยี่ยมสุด ๆ คะ คุณครูผู้มีแต่ให้
ตอบลบ