├── README.md ├── face.html ├── faceMesh.html ├── hands.html ├── holistic.html ├── img ├── a.png ├── b.png ├── c.png ├── d.png └── e.png ├── index.html ├── js ├── face.js ├── faceMesh.js ├── hands.js ├── holistic.js └── pose.js └── pose.html /README.md: -------------------------------------------------------------------------------- 1 | ![simplinnovation](https://1.bp.blogspot.com/-wStk0VZDfMk/YCC0GIRPrDI/AAAAAAAAAGc/1yj7IOUedvoeO1CuCxq7ETLW0FqXni6mwCLcBGAsYHQ/s320/logotext.png) 2 | 3 | # __[MediaPipe](https://github.com/google/mediapipe)'s ML Solutions for JavaScript__ 4 | 5 | [![Video](https://img.youtube.com/vi/kuY-m6id4F4/0.jpg)](https://youtu.be/kuY-m6id4F4) 6 | 7 | A simple demonstration of [Mediapipe](https://github.com/google/mediapipe)'s ML solutions in pure JavaScript: face detection, face mesh, hands (palm) detection, pose detection, and holistic (face, hands & pose detection). Supported package: Bulma CSS. 8 | 9 | - 📝 Source code: [GitHub repo](https://github.com/LintangWisesa/MediaPipe-in-JavaScript) 10 | - 🎥 Video demo: [YouTube video](https://youtu.be/kuY-m6id4F4) 11 | - 🎉 Live demo: [GitHub page](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/index.html) 12 | 13 |
14 | 15 | 1. [Face Detection](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/face.html) 16 | 17 | [![face](/img/a.png)](https://youtu.be/kuY-m6id4F4) 18 | 19 | 1. [Face Mesh](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/faceMesh.html) 20 | 21 | 22 | [![faceMesh](/img/b.png)](https://youtu.be/kuY-m6id4F4) 23 | 24 | 1. [Hands detection](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/hands.html) 25 | 26 | 27 | [![hands](/img/c.png)](https://youtu.be/kuY-m6id4F4) 28 | 29 | 1. [Pose detection](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/pose.html) 30 | 31 | [![pose](/img/d.png)](https://youtu.be/kuY-m6id4F4) 32 | 33 | 1. [Holistic (Face, Hands & Pose)](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/holistic.html) 34 | 35 | [![face](/img/e.png)](https://youtu.be/kuY-m6id4F4) 36 | 37 |
38 | 39 | #### 🍔 Lintang Wisesa 40 | 41 |
42 | 43 | 44 | lintang ymail 45 | 46 | 47 | 48 | lintang facebook 49 | 50 | 51 | 52 | lintang twitter 53 | 54 | 55 | 56 | lintang youtube 57 | 58 | 59 | 60 | lintang linkedin 61 | 62 | 63 | 64 | lintang github 65 | 66 | 67 | 68 | lintang hackster 69 | 70 | 71 | 72 | lintang bio 73 | -------------------------------------------------------------------------------- /face.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 |

67 | Webcam Input 68 |

69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 |
78 |

79 | Mediapipe Face Detection 80 |

81 |
82 | 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 | 93 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /faceMesh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 |

67 | Webcam Input 68 |

69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 |
78 |

79 | Mediapipe Face Mesh 80 |

81 |
82 | 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /hands.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
57 | 58 | 59 | 60 |
61 | 62 |
63 | 64 | 65 |
66 |
67 |

68 | Webcam Input 69 |

70 |
71 | 72 |
73 |
74 |
75 | 76 | 77 |
78 |
79 |

80 | Mediapipe Hands Detection 81 |

82 |
83 | 84 |
85 |
86 |
87 |
88 | 89 |
90 |
91 |
92 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /holistic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
57 | 58 | 59 | 60 |
61 | 62 |
63 | 64 | 65 |
66 |
67 |

68 | Webcam Input 69 |

70 |
71 | 72 |
73 |
74 |
75 | 76 | 77 |
78 |
79 |

80 | Mediapipe Face, Hands & Pose Detection 81 |

82 |
83 | 84 |
85 |
86 |
87 |
88 | 89 |
90 |
91 |
92 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /img/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LintangWisesa/MediaPipe-in-JavaScript/1e98f72805f69530643952e58b4d6db6e2bf37d7/img/a.png -------------------------------------------------------------------------------- /img/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LintangWisesa/MediaPipe-in-JavaScript/1e98f72805f69530643952e58b4d6db6e2bf37d7/img/b.png -------------------------------------------------------------------------------- /img/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LintangWisesa/MediaPipe-in-JavaScript/1e98f72805f69530643952e58b4d6db6e2bf37d7/img/c.png -------------------------------------------------------------------------------- /img/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LintangWisesa/MediaPipe-in-JavaScript/1e98f72805f69530643952e58b4d6db6e2bf37d7/img/d.png -------------------------------------------------------------------------------- /img/e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LintangWisesa/MediaPipe-in-JavaScript/1e98f72805f69530643952e58b4d6db6e2bf37d7/img/e.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 |

67 | Webcam Input 68 |

69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 |
78 |

79 | Mediapipe Face Detection 80 |

81 |
82 | 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 | 93 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js/face.js: -------------------------------------------------------------------------------- 1 | const video1 = document.getElementsByClassName('input_video1')[0]; 2 | const out1 = document.getElementsByClassName('output1')[0]; 3 | const controlsElement1 = document.getElementsByClassName('control1')[0]; 4 | const canvasCtx1 = out1.getContext('2d'); 5 | const fpsControl = new FPS(); 6 | 7 | const spinner = document.querySelector('.loading'); 8 | spinner.ontransitionend = () => { 9 | spinner.style.display = 'none'; 10 | }; 11 | 12 | function onResultsFace(results) { 13 | document.body.classList.add('loaded'); 14 | fpsControl.tick(); 15 | canvasCtx1.save(); 16 | canvasCtx1.clearRect(0, 0, out1.width, out1.height); 17 | canvasCtx1.drawImage( 18 | results.image, 0, 0, out1.width, out1.height); 19 | if (results.detections.length > 0) { 20 | drawRectangle( 21 | canvasCtx1, results.detections[0].boundingBox, 22 | {color: 'blue', lineWidth: 4, fillColor: '#00000000'}); 23 | drawLandmarks(canvasCtx1, results.detections[0].landmarks, { 24 | color: 'red', 25 | radius: 5, 26 | }); 27 | } 28 | canvasCtx1.restore(); 29 | } 30 | 31 | const faceDetection = new FaceDetection({locateFile: (file) => { 32 | return `https://cdn.jsdelivr.net/npm/@mediapipe/face_detection@0.0/${file}`; 33 | }}); 34 | faceDetection.onResults(onResultsFace); 35 | 36 | const camera = new Camera(video1, { 37 | onFrame: async () => { 38 | await faceDetection.send({image: video1}); 39 | }, 40 | width: 480, 41 | height: 480 42 | }); 43 | camera.start(); 44 | 45 | new ControlPanel(controlsElement1, { 46 | selfieMode: true, 47 | minDetectionConfidence: 0.5, 48 | }) 49 | .add([ 50 | new StaticText({title: 'MediaPipe Face Detection'}), 51 | fpsControl, 52 | new Toggle({title: 'Selfie Mode', field: 'selfieMode'}), 53 | new Slider({ 54 | title: 'Min Detection Confidence', 55 | field: 'minDetectionConfidence', 56 | range: [0, 1], 57 | step: 0.01 58 | }), 59 | ]) 60 | .on(options => { 61 | video1.classList.toggle('selfie', options.selfieMode); 62 | faceDetection.setOptions(options); 63 | }); -------------------------------------------------------------------------------- /js/faceMesh.js: -------------------------------------------------------------------------------- 1 | const video2 = document.getElementsByClassName('input_video2')[0]; 2 | const out2 = document.getElementsByClassName('output2')[0]; 3 | const controlsElement2 = document.getElementsByClassName('control2')[0]; 4 | const canvasCtx = out2.getContext('2d'); 5 | 6 | const fpsControl = new FPS(); 7 | const spinner = document.querySelector('.loading'); 8 | spinner.ontransitionend = () => { 9 | spinner.style.display = 'none'; 10 | }; 11 | 12 | function onResultsFaceMesh(results) { 13 | document.body.classList.add('loaded'); 14 | fpsControl.tick(); 15 | 16 | canvasCtx.save(); 17 | canvasCtx.clearRect(0, 0, out2.width, out2.height); 18 | canvasCtx.drawImage( 19 | results.image, 0, 0, out2.width, out2.height); 20 | if (results.multiFaceLandmarks) { 21 | for (const landmarks of results.multiFaceLandmarks) { 22 | drawConnectors( 23 | canvasCtx, landmarks, FACEMESH_TESSELATION, 24 | {color: '#C0C0C070', lineWidth: 1}); 25 | drawConnectors( 26 | canvasCtx, landmarks, FACEMESH_RIGHT_EYE, 27 | {color: '#FF3030'}); 28 | drawConnectors( 29 | canvasCtx, landmarks, FACEMESH_RIGHT_EYEBROW, 30 | {color: '#FF3030'}); 31 | drawConnectors( 32 | canvasCtx, landmarks, FACEMESH_LEFT_EYE, 33 | {color: '#30FF30'}); 34 | drawConnectors( 35 | canvasCtx, landmarks, FACEMESH_LEFT_EYEBROW, 36 | {color: '#30FF30'}); 37 | drawConnectors( 38 | canvasCtx, landmarks, FACEMESH_FACE_OVAL, 39 | {color: '#E0E0E0'}); 40 | drawConnectors( 41 | canvasCtx, landmarks, FACEMESH_LIPS, 42 | {color: '#E0E0E0'}); 43 | } 44 | } 45 | canvasCtx.restore(); 46 | } 47 | 48 | const faceMesh = new FaceMesh({locateFile: (file) => { 49 | return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@0.1/${file}`; 50 | }}); 51 | faceMesh.onResults(onResultsFaceMesh); 52 | 53 | const camera = new Camera(video2, { 54 | onFrame: async () => { 55 | await faceMesh.send({image: video2}); 56 | }, 57 | width: 480, 58 | height: 480 59 | }); 60 | camera.start(); 61 | 62 | new ControlPanel(controlsElement2, { 63 | selfieMode: true, 64 | maxNumFaces: 1, 65 | minDetectionConfidence: 0.5, 66 | minTrackingConfidence: 0.5 67 | }) 68 | .add([ 69 | new StaticText({title: 'MediaPipe Face Mesh'}), 70 | fpsControl, 71 | new Toggle({title: 'Selfie Mode', field: 'selfieMode'}), 72 | new Slider({ 73 | title: 'Max Number of Faces', 74 | field: 'maxNumFaces', 75 | range: [1, 4], 76 | step: 1 77 | }), 78 | new Slider({ 79 | title: 'Min Detection Confidence', 80 | field: 'minDetectionConfidence', 81 | range: [0, 1], 82 | step: 0.01 83 | }), 84 | new Slider({ 85 | title: 'Min Tracking Confidence', 86 | field: 'minTrackingConfidence', 87 | range: [0, 1], 88 | step: 0.01 89 | }), 90 | ]) 91 | .on(options => { 92 | video2.classList.toggle('selfie', options.selfieMode); 93 | faceMesh.setOptions(options); 94 | }); -------------------------------------------------------------------------------- /js/hands.js: -------------------------------------------------------------------------------- 1 | const video3 = document.getElementsByClassName('input_video3')[0]; 2 | const out3 = document.getElementsByClassName('output3')[0]; 3 | const controlsElement3 = document.getElementsByClassName('control3')[0]; 4 | const canvasCtx3 = out3.getContext('2d'); 5 | const fpsControl = new FPS(); 6 | 7 | const spinner = document.querySelector('.loading'); 8 | spinner.ontransitionend = () => { 9 | spinner.style.display = 'none'; 10 | }; 11 | 12 | function onResultsHands(results) { 13 | document.body.classList.add('loaded'); 14 | fpsControl.tick(); 15 | 16 | canvasCtx3.save(); 17 | canvasCtx3.clearRect(0, 0, out3.width, out3.height); 18 | canvasCtx3.drawImage( 19 | results.image, 0, 0, out3.width, out3.height); 20 | if (results.multiHandLandmarks && results.multiHandedness) { 21 | for (let index = 0; index < results.multiHandLandmarks.length; index++) { 22 | const classification = results.multiHandedness[index]; 23 | const isRightHand = classification.label === 'Right'; 24 | const landmarks = results.multiHandLandmarks[index]; 25 | drawConnectors( 26 | canvasCtx3, landmarks, HAND_CONNECTIONS, 27 | {color: isRightHand ? '#00FF00' : '#FF0000'}), 28 | drawLandmarks(canvasCtx3, landmarks, { 29 | color: isRightHand ? '#00FF00' : '#FF0000', 30 | fillColor: isRightHand ? '#FF0000' : '#00FF00', 31 | radius: (x) => { 32 | return lerp(x.from.z, -0.15, .1, 10, 1); 33 | } 34 | }); 35 | } 36 | } 37 | canvasCtx3.restore(); 38 | } 39 | 40 | const hands = new Hands({locateFile: (file) => { 41 | return `https://cdn.jsdelivr.net/npm/@mediapipe/hands@0.1/${file}`; 42 | }}); 43 | hands.onResults(onResultsHands); 44 | 45 | const camera = new Camera(video3, { 46 | onFrame: async () => { 47 | await hands.send({image: video3}); 48 | }, 49 | width: 480, 50 | height: 480 51 | }); 52 | camera.start(); 53 | 54 | new ControlPanel(controlsElement3, { 55 | selfieMode: true, 56 | maxNumHands: 2, 57 | minDetectionConfidence: 0.5, 58 | minTrackingConfidence: 0.5 59 | }) 60 | .add([ 61 | new StaticText({title: 'MediaPipe Hands'}), 62 | fpsControl, 63 | new Toggle({title: 'Selfie Mode', field: 'selfieMode'}), 64 | new Slider( 65 | {title: 'Max Number of Hands', field: 'maxNumHands', range: [1, 4], step: 1}), 66 | new Slider({ 67 | title: 'Min Detection Confidence', 68 | field: 'minDetectionConfidence', 69 | range: [0, 1], 70 | step: 0.01 71 | }), 72 | new Slider({ 73 | title: 'Min Tracking Confidence', 74 | field: 'minTrackingConfidence', 75 | range: [0, 1], 76 | step: 0.01 77 | }), 78 | ]) 79 | .on(options => { 80 | video3.classList.toggle('selfie', options.selfieMode); 81 | hands.setOptions(options); 82 | }); -------------------------------------------------------------------------------- /js/holistic.js: -------------------------------------------------------------------------------- 1 | const video4 = document.getElementsByClassName('input_video4')[0]; 2 | const out4 = document.getElementsByClassName('output4')[0]; 3 | const controlsElement4 = document.getElementsByClassName('control4')[0]; 4 | const canvasCtx4 = out4.getContext('2d'); 5 | 6 | const fpsControl = new FPS(); 7 | const spinner = document.querySelector('.loading'); 8 | spinner.ontransitionend = () => { 9 | spinner.style.display = 'none'; 10 | }; 11 | 12 | function removeElements(landmarks, elements) { 13 | for (const element of elements) { 14 | delete landmarks[element]; 15 | } 16 | } 17 | 18 | function removeLandmarks(results) { 19 | if (results.poseLandmarks) { 20 | removeElements( 21 | results.poseLandmarks, 22 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22]); 23 | } 24 | } 25 | 26 | function connect(ctx, connectors) { 27 | const canvas = ctx.canvas; 28 | for (const connector of connectors) { 29 | const from = connector[0]; 30 | const to = connector[1]; 31 | if (from && to) { 32 | if (from.visibility && to.visibility && 33 | (from.visibility < 0.1 || to.visibility < 0.1)) { 34 | continue; 35 | } 36 | ctx.beginPath(); 37 | ctx.moveTo(from.x * canvas.width, from.y * canvas.height); 38 | ctx.lineTo(to.x * canvas.width, to.y * canvas.height); 39 | ctx.stroke(); 40 | } 41 | } 42 | } 43 | 44 | function onResultsHolistic(results) { 45 | document.body.classList.add('loaded'); 46 | removeLandmarks(results); 47 | fpsControl.tick(); 48 | 49 | canvasCtx4.save(); 50 | canvasCtx4.clearRect(0, 0, out4.width, out4.height); 51 | canvasCtx4.drawImage( 52 | results.image, 0, 0, out4.width, out4.height); 53 | canvasCtx4.lineWidth = 5; 54 | if (results.poseLandmarks) { 55 | if (results.rightHandLandmarks) { 56 | canvasCtx4.strokeStyle = '#00FF00'; 57 | connect(canvasCtx4, [[ 58 | results.poseLandmarks[POSE_LANDMARKS.RIGHT_ELBOW], 59 | results.rightHandLandmarks[0] 60 | ]]); 61 | } 62 | if (results.leftHandLandmarks) { 63 | canvasCtx4.strokeStyle = '#FF0000'; 64 | connect(canvasCtx4, [[ 65 | results.poseLandmarks[POSE_LANDMARKS.LEFT_ELBOW], 66 | results.leftHandLandmarks[0] 67 | ]]); 68 | } 69 | } 70 | drawConnectors( 71 | canvasCtx4, results.poseLandmarks, POSE_CONNECTIONS, 72 | {color: '#00FF00'}); 73 | drawLandmarks( 74 | canvasCtx4, results.poseLandmarks, 75 | {color: '#00FF00', fillColor: '#FF0000'}); 76 | drawConnectors( 77 | canvasCtx4, results.rightHandLandmarks, HAND_CONNECTIONS, 78 | {color: '#00CC00'}); 79 | drawLandmarks( 80 | canvasCtx4, results.rightHandLandmarks, { 81 | color: '#00FF00', 82 | fillColor: '#FF0000', 83 | lineWidth: 2, 84 | radius: (data) => { 85 | return lerp(data.from.z, -0.15, .1, 10, 1); 86 | } 87 | }); 88 | drawConnectors( 89 | canvasCtx4, results.leftHandLandmarks, HAND_CONNECTIONS, 90 | {color: '#CC0000'}); 91 | drawLandmarks( 92 | canvasCtx4, results.leftHandLandmarks, { 93 | color: '#FF0000', 94 | fillColor: '#00FF00', 95 | lineWidth: 2, 96 | radius: (data) => { 97 | return lerp(data.from.z, -0.15, .1, 10, 1); 98 | } 99 | }); 100 | drawConnectors( 101 | canvasCtx4, results.faceLandmarks, FACEMESH_TESSELATION, 102 | {color: '#C0C0C070', lineWidth: 1}); 103 | drawConnectors( 104 | canvasCtx4, results.faceLandmarks, FACEMESH_RIGHT_EYE, 105 | {color: '#FF3030'}); 106 | drawConnectors( 107 | canvasCtx4, results.faceLandmarks, FACEMESH_RIGHT_EYEBROW, 108 | {color: '#FF3030'}); 109 | drawConnectors( 110 | canvasCtx4, results.faceLandmarks, FACEMESH_LEFT_EYE, 111 | {color: '#30FF30'}); 112 | drawConnectors( 113 | canvasCtx4, results.faceLandmarks, FACEMESH_LEFT_EYEBROW, 114 | {color: '#30FF30'}); 115 | drawConnectors( 116 | canvasCtx4, results.faceLandmarks, FACEMESH_FACE_OVAL, 117 | {color: '#E0E0E0'}); 118 | drawConnectors( 119 | canvasCtx4, results.faceLandmarks, FACEMESH_LIPS, 120 | {color: '#E0E0E0'}); 121 | 122 | canvasCtx4.restore(); 123 | } 124 | 125 | const holistic = new Holistic({locateFile: (file) => { 126 | return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.1/${file}`; 127 | }}); 128 | holistic.onResults(onResultsHolistic); 129 | 130 | const camera = new Camera(video4, { 131 | onFrame: async () => { 132 | await holistic.send({image: video4}); 133 | }, 134 | width: 480, 135 | height: 480 136 | }); 137 | camera.start(); 138 | 139 | new ControlPanel(controlsElement4, { 140 | selfieMode: true, 141 | upperBodyOnly: true, 142 | smoothLandmarks: true, 143 | minDetectionConfidence: 0.5, 144 | minTrackingConfidence: 0.5 145 | }) 146 | .add([ 147 | new StaticText({title: 'MediaPipe Holistic'}), 148 | fpsControl, 149 | new Toggle({title: 'Selfie Mode', field: 'selfieMode'}), 150 | new Toggle({title: 'Upper-body Only', field: 'upperBodyOnly'}), 151 | new Toggle( 152 | {title: 'Smooth Landmarks', field: 'smoothLandmarks'}), 153 | new Slider({ 154 | title: 'Min Detection Confidence', 155 | field: 'minDetectionConfidence', 156 | range: [0, 1], 157 | step: 0.01 158 | }), 159 | new Slider({ 160 | title: 'Min Tracking Confidence', 161 | field: 'minTrackingConfidence', 162 | range: [0, 1], 163 | step: 0.01 164 | }), 165 | ]) 166 | .on(options => { 167 | video4.classList.toggle('selfie', options.selfieMode); 168 | holistic.setOptions(options); 169 | }); -------------------------------------------------------------------------------- /js/pose.js: -------------------------------------------------------------------------------- 1 | const video5 = document.getElementsByClassName('input_video5')[0]; 2 | const out5 = document.getElementsByClassName('output5')[0]; 3 | const controlsElement5 = document.getElementsByClassName('control5')[0]; 4 | const canvasCtx5 = out5.getContext('2d'); 5 | 6 | const fpsControl = new FPS(); 7 | 8 | const spinner = document.querySelector('.loading'); 9 | spinner.ontransitionend = () => { 10 | spinner.style.display = 'none'; 11 | }; 12 | 13 | function zColor(data) { 14 | const z = clamp(data.from.z + 0.5, 0, 1); 15 | return `rgba(0, ${255 * z}, ${255 * (1 - z)}, 1)`; 16 | } 17 | 18 | function onResultsPose(results) { 19 | document.body.classList.add('loaded'); 20 | fpsControl.tick(); 21 | 22 | canvasCtx5.save(); 23 | canvasCtx5.clearRect(0, 0, out5.width, out5.height); 24 | canvasCtx5.drawImage( 25 | results.image, 0, 0, out5.width, out5.height); 26 | drawConnectors( 27 | canvasCtx5, results.poseLandmarks, POSE_CONNECTIONS, { 28 | color: (data) => { 29 | const x0 = out5.width * data.from.x; 30 | const y0 = out5.height * data.from.y; 31 | const x1 = out5.width * data.to.x; 32 | const y1 = out5.height * data.to.y; 33 | 34 | const z0 = clamp(data.from.z + 0.5, 0, 1); 35 | const z1 = clamp(data.to.z + 0.5, 0, 1); 36 | 37 | const gradient = canvasCtx5.createLinearGradient(x0, y0, x1, y1); 38 | gradient.addColorStop( 39 | 0, `rgba(0, ${255 * z0}, ${255 * (1 - z0)}, 1)`); 40 | gradient.addColorStop( 41 | 1.0, `rgba(0, ${255 * z1}, ${255 * (1 - z1)}, 1)`); 42 | return gradient; 43 | } 44 | }); 45 | drawLandmarks( 46 | canvasCtx5, 47 | Object.values(POSE_LANDMARKS_LEFT) 48 | .map(index => results.poseLandmarks[index]), 49 | {color: zColor, fillColor: '#FF0000'}); 50 | drawLandmarks( 51 | canvasCtx5, 52 | Object.values(POSE_LANDMARKS_RIGHT) 53 | .map(index => results.poseLandmarks[index]), 54 | {color: zColor, fillColor: '#00FF00'}); 55 | drawLandmarks( 56 | canvasCtx5, 57 | Object.values(POSE_LANDMARKS_NEUTRAL) 58 | .map(index => results.poseLandmarks[index]), 59 | {color: zColor, fillColor: '#AAAAAA'}); 60 | canvasCtx5.restore(); 61 | } 62 | 63 | const pose = new Pose({locateFile: (file) => { 64 | return `https://cdn.jsdelivr.net/npm/@mediapipe/pose@0.2/${file}`; 65 | }}); 66 | pose.onResults(onResultsPose); 67 | 68 | const camera = new Camera(video5, { 69 | onFrame: async () => { 70 | await pose.send({image: video5}); 71 | }, 72 | width: 480, 73 | height: 480 74 | }); 75 | camera.start(); 76 | 77 | new ControlPanel(controlsElement5, { 78 | selfieMode: true, 79 | upperBodyOnly: false, 80 | smoothLandmarks: true, 81 | minDetectionConfidence: 0.5, 82 | minTrackingConfidence: 0.5 83 | }) 84 | .add([ 85 | new StaticText({title: 'MediaPipe Pose'}), 86 | fpsControl, 87 | new Toggle({title: 'Selfie Mode', field: 'selfieMode'}), 88 | new Toggle({title: 'Upper-body Only', field: 'upperBodyOnly'}), 89 | new Toggle({title: 'Smooth Landmarks', field: 'smoothLandmarks'}), 90 | new Slider({ 91 | title: 'Min Detection Confidence', 92 | field: 'minDetectionConfidence', 93 | range: [0, 1], 94 | step: 0.01 95 | }), 96 | new Slider({ 97 | title: 'Min Tracking Confidence', 98 | field: 'minTrackingConfidence', 99 | range: [0, 1], 100 | step: 0.01 101 | }), 102 | ]) 103 | .on(options => { 104 | video5.classList.toggle('selfie', options.selfieMode); 105 | pose.setOptions(options); 106 | }); 107 | -------------------------------------------------------------------------------- /pose.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 58 | 59 | 60 |
61 | 62 |
63 | 64 | 65 |
66 |
67 |

68 | Webcam Input 69 |

70 |
71 | 72 |
73 |
74 |
75 | 76 | 77 |
78 |
79 |

80 | Mediapipe Pose Detection 81 |

82 |
83 | 84 |
85 |
86 |
87 |
88 | 89 |
90 |
91 |
92 | 94 | 95 | 96 | 97 | 98 | --------------------------------------------------------------------------------