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)';