gustavo_malmoria commited on
Commit
9b56ece
·
1 Parent(s): 5c5d646
Files changed (3) hide show
  1. index.html +56 -0
  2. script.js +172 -0
  3. style.css +113 -0
index.html ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" >
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Curl Detector</title>
6
+ <link rel="stylesheet" href="./style.css">
7
+
8
+ </head>
9
+ <body>
10
+
11
+ <link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
12
+ <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
13
+ </script>
14
+
15
+ <body>
16
+ <h1>Excercise counter with Mediapipe</h1>
17
+
18
+ <section id="demos" class="invisible">
19
+ <p>Stand in front of your webcam to get real-time pose landmarker detection.</br>Click <b>enable webcam</b> below and grant access to the webcam if prompted.</p>
20
+
21
+ <div id="liveView" class="videoView">
22
+ <button id="webcamButton" class="mdc-button mdc-button--raised">
23
+ <span class="mdc-button__ripple"></span>
24
+ <span class="mdc-button__label">ENABLE WEBCAM</span>
25
+ </button>
26
+ <button id="startButton" class="mdc-button mdc-button--raised">
27
+ START COUNTING
28
+ </button>
29
+ <button id="stopButton" class="mdc-button mdc-button--raised">
30
+ STOP COUNTING
31
+ </button>
32
+ <button id="restartButton" class="mdc-button mdc-button--raised">
33
+ RESTART COUNTING
34
+ </button>
35
+ <div style="position: relative;">
36
+ <video id="webcam" style="width: 1280px; height: 720px; position: abso" autoplay playsinline></video>
37
+ <canvas class="output_canvas" id="output_canvas" width="1280" height="720" style="position: absolute; left: 0px; top: 0px;"></canvas>
38
+ </div>
39
+ </div>
40
+ <fieldset>
41
+ <legend>Choose your excercise</legend>
42
+ <div>
43
+ <input type="radio" id="curl" name="excercise" value="curl" />
44
+ <label for="curl">curl</label>
45
+ </div>
46
+ <div>
47
+ <input type="radio" id="squat" name="excercise" value="squat" />
48
+ <label for="squat">squat</label>
49
+ </div>
50
+ </fieldset>
51
+ <div id="counter"></div>
52
+ </section>
53
+ <script type="module" src="./script.js"></script>
54
+
55
+ </body>
56
+ </html>
script.js ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PoseLandmarker, FilesetResolver, DrawingUtils } from "https://cdn.skypack.dev/@mediapipe/tasks-vision@0.10.0";
2
+
3
+ const demosSection = document.getElementById("demos");
4
+ let poseLandmarker = undefined;
5
+ let runningMode = "IMAGE";
6
+ let enableWebcamButton;
7
+ let enableCounter;
8
+ let webcamRunning = false;
9
+ const videoHeight = "360px";
10
+ const videoWidth = "480px";
11
+ let shoulder = [];
12
+ let elbow = [];
13
+ let wrist = [];
14
+ let hip = [];
15
+ let ankle = [];
16
+ let knee = [];
17
+ let stage;
18
+ let angle;
19
+ let counter = 0;
20
+ let starter = 0;
21
+
22
+ const createPoseLandmarker = async () => {
23
+ const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm");
24
+ poseLandmarker = await PoseLandmarker.createFromOptions(vision, {
25
+ baseOptions: {
26
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task`,
27
+ delegate: "GPU"
28
+ },
29
+ runningMode: runningMode,
30
+ numPoses: 2
31
+ });
32
+ demosSection.classList.remove("invisible");
33
+ };
34
+ createPoseLandmarker();
35
+
36
+
37
+ const video = document.getElementById("webcam");
38
+ const canvasElement = document.getElementById("output_canvas");
39
+ const canvasCtx = canvasElement.getContext("2d");
40
+ const drawingUtils = new DrawingUtils(canvasCtx);
41
+
42
+
43
+ const hasGetUserMedia = () => { var _a; return !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia); };
44
+
45
+ if (hasGetUserMedia()) {
46
+ enableWebcamButton = document.getElementById("webcamButton");
47
+ enableWebcamButton.addEventListener("click", enableCam);
48
+ }
49
+ else {
50
+ console.warn("getUserMedia() is not supported by your browser");
51
+ }
52
+
53
+ function enableCam(event) {
54
+ if (!poseLandmarker) {
55
+ console.log("Wait! poseLandmaker not loaded yet.");
56
+ return;
57
+ }
58
+ if (webcamRunning === true) {
59
+ webcamRunning = false;
60
+ enableWebcamButton.innerText = "ENABLE PREDICTIONS";
61
+ }
62
+ else {
63
+ webcamRunning = true;
64
+ enableWebcamButton.innerText = "DISABLE PREDICTIONS";
65
+ }
66
+
67
+ const constraints = {
68
+ video: true
69
+ };
70
+
71
+ navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
72
+ video.srcObject = stream;
73
+ video.addEventListener("loadeddata", predictWebcam);
74
+ });
75
+ }
76
+ let lastVideoTime = -1;
77
+ async function predictWebcam() {
78
+ canvasElement.style.height = videoHeight;
79
+ video.style.height = videoHeight;
80
+ canvasElement.style.width = videoWidth;
81
+ video.style.width = videoWidth;
82
+
83
+ if (runningMode === "IMAGE") {
84
+ runningMode = "VIDEO";
85
+ await poseLandmarker.setOptions({ runningMode: "VIDEO" });
86
+ }
87
+ let startTimeMs = performance.now();
88
+ if (lastVideoTime !== video.currentTime) {
89
+ lastVideoTime = video.currentTime;
90
+ poseLandmarker.detectForVideo(video, startTimeMs, (result) => {
91
+ canvasCtx.save();
92
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
93
+ for (const landmark of result.landmarks) {
94
+ shoulder = [landmark[11].x, landmark[11].y]
95
+ elbow = [landmark[13].x, landmark[13].y]
96
+ wrist = [landmark[15].x, landmark[15].y]
97
+ hip = [landmark[23].x, landmark[23].y]
98
+ knee = [landmark[25].x, landmark[25].y]
99
+ ankle = [landmark[27].x, landmark[27].y]
100
+
101
+ drawingUtils.drawLandmarks(landmark, {
102
+ radius: (data) => DrawingUtils.lerp(data.from.z, -0.15, 0.1, 5, 1)
103
+ });
104
+ drawingUtils.drawConnectors(landmark, PoseLandmarker.POSE_CONNECTIONS);
105
+ }
106
+ canvasCtx.restore();
107
+ });
108
+ }
109
+
110
+ function calculateAngle(a, b, c) {
111
+ const vecA = new Array(a[0], a[1]); // First
112
+ const vecB = new Array(b[0], b[1]); // Mid
113
+ const vecC = new Array(c[0], c[1]); // End
114
+
115
+ const radians = Math.atan2(vecC[1] - vecB[1], vecC[0] - vecB[0]) - Math.atan2(vecA[1] - vecB[1], vecA[0] - vecB[0]);
116
+ let angle = Math.abs(radians * (180.0 / Math.PI));
117
+
118
+ if (angle > 180.0) {
119
+ angle = 360 - angle;
120
+ }
121
+
122
+ return angle;
123
+ }
124
+
125
+ const startButton = document.getElementById('startButton');
126
+ startButton.addEventListener('click', () => {
127
+ starter = 1
128
+ });
129
+
130
+ const stopButton = document.getElementById('stopButton');
131
+ stopButton.addEventListener('click', () => {
132
+ starter = 0
133
+ });
134
+
135
+ const restartButton = document.getElementById('restartButton');
136
+ restartButton.addEventListener('click', () => {
137
+ counter = 0
138
+ });
139
+
140
+ if (starter == 1){
141
+ if(document.getElementById('curl').checked) {
142
+ angle = calculateAngle(shoulder,elbow,wrist)
143
+
144
+ if (angle > 160){
145
+ stage = "down"
146
+ }
147
+ if (angle < 30 & stage =='down'){
148
+ stage="up"
149
+ counter +=1
150
+ }
151
+ }
152
+ if(document.getElementById('squat').checked) {
153
+ angle = calculateAngle(hip,knee,ankle)
154
+
155
+ if (angle > 169){
156
+ stage = "up"
157
+ }
158
+ if (angle < 90 & stage =='up'){
159
+ stage="down"
160
+ counter +=1
161
+ }
162
+ }
163
+ }
164
+
165
+ const landmarkValuesDiv = document.getElementById('counter');
166
+ landmarkValuesDiv.innerHTML = `stage: ${stage}, count: ${counter}`;
167
+
168
+
169
+ if (webcamRunning === true) {
170
+ window.requestAnimationFrame(predictWebcam);
171
+ }
172
+ }
style.css ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Copyright 2023 The MediaPipe Authors.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License. */
14
+
15
+ @use "@material";
16
+ body {
17
+ font-family: roboto;
18
+ margin: 2em;
19
+ color: #3d3d3d;
20
+ --mdc-theme-primary: #007f8b;
21
+ --mdc-theme-on-primary: #f1f3f4;
22
+ }
23
+
24
+ h1 {
25
+ color: #007f8b;
26
+ }
27
+
28
+ h2 {
29
+ clear: both;
30
+ }
31
+
32
+ em {
33
+ font-weight: bold;
34
+ }
35
+
36
+ video {
37
+ clear: both;
38
+ display: block;
39
+ transform: rotateY(180deg);
40
+ -webkit-transform: rotateY(180deg);
41
+ -moz-transform: rotateY(180deg);
42
+ }
43
+
44
+ section {
45
+ opacity: 1;
46
+ transition: opacity 500ms ease-in-out;
47
+ }
48
+
49
+ header,
50
+ footer {
51
+ clear: both;
52
+ }
53
+
54
+ .removed {
55
+ display: none;
56
+ }
57
+
58
+ .invisible {
59
+ opacity: 0.2;
60
+ }
61
+
62
+ .note {
63
+ font-style: italic;
64
+ font-size: 130%;
65
+ }
66
+
67
+ .videoView,
68
+ .detectOnClick {
69
+ position: relative;
70
+ float: left;
71
+ width: 48%;
72
+ margin: 2% 1%;
73
+ cursor: pointer;
74
+ }
75
+
76
+ .videoView p,
77
+ .detectOnClick p {
78
+ position: absolute;
79
+ padding: 5px;
80
+ background-color: #007f8b;
81
+ color: #fff;
82
+ border: 1px dashed rgba(255, 255, 255, 0.7);
83
+ z-index: 2;
84
+ font-size: 12px;
85
+ margin: 0;
86
+ }
87
+
88
+ .highlighter {
89
+ background: rgba(0, 255, 0, 0.25);
90
+ border: 1px dashed #fff;
91
+ z-index: 1;
92
+ position: absolute;
93
+ }
94
+
95
+ .canvas {
96
+ z-index: 1;
97
+ position: absolute;
98
+ pointer-events: none;
99
+ }
100
+
101
+ .output_canvas {
102
+ transform: rotateY(180deg);
103
+ -webkit-transform: rotateY(180deg);
104
+ -moz-transform: rotateY(180deg);
105
+ }
106
+
107
+ .detectOnClick {
108
+ z-index: 0;
109
+ }
110
+
111
+ .detectOnClick img {
112
+ width: 100%;
113
+ }