mistpe commited on
Commit
d6ec1d1
·
verified ·
1 Parent(s): 5c65d4f

Upload 3 files

Browse files
templates/holistic.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
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>3D姿态估计演示</title>
8
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/holistic.css') }}" crossorigin="anonymous">
9
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/control_utils.css" crossorigin="anonymous">
10
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/camera_utils.js" crossorigin="anonymous"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/control_utils.js" crossorigin="anonymous"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/drawing_utils.js" crossorigin="anonymous"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/holistic.js" crossorigin="anonymous"></script>
14
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
16
+ </head>
17
+ <body>
18
+ <div class="container">
19
+ <div class="card control-panel"></div>
20
+ <div class="card">
21
+ <video class="input_video"></video>
22
+ <div class="canvas-container">
23
+ <canvas class="output_canvas"></canvas>
24
+ </div>
25
+ <div class="logo">
26
+ <img src="{{ url_for('static', filename='img/logo_white.png') }}" alt="MediaPipe Logo">
27
+ <span class="title">MediaPipe</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="additional-views">
33
+ <div class="view" id="3d-model"></div>
34
+ <div class="view">
35
+ <canvas id="speedChart"></canvas>
36
+ </div>
37
+ <div class="view">
38
+ <canvas id="accelerationChart"></canvas>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="loading">
43
+ <div class="spinner"></div>
44
+ <div class="message">加载中</div>
45
+ </div>
46
+
47
+ <script type="module" src="{{ url_for('static', filename='js/holistic.js') }}"></script>
48
+ </body>
49
+ </html>
templates/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Pose Estimation Apps</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ display: flex;
11
+ justify-content: center;
12
+ align-items: center;
13
+ height: 100vh;
14
+ margin: 0;
15
+ background-color: #f0f4f8;
16
+ }
17
+ .container {
18
+ text-align: center;
19
+ }
20
+ h1 {
21
+ color: #4a90e2;
22
+ }
23
+ .button {
24
+ display: inline-block;
25
+ padding: 10px 20px;
26
+ margin: 10px;
27
+ background-color: #4a90e2;
28
+ color: white;
29
+ text-decoration: none;
30
+ border-radius: 5px;
31
+ transition: background-color 0.3s;
32
+ }
33
+ .button:hover {
34
+ background-color: #3a7bd5;
35
+ }
36
+ </style>
37
+ </head>
38
+ <body>
39
+ <div class="container">
40
+ <h1>3D Pose Estimation Apps</h1>
41
+ <a href="/web_app" class="button">Web Camera App</a>
42
+ <a href="/video_app" class="button">Video Processing App</a>
43
+ </div>
44
+ </body>
45
+ </html>
templates/video_app.html ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Pose Estimation App</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
12
+ <style>
13
+ :root {
14
+ --primary-color: #4a90e2;
15
+ --secondary-color: #f5a623;
16
+ --background-color: #f0f4f8;
17
+ --card-background: #ffffff;
18
+ --text-color: #333333;
19
+ --border-radius: 12px;
20
+ --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ margin: 0;
26
+ padding: 0;
27
+ }
28
+
29
+ body {
30
+ font-family: 'Roboto', sans-serif;
31
+ background: var(--background-color);
32
+ color: var(--text-color);
33
+ line-height: 1.6;
34
+ }
35
+
36
+ .container {
37
+ max-width: 1400px;
38
+ margin: 0 auto;
39
+ padding: 20px;
40
+ }
41
+
42
+ .app-header {
43
+ text-align: center;
44
+ margin-bottom: 30px;
45
+ }
46
+
47
+ .app-title {
48
+ font-size: 2.5em;
49
+ color: var(--primary-color);
50
+ margin-bottom: 10px;
51
+ }
52
+
53
+ .app-description {
54
+ font-size: 1.1em;
55
+ color: var(--text-color);
56
+ opacity: 0.8;
57
+ }
58
+
59
+ .card-grid {
60
+ display: grid;
61
+ grid-template-columns: repeat(2, 1fr);
62
+ gap: 20px;
63
+ }
64
+
65
+ .card {
66
+ background: var(--card-background);
67
+ border-radius: var(--border-radius);
68
+ box-shadow: var(--box-shadow);
69
+ overflow: hidden;
70
+ transition: transform 0.3s ease;
71
+ }
72
+
73
+ .card:hover {
74
+ transform: translateY(-5px);
75
+ }
76
+
77
+ .card-header {
78
+ background: var(--primary-color);
79
+ color: white;
80
+ padding: 15px;
81
+ font-size: 1.2em;
82
+ font-weight: bold;
83
+ display: flex;
84
+ align-items: center;
85
+ }
86
+
87
+ .card-header i {
88
+ margin-right: 10px;
89
+ }
90
+
91
+ .card-body {
92
+ padding: 20px;
93
+ }
94
+
95
+ .view {
96
+ width: 100%;
97
+ padding-bottom: 56.25%; /* 16:9 aspect ratio */
98
+ position: relative;
99
+ border-radius: var(--border-radius);
100
+ overflow: hidden;
101
+ }
102
+
103
+ .view img, .view canvas {
104
+ position: absolute;
105
+ top: 0;
106
+ left: 0;
107
+ width: 100%;
108
+ height: 100%;
109
+ object-fit: contain;
110
+ }
111
+
112
+ .controls {
113
+ margin-top: 20px;
114
+ }
115
+
116
+ .slider-container {
117
+ display: flex;
118
+ align-items: center;
119
+ margin-bottom: 15px;
120
+ }
121
+
122
+ .slider-container label {
123
+ width: 80px;
124
+ margin-right: 10px;
125
+ }
126
+
127
+ .slider {
128
+ flex-grow: 1;
129
+ -webkit-appearance: none;
130
+ height: 5px;
131
+ background: #d7dcdf;
132
+ outline: none;
133
+ opacity: 0.7;
134
+ transition: opacity .2s;
135
+ border-radius: 5px;
136
+ }
137
+
138
+ .slider:hover {
139
+ opacity: 1;
140
+ }
141
+
142
+ .slider::-webkit-slider-thumb {
143
+ -webkit-appearance: none;
144
+ appearance: none;
145
+ width: 18px;
146
+ height: 18px;
147
+ background: var(--secondary-color);
148
+ cursor: pointer;
149
+ border-radius: 50%;
150
+ }
151
+
152
+ .btn {
153
+ padding: 12px 20px;
154
+ border: none;
155
+ border-radius: var(--border-radius);
156
+ background-color: var(--primary-color);
157
+ color: white;
158
+ cursor: pointer;
159
+ transition: background-color 0.3s ease, transform 0.1s ease;
160
+ margin-top: 10px;
161
+ width: 100%;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ font-size: 1em;
166
+ font-weight: bold;
167
+ }
168
+
169
+ .btn:hover {
170
+ background-color: #3a7bd5;
171
+ transform: translateY(-2px);
172
+ }
173
+
174
+ .btn:active {
175
+ transform: translateY(0);
176
+ }
177
+
178
+ .btn i {
179
+ margin-right: 10px;
180
+ }
181
+
182
+ .chart-container {
183
+ display: flex;
184
+ flex-wrap: wrap;
185
+ justify-content: space-around;
186
+ gap: 20px;
187
+ }
188
+
189
+ .chart-container canvas {
190
+ max-width: 100%;
191
+ height: auto !important;
192
+ }
193
+
194
+ #message-box {
195
+ position: fixed;
196
+ top: 20px;
197
+ left: 50%;
198
+ transform: translateX(-50%);
199
+ background: var(--primary-color);
200
+ color: white;
201
+ padding: 15px 20px;
202
+ border-radius: var(--border-radius);
203
+ z-index: 1000;
204
+ display: none;
205
+ box-shadow: var(--box-shadow);
206
+ }
207
+
208
+ #loading-indicator {
209
+ position: fixed;
210
+ top: 50%;
211
+ left: 50%;
212
+ transform: translate(-50%, -50%);
213
+ z-index: 1000;
214
+ display: none;
215
+ background: rgba(0, 0, 0, 0.8);
216
+ color: white;
217
+ padding: 20px;
218
+ border-radius: var(--border-radius);
219
+ box-shadow: var(--box-shadow);
220
+ }
221
+
222
+ #loading-indicator i {
223
+ margin-right: 10px;
224
+ }
225
+
226
+ @media (max-width: 1024px) {
227
+ .card-grid {
228
+ grid-template-columns: 1fr;
229
+ }
230
+
231
+ .app-title {
232
+ font-size: 2em;
233
+ }
234
+
235
+ .app-description {
236
+ font-size: 1em;
237
+ }
238
+ }
239
+
240
+ @media (max-width: 768px) {
241
+ .container {
242
+ padding: 10px;
243
+ }
244
+
245
+ .card-header {
246
+ font-size: 1.1em;
247
+ }
248
+
249
+ .slider-container {
250
+ flex-direction: column;
251
+ align-items: flex-start;
252
+ }
253
+
254
+ .slider-container label {
255
+ margin-bottom: 5px;
256
+ }
257
+
258
+ .btn {
259
+ padding: 10px 15px;
260
+ }
261
+ }
262
+ </style>
263
+ </head>
264
+ <body>
265
+ <div id="message-box">
266
+ <span id="message-text"></span>
267
+ </div>
268
+
269
+ <div id="loading-indicator">
270
+ <i class="fas fa-spinner fa-spin"></i> Processing...
271
+ </div>
272
+
273
+ <div class="container">
274
+ <header class="app-header">
275
+ <h1 class="app-title">3D Pose Estimation App</h1>
276
+ <p class="app-description">Analyze and visualize human pose in real-time</p>
277
+ </header>
278
+
279
+ <div class="card-grid">
280
+ <div class="card">
281
+ <div class="card-header">
282
+ <i class="fas fa-video"></i> Video Input
283
+ </div>
284
+ <div class="card-body">
285
+ <div class="view" id="video-container">
286
+ <img id="video-feed" src="{{ url_for('video_feed') }}" alt="Video Feed">
287
+ </div>
288
+ <div class="controls">
289
+ <div class="slider-container">
290
+ <label for="video-scale"><i class="fas fa-search"></i> Scale:</label>
291
+ <input type="range" min="0.5" max="2" step="0.1" value="1" class="slider" id="video-scale" aria-label="Video Scale Slider">
292
+ </div>
293
+ <label for="video-upload" class="btn">
294
+ <i class="fas fa-upload"></i> Upload Video
295
+ <input type="file" id="video-upload" accept="video/*" style="display: none;">
296
+ </label>
297
+ </div>
298
+ </div>
299
+ </div>
300
+
301
+ <div class="card">
302
+ <div class="card-header">
303
+ <i class="fas fa-cube"></i> 3D Model Visualization
304
+ </div>
305
+ <div class="card-body">
306
+ <div class="view" id="model-container"></div>
307
+ <div class="controls">
308
+ <div class="slider-container">
309
+ <label for="model-scale"><i class="fas fa-search"></i> Scale:</label>
310
+ <input type="range" min="0.5" max="2" step="0.1" value="1" class="slider" id="model-scale" aria-label="3D Model Scale Slider">
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
316
+ <div class="card">
317
+ <div class="card-header">
318
+ <i class="fas fa-tachometer-alt"></i> Speed Analysis
319
+ </div>
320
+ <div class="card-body">
321
+ <canvas id="speedChart" aria-label="Speed Chart" role="img"></canvas>
322
+ </div>
323
+ </div>
324
+
325
+ <div class="card">
326
+ <div class="card-header">
327
+ <i class="fas fa-bolt"></i> Acceleration Analysis
328
+ </div>
329
+ <div class="card-body">
330
+ <canvas id="accelerationChart" aria-label="Acceleration Chart" role="img"></canvas>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </div>
335
+
336
+
337
+ <script>
338
+ // 连接到Socket.IO服务器
339
+ const socket = io();
340
+
341
+ // Three.js设置
342
+ const scene = new THREE.Scene();
343
+ scene.background = new THREE.Color(0xf0f4f8);
344
+ const aspect = 4 / 3;
345
+ const camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 1000);
346
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
347
+ const modelContainer = document.getElementById('model-container');
348
+ renderer.setSize(modelContainer.clientWidth, modelContainer.clientWidth / aspect);
349
+ modelContainer.appendChild(renderer.domElement);
350
+
351
+ camera.position.set(0, 0, 1.5);
352
+ camera.lookAt(0, 0, 0);
353
+
354
+ // 添加灯光
355
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
356
+ scene.add(ambientLight);
357
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
358
+ directionalLight.position.set(1, 1, 1);
359
+ scene.add(directionalLight);
360
+
361
+ // 创建用于标记关键点的球体
362
+ const spheres = [];
363
+ for (let i = 0; i < 33; i++) {
364
+ const geometry = new THREE.SphereGeometry(0.015, 32, 32);
365
+ const material = new THREE.MeshPhongMaterial({
366
+ color: 0x3498db,
367
+ shininess: 100,
368
+ specular: 0x111111
369
+ });
370
+ const sphere = new THREE.Mesh(geometry, material);
371
+ scene.add(sphere);
372
+ spheres.push(sphere);
373
+ }
374
+
375
+ // 创建圆柱体的函数
376
+ const cylinders = [];
377
+ function createCylinder(point1, point2) {
378
+ const direction = new THREE.Vector3().subVectors(point2, point1);
379
+ const cylinder = new THREE.Mesh(
380
+ new THREE.CylinderGeometry(0.007, 0.007, direction.length(), 8, 1),
381
+ new THREE.MeshPhongMaterial({
382
+ color: 0x2c3e50,
383
+ shininess: 100,
384
+ specular: 0x111111
385
+ })
386
+ );
387
+ cylinder.position.copy(point1);
388
+ cylinder.position.addScaledVector(direction, 0.5);
389
+ cylinder.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction.normalize());
390
+ scene.add(cylinder);
391
+ return cylinder;
392
+ }
393
+
394
+ // 初始化图表
395
+ const speedChartCtx = document.getElementById('speedChart').getContext('2d');
396
+ const accelerationChartCtx = document.getElementById('accelerationChart').getContext('2d');
397
+ const speedChart = new Chart(speedChartCtx, {
398
+ type: 'line',
399
+ data: {
400
+ labels: [],
401
+ datasets: [{
402
+ label: 'Speed',
403
+ data: [],
404
+ borderColor: 'rgba(75, 192, 192, 1)',
405
+ borderWidth: 1,
406
+ fill: false
407
+ }]
408
+ },
409
+ options: {
410
+ scales: {
411
+ y: { beginAtZero: true }
412
+ }
413
+ }
414
+ });
415
+ const accelerationChart = new Chart(accelerationChartCtx, {
416
+ type: 'line',
417
+ data: {
418
+ labels: [],
419
+ datasets: [{
420
+ label: 'Acceleration',
421
+ data: [],
422
+ borderColor: 'rgba(255, 99, 132, 1)',
423
+ borderWidth: 1,
424
+ fill: false
425
+ }]
426
+ },
427
+ options: {
428
+ scales: {
429
+ y: { beginAtZero: true }
430
+ }
431
+ }
432
+ });
433
+
434
+ // 更新图表的函数
435
+ function updateChart(chart, newData) {
436
+ const currentTime = new Date().toLocaleTimeString();
437
+ chart.data.labels.push(currentTime);
438
+ chart.data.datasets.forEach((dataset) => {
439
+ dataset.data.push(newData.reduce((a, b) => a + b, 0) / newData.length);
440
+ });
441
+ chart.update();
442
+ }
443
+
444
+ // 监听从服务器发送的pose_data事件
445
+ socket.on('pose_data', function(data) {
446
+ // 更新3D模型
447
+ const landmarks = data.landmarks;
448
+ landmarks.forEach((coord, index) => {
449
+ spheres[index].position.set((coord[0] - 0.5) * 2, -(coord[1] - 0.5) * 2, -coord[2] * 0.5);
450
+ });
451
+
452
+ // 清除现有的圆柱体
453
+ cylinders.forEach(cylinder => scene.remove(cylinder));
454
+ cylinders.length = 0;
455
+
456
+ // 创建新的圆柱体
457
+ const connections = [
458
+ // Face
459
+ [0, 1], [1, 4], [4, 7], [7, 8], [8, 5], [5, 2], [2, 0],
460
+ [0, 9], [9, 10], [0, 10],
461
+ // Arms
462
+ [11, 13], [13, 15], [15, 17], [15, 19], [15, 21],
463
+ [12, 14], [14, 16], [16, 18], [16, 20], [16, 22],
464
+ // Body
465
+ [11, 12], [11, 23], [12, 24], [23, 24],
466
+ // Legs
467
+ [23, 25], [25, 27], [27, 29],
468
+ [24, 26], [26, 28], [28, 30], [28, 32]
469
+ ];
470
+
471
+ connections.forEach(([i, j]) => {
472
+ cylinders.push(createCylinder(spheres[i].position, spheres[j].position));
473
+ });
474
+
475
+ // 更新速度和加速度图表
476
+ if (data.velocities) {
477
+ updateChart(speedChart, data.velocities);
478
+ }
479
+ if (data.accelerations) {
480
+ updateChart(accelerationChart, data.accelerations);
481
+ }
482
+ });
483
+
484
+ function animate() {
485
+ requestAnimationFrame(animate);
486
+ renderer.render(scene, camera);
487
+ }
488
+ animate();
489
+
490
+ function showMessage(message) {
491
+ const messageBox = document.getElementById('message-box');
492
+ const messageText = document.getElementById('message-text');
493
+ messageText.textContent = message;
494
+ messageBox.style.display = 'block';
495
+
496
+ // 2秒后自动隐藏
497
+ setTimeout(() => {
498
+ messageBox.style.display = 'none';
499
+ }, 2000);
500
+ }
501
+
502
+ // 视频上传处理
503
+ document.getElementById('video-upload').addEventListener('change', function(e) {
504
+ const file = e.target.files[0];
505
+ if (file) {
506
+ const formData = new FormData();
507
+ formData.append('video', file);
508
+
509
+ // 显示加载指示器
510
+ showLoadingIndicator(true);
511
+
512
+ fetch('/upload_video', {
513
+ method: 'POST',
514
+ body: formData
515
+ }).then(response => response.json())
516
+ .then(data => {
517
+ showLoadingIndicator(false); // 隐藏加载指示器
518
+ if (data.success) {
519
+ showMessage(data.message);
520
+ document.getElementById('video-feed').src = "{{ url_for('video_feed') }}?" + new Date().getTime();
521
+ } else {
522
+ showMessage('Error uploading video');
523
+ }
524
+ }).catch(error => {
525
+ showLoadingIndicator(false); // 隐藏加载指示器
526
+ showMessage('Upload failed: ' + error.message);
527
+ });
528
+ }
529
+ });
530
+
531
+ // 切换到相机模式
532
+ document.getElementById('camera-switch').addEventListener('click', function() {
533
+ showLoadingIndicator(true); // 显示加载指示器
534
+
535
+ fetch('/switch_to_camera', {
536
+ method: 'POST'
537
+ }).then(response => response.json())
538
+ .then(data => {
539
+ showLoadingIndicator(false); // 隐藏加载指示器
540
+ if (data.success) {
541
+ showMessage(data.message);
542
+ document.getElementById('video-feed').src = "{{ url_for('video_feed') }}?" + new Date().getTime();
543
+ } else {
544
+ showMessage('Error switching to camera');
545
+ }
546
+ }).catch(error => {
547
+ showLoadingIndicator(false); // 隐藏加载指示器
548
+ showMessage('Switch failed: ' + error.message);
549
+ });
550
+ });
551
+ function showLoadingIndicator(isLoading) {
552
+ const loadingIndicator = document.getElementById('loading-indicator');
553
+ loadingIndicator.style.display = isLoading ? 'block' : 'none';
554
+ }
555
+
556
+
557
+ // 缩放处理
558
+ document.getElementById('video-scale').addEventListener('input', function(e) {
559
+ document.getElementById('video-feed').style.transform = `scale(${e.target.value})`;
560
+ });
561
+
562
+ document.getElementById('model-scale').addEventListener('input', function(e) {
563
+ const newWidth = modelContainer.clientWidth * e.target.value;
564
+ const newHeight = newWidth / aspect;
565
+ renderer.setSize(newWidth, newHeight);
566
+ });
567
+
568
+ // 窗口大小调整处理
569
+ window.addEventListener('resize', function() {
570
+ const width = modelContainer.clientWidth;
571
+ const height = width / aspect;
572
+ camera.aspect = aspect;
573
+ camera.updateProjectionMatrix();
574
+ renderer.setSize(width, height);
575
+ });
576
+
577
+ navigator.mediaDevices.getUserMedia({
578
+ video: {
579
+ facingMode: 'user',
580
+ width: { ideal: 640 },
581
+ height: { ideal: 480 },
582
+ frameRate: { ideal: 15 }
583
+ }
584
+ })
585
+ .then(stream => {
586
+ const video = document.createElement('video');
587
+ video.srcObject = stream;
588
+ video.play();
589
+ const videoFeed = document.getElementById('video-feed');
590
+ videoFeed.srcObject = stream;
591
+ videoFeed.play();
592
+ })
593
+ .catch(error => {
594
+ console.error('Error accessing camera: ', error);
595
+ });
596
+
597
+
598
+
599
+ </script>
600
+
601
+ </body>
602
+ </html>