dobby23 commited on
Commit
e236e89
·
verified ·
1 Parent(s): 592b7dd

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +549 -19
index.html CHANGED
@@ -1,19 +1,549 @@
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="ko">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
7
+ <title>사진 속 물체 카운터</title>
8
+
9
+ <style>
10
+ body {
11
+ font-family: "맑은 고딕", sans-serif;
12
+ max-width: 800px;
13
+ margin: 24px auto;
14
+ padding: 0 16px;
15
+ color: #202124;
16
+ }
17
+
18
+ h1 {
19
+ font-size: 26px;
20
+ margin-bottom: 6px;
21
+ }
22
+
23
+ .sub {
24
+ color: #5f6368;
25
+ margin-top: 0;
26
+ }
27
+
28
+ #status {
29
+ display: block;
30
+ margin: 14px 0;
31
+ padding: 10px 12px;
32
+ background: #f1f3f4;
33
+ border-radius: 6px;
34
+ }
35
+
36
+ .control {
37
+ margin: 15px 0;
38
+ }
39
+
40
+ input[type="file"] {
41
+ margin-top: 8px;
42
+ }
43
+
44
+ input[type="range"] {
45
+ width: 300px;
46
+ }
47
+
48
+ button {
49
+ padding: 9px 16px;
50
+ margin-top: 10px;
51
+ cursor: pointer;
52
+ font-size: 15px;
53
+ }
54
+
55
+ #canvas {
56
+ width: 100%;
57
+ max-width: 720px;
58
+ height: auto;
59
+ margin-top: 15px;
60
+ border: 1px solid #dadce0;
61
+ border-radius: 6px;
62
+ display: none;
63
+ }
64
+
65
+ #result {
66
+ margin-top: 16px;
67
+ padding: 14px;
68
+ min-height: 60px;
69
+ border: 1px solid #dadce0;
70
+ border-radius: 6px;
71
+ white-space: pre-line;
72
+ line-height: 1.7;
73
+ }
74
+
75
+ .label {
76
+ font-weight: bold;
77
+ }
78
+ </style>
79
+ </head>
80
+
81
+ <body>
82
+
83
+ <h1>📷 사진 속 물체 카운터</h1>
84
+
85
+ <p class="sub">
86
+ 사진을 올리면 AI가 물체를 찾아 위치를 표시하고,
87
+ 종류별 개수와 전체 개수를 알려줍니다.
88
+ </p>
89
+
90
+
91
+ <!-- =====================================================
92
+ 입력 1 : 이미지
93
+ ===================================================== -->
94
+
95
+ <div class="control">
96
+
97
+ <div class="label">① 사진 선택</div>
98
+
99
+ <input
100
+ type="file"
101
+ id="imageInput"
102
+ accept="image/*"
103
+ />
104
+
105
+ </div>
106
+
107
+
108
+ <!-- =====================================================
109
+ 입력 2 : 기준 점수
110
+ ===================================================== -->
111
+
112
+ <div class="control">
113
+
114
+ <div class="label">
115
+ ② 기준 점수 :
116
+ <span id="thresholdValue">0.50</span>
117
+ </div>
118
+
119
+ <input
120
+ type="range"
121
+ id="threshold"
122
+ min="0.1"
123
+ max="0.95"
124
+ step="0.05"
125
+ value="0.5"
126
+ />
127
+
128
+ </div>
129
+
130
+
131
+ <button id="runButton">
132
+ 🔍 물체 분석하기
133
+ </button>
134
+
135
+
136
+ <!-- 상태 표시 -->
137
+ <span id="status">
138
+ AI 모델 불러오는 중...
139
+ </span>
140
+
141
+
142
+ <!-- 결과 이미지 -->
143
+ <canvas id="canvas"></canvas>
144
+
145
+
146
+ <!-- 개수 결과 -->
147
+ <div id="result">
148
+ 사진을 선택한 뒤 물체 분석하기 버튼을 눌러 주세요.
149
+ </div>
150
+
151
+
152
+ <script type="module">
153
+
154
+ import { pipeline }
155
+ from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.6";
156
+
157
+
158
+ // =====================================================
159
+ // 모델 설정
160
+ // =====================================================
161
+
162
+ const TASK = "object-detection";
163
+
164
+ const MODEL_NAME =
165
+ "Xenova/yolos-tiny";
166
+
167
+
168
+ // =====================================================
169
+ // HTML 요소
170
+ // =====================================================
171
+
172
+ const imageInput =
173
+ document.getElementById("imageInput");
174
+
175
+ const threshold =
176
+ document.getElementById("threshold");
177
+
178
+ const thresholdValue =
179
+ document.getElementById("thresholdValue");
180
+
181
+ const runButton =
182
+ document.getElementById("runButton");
183
+
184
+ const statusBox =
185
+ document.getElementById("status");
186
+
187
+ const resultBox =
188
+ document.getElementById("result");
189
+
190
+ const canvas =
191
+ document.getElementById("canvas");
192
+
193
+ const ctx =
194
+ canvas.getContext("2d");
195
+
196
+
197
+ // =====================================================
198
+ // 기준 점수 숫자 표시
199
+ // =====================================================
200
+
201
+ threshold.addEventListener(
202
+ "input",
203
+ () => {
204
+
205
+ thresholdValue.textContent =
206
+ Number(threshold.value).toFixed(2);
207
+
208
+ }
209
+ );
210
+
211
+
212
+ // =====================================================
213
+ // AI 모델 불러오기
214
+ // =====================================================
215
+
216
+ let detector = null;
217
+ let loadSeconds = "";
218
+
219
+
220
+ try {
221
+
222
+ const start =
223
+ performance.now();
224
+
225
+
226
+ detector =
227
+ await pipeline(
228
+ TASK,
229
+ MODEL_NAME
230
+ );
231
+
232
+
233
+ loadSeconds =
234
+ (
235
+ (performance.now() - start)
236
+ / 1000
237
+ ).toFixed(1);
238
+
239
+
240
+ statusBox.textContent =
241
+ `준비 완료 · 모델 불러오기 ${loadSeconds}초`;
242
+
243
+
244
+ } catch (error) {
245
+
246
+ statusBox.textContent =
247
+ `❌ 모델을 불러오지 못함 : ${error.message}`;
248
+
249
+ console.error(error);
250
+
251
+ }
252
+
253
+
254
+ // =====================================================
255
+ // 분석 버튼
256
+ // =====================================================
257
+
258
+ runButton.addEventListener(
259
+ "click",
260
+ analyzeImage
261
+ );
262
+
263
+
264
+ // =====================================================
265
+ // 사진 분석
266
+ // =====================================================
267
+
268
+ async function analyzeImage() {
269
+
270
+ if (detector === null) {
271
+
272
+ resultBox.textContent =
273
+ "AI 모델을 아직 불러오는 중입니다.";
274
+
275
+ return;
276
+
277
+ }
278
+
279
+
280
+ const file =
281
+ imageInput.files[0];
282
+
283
+
284
+ if (!file) {
285
+
286
+ resultBox.textContent =
287
+ "사진을 먼저 선택해 주세요.";
288
+
289
+ return;
290
+
291
+ }
292
+
293
+
294
+ const score =
295
+ Number(threshold.value);
296
+
297
+
298
+ statusBox.textContent =
299
+ "물체를 찾는 중...";
300
+
301
+
302
+ resultBox.textContent =
303
+ "분석 중...";
304
+
305
+
306
+ // -----------------------------------------------------
307
+ // 이미지 URL 생성
308
+ // -----------------------------------------------------
309
+
310
+ const imageURL =
311
+ URL.createObjectURL(file);
312
+
313
+
314
+ const image =
315
+ new Image();
316
+
317
+
318
+ image.onload =
319
+ async () => {
320
+
321
+ try {
322
+
323
+ // ------------------------------------------------
324
+ // Canvas에 원본 이미지 표시
325
+ // ------------------------------------------------
326
+
327
+ canvas.width =
328
+ image.naturalWidth;
329
+
330
+ canvas.height =
331
+ image.naturalHeight;
332
+
333
+
334
+ ctx.clearRect(
335
+ 0,
336
+ 0,
337
+ canvas.width,
338
+ canvas.height
339
+ );
340
+
341
+
342
+ ctx.drawImage(
343
+ image,
344
+ 0,
345
+ 0
346
+ );
347
+
348
+
349
+ canvas.style.display =
350
+ "block";
351
+
352
+
353
+ // ------------------------------------------------
354
+ // YOLOS 물체 탐지
355
+ // ------------------------------------------------
356
+
357
+ const start =
358
+ performance.now();
359
+
360
+
361
+ const output =
362
+ await detector(
363
+ imageURL,
364
+ {
365
+ threshold: score
366
+ }
367
+ );
368
+
369
+
370
+ const seconds =
371
+ (
372
+ (performance.now() - start)
373
+ / 1000
374
+ ).toFixed(1);
375
+
376
+
377
+ // ------------------------------------------------
378
+ // 개수 저장
379
+ // ------------------------------------------------
380
+
381
+ const counts = {};
382
+
383
+
384
+ // ------------------------------------------------
385
+ // 찾은 물체 하나씩 처리
386
+ // ------------------------------------------------
387
+
388
+ for (const item of output) {
389
+
390
+ const label =
391
+ item.label;
392
+
393
+ const score =
394
+ item.score;
395
+
396
+ const box =
397
+ item.box;
398
+
399
+
400
+ // 개수 증가
401
+ if (counts[label]) {
402
+
403
+ counts[label]++;
404
+
405
+ } else {
406
+
407
+ counts[label] = 1;
408
+
409
+ }
410
+
411
+
412
+ // ----------------------------------------------
413
+ // 네모 박스 그리기
414
+ // ----------------------------------------------
415
+
416
+ const x =
417
+ box.xmin;
418
+
419
+ const y =
420
+ box.ymin;
421
+
422
+ const width =
423
+ box.xmax - box.xmin;
424
+
425
+ const height =
426
+ box.ymax - box.ymin;
427
+
428
+
429
+ ctx.lineWidth = 4;
430
+
431
+ ctx.strokeStyle =
432
+ "#ff3366";
433
+
434
+ ctx.strokeRect(
435
+ x,
436
+ y,
437
+ width,
438
+ height
439
+ );
440
+
441
+
442
+ // ----------------------------------------------
443
+ // 물체 이름 표시
444
+ // ----------------------------------------------
445
+
446
+ const text =
447
+ `${label} ${(score * 100).toFixed(0)}%`;
448
+
449
+
450
+ ctx.font =
451
+ "20px Arial";
452
+
453
+
454
+ const textWidth =
455
+ ctx.measureText(text).width;
456
+
457
+
458
+ ctx.fillStyle =
459
+ "#ff3366";
460
+
461
+
462
+ ctx.fillRect(
463
+ x,
464
+ Math.max(0, y - 28),
465
+ textWidth + 12,
466
+ 28
467
+ );
468
+
469
+
470
+ ctx.fillStyle =
471
+ "white";
472
+
473
+
474
+ ctx.fillText(
475
+ text,
476
+ x + 6,
477
+ Math.max(20, y - 7)
478
+ );
479
+
480
+ }
481
+
482
+
483
+ // ------------------------------------------------
484
+ // 결과 문장 만들기
485
+ // ------------------------------------------------
486
+
487
+ if (output.length === 0) {
488
+
489
+ resultBox.textContent =
490
+ "기준 점수 이상인 물체를 찾지 못했습니다.";
491
+
492
+ } else {
493
+
494
+ let resultText =
495
+ "📊 물체 분석 결과\n\n";
496
+
497
+
498
+ for (
499
+ const [label, count]
500
+ of Object.entries(counts)
501
+ ) {
502
+
503
+ resultText +=
504
+ `${label} : ${count}개\n`;
505
+
506
+ }
507
+
508
+
509
+ resultText +=
510
+ `\n총 ${output.length}개의 물체를 찾았습니다.`;
511
+
512
+
513
+ resultBox.textContent =
514
+ resultText;
515
+
516
+ }
517
+
518
+
519
+ statusBox.textContent =
520
+ `완료 · 분석 ${seconds}초 · 기준 점수 ${score.toFixed(2)} · (모델 불러오기 ${loadSeconds}초)`;
521
+
522
+
523
+ } catch (error) {
524
+
525
+ statusBox.textContent =
526
+ `❌ 분석 실패 : ${error.message}`;
527
+
528
+ console.error(error);
529
+
530
+ } finally {
531
+
532
+ URL.revokeObjectURL(
533
+ imageURL
534
+ );
535
+
536
+ }
537
+
538
+ };
539
+
540
+
541
+ image.src =
542
+ imageURL;
543
+
544
+ }
545
+
546
+ </script>
547
+
548
+ </body>
549
+ </html>