const video = document.getElementById('video'); const imageUpload = document.getElementById('imageUpload'); const refImage = document.getElementById('refImage'); const statusDiv = document.getElementById('status'); const resultDiv = document.getElementById('result'); let referenceDescriptor; // ตัวแปรสำหรับเก็บ "ลายเซ็น" ของใบหน้าต้นฉบับ // ฟังก์ชันสำหรับโหลดโมเดลทั้งหมด async function loadModels() { const MODEL_URL = 'https://raw.githubusercontent.com/justadudewhohacks/face-api.js/master/weights'; statusDiv.innerText = 'กำลังโหลดโมเดล AI จาก GitHub...'; try { await faceapi.nets.tinyFaceDetector.loadFromUri(MODEL_URL); await faceapi.nets.faceLandmark68Net.loadFromUri(MODEL_URL); await faceapi.nets.faceRecognitionNet.loadFromUri(MODEL_URL); statusDiv.innerText = '✅ โมเดล AI พร้อมใช้งาน! กรุณาอัปโหลดรูปภาพ'; } catch (error) { console.error("เกิดข้อผิดพลาดในการโหลดโมเดล:", error); statusDiv.innerText = '❌ ไม่สามารถโหลดโมเดล AI ได้ - ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต'; } } // ฟังก์ชันสำหรับเริ่มวิดีโอจาก Webcam function startVideo() { navigator.mediaDevices.getUserMedia({video: {}}) .then(stream => { video.srcObject = stream; }) .catch(err => { console.error("เกิดข้อผิดพลาดในการเข้าถึงกล้อง:", err); statusDiv.innerText = 'ไม่สามารถเข้าถึงกล้องได้'; }); } // Minimal style overlay for video and image function applyMinimalOverlayStyle(target) { target.style.borderRadius = '12px'; target.style.boxShadow = '0 2px 16px rgba(0,0,0,0.08)'; target.style.background = '#fff'; target.style.border = '1.5px solid #e0e0e0'; target.style.display = 'block'; target.style.margin = '0 auto 16px auto'; target.style.maxWidth = '100%'; } // ปรับ canvas overlay ให้ minimal function styleOverlayCanvas(canvas) { canvas.style.borderRadius = '12px'; canvas.style.boxShadow = '0 2px 16px rgba(0,0,0,0.08)'; canvas.style.border = '1.5px solid #e0e0e0'; canvas.style.pointerEvents = 'none'; } // เพิ่มฟังก์ชันวาดกรอบใบหน้าบน canvas function drawBoxOnImage(image, detection) { // ลบ canvas เดิมถ้ามี const oldCanvas = document.getElementById('refCanvas'); if (oldCanvas) oldCanvas.remove(); // สร้าง canvas ใหม่ const canvas = faceapi.createCanvasFromMedia(image); canvas.id = 'refCanvas'; canvas.style.position = 'absolute'; canvas.style.left = image.offsetLeft + 'px'; canvas.style.top = image.offsetTop + 'px'; canvas.style.zIndex = 10; image.parentNode.appendChild(canvas); faceapi.matchDimensions(canvas, {width: image.width, height: image.height}); faceapi.draw.drawDetections(canvas, [detection]); styleOverlayCanvas(canvas); } // ฟังก์ชันสำหรับ crop ใบหน้าจากรูปภาพ function cropFaceFromImage(img, detection) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // ขยายขอบเขตของใบหน้าออกมาเล็กน้อยเพื่อให้ดูสมบูรณ์ const padding = 30; const box = detection.box; const cropX = Math.max(0, box.x - padding); const cropY = Math.max(0, box.y - padding); const cropWidth = Math.min(img.width - cropX, box.width + padding * 2); const cropHeight = Math.min(img.height - cropY, box.height + padding * 2); canvas.width = cropWidth; canvas.height = cropHeight; // วาดส่วนที่ crop มาลงใน canvas ctx.drawImage( img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight ); return canvas.toDataURL(); } // เมื่อมีการอัปโหลดรูปภาพ imageUpload.addEventListener('change', async () => { if (imageUpload.files && imageUpload.files[0]) { statusDiv.innerText = 'กำลังวิเคราะห์ภาพต้นฉบับ...'; try { // โหลดรูปภาพ const image = await faceapi.bufferToImage(imageUpload.files[0]); console.log('Image loaded:', image.width, 'x', image.height); // แสดงรูปภาพต้นฉบับก่อน refImage.src = image.src; refImage.style.display = 'block'; applyMinimalOverlayStyle(refImage); // รอให้รูปโหลดแล้วตรวจจับใบหน้า await new Promise(resolve => { if (refImage.complete) { resolve(); } else { refImage.onload = resolve; } }); // ตรวจจับใบหน้าจากรูปต้นฉบับ const detections = await faceapi.detectSingleFace(image, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceDescriptor(); if (detections) { referenceDescriptor = detections.descriptor; // สร้างรูปภาพที่ crop ใบหน้า const croppedFaceDataUrl = cropFaceFromImage(image, detections.detection); console.log('Cropped face created, data URL length:', croppedFaceDataUrl.length); // แสดงรูปที่ crop แล้ว refImage.src = croppedFaceDataUrl; refImage.classList.add('loaded'); statusDiv.innerText = '✅ วิเคราะห์ภาพต้นฉบับสำเร็จ! กรุณามองกล้องเพื่อเปรียบเทียบ'; } else { statusDiv.innerText = '❌ ไม่พบใบหน้าในรูปภาพต้นฉบับ! กรุณาเลือกรูปที่มีใบหน้าชัดเจน'; referenceDescriptor = null; } } catch (error) { console.error('Error processing image:', error); statusDiv.innerText = '❌ เกิดข้อผิดพลาดในการประมวลผลรูปภาพ'; } } }); // สำหรับวาดกรอบบนวิดีโอ function drawBoxOnVideo(detection) { let canvas = document.getElementById('videoCanvas'); if (!canvas) { canvas = faceapi.createCanvasFromMedia(video); canvas.id = 'videoCanvas'; canvas.style.position = 'absolute'; canvas.style.left = video.offsetLeft + 'px'; canvas.style.top = video.offsetTop + 'px'; canvas.style.zIndex = 10; video.parentNode.appendChild(canvas); } faceapi.matchDimensions(canvas, {width: video.width, height: video.height}); const resizedDetections = faceapi.resizeResults(detection, {width: video.width, height: video.height}); canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); faceapi.draw.drawDetections(canvas, [resizedDetections.detection]); styleOverlayCanvas(canvas); } // เมื่อวิดีโอเริ่มเล่น จะเริ่มทำการตรวจสอบใบหน้า video.addEventListener('play', () => { setInterval(async () => { // ถ้ายังไม่มีรูปต้นฉบับ ก็ไม่ต้องทำอะไร if (!referenceDescriptor) { return; } // ตรวจจับใบหน้าจากวิดีโอ const detections = await faceapi.detectSingleFace(video, new faceapi.TinyFaceDetectorOptions()) .withFaceLandmarks() .withFaceDescriptor(); if (detections) { // สร้าง FaceMatcher เพื่อเปรียบเทียบ const faceMatcher = new faceapi.FaceMatcher(referenceDescriptor); // หาใบหน้าที่คล้ายที่สุดในวิดีโอ const bestMatch = faceMatcher.findBestMatch(detections.descriptor); const similarity = (1 - bestMatch.distance).toFixed(2); // ค่า distance ยิ่งน้อยยิ่งเหมือน (0 = เหมือนสุดๆ, 1 = ไม่เหมือนเลย) // เราจะใช้ 1 - distance เพื่อให้ค่าความเหมือนยิ่งเยอะยิ่งดี const threshold = 0.5; // ค่าเกณฑ์สำหรับตัดสินว่าใช่คนเดียวกันหรือไม่ (ปรับค่าได้ 0-1) if (bestMatch.distance < threshold) { resultDiv.innerHTML = `ตรงกัน!
ความคล้ายคลึง: ${similarity}`; resultDiv.style.color = 'green'; } else { resultDiv.innerHTML = `ไม่ตรงกัน
ความคล้ายคลึง: ${similarity}`; resultDiv.style.color = 'red'; } drawBoxOnVideo(detections); // วาดกรอบบนวิดีโอ } else { resultDiv.innerText = 'ไม่พบใบหน้าในกล้อง...'; resultDiv.style.color = '#333'; // ลบกรอบถ้าไม่เจอใบหน้า const canvas = document.getElementById('videoCanvas'); if (canvas) canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); } }, 200); // ตรวจสอบทุกๆ 200 มิลลิวินาที }); // เริ่มต้นการทำงานทั้งหมด loadModels().then(startVideo); // ปรับแต่ง status/result ให้ minimal statusDiv.style.fontFamily = 'Inter, Segoe UI, sans-serif'; statusDiv.style.fontSize = '1.1em'; statusDiv.style.color = '#222'; statusDiv.style.background = '#f7f7f9'; statusDiv.style.borderRadius = '8px'; statusDiv.style.padding = '10px 18px'; statusDiv.style.margin = '12px auto'; statusDiv.style.maxWidth = '420px'; statusDiv.style.boxShadow = '0 1px 6px rgba(0,0,0,0.04)'; resultDiv.style.fontFamily = 'Inter, Segoe UI, sans-serif'; resultDiv.style.fontSize = '1.2em'; resultDiv.style.background = '#fff'; resultDiv.style.borderRadius = '8px'; resultDiv.style.padding = '12px 18px'; resultDiv.style.margin = '12px auto'; resultDiv.style.maxWidth = '420px'; resultDiv.style.boxShadow = '0 1px 6px rgba(0,0,0,0.04)';