├── 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 | 
2 |
3 | # __[MediaPipe](https://github.com/google/mediapipe)'s ML Solutions for JavaScript__
4 |
5 | [](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 | [](https://youtu.be/kuY-m6id4F4)
18 |
19 | 1. [Face Mesh](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/faceMesh.html)
20 |
21 |
22 | [](https://youtu.be/kuY-m6id4F4)
23 |
24 | 1. [Hands detection](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/hands.html)
25 |
26 |
27 | [](https://youtu.be/kuY-m6id4F4)
28 |
29 | 1. [Pose detection](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/pose.html)
30 |
31 | [](https://youtu.be/kuY-m6id4F4)
32 |
33 | 1. [Holistic (Face, Hands & Pose)](https://lintangwisesa.github.io/MediaPipe-in-JavaScript/holistic.html)
34 |
35 | [](https://youtu.be/kuY-m6id4F4)
36 |
37 |
38 |
39 | #### 🍔 Lintang Wisesa
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
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 |
91 |
92 |
93 |
94 |
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 |
91 |
92 |
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 |
92 |
93 |
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 |
92 |
93 |
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 |
91 |
92 |
93 |
94 |
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 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------