Skip to content

Commit ad95c70

Browse files
authored
[pose-detection] Setup demo page[2] (#615)
FEATURE
1 parent 8f31702 commit ad95c70

File tree

10 files changed

+312
-114
lines changed

10 files changed

+312
-114
lines changed

pose-detection/demo/index.html

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@
2727
top: 0;
2828
left: 0;
2929
}
30-
31-
#description {
32-
margin-top: 20px;
33-
width: 600px;
34-
}
35-
36-
#description-title {
37-
font-weight: bold;
38-
font-size: 18px;
39-
}
4030
</style>
4131
<body>
4232
<div id="main">
@@ -57,4 +47,4 @@
5747
</body>
5848
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
5949
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
60-
<script src="index.js"></script>
50+
<script src="src/index.js"></script>

pose-detection/demo/index.js

Lines changed: 0 additions & 98 deletions
This file was deleted.

pose-detection/demo/src/camera.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
import {VIDEO_SIZE} from './params';
18+
import {isMobile} from './util';
19+
20+
export class Camera {
21+
constructor() {
22+
this.video = document.getElementById('video');
23+
this.canvas = document.getElementById('output');
24+
this.ctx = this.canvas.getContext('2d');
25+
26+
// The video frame rate may be lower than the browser animate frame
27+
// rate. We use this to avoid processing the same frame twice.
28+
this.lastVideoTime = 0;
29+
}
30+
31+
/**
32+
* Initiate a Camera instance and wait for the camera stream to be ready.
33+
* @param cameraParam From app `STATE.camera`.
34+
*/
35+
static async setupCamera(cameraParam) {
36+
const {targetFPS, sizeOption} = cameraParam;
37+
const $size = VIDEO_SIZE[sizeOption];
38+
const videoConfig = {
39+
'audio': false,
40+
'video': {
41+
facingMode: 'user',
42+
// Only setting the video to a specified size for large screen, on
43+
// mobile devices accept the default size.
44+
width: isMobile() ? undefined : $size.width,
45+
height: isMobile() ? undefined : $size.height,
46+
frameRate: {
47+
ideal: targetFPS,
48+
}
49+
}
50+
};
51+
52+
const stream = await navigator.mediaDevices.getUserMedia(videoConfig);
53+
54+
const camera = new Camera();
55+
camera.video.srcObject = stream;
56+
57+
await new Promise((resolve) => {
58+
camera.video.onloadedmetadata = () => {
59+
resolve(video);
60+
};
61+
});
62+
63+
camera.video.play();
64+
65+
const videoWidth = camera.video.videoWidth;
66+
const videoHeight = camera.video.videoHeight;
67+
// Must set below two lines, otherwise video element doesn't show.
68+
camera.video.width = videoWidth;
69+
camera.video.height = videoHeight;
70+
71+
camera.canvas.width = videoWidth;
72+
camera.canvas.height = videoHeight;
73+
const canvasContainer = document.querySelector('.canvas-wrapper');
74+
canvasContainer.style = `width: ${videoWidth}px; height: ${videoHeight}px`;
75+
76+
// Because the image from camera is mirrored, need to flip horizontally.
77+
camera.ctx.translate(camera.video.videoWidth, 0);
78+
camera.ctx.scale(-1, 1);
79+
80+
return camera;
81+
}
82+
83+
drawCtx() {
84+
this.ctx.drawImage(
85+
this.video, 0, 0, this.video.videoWidth, this.video.videoHeight);
86+
}
87+
88+
clearCtx() {
89+
this.ctx.clearRect(0, 0, this.video.videoWidth, this.video.videoHeight);
90+
}
91+
92+
drawResult(pose) {
93+
this.drawKeypoints(pose.keypoints);
94+
}
95+
96+
drawKeypoints(keypoints) {
97+
this.ctx.fillStyle = 'red';
98+
this.ctx.strokeStyle = 'white';
99+
this.ctx.lineWidth = 4;
100+
keypoints.forEach(keypoint => {
101+
const circle = new Path2D();
102+
circle.arc(keypoint.x, keypoint.y, 4, 0, 2 * Math.PI);
103+
this.ctx.fill(circle);
104+
this.ctx.stroke(circle);
105+
});
106+
}
107+
}

