mistpe commited on
Commit
7183b21
·
verified ·
1 Parent(s): 7bc6818

Upload 4 files

Browse files
Files changed (4) hide show
  1. holistic.css +132 -0
  2. holistic.html +51 -0
  3. holistic.js +260 -0
  4. index.html +15 -0
holistic.css ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @keyframes spin {
2
+ 0% {
3
+ transform: rotate(0deg);
4
+ }
5
+ 100% {
6
+ transform: rotate(360deg);
7
+ }
8
+ }
9
+ .abs {
10
+ position: absolute;
11
+ }
12
+
13
+ a {
14
+ color: white;
15
+ text-decoration: none;
16
+ }
17
+ a:hover {
18
+ color: lightblue;
19
+ }
20
+
21
+ body {
22
+ bottom: 0;
23
+ font-family: "Titillium Web", sans-serif;
24
+ color: white;
25
+ left: 0;
26
+ margin: 0;
27
+ position: absolute;
28
+ right: 0;
29
+ top: 0;
30
+ transform-origin: 0px 0px;
31
+ overflow: hidden;
32
+ }
33
+
34
+ .container {
35
+ position: absolute;
36
+ background-color: #596e73;
37
+ width: 100%;
38
+ max-height: 100%;
39
+ }
40
+
41
+ .input_video {
42
+ display: none;
43
+ position: absolute;
44
+ top: 0;
45
+ left: 0;
46
+ right: 0;
47
+ bottom: 0;
48
+ }
49
+ .input_video.selfie {
50
+ transform: scale(-1, 1);
51
+ }
52
+
53
+ .input_image {
54
+ position: absolute;
55
+ }
56
+
57
+ .canvas-container {
58
+ display: flex;
59
+ height: 100%;
60
+ width: 100%;
61
+ justify-content: center;
62
+ align-items: center;
63
+ }
64
+
65
+ .output_canvas {
66
+ max-width: 100%;
67
+ display: block;
68
+ position: relative;
69
+ left: 0;
70
+ top: 0;
71
+ }
72
+
73
+ .logo {
74
+ bottom: 10px;
75
+ right: 20px;
76
+ }
77
+ .logo .title {
78
+ color: white;
79
+ font-size: 28px;
80
+ }
81
+ .logo .subtitle {
82
+ position: relative;
83
+ color: white;
84
+ font-size: 10px;
85
+ left: -30px;
86
+ top: 20px;
87
+ }
88
+
89
+ .control-panel {
90
+ position: absolute;
91
+ left: 10px;
92
+ top: 10px;
93
+ }
94
+
95
+ .loading {
96
+ display: flex;
97
+ position: absolute;
98
+ top: 0;
99
+ right: 0;
100
+ bottom: 0;
101
+ left: 0;
102
+ align-items: center;
103
+ backface-visibility: hidden;
104
+ justify-content: center;
105
+ opacity: 1;
106
+ transition: opacity 1s;
107
+ }
108
+ .loading .message {
109
+ font-size: x-large;
110
+ }
111
+ .loading .spinner {
112
+ position: absolute;
113
+ width: 120px;
114
+ height: 120px;
115
+ animation: spin 1s linear infinite;
116
+ border: 32px solid #bebebe;
117
+ border-top: 32px solid #3498db;
118
+ border-radius: 50%;
119
+ }
120
+
121
+ .loaded .loading {
122
+ opacity: 0;
123
+ }
124
+
125
+ .shoutout {
126
+ left: 0;
127
+ right: 0;
128
+ bottom: 40px;
129
+ text-align: center;
130
+ font-size: 24px;
131
+ position: absolute;
132
+ }
holistic.html ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ MediaPipe Holistic
3
+ https://google.github.io/mediapipe/solutions/holistic.html
4
+ -->
5
+ <!DOCTYPE html>
6
+ <html lang="zh">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <title>演示</title>
12
+ <link rel="stylesheet" type="text/css" href="holistic.css" crossorigin="anonymous">
13
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils@0.6/control_utils.css" crossorigin="anonymous">
14
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils@0.3/camera_utils.js" crossorigin="anonymous"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils@0.6/control_utils.js" crossorigin="anonymous"></script>
16
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils@0.3/drawing_utils.js" crossorigin="anonymous"></script>
17
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.5/holistic.js" crossorigin="anonymous"></script>
18
+ </head>
19
+ <body>
20
+ <div class="container">
21
+ <video class="input_video"></video>
22
+ <div class="canvas-container">
23
+ <canvas class="output_canvas" width="1280px" height="720px">
24
+ </canvas>
25
+ </div>
26
+ <div class="loading">
27
+ <div class="spinner"></div>
28
+ <div class="message">
29
+ 加载中
30
+ </div>
31
+ </div>
32
+ <a class="abs logo" href="http://www.mediapipe.dev" target="_blank">
33
+ <div style="display: flex;align-items: center;bottom: 0;right: 10px;">
34
+ <img class="logo" src="./img/logo_white.png" alt="" style="
35
+ height: 50px;">
36
+ <span class="title">MediaPipe</span>
37
+ </div>
38
+ </a>
39
+ <div class="shoutout">
40
+ <div>
41
+ <a href="https://solutions.mediapipe.dev/holistic" target="_blank">
42
+ 点击这里获取更多信息
43
+ </a>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <div class="control-panel">
48
+ </div>
49
+ </body>
50
+ </html>
51
+ <script type="module" src="holistic.js"></script>
holistic.js ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import DeviceDetector from "https://cdn.skypack.dev/device-detector-js@2.2.10";
2
+ // 用法: testSupport({client?: string, os?: string}[])
3
+ // Client 和 os 是正则表达式。
4
+ // 参见: https://cdn.jsdelivr.net/npm/device-detector-js@2.2.10/README.md
5
+ // 了解 client 和 os 的合法值
6
+ testSupport([
7
+ { client: 'Chrome' },
8
+ ]);
9
+
10
+ function testSupport(supportedDevices) {
11
+ const deviceDetector = new DeviceDetector();
12
+ const detectedDevice = deviceDetector.parse(navigator.userAgent);
13
+ let isSupported = false;
14
+ for (const device of supportedDevices) {
15
+ if (device.client !== undefined) {
16
+ const re = new RegExp(`^${device.client}$`);
17
+ if (!re.test(detectedDevice.client.name)) {
18
+ continue;
19
+ }
20
+ }
21
+ if (device.os !== undefined) {
22
+ const re = new RegExp(`^${device.os}$`);
23
+ if (!re.test(detectedDevice.os.name)) {
24
+ continue;
25
+ }
26
+ }
27
+ isSupported = true;
28
+ break;
29
+ }
30
+ if (!isSupported) {
31
+ alert(`此演示在 ${detectedDevice.client.name}/${detectedDevice.os.name} 上运行时 ` +
32
+ `目前不能很好地支持,继续使用需自担风险。`);
33
+ }
34
+ }
35
+
36
+ const controls = window;
37
+ const mpHolistic = window;
38
+ const drawingUtils = window;
39
+ const config = { locateFile: (file) => {
40
+ return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@` +
41
+ `${mpHolistic.VERSION}/${file}`;
42
+ } };
43
+
44
+ // 我们的输入帧将来自这里。
45
+ const videoElement = document.getElementsByClassName('input_video')[0];
46
+ const canvasElement = document.getElementsByClassName('output_canvas')[0];
47
+ const controlsElement = document.getElementsByClassName('control-panel')[0];
48
+ const canvasCtx = canvasElement.getContext('2d');
49
+
50
+ // 我们稍后会将这个添加到控制面板中,但我们会在这里保存它,
51
+ // 以便每次图形运行时都可以调用 tick()。
52
+ const fpsControl = new controls.FPS();
53
+
54
+ // 优化:在隐藏动画完成后关闭动画旋转器。
55
+ const spinner = document.querySelector('.loading');
56
+ spinner.ontransitionend = () => {
57
+ spinner.style.display = 'none';
58
+ };
59
+
60
+ function removeElements(landmarks, elements) {
61
+ for (const element of elements) {
62
+ delete landmarks[element];
63
+ }
64
+ }
65
+
66
+ function removeLandmarks(results) {
67
+ if (results.poseLandmarks) {
68
+ removeElements(results.poseLandmarks, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22]);
69
+ }
70
+ }
71
+
72
+ function connect(ctx, connectors) {
73
+ const canvas = ctx.canvas;
74
+ for (const connector of connectors) {
75
+ const from = connector[0];
76
+ const to = connector[1];
77
+ if (from && to) {
78
+ if (from.visibility && to.visibility &&
79
+ (from.visibility < 0.1 || to.visibility < 0.1)) {
80
+ continue;
81
+ }
82
+ ctx.beginPath();
83
+ ctx.moveTo(from.x * canvas.width, from.y * canvas.height);
84
+ ctx.lineTo(to.x * canvas.width, to.y * canvas.height);
85
+ ctx.stroke();
86
+ }
87
+ }
88
+ }
89
+
90
+ let activeEffect = 'mask';
91
+
92
+ function onResults(results) {
93
+ // 隐藏旋转器。
94
+ document.body.classList.add('loaded');
95
+
96
+ // 移除我们不想绘制的关键点。
97
+ removeLandmarks(results);
98
+
99
+ // 更新帧率。
100
+ fpsControl.tick();
101
+
102
+ // 绘制叠加层。
103
+ canvasCtx.save();
104
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
105
+
106
+ if (results.segmentationMask) {
107
+ canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElement.width, canvasElement.height);
108
+
109
+ // 仅覆盖现有像素。
110
+ if (activeEffect === 'mask' || activeEffect === 'both') {
111
+ canvasCtx.globalCompositeOperation = 'source-in';
112
+ // 这可以是颜色、纹理或其他...
113
+ canvasCtx.fillStyle = '#00FF007F';
114
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
115
+ }
116
+ else {
117
+ canvasCtx.globalCompositeOperation = 'source-out';
118
+ canvasCtx.fillStyle = '#0000FF7F';
119
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
120
+ }
121
+
122
+ // 仅覆盖缺失的像素。
123
+ canvasCtx.globalCompositeOperation = 'destination-atop';
124
+ canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
125
+ canvasCtx.globalCompositeOperation = 'source-over';
126
+ }
127
+ else {
128
+ canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
129
+ }
130
+
131
+ // 连接肘部到手部。首先做这个,这样其他图形将绘制在这些标记之上。
132
+ canvasCtx.lineWidth = 5;
133
+ if (results.poseLandmarks) {
134
+ if (results.rightHandLandmarks) {
135
+ canvasCtx.strokeStyle = 'white';
136
+ connect(canvasCtx, [[
137
+ results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW],
138
+ results.rightHandLandmarks[0]
139
+ ]]);
140
+ }
141
+ if (results.leftHandLandmarks) {
142
+ canvasCtx.strokeStyle = 'white';
143
+ connect(canvasCtx, [[
144
+ results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW],
145
+ results.leftHandLandmarks[0]
146
+ ]]);
147
+ }
148
+ }
149
+
150
+ // 姿势...
151
+ drawingUtils.drawConnectors(canvasCtx, results.poseLandmarks, mpHolistic.POSE_CONNECTIONS, { color: 'white' });
152
+ drawingUtils.drawLandmarks(canvasCtx, Object.values(mpHolistic.POSE_LANDMARKS_LEFT)
153
+ .map(index => results.poseLandmarks[index]), { visibilityMin: 0.65, color: 'white', fillColor: 'rgb(255,138,0)' });
154
+ drawingUtils.drawLandmarks(canvasCtx, Object.values(mpHolistic.POSE_LANDMARKS_RIGHT)
155
+ .map(index => results.poseLandmarks[index]), { visibilityMin: 0.65, color: 'white', fillColor: 'rgb(0,217,231)' });
156
+
157
+ // 手...
158
+ drawingUtils.drawConnectors(canvasCtx, results.rightHandLandmarks, mpHolistic.HAND_CONNECTIONS, { color: 'white' });
159
+ drawingUtils.drawLandmarks(canvasCtx, results.rightHandLandmarks, {
160
+ color: 'white',
161
+ fillColor: 'rgb(0,217,231)',
162
+ lineWidth: 2,
163
+ radius: (data) => {
164
+ return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1);
165
+ }
166
+ });
167
+ drawingUtils.drawConnectors(canvasCtx, results.leftHandLandmarks, mpHolistic.HAND_CONNECTIONS, { color: 'white' });
168
+ drawingUtils.drawLandmarks(canvasCtx, results.leftHandLandmarks, {
169
+ color: 'white',
170
+ fillColor: 'rgb(255,138,0)',
171
+ lineWidth: 2,
172
+ radius: (data) => {
173
+ return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1);
174
+ }
175
+ });
176
+
177
+ // 面部...
178
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_TESSELATION, { color: '#C0C0C070', lineWidth: 1 });
179
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYE, { color: 'rgb(0,217,231)' });
180
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_RIGHT_EYEBROW, { color: 'rgb(0,217,231)' });
181
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYE, { color: 'rgb(255,138,0)' });
182
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LEFT_EYEBROW, { color: 'rgb(255,138,0)' });
183
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_FACE_OVAL, { color: '#E0E0E0', lineWidth: 5 });
184
+ drawingUtils.drawConnectors(canvasCtx, results.faceLandmarks, mpHolistic.FACEMESH_LIPS, { color: '#E0E0E0', lineWidth: 5 });
185
+
186
+ canvasCtx.restore();
187
+ }
188
+
189
+ const holistic = new mpHolistic.Holistic(config);
190
+ holistic.onResults(onResults);
191
+
192
+ // 呈现一个控制面板,用户可以通过它操作解决方案选项。
193
+ new controls
194
+ .ControlPanel(controlsElement, {
195
+ selfieMode: true,
196
+ modelComplexity: 1,
197
+ smoothLandmarks: true,
198
+ enableSegmentation: false,
199
+ smoothSegmentation: true,
200
+ minDetectionConfidence: 0.5,
201
+ minTrackingConfidence: 0.5,
202
+ effect: 'background',
203
+ })
204
+ .add([
205
+ new controls.StaticText({ title: 'MediaPipe 全身姿态检测' }),
206
+ fpsControl,
207
+ new controls.Toggle({ title: '自拍模式', field: 'selfieMode' }),
208
+ new controls.SourcePicker({
209
+ onSourceChanged: () => {
210
+ // 重置,因为在源更改之间重置时,姿势会给出更好的结果。
211
+ holistic.reset();
212
+ },
213
+ onFrame: async (input, size) => {
214
+ const aspect = size.height / size.width;
215
+ let width, height;
216
+ if (window.innerWidth > window.innerHeight) {
217
+ height = window.innerHeight;
218
+ width = height / aspect;
219
+ }
220
+ else {
221
+ width = window.innerWidth;
222
+ height = width * aspect;
223
+ }
224
+ canvasElement.width = width;
225
+ canvasElement.height = height;
226
+ await holistic.send({ image: input });
227
+ },
228
+ }),
229
+ new controls.Slider({
230
+ title: '模型复杂度',
231
+ field: 'modelComplexity',
232
+ discrete: ['轻量', '完整', '重度'],
233
+ }),
234
+ new controls.Toggle({ title: '平滑关键点', field: 'smoothLandmarks' }),
235
+ new controls.Toggle({ title: '启用分割', field: 'enableSegmentation' }),
236
+ new controls.Toggle({ title: '平滑分割', field: 'smoothSegmentation' }),
237
+ new controls.Slider({
238
+ title: '最小检测置信度',
239
+ field: 'minDetectionConfidence',
240
+ range: [0, 1],
241
+ step: 0.01
242
+ }),
243
+ new controls.Slider({
244
+ title: '最小跟踪置信度',
245
+ field: 'minTrackingConfidence',
246
+ range: [0, 1],
247
+ step: 0.01
248
+ }),
249
+ new controls.Slider({
250
+ title: '效果',
251
+ field: 'effect',
252
+ discrete: { 'background': '背景', 'mask': '前景' },
253
+ }),
254
+ ])
255
+ .on(x => {
256
+ const options = x;
257
+ videoElement.classList.toggle('selfie', options.selfieMode);
258
+ activeEffect = x['effect'];
259
+ holistic.setOptions(options);
260
+ });
index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Redirecting to Holistic Demo</title>
8
+ <script>
9
+ window.location.href = "holistic.html";
10
+ </script>
11
+ </head>
12
+ <body>
13
+ <p>If you are not redirected automatically, please <a href="holistic.html">click here</a>.</p>
14
+ </body>
15
+ </html>