| <!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 - Suara</title> |
| <style> |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| background-color: #f0f2f5; |
| padding: 20px; |
| } |
| h2 { color: #333; } |
| #canvas { |
| border: 4px solid #fff; |
| border-radius: 10px; |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
| background-color: #000; |
| } |
| #label-container { |
| margin-top: 20px; |
| width: 100%; |
| max-width: 300px; |
| } |
| #label-container div { |
| background: #fff; |
| padding: 10px; |
| margin-bottom: 5px; |
| border-radius: 5px; |
| font-weight: bold; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); |
| display: flex; |
| justify-content: space-between; |
| } |
| button { |
| padding: 12px 24px; |
| font-size: 18px; |
| background-color: #007bff; |
| color: white; |
| border: none; |
| border-radius: 5px; |
| cursor: pointer; |
| margin-bottom: 20px; |
| transition: background 0.3s; |
| } |
| button:hover { background-color: #0056b3; } |
| </style> |
| </head> |
| <body> |
|
|
| <h2>Deteksi Pose & Suara</h2> |
| <button type="button" onclick="init()">Mulai Kamera</button> |
| |
| <div><canvas id="canvas"></canvas></div> |
| <div id="label-container"></div> |
|
|
| |
| <audio id="audio-kicaw" src="kicaw_mania.mp3"></audio> |
| <audio id="audio-semangat" src="semangat.mp3"></audio> |
| <audio id="audio-berjuang" src="berjuang.mp3"></audio> |
| <audio id="audio-sukses" src="sukses.mp3"></audio> |
|
|
| |
| <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 = "./my_model/"; |
| let model, webcam, ctx, labelContainer, maxPredictions; |
| |
| |
| let lastPlayedClass = ""; |
| |
| 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); |
| |
| let highestProb = 0; |
| let currentClass = ""; |
| |
| for (let i = 0; i < maxPredictions; i++) { |
| const prob = prediction[i].probability.toFixed(2); |
| const className = prediction[i].className; |
| |
| labelContainer.childNodes[i].innerHTML = `<span>${className}</span> <span>${(prob * 100).toFixed(0)}%</span>`; |
| |
| |
| if (prediction[i].probability > highestProb) { |
| highestProb = prediction[i].probability; |
| currentClass = className; |
| } |
| } |
| |
| |
| |
| if (highestProb > 0.85 && currentClass !== lastPlayedClass) { |
| playSound(currentClass); |
| lastPlayedClass = currentClass; |
| } |
| |
| drawPose(pose); |
| } |
| |
| function playSound(className) { |
| |
| const audios = document.querySelectorAll('audio'); |
| audios.forEach(a => { |
| a.pause(); |
| a.currentTime = 0; |
| }); |
| |
| |
| let id = ""; |
| if (className === "kicaw kicaw mania") id = "audio-kicaw"; |
| else if (className === "semangat") id = "audio-semangat"; |
| else if (className === "berjuang") id = "audio-berjuang"; |
| else if (className === "sukses") id = "audio-sukses"; |
| |
| if (id) { |
| const playPromise = document.getElementById(id).play(); |
| if (playPromise !== undefined) { |
| playPromise.catch(error => { |
| console.log("Autoplay dicegah oleh browser, butuh interaksi user."); |
| }); |
| } |
| } |
| } |
| |
| 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> |