| <!DOCTYPE html> |
| <html lang="id"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Pose Recognition AI</title> |
| <style> |
| body { font-family: sans-serif; text-align: center; background: #f0f2f5; padding: 20px; } |
| #canvas { border: 5px solid #007bff; border-radius: 10px; background: #000; max-width: 100%; height: auto; } |
| button { padding: 15px 30px; font-size: 18px; font-weight: bold; cursor: pointer; background: #28a745; color: white; border: none; border-radius: 50px; margin: 10px; } |
| #label-container { margin-top: 20px; display: flex; flex-direction: column; align-items: center; } |
| #label-container div { background: white; margin: 5px; padding: 10px; width: 280px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); font-weight: bold; } |
| #status { color: #555; margin-bottom: 10px; font-style: italic; } |
| </style> |
| </head> |
| <body> |
|
|
| <h2>AI Pengenalan Pose + Suara</h2> |
| <div id="status">Harap upload file model.json, metadata.json, dan weights.bin</div> |
| <button type="button" onclick="init()">MULAI KAMERA</button> |
| |
| <div><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 = "./"; |
| let model, webcam, ctx, labelContainer, maxPredictions; |
| |
| |
| const audioFiles = { |
| "semangat": new Audio('selamat.mp3'), |
| "berjuang": new Audio('berjuang.mp3'), |
| "sukses": new Audio('sukses.mp3'), |
| "hidup jokowi": new Audio('hidup jokowi.mp3'), |
| "mp3kicaukicaw": new Audio('kicawkicaw.mp3') |
| }; |
| |
| let lastPlayedClass = ""; |
| |
| async function init() { |
| const statusDiv = document.getElementById("status"); |
| statusDiv.innerHTML = "Memuat model..."; |
| |
| try { |
| model = await tmPose.load(URL + "model.json", URL + "metadata.json"); |
| maxPredictions = model.getTotalClasses(); |
| |
| webcam = new tmPose.Webcam(400, 400, true); |
| await webcam.setup(); |
| await webcam.play(); |
| |
| statusDiv.innerHTML = "Kamera Aktif!"; |
| window.requestAnimationFrame(loop); |
| |
| ctx = document.getElementById("canvas").getContext("2d"); |
| document.getElementById("canvas").width = 400; |
| document.getElementById("canvas").height = 400; |
| |
| labelContainer = document.getElementById("label-container"); |
| labelContainer.innerHTML = ""; |
| for (let i = 0; i < maxPredictions; i++) { |
| labelContainer.appendChild(document.createElement("div")); |
| } |
| } catch (e) { |
| statusDiv.innerHTML = "Error: File model tidak ditemukan atau kamera ditolak!"; |
| console.error(e); |
| } |
| } |
| |
| async function loop() { |
| 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 prob = prediction[i].probability; |
| labelContainer.childNodes[i].innerHTML = className + ": " + (prob * 100).toFixed(0) + "%"; |
| |
| if (prob > 0.90 && lastPlayedClass !== className) { |
| playVoice(className); |
| lastPlayedClass = className; |
| } |
| } |
| drawPose(pose); |
| } |
| |
| function playVoice(className) { |
| Object.values(audioFiles).forEach(a => { a.pause(); a.currentTime = 0; }); |
| if (audioFiles[className]) { |
| audioFiles[className].play().catch(e => console.log("Audio diblokir browser")); |
| } |
| } |
| |
| function drawPose(pose) { |
| if (webcam.canvas) { |
| ctx.drawImage(webcam.canvas, 0, 0); |
| if (pose) { |
| tmPose.drawKeypoints(pose.keypoints, 0.5, ctx); |
| tmPose.drawSkeleton(pose.keypoints, 0.5, ctx); |
| } |
| } |
| } |
| </script> |
| </body> |
| </html> |