HsuHH commited on
Commit
ebfb673
·
verified ·
1 Parent(s): ff1deca

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +175 -19
index.html CHANGED
@@ -1,19 +1,175 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-TW">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MS COCO邊緣端物件偵測 (Web Accelerator)</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script>
8
+ <style>
9
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 20px auto; text-align: center; background-color: #f5f5f5; }
10
+ .container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
11
+ #canvasContainer { position: relative; display: inline-block; margin-top: 20px; }
12
+ canvas { position: absolute; left: 0; top: 0; }
13
+ img { max-width: 100%; height: auto; display: block; }
14
+ .status { color: #666; margin: 10px 0; font-style: italic; }
15
+
16
+ /* 新增:Loading 遮罩與動畫樣式 */
17
+ #loadingOverlay {
18
+ display: none; /* 預設隱藏 */
19
+ position: absolute;
20
+ top: 0; left: 0; width: 100%; height: 100%;
21
+ background: rgba(255, 255, 255, 0.8);
22
+ z-index: 10;
23
+ justify-content: center;
24
+ align-items: center;
25
+ flex-direction: column;
26
+ border-radius: 4px;
27
+ }
28
+ .spinner {
29
+ border: 5px solid #f3f3f3;
30
+ border-top: 5px solid #007bff;
31
+ border-radius: 50%;
32
+ width: 40px;
33
+ height: 40px;
34
+ animation: spin 1s linear infinite;
35
+ }
36
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
37
+ </style>
38
+ </head>
39
+ <body>
40
+
41
+ <div class="container">
42
+ <h2>MS COCO 邊緣端物件偵測 ( Web Accelerator)</h2>
43
+ <p class="status" id="status">正在初始化模型環境...</p>
44
+
45
+ <input type="file" id="imageLoader" accept="image/*" disabled>
46
+ <br>
47
+
48
+ <div id="canvasContainer">
49
+ <!-- 新增:載入中的遮罩 UI -->
50
+ <div id="loadingOverlay">
51
+ <div class="spinner"></div>
52
+ <p style="font-weight: bold; color: #333; margin-top: 15px;">神經網路推論中,請稍候...</p>
53
+ </div>
54
+ <img id="inputImage" src="" alt="">
55
+ <canvas id="outputCanvas"></canvas>
56
+ </div>
57
+ </div>
58
+
59
+ <script>
60
+ let session = null;
61
+ const statusText = document.getElementById('status');
62
+ const imageLoader = document.getElementById('imageLoader');
63
+ const imgElement = document.getElementById('inputImage');
64
+ const canvas = document.getElementById('outputCanvas');
65
+ const loadingOverlay = document.getElementById('loadingOverlay'); // 取得 Loading DOM
66
+ const ctx = canvas.getContext('2d');
67
+
68
+ // 1. 異步載入 ONNX 模型
69
+ async function initModel() {
70
+ try {
71
+ statusText.innerText = "正在下載並載入 best.onnx (這可能需要一點時間)...";
72
+ session = await ort.InferenceSession.create('./best.onnx', { executionProviders: ['wasm'] });
73
+ statusText.innerText = "模型載入成功!請上傳一張圖片進行物件偵測。";
74
+ imageLoader.disabled = false;
75
+ } catch (e) {
76
+ statusText.innerText = "模型載入失敗: " + e.message;
77
+ console.error(e);
78
+ }
79
+ }
80
+
81
+ // 2. 監聽圖片上傳
82
+ imageLoader.addEventListener('change', handleImage, false);
83
+
84
+ function handleImage(e) {
85
+ const reader = new FileReader();
86
+ reader.onload = function(event) {
87
+ imgElement.src = event.target.result;
88
+ imgElement.onload = async function() {
89
+ canvas.width = imgElement.clientWidth;
90
+ canvas.height = imgElement.clientHeight;
91
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
92
+
93
+ statusText.innerText = "準備執行推論...";
94
+
95
+ // ★ 關鍵:顯示 Loading 動畫
96
+ loadingOverlay.style.display = 'flex';
97
+
98
+ // ★ 關鍵技巧:強迫暫停 50 毫秒,讓瀏覽器有時間把 Loading 畫面畫出來,再進入繁重的推論
99
+ await new Promise(resolve => setTimeout(resolve, 50));
100
+
101
+ runInference();
102
+ }
103
+ }
104
+ reader.readAsDataURL(e.target.files[0]);
105
+ }
106
+
107
+ // 3. 執行推論與畫框
108
+ async function runInference() {
109
+ if (!session) return;
110
+
111
+ try {
112
+ // --- 影像前處理開始 ---
113
+ const tmpCanvas = document.createElement('canvas');
114
+ const targetSize = 640;
115
+ tmpCanvas.width = targetSize;
116
+ tmpCanvas.height = targetSize;
117
+ const tctx = tmpCanvas.getContext('2d');
118
+
119
+ tctx.drawImage(imgElement, 0, 0, targetSize, targetSize);
120
+ const imgData = tctx.getImageData(0, 0, targetSize, targetSize);
121
+ const data = imgData.data;
122
+
123
+ const hw = targetSize * targetSize;
124
+ const input = new Float32Array(3 * hw);
125
+ for (let i = 0; i < hw; i++) {
126
+ const dataIdx = i * 4;
127
+ input[i] = data[dataIdx] / 255.0; // R 通道
128
+ input[hw + i] = data[dataIdx + 1] / 255.0; // G 通道
129
+ input[2 * hw + i] = data[dataIdx + 2] / 255.0; // B 通道
130
+ }
131
+ // --- 影像前處理結束 ---
132
+
133
+ const tensor = new ort.Tensor('float32', input, [1, 3, targetSize, targetSize]);
134
+ const feeds = { input_tensor: tensor };
135
+ const results = await session.run(feeds);
136
+
137
+ const boxes = results.boxes.data;
138
+ const scores = results.scores.data;
139
+ const labels = results.labels.data;
140
+
141
+ // 繪製 Bounding Boxes
142
+ let detectedCount = 0;
143
+ ctx.strokeStyle = '#00ff00';
144
+ ctx.lineWidth = 3;
145
+ ctx.font = '18px Arial'; // 字體稍微調大一點
146
+ ctx.fillStyle = '#00ff00';
147
+
148
+ for (let i = 0; i < scores.length; i++) {
149
+ if (scores[i] > 0.5) {
150
+ detectedCount++;
151
+ const xmin = boxes[i*4] * (canvas.width / 640);
152
+ const ymin = boxes[i*4+1] * (canvas.height / 640);
153
+ const xmax = boxes[i*4+2] * (canvas.width / 640);
154
+ const ymax = boxes[i*4+3] * (canvas.height / 640);
155
+
156
+ ctx.strokeRect(xmin, ymin, xmax - xmin, ymax - ymin);
157
+ ctx.fillText(`Obj ID: ${labels[i]} (${(scores[i]*100).toFixed(1)}%)`, xmin, ymin - 5);
158
+ }
159
+ }
160
+ statusText.innerText = `偵測結束,共發現 ${detectedCount} 個置信度大於 50% 的物件。`;
161
+
162
+ } catch (error) {
163
+ statusText.innerText = "推論發生錯誤: " + error.message;
164
+ console.error(error);
165
+ } finally {
166
+ // ★ 無論成功或失敗,最後一定要把 Loading 遮罩關閉
167
+ loadingOverlay.style.display = 'none';
168
+ }
169
+ }
170
+
171
+ // 啟動初始化
172
+ initModel();
173
+ </script>
174
+ </body>
175
+ </html>