YoungjaeDev Claude commited on
Commit
cacaeb4
·
1 Parent(s): cf149a2

feat: HF Space Gradio 앱 구현 (Zero GPU)

Browse files

- PRITHIVSAKTHIUR 스타일 커스텀 테마 적용
- @spaces.GPU(duration=120) 데코레이터로 Zero GPU 통합
- YOLOv11m-pose + ST-GCN 2-stage 파이프라인 통합
- 비디오 업로드 -> 결과 비디오 + Plotly 확률 그래프 출력
- 결과 비디오 H.264 인코딩 (브라우저 호환)
- requirements.txt 및 README.md (HF Space 메타데이터) 작성

Closes #71

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +68 -0
  2. app.py +497 -0
  3. requirements.txt +23 -0
README.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Fall Detection Demo
3
+ emoji: "!"
4
+ colorFrom: blue
5
+ colorTo: red
6
+ sdk: gradio
7
+ sdk_version: "4.44.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ hardware: zero-a100
12
+ ---
13
+
14
+ # Fall Detection Demo
15
+
16
+ YOLOv11-Pose + ST-GCN 2-stage 파이프라인을 사용한 실시간 낙상 감지 데모입니다.
17
+
18
+ ## 파이프라인 구성
19
+
20
+ - **Stage 1: Pose Estimation** - YOLOv11m-pose
21
+ - 입력: 비디오 프레임
22
+ - 출력: 17개 COCO keypoints (x, y, confidence)
23
+
24
+ - **Stage 2: Temporal Classification** - ST-GCN
25
+ - 입력: 60 프레임 슬라이딩 윈도우 (2초 @ 30fps)
26
+ - 출력: Fall/Non-Fall 확률
27
+
28
+ ## 사용법
29
+
30
+ 1. 비디오 파일을 업로드합니다 (MP4, AVI, MOV 지원)
31
+ 2. 필요시 고급 설정에서 파라미터를 조정합니다
32
+ 3. "분석 시작" 버튼을 클릭합니다
33
+ 4. 결과 비디오와 확률 그래프를 확인합니다
34
+
35
+ ## 파라미터 설명
36
+
37
+ - **낙상 판정 임계값 (Fall Threshold)**: 이 값 이상의 확률이면 낙상으로 판정 (기본값: 0.7)
38
+ - **키포인트 표시 (Keypoint Mode)**:
39
+ - `all`: 전체 17개 키포인트 표시
40
+ - `major`: 주요 9개 키포인트만 표시 (코, 어깨, 엉덩이, 무릎, 발목)
41
+
42
+ ## 모델 정보
43
+
44
+ | 모델 | 용도 | 파라미터 |
45
+ |------|------|----------|
46
+ | YOLOv11m-pose | Pose Estimation | ~25M |
47
+ | ST-GCN | Temporal Classification | ~3M |
48
+
49
+ ## 성능
50
+
51
+ - **Latency**: ~2초 (60 프레임 윈도우 기준)
52
+ - **Accuracy**: 99.6% (AI Hub 검증 데이터셋)
53
+
54
+ ## 로컬 실행
55
+
56
+ ```bash
57
+ # 의존성 설치
58
+ pip install -r requirements.txt
59
+
60
+ # 앱 실행
61
+ python app.py
62
+ ```
63
+
64
+ ## 참고자료
65
+
66
+ - [YOLOv11 (Ultralytics)](https://github.com/ultralytics/ultralytics)
67
+ - [ST-GCN Paper](https://arxiv.org/abs/1801.07455)
68
+ - AI Hub 낙상사고 위험동작 영상-센서 쌍 데이터
app.py ADDED
@@ -0,0 +1,497 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fall Detection Gradio App
4
+
5
+ YOLOv11-Pose + ST-GCN 2-stage 파이프라인을 사용한 낙상 감지 데모입니다.
6
+ HF Spaces Zero GPU 환경에서 실행됩니다.
7
+
8
+ 사용법 (로컬):
9
+ python demo_gradio/app.py
10
+
11
+ 사용법 (HF Spaces):
12
+ 자동으로 app.py가 실행됩니다.
13
+
14
+ 작성자: Fall Detection Pipeline Team
15
+ 작성일: 2025-11-26
16
+ """
17
+
18
+ import os
19
+ import sys
20
+ import tempfile
21
+ import time
22
+ from pathlib import Path
23
+ from typing import Iterable, Optional, Tuple
24
+
25
+ import cv2
26
+ import gradio as gr
27
+ import numpy as np
28
+ import plotly.graph_objects as go
29
+ import torch
30
+ from gradio.themes import Soft
31
+ from gradio.themes.utils import colors, fonts, sizes
32
+
33
+ # 프로젝트 루트를 Python path에 추가
34
+ # pipeline/demo_gradio/app.py -> pipeline -> project_root
35
+ PROJECT_ROOT = Path(__file__).parent.parent.parent
36
+ sys.path.insert(0, str(PROJECT_ROOT))
37
+
38
+ # Zero GPU 호환 설정
39
+ try:
40
+ import spaces
41
+ SPACES_AVAILABLE = True
42
+ except ImportError:
43
+ SPACES_AVAILABLE = False
44
+
45
+
46
+ # -----------------------------------------------------------------------------
47
+ # 커스텀 테마 (PRITHIVSAKTHIUR 스타일)
48
+ # -----------------------------------------------------------------------------
49
+ colors.custom_color = colors.Color(
50
+ name="custom_color",
51
+ c50="#EBF3F8", c100="#D3E5F0", c200="#A8CCE1",
52
+ c300="#7DB3D2", c400="#529AC3", c500="#4682B4",
53
+ c600="#3E72A0", c700="#36638C", c800="#2E5378",
54
+ c900="#264364", c950="#1E3450",
55
+ )
56
+
57
+
58
+ class CustomTheme(Soft):
59
+ def __init__(
60
+ self,
61
+ *,
62
+ primary_hue: colors.Color | str = colors.gray,
63
+ secondary_hue: colors.Color | str = colors.custom_color,
64
+ neutral_hue: colors.Color | str = colors.slate,
65
+ text_size: sizes.Size | str = sizes.text_lg,
66
+ font: fonts.Font | str | Iterable[fonts.Font | str] = (
67
+ fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
68
+ ),
69
+ font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
70
+ fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
71
+ ),
72
+ ):
73
+ super().__init__(
74
+ primary_hue=primary_hue,
75
+ secondary_hue=secondary_hue,
76
+ neutral_hue=neutral_hue,
77
+ text_size=text_size,
78
+ font=font,
79
+ font_mono=font_mono,
80
+ )
81
+ super().set(
82
+ background_fill_primary="*primary_50",
83
+ body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
84
+ button_primary_text_color="white",
85
+ button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
86
+ button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
87
+ slider_color="*secondary_500",
88
+ block_title_text_weight="600",
89
+ block_border_width="3px",
90
+ block_shadow="*shadow_drop_lg",
91
+ button_primary_shadow="*shadow_drop_lg",
92
+ )
93
+
94
+
95
+ custom_theme = CustomTheme()
96
+
97
+ # -----------------------------------------------------------------------------
98
+ # CSS 스타일
99
+ # -----------------------------------------------------------------------------
100
+ css = """
101
+ #col-container { margin: 0 auto; max-width: 1200px; }
102
+ #main-title h1 { font-size: 2.3em !important; }
103
+ .submit-btn {
104
+ background-color: #4682B4 !important;
105
+ color: white !important;
106
+ }
107
+ .submit-btn:hover {
108
+ background-color: #5A9BD4 !important;
109
+ }
110
+ .result-label {
111
+ font-size: 1.5em !important;
112
+ font-weight: bold !important;
113
+ padding: 10px !important;
114
+ border-radius: 8px !important;
115
+ }
116
+ .fall-detected {
117
+ background-color: #FF4444 !important;
118
+ color: white !important;
119
+ }
120
+ .non-fall {
121
+ background-color: #44BB44 !important;
122
+ color: white !important;
123
+ }
124
+ """
125
+
126
+ # -----------------------------------------------------------------------------
127
+ # 디바이스 설정
128
+ # -----------------------------------------------------------------------------
129
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
130
+
131
+
132
+ # -----------------------------------------------------------------------------
133
+ # GPU 데코레이터 (로컬/HF Spaces 호환)
134
+ # -----------------------------------------------------------------------------
135
+ def gpu_decorator(duration: int = 120):
136
+ """로컬에서는 그냥 실행, Spaces에서는 GPU 할당"""
137
+ def decorator(func):
138
+ if SPACES_AVAILABLE:
139
+ return spaces.GPU(duration=duration)(func)
140
+ return func
141
+ return decorator
142
+
143
+
144
+ # -----------------------------------------------------------------------------
145
+ # 파이프라인 초기화 (지연 로딩)
146
+ # -----------------------------------------------------------------------------
147
+ _pipeline = None
148
+
149
+
150
+ def get_pipeline():
151
+ """파이프라인 싱글톤 반환 (지연 로딩)"""
152
+ global _pipeline
153
+ if _pipeline is None:
154
+ from pipeline.core.pipeline import FallDetectionPipeline
155
+
156
+ # HF Spaces에서는 models 폴더에서 로드
157
+ pose_model_path = "pipeline/demo_gradio/models/yolo11m-pose.pt"
158
+ stgcn_checkpoint = "pipeline/demo_gradio/models/best_acc.pth"
159
+
160
+ # 로컬 경로 폴백
161
+ if not Path(pose_model_path).exists():
162
+ pose_model_path = "yolo11m-pose.pt"
163
+ if not Path(stgcn_checkpoint).exists():
164
+ stgcn_checkpoint = "runs/stgcn_binary_exp2_fixed_graph/best_acc.pth"
165
+
166
+ _pipeline = FallDetectionPipeline(
167
+ pose_model_path=pose_model_path,
168
+ stgcn_checkpoint=stgcn_checkpoint,
169
+ window_size=60,
170
+ conf_threshold=0.5,
171
+ fall_threshold=0.7,
172
+ temporal_window=5,
173
+ stgcn_stride=5,
174
+ alert_duration=150,
175
+ post_fall_frames=3,
176
+ device=str(device),
177
+ debug=False,
178
+ headless=False,
179
+ viz_keypoints="all",
180
+ viz_scale=1.0,
181
+ viz_optimized=True
182
+ )
183
+ return _pipeline
184
+
185
+
186
+ # -----------------------------------------------------------------------------
187
+ # 확률 그래프 생성
188
+ # -----------------------------------------------------------------------------
189
+ def create_probability_graph(
190
+ frame_indices: list,
191
+ probabilities: list,
192
+ fall_threshold: float = 0.7
193
+ ) -> go.Figure:
194
+ """
195
+ 낙상 확률 그래프 생성
196
+
197
+ Args:
198
+ frame_indices: 프레임 인덱스 리스트
199
+ probabilities: 낙상 확률 리스트 (0.0-1.0)
200
+ fall_threshold: 낙상 판정 임계값
201
+
202
+ Returns:
203
+ Plotly Figure 객체
204
+ """
205
+ fig = go.Figure()
206
+
207
+ # 확률 라인
208
+ fig.add_trace(go.Scatter(
209
+ x=frame_indices,
210
+ y=probabilities,
211
+ mode='lines',
212
+ name='Fall Probability',
213
+ line=dict(color='#4682B4', width=2),
214
+ fill='tozeroy',
215
+ fillcolor='rgba(70, 130, 180, 0.3)'
216
+ ))
217
+
218
+ # 임계값 라인
219
+ fig.add_hline(
220
+ y=fall_threshold,
221
+ line_dash="dash",
222
+ line_color="red",
223
+ annotation_text=f"Threshold ({fall_threshold})",
224
+ annotation_position="right"
225
+ )
226
+
227
+ # 레이아웃
228
+ fig.update_layout(
229
+ title="Fall Detection Probability Over Time",
230
+ xaxis_title="Frame",
231
+ yaxis_title="Probability",
232
+ yaxis=dict(range=[0, 1]),
233
+ template="plotly_white",
234
+ height=300,
235
+ margin=dict(l=50, r=50, t=50, b=50),
236
+ showlegend=True,
237
+ legend=dict(
238
+ orientation="h",
239
+ yanchor="bottom",
240
+ y=1.02,
241
+ xanchor="right",
242
+ x=1
243
+ )
244
+ )
245
+
246
+ return fig
247
+
248
+
249
+ # -----------------------------------------------------------------------------
250
+ # 메인 추론 함수
251
+ # -----------------------------------------------------------------------------
252
+ @gpu_decorator(duration=120)
253
+ def process_video(
254
+ video_path: str,
255
+ fall_threshold: float,
256
+ viz_keypoints: str,
257
+ progress: gr.Progress = gr.Progress()
258
+ ) -> Tuple[Optional[str], Optional[go.Figure], str]:
259
+ """
260
+ 비디오 처리 및 낙상 감지
261
+
262
+ Args:
263
+ video_path: 입력 비디오 경로
264
+ fall_threshold: 낙상 판정 임계값 (0.0-1.0)
265
+ viz_keypoints: 키포인트 표시 모드 ('all' 또는 'major')
266
+ progress: Gradio 진행률 표시
267
+
268
+ Returns:
269
+ output_video_path: 결과 비디오 경로
270
+ probability_graph: 확률 그래프
271
+ result_text: 최종 판정 텍스트
272
+ """
273
+ if video_path is None:
274
+ return None, None, "비디오를 업로드해주세요."
275
+
276
+ try:
277
+ # 파이프라인 로드
278
+ progress(0.1, desc="모델 로딩 중...")
279
+ pipeline = get_pipeline()
280
+ pipeline.fall_threshold = fall_threshold
281
+ pipeline.stgcn_classifier.fall_threshold = fall_threshold
282
+ pipeline.viz_keypoints = viz_keypoints
283
+ pipeline.reset()
284
+
285
+ # 비디오 열기
286
+ progress(0.2, desc="비디오 열기...")
287
+ cap = cv2.VideoCapture(video_path)
288
+ if not cap.isOpened():
289
+ return None, None, "비디오를 열 수 없습니다."
290
+
291
+ # 비디오 정보
292
+ fps = cap.get(cv2.CAP_PROP_FPS)
293
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
294
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
295
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
296
+
297
+ # 출력 비디오 설정
298
+ output_path = tempfile.mktemp(suffix=".mp4")
299
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
300
+ # Info panel 추가로 높이 80px 증가
301
+ out = cv2.VideoWriter(output_path, fourcc, fps, (width, height + 80))
302
+
303
+ # 처리 루프
304
+ frame_idx = 0
305
+ frame_indices = []
306
+ probabilities = []
307
+ fall_detected = False
308
+ max_confidence = 0.0
309
+
310
+ while True:
311
+ ret, frame = cap.read()
312
+ if not ret:
313
+ break
314
+
315
+ # 프레임 처리
316
+ vis_frame, info = pipeline.process_frame(frame, frame_idx)
317
+
318
+ # 확률 기록
319
+ if info['confidence'] is not None:
320
+ frame_indices.append(frame_idx)
321
+ probabilities.append(info['confidence'])
322
+ max_confidence = max(max_confidence, info['confidence'])
323
+
324
+ # 낙상 감지 확인
325
+ if info['alert']:
326
+ fall_detected = True
327
+
328
+ # 출력 저장
329
+ out.write(vis_frame)
330
+
331
+ frame_idx += 1
332
+
333
+ # 진행률 업데이트
334
+ if frame_idx % 10 == 0:
335
+ progress_val = 0.2 + 0.7 * (frame_idx / total_frames)
336
+ progress(progress_val, desc=f"처리 중... ({frame_idx}/{total_frames})")
337
+
338
+ # 리소스 해제
339
+ cap.release()
340
+ out.release()
341
+
342
+ # H.264 코덱으로 재인코딩 (브라우저 호환)
343
+ progress(0.9, desc="비디오 인코딩 중...")
344
+ output_h264 = tempfile.mktemp(suffix=".mp4")
345
+ os.system(f'ffmpeg -y -i "{output_path}" -c:v libx264 -preset fast -crf 23 "{output_h264}" -loglevel quiet')
346
+
347
+ # mp4v 임시 파일 삭제
348
+ if os.path.exists(output_path):
349
+ os.remove(output_path)
350
+
351
+ # H.264 변환 성공 여부 확인
352
+ if os.path.exists(output_h264):
353
+ final_output = output_h264
354
+ else:
355
+ final_output = output_path # 폴백
356
+
357
+ # 확률 그래프 생성
358
+ progress(0.95, desc="그래프 생성 중...")
359
+ if frame_indices and probabilities:
360
+ fig = create_probability_graph(frame_indices, probabilities, fall_threshold)
361
+ else:
362
+ fig = None
363
+
364
+ # 최종 판정
365
+ progress(1.0, desc="완료!")
366
+ if fall_detected:
367
+ result_text = f"[FALL DETECTED] 낙상이 감지되었습니다! (최대 확률: {max_confidence:.1%})"
368
+ else:
369
+ result_text = f"[Non-Fall] 낙상이 감지되지 않았습니다. (최대 확률: {max_confidence:.1%})"
370
+
371
+ return final_output, fig, result_text
372
+
373
+ except Exception as e:
374
+ import traceback
375
+ error_msg = f"처리 중 오류 발생: {str(e)}\n{traceback.format_exc()}"
376
+ return None, None, error_msg
377
+
378
+
379
+ # -----------------------------------------------------------------------------
380
+ # Gradio UI
381
+ # -----------------------------------------------------------------------------
382
+ def create_demo() -> gr.Blocks:
383
+ """Gradio 데모 생성"""
384
+
385
+ with gr.Blocks() as demo:
386
+ gr.Markdown(
387
+ """
388
+ # Fall Detection Demo
389
+
390
+ YOLOv11-Pose + ST-GCN 2-stage 파이프라인을 사용한 실시간 낙상 감지 데모입니다.
391
+ 비디오를 업로드하면 낙상 여부를 분석하고, 결과 비디오와 확률 그래프를 제공합니다.
392
+
393
+ **파이프라인 구성:**
394
+ - Stage 1: YOLOv11m-pose (Pose Estimation)
395
+ - Stage 2: ST-GCN (Temporal Classification)
396
+ - Window Size: 60 frames (2초 @ 30fps)
397
+ """,
398
+ elem_id="main-title"
399
+ )
400
+
401
+ with gr.Row():
402
+ with gr.Column(scale=1):
403
+ # 입력 섹션
404
+ gr.Markdown("### 입력")
405
+ video_input = gr.Video(
406
+ label="비디오 업로드",
407
+ sources=["upload"],
408
+ )
409
+
410
+ with gr.Accordion("고급 설정", open=False):
411
+ fall_threshold = gr.Slider(
412
+ minimum=0.5,
413
+ maximum=0.95,
414
+ value=0.7,
415
+ step=0.05,
416
+ label="낙상 판정 임계값",
417
+ info="이 값 이상의 확률이면 낙상으로 판정합니다."
418
+ )
419
+ viz_keypoints = gr.Radio(
420
+ choices=["all", "major"],
421
+ value="all",
422
+ label="키포인트 표시",
423
+ info="all: 전체 17개, major: 주요 9개"
424
+ )
425
+
426
+ submit_btn = gr.Button(
427
+ "분석 시작",
428
+ variant="primary",
429
+ elem_classes="submit-btn"
430
+ )
431
+
432
+ with gr.Column(scale=1):
433
+ # 출력 섹션
434
+ gr.Markdown("### 결과")
435
+ result_text = gr.Textbox(
436
+ label="판정 결과",
437
+ lines=2,
438
+ interactive=False
439
+ )
440
+ video_output = gr.Video(
441
+ label="결과 비디오",
442
+ )
443
+ prob_graph = gr.Plot(
444
+ label="낙상 확률 그래프",
445
+ )
446
+
447
+ # 예제 비디오
448
+ gr.Markdown("### 예제 비디오")
449
+ example_dir = Path(__file__).parent / "examples"
450
+ examples = []
451
+ if example_dir.exists():
452
+ for ext in ["*.mp4", "*.avi", "*.mov"]:
453
+ examples.extend([str(p) for p in example_dir.glob(ext)])
454
+
455
+ if examples:
456
+ gr.Examples(
457
+ examples=[[ex, 0.7, "all"] for ex in examples[:3]],
458
+ inputs=[video_input, fall_threshold, viz_keypoints],
459
+ outputs=[video_output, prob_graph, result_text],
460
+ fn=process_video,
461
+ cache_examples=False,
462
+ )
463
+
464
+ # 이벤트 연결
465
+ submit_btn.click(
466
+ fn=process_video,
467
+ inputs=[video_input, fall_threshold, viz_keypoints],
468
+ outputs=[video_output, prob_graph, result_text],
469
+ )
470
+
471
+ # 푸터
472
+ gr.Markdown(
473
+ """
474
+ ---
475
+ **References:**
476
+ - [YOLOv11](https://github.com/ultralytics/ultralytics) - Pose Estimation
477
+ - [ST-GCN](https://arxiv.org/abs/1801.07455) - Spatial Temporal Graph Convolutional Networks
478
+ - AI Hub Fall Detection Dataset
479
+ """
480
+ )
481
+
482
+ return demo
483
+
484
+
485
+ # -----------------------------------------------------------------------------
486
+ # 메인 실행
487
+ # -----------------------------------------------------------------------------
488
+ if __name__ == "__main__":
489
+ demo = create_demo()
490
+ demo.queue(max_size=10).launch(
491
+ server_name="0.0.0.0",
492
+ server_port=7860,
493
+ share=False,
494
+ show_error=True,
495
+ theme=custom_theme,
496
+ css=css,
497
+ )
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fall Detection Gradio App Dependencies
2
+ # HF Spaces Zero GPU 환경용
3
+
4
+ # Core ML
5
+ torch>=2.0.0
6
+ torchvision>=0.15.0
7
+
8
+ # Pose Estimation
9
+ ultralytics>=8.0.0
10
+
11
+ # Web UI
12
+ gradio>=4.0.0
13
+ plotly>=5.0.0
14
+
15
+ # Computer Vision
16
+ opencv-python>=4.8.0
17
+ numpy>=1.24.0
18
+
19
+ # HF Spaces (Zero GPU)
20
+ # spaces 패키지는 HF Spaces 환경에서 자동 설치됨
21
+
22
+ # Video processing
23
+ ffmpeg-python>=0.2.0