pose-detection/demo/src/index.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import '@tensorflow/tfjs-backend-webgl';
19+
20+
import * as posedetection from '@tensorflow-models/posedetection';
21+
import * as tf from '@tensorflow/tfjs-core';
22+
23+
import {Camera} from './camera';
24+
import {setupDatGui} from './option_panel';
25+
import {STATE} from './params';
26+
import {setupStats} from './stats_panel';
27+
28+
let detector, camera, stats;
29+
30+
async function checkGuiUpdate() {
31+
if (STATE.changeToTargetFPS || STATE.changeToSizeOption) {
32+
if (STATE.changeToTargetFPS) {
33+
STATE.camera.targetFPS = STATE.changeToTargetFPS;
34+
STATE.changeToTargetFPS = null;
35+
}
36+
37+
if (STATE.changeToSizeOption) {
38+
STATE.camera.sizeOption = STATE.changeToSizeOption;
39+
STATE.changeToSizeOption = null;
40+
}
41+
42+
camera = await Camera.setupCamera(STATE.camera);
43+
}
44+
45+
await tf.nextFrame();
46+
}
47+
48+
async function renderResult() {
49+
if (camera.video.currentTime !== camera.lastVideoTime) {
50+
camera.lastVideoTime = camera.video.currentTime;
51+
52+
const poses = await detector.estimatePoses(
53+
video, {maxPoses: 1, flipHorizontal: false});
54+
55+
camera.drawCtx();
56+
57+
if (poses.length > 0) {
58+
camera.drawResult(poses[0]);
59+
}
60+
}
61+
}
62+
63+
async function renderPrediction() {
64+
await checkGuiUpdate();
65+
66+
stats.begin();
67+
68+
await renderResult();
69+
70+
stats.end();
71+
72+
requestAnimationFrame(renderPrediction);
73+
};
74+
75+
async function app() {
76+
await tf.setBackend('webgl');
77+
setupDatGui();
78+
stats = setupStats();
79+
camera = await Camera.setupCamera(STATE.camera);
80+
81+
detector = await posedetection.createDetector(
82+
posedetection.SupportedModels.PoseNet, {
83+
quantBytes: 4,
84+
architecture: 'MobileNetV1',
85+
outputStride: 16,
86+
inputResolution: {width: 500, height: 500},
87+
multiplier: 0.75
88+
});
89+
90+
renderPrediction();
91+
};
92+
93+
app();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
import {STATE, VIDEO_SIZE} from './params';
18+
19+
export function setupDatGui() {
20+
const gui = new dat.GUI({width: 300});
21+
22+
// The camera folder contains options for video settings.
23+
const cameraFolder = gui.addFolder('Camera');
24+
const fpsController = cameraFolder.add(STATE.camera, 'targetFPS');
25+
fpsController.onChange((targetFPS) => {
26+
STATE.changeToTargetFPS = +targetFPS;
27+
});
28+
const sizeController =
29+
cameraFolder.add(STATE.camera, 'sizeOption', Object.keys(VIDEO_SIZE));
30+
sizeController.onChange(option => {
31+
STATE.changeToSizeOption = option;
32+
});
33+
cameraFolder.open();
34+
35+
return gui;
36+
}

pose-detection/demo/src/params.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
export const DEFAULT_LINE_WIDTH = 4;
18+
19+
export const VIDEO_SIZE = {
20+
'640 X 480': {width: 640, height: 480},
21+
'640 X 360': {width: 640, height: 360}
22+
};
23+
export const STATE = {
24+
camera: {targetFPS: 60, sizeOption: '640 X 480'}
25+
};

0 commit comments

Comments
 (0)