| <!DOCTYPE html> |
| <html lang="id"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AI Pose Recognition with Audio</title> |
| <style> |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| text-align: center; |
| background-color: #1a1a1a; |
| color: white; |
| padding: 20px; |
| } |
| h2 { color: #00d4ff; } |
| #canvas-container { |
| position: relative; |
| display: inline-block; |
| border: 5px solid #333; |
| border-radius: 15px; |
| overflow: hidden; |
| background: #000; |
| } |
| #label-container { |
| margin-top: 20px; |
| display: flex; |
| justify-content: center; |
| gap: 15px; |
| } |
| #label-container div { |
| background: #333; |
| padding: 10px 20px; |
| border-radius: 8px; |
| font-size: 18px; |
| min-width: 120px; |
| } |
| button { |
| padding: 15px 35px; |
| font-size: 18px; |
| font-weight: bold; |
| cursor: pointer; |
| background: linear-gradient(45deg, #00d4ff, #0056b3); |
| color: white; |
| border: none; |
| border-radius: 50px; |
| transition: 0.3s; |
| margin-bottom: 20px; |
| box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3); |
| } |
| button:hover { |
| transform: scale(1.05); |
| box-shadow: 0 6px 20px rgba(0, 212, 255, 0.5); |
| } |
| .active-pose { |
| border: 2px solid #00d4ff !important; |
| background: #0056b3 !important; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <h2>AI Pose Detector: Peace, Mantap, Metal</h2> |
| <p>Pastikan kamera aktif dan tekan tombol di bawah:</p> |
| |
| <button type="button" onclick="init()">MULAI KAMERA</button> |
|
|
| <div id="status"></div> |
| |
| <div id="canvas-container"> |
| <canvas id="canvas"></canvas> |
| </div> |
|
|
| <div id="label-container"></div> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@teachablemachine/pose@0.8/dist/teachablemachine-pose.min.js"></script> |
|
|
| <script type="text/javascript"> |
| |
| const URL = "https://teachablemachine.withgoogle.com/models/0Y2ifsgF4/"; |
| let model, webcam, ctx, labelContainer, maxPredictions; |
| |
| |
| const sounds = { |
| "peace": new Audio("mp3peace.mp3"), |
| "mantap": new Audio("mp3mantap.mp3"), |
| "metal": new Audio("mp3metal.mp3") |
| }; |
| |
| let lastPlayedPose = ""; |
| let silenceTimer; |
| |
| async function init() { |
| const modelURL = URL + "model.json"; |
| const metadataURL = URL + "metadata.json"; |
| |
| |
| model = await tmPose.load(modelURL, metadataURL); |
| maxPredictions = model.getTotalClasses(); |
| |
| |
| const size = 400; |
| const flip = true; |
| webcam = new tmPose.Webcam(size, size, flip); |
| await webcam.setup(); |
| await webcam.play(); |
| window.requestAnimationFrame(loop); |
| |
| |
| const canvas = document.getElementById("canvas"); |
| canvas.width = size; canvas.height = size; |
| ctx = canvas.getContext("2d"); |
| labelContainer = document.getElementById("label-container"); |
| labelContainer.innerHTML = ""; |
| for (let i = 0; i < maxPredictions; i++) { |
| labelContainer.appendChild(document.createElement("div")); |
| } |
| } |
| |
| async function loop(timestamp) { |
| webcam.update(); |
| await predict(); |
| window.requestAnimationFrame(loop); |
| } |
| |
| async function predict() { |
| const { pose, posenetOutput } = await model.estimatePose(webcam.canvas); |
| const prediction = await model.predict(posenetOutput); |
| |
| for (let i = 0; i < maxPredictions; i++) { |
| const className = prediction[i].className; |
| const probability = prediction[i].probability; |
| |
| const element = labelContainer.childNodes[i]; |
| element.innerHTML = `${className}: ${(probability * 100).toFixed(0)}%`; |
| |
| |
| if (probability > 0.90) { |
| element.classList.add("active-pose"); |
| |
| |
| if (lastPlayedPose !== className) { |
| playAudio(className); |
| } |
| } else { |
| element.classList.remove("active-pose"); |
| } |
| } |
| drawPose(pose); |
| } |
| |
| function playAudio(poseName) { |
| const soundKey = poseName.toLowerCase(); |
| if (sounds[soundKey]) { |
| |
| Object.values(sounds).forEach(s => { |
| s.pause(); |
| s.currentTime = 0; |
| }); |
| |
| |
| sounds[soundKey].play().catch(e => console.log("Izin audio diperlukan")); |
| lastPlayedPose = poseName; |
| |
| |
| clearTimeout(silenceTimer); |
| silenceTimer = setTimeout(() => { |
| lastPlayedPose = ""; |
| }, 3000); |
| } |
| } |
| |
| function drawPose(pose) { |
| if (webcam.canvas) { |
| ctx.drawImage(webcam.canvas, 0, 0); |
| if (pose) { |
| const minPartConfidence = 0.5; |
| tmPose.drawKeypoints(pose.keypoints, minPartConfidence, ctx); |
| tmPose.drawSkeleton(pose.keypoints, minPartConfidence, ctx); |
| } |
| } |
| } |
| </script> |
| </body> |
| </html> |