การสร้างเว็บแอปตรวจจับวัตถุแบบเรียลไทม์ผ่านกล้องของอุปกรณ์ โดยใช้ 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>
เยี่ยมสุด ๆ คะ คุณครูผู้มีแต่ให้
ตอบลบ