สแกนวัตถุแบบเรียลไทม์ด้วย TensorFlow.js

 


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

    


ชื่อคลาสวัตถุที่เพิ่มตรงกับที่โมเดล COCO-SSD รองรับหรือไม่ (ดูรายชื่อวัตถุที่รองรับ ที่นี่)

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>

    


    

1 ความคิดเห็น

  1. ไม่ระบุชื่อ10 มีนาคม, 2568 14:45

    เยี่ยมสุด ๆ คะ คุณครูผู้มีแต่ให้

    ตอบลบ
แสดงความคิดเห็น
ใหม่กว่า เก่ากว่า