fall-detection-demo / visualization.py
YoungjaeDev
fix: HF Spaces import ์—๋Ÿฌ ํ•ด๊ฒฐ - self-contained ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝ
8133f1d
"""
Real-time Fall Detection Visualization Module
์ด ๋ชจ๋“ˆ์€ ์‹ค์‹œ๊ฐ„ ๋‚™์ƒ ๊ฐ์ง€ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์‹œ๊ฐํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
COCO 17 keypoints ์Šค์ผˆ๋ ˆํ†ค ๋ Œ๋”๋ง, ์˜ˆ์ธก ๊ฒฐ๊ณผ ์˜ค๋ฒ„๋ ˆ์ด, ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ํ‘œ์‹œ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
์ฃผ์š” ๊ธฐ๋Šฅ:
- COCO 17 keypoints ์Šค์ผˆ๋ ˆํ†ค ๋ Œ๋”๋ง
- Bounding box ๋ Œ๋”๋ง
- Fall/Non-Fall ๋ผ๋ฒจ + ์‹ ๋ขฐ๋„ ํ‘œ์‹œ
- FPS/Latency ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ
- ์ƒ‰์ƒ ์ฝ”๋”ฉ (Fall: ๋นจ๊ฐ•, Non-Fall: ์ดˆ๋ก)
์ตœ์ ํ™” (Issue #56):
- NumPy ๋ฒกํ„ฐํ™”๋กœ cv2.circle()/cv2.line() ๋ฃจํ”„ ๋Œ€์ฒด
- morphological dilation์œผ๋กœ keypoint ์› ๊ทธ๋ฆฌ๊ธฐ (30๋ฐฐ ์†๋„ ํ–ฅ์ƒ)
- cv2.polylines()๋กœ skeleton ์„  ์ผ๊ด„ ๊ทธ๋ฆฌ๊ธฐ
- ์ฃผ์š” keypoint๋งŒ ํ‘œ์‹œ ์˜ต์…˜ (--viz-keypoints major)
- ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์กฐ์ ˆ ์˜ต์…˜ (--viz-scale 0.5)
Reference:
- COCO Keypoints: https://cocodataset.org/#keypoints-2017
"""
import cv2
import numpy as np
from typing import Tuple, Optional, List, Literal
# COCO 17 keypoints ์ธ๋ฑ์Šค
COCO_KEYPOINT_NAMES = [
'nose', # 0
'left_eye', # 1
'right_eye', # 2
'left_ear', # 3
'right_ear', # 4
'left_shoulder', # 5
'right_shoulder', # 6
'left_elbow', # 7
'right_elbow', # 8
'left_wrist', # 9
'right_wrist', # 10
'left_hip', # 11
'right_hip', # 12
'left_knee', # 13
'right_knee', # 14
'left_ankle', # 15
'right_ankle', # 16
]
# COCO ์Šค์ผˆ๋ ˆํ†ค ์—ฐ๊ฒฐ ์ •์˜ (๋ผˆ๋Œ€ ๊ตฌ์กฐ)
COCO_SKELETON = [
# ์–ผ๊ตด
(0, 1), # nose -> left_eye
(0, 2), # nose -> right_eye
(1, 3), # left_eye -> left_ear
(2, 4), # right_eye -> right_ear
# ์ƒ์ฒด
(0, 5), # nose -> left_shoulder
(0, 6), # nose -> right_shoulder
(5, 6), # left_shoulder <-> right_shoulder
# ์™ผํŒ”
(5, 7), # left_shoulder -> left_elbow
(7, 9), # left_elbow -> left_wrist
# ์˜ค๋ฅธํŒ”
(6, 8), # right_shoulder -> right_elbow
(8, 10), # right_elbow -> right_wrist
# ๋ชธํ†ต
(5, 11), # left_shoulder -> left_hip
(6, 12), # right_shoulder -> right_hip
(11, 12), # left_hip <-> right_hip
# ์™ผ๋‹ค๋ฆฌ
(11, 13), # left_hip -> left_knee
(13, 15), # left_knee -> left_ankle
# ์˜ค๋ฅธ๋‹ค๋ฆฌ
(12, 14), # right_hip -> right_knee
(14, 16), # right_knee -> right_ankle
]
# ์‹ ์ฒด ๋ถ€์œ„๋ณ„ ์ƒ‰์ƒ ์ •์˜ (BGR ํฌ๋งท)
BODY_PART_COLORS = {
'face': (0, 255, 255), # ๋…ธ๋ž€์ƒ‰
'left_arm': (255, 0, 180), # ๋ถ„ํ™์ƒ‰
'right_arm': (0, 165, 255), # ์˜ค๋ Œ์ง€์ƒ‰
'torso': (255, 150, 0), # ํŒŒ๋ž€์ƒ‰
'left_leg': (0, 0, 255), # ๋นจ๊ฐ„์ƒ‰
'right_leg': (180, 0, 255), # ๋ณด๋ผ์ƒ‰
}
# ๊ฐ ์Šค์ผˆ๋ ˆํ†ค ์—ฐ๊ฒฐ์— ๋Œ€ํ•œ ์‹ ์ฒด ๋ถ€์œ„ ๋งคํ•‘
SKELETON_PART_MAPPING = [
'face', # (0, 1) nose -> left_eye
'face', # (0, 2) nose -> right_eye
'face', # (1, 3) left_eye -> left_ear
'face', # (2, 4) right_eye -> right_ear
'face', # (0, 5) nose -> left_shoulder
'face', # (0, 6) nose -> right_shoulder
'torso', # (5, 6) left_shoulder <-> right_shoulder
'left_arm', # (5, 7) left_shoulder -> left_elbow
'left_arm', # (7, 9) left_elbow -> left_wrist
'right_arm', # (6, 8) right_shoulder -> right_elbow
'right_arm', # (8, 10) right_elbow -> right_wrist
'torso', # (5, 11) left_shoulder -> left_hip
'torso', # (6, 12) right_shoulder -> right_hip
'torso', # (11, 12) left_hip <-> right_hip
'left_leg', # (11, 13) left_hip -> left_knee
'left_leg', # (13, 15) left_knee -> left_ankle
'right_leg', # (12, 14) right_hip -> right_knee
'right_leg', # (14, 16) right_knee -> right_ankle
]
# ์˜ˆ์ธก ๊ฒฐ๊ณผ ์ƒ‰์ƒ ์ •์˜
PREDICTION_COLORS = {
'Fall': (0, 0, 255), # ๋นจ๊ฐ•
'Non-Fall': (0, 255, 0), # ์ดˆ๋ก
}
# ์ฃผ์š” keypoint ์ธ๋ฑ์Šค (9๊ฐœ: ์ฝ”, ์–ด๊นจ, ์—‰๋ฉ์ด, ๋ฌด๋ฆŽ, ๋ฐœ๋ชฉ)
# ๋‚™์ƒ ๊ฐ์ง€์— ์ค‘์š”ํ•œ ์‹ ์ฒด ๋ถ€์œ„๋งŒ ์„ ํƒ
MAJOR_KEYPOINT_INDICES = [
0, # nose - ๋จธ๋ฆฌ ์œ„์น˜
5, # left_shoulder
6, # right_shoulder
11, # left_hip
12, # right_hip
13, # left_knee
14, # right_knee
15, # left_ankle
16, # right_ankle
]
# ์ฃผ์š” keypoint์šฉ skeleton ์—ฐ๊ฒฐ (8๊ฐœ ์—ฐ๊ฒฐ)
MAJOR_SKELETON = [
(5, 6), # left_shoulder <-> right_shoulder
(5, 11), # left_shoulder -> left_hip
(6, 12), # right_shoulder -> right_hip
(11, 12), # left_hip <-> right_hip
(11, 13), # left_hip -> left_knee
(12, 14), # right_hip -> right_knee
(13, 15), # left_knee -> left_ankle
(14, 16), # right_knee -> right_ankle
]
# ์ฃผ์š” skeleton ์‹ ์ฒด ๋ถ€์œ„ ๋งคํ•‘
MAJOR_SKELETON_PART_MAPPING = [
'torso', # (5, 6)
'torso', # (5, 11)
'torso', # (6, 12)
'torso', # (11, 12)
'left_leg', # (11, 13)
'right_leg', # (12, 14)
'left_leg', # (13, 15)
'right_leg', # (14, 16)
]
# Morphological dilation์šฉ ์ปค๋„ ์บ์‹œ (๋™์ผ ํฌ๊ธฐ ์žฌ์‚ฌ์šฉ)
_KERNEL_CACHE = {}
def draw_skeleton(
frame: np.ndarray,
keypoints: np.ndarray,
color: Tuple[int, int, int] = (0, 255, 0),
thickness: int = 2,
conf_threshold: float = 0.5,
keypoint_radius: int = 4,
use_body_part_colors: bool = True
) -> np.ndarray:
"""
COCO 17 keypoints ์Šค์ผˆ๋ ˆํ†ค ๋ Œ๋”๋ง
Args:
frame: OpenCV ์ด๋ฏธ์ง€ (H, W, 3) BGR ํฌ๋งท
keypoints: (17, 3) numpy array - (x, y, conf)
color: BGR ์ƒ‰์ƒ (use_body_part_colors=False์ผ ๋•Œ ์‚ฌ์šฉ)
thickness: ์„  ๋‘๊ป˜
conf_threshold: ์ตœ์†Œ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (์ด ๊ฐ’ ์ดํ•˜๋Š” ๊ทธ๋ฆฌ์ง€ ์•Š์Œ)
keypoint_radius: ํ‚คํฌ์ธํŠธ ์›์˜ ๋ฐ˜์ง€๋ฆ„
use_body_part_colors: True๋ฉด ์‹ ์ฒด ๋ถ€์œ„๋ณ„ ์ƒ‰์ƒ ์‚ฌ์šฉ, False๋ฉด ๋‹จ์ผ ์ƒ‰์ƒ ์‚ฌ์šฉ
Returns:
frame: ์Šค์ผˆ๋ ˆํ†ค์ด ๋ Œ๋”๋ง๋œ ์ด๋ฏธ์ง€
"""
if keypoints.shape != (17, 3):
raise ValueError(f"Expected keypoints shape (17, 3), got {keypoints.shape}")
frame = frame.copy()
# 1. ์Šค์ผˆ๋ ˆํ†ค ์—ฐ๊ฒฐ์„  ๊ทธ๋ฆฌ๊ธฐ
for i, (start_idx, end_idx) in enumerate(COCO_SKELETON):
x1, y1, conf1 = keypoints[start_idx]
x2, y2, conf2 = keypoints[end_idx]
# ๋‘ ํ‚คํฌ์ธํŠธ ๋ชจ๋‘ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’์„ ๋„˜์–ด์•ผ ์„ ์„ ๊ทธ๋ฆผ
if conf1 > conf_threshold and conf2 > conf_threshold:
# ์‹ ์ฒด ๋ถ€์œ„๋ณ„ ์ƒ‰์ƒ ๋˜๋Š” ๋‹จ์ผ ์ƒ‰์ƒ ์„ ํƒ
if use_body_part_colors:
part_name = SKELETON_PART_MAPPING[i]
line_color = BODY_PART_COLORS[part_name]
else:
line_color = color
# ์„  ๊ทธ๋ฆฌ๊ธฐ
pt1 = (int(x1), int(y1))
pt2 = (int(x2), int(y2))
cv2.line(frame, pt1, pt2, line_color, thickness, cv2.LINE_AA)
# 2. ํ‚คํฌ์ธํŠธ ์› ๊ทธ๋ฆฌ๊ธฐ (์„  ์œ„์— ๊ทธ๋ ค์„œ ๋” ๋ˆˆ์— ๋„๊ฒŒ)
for i, (x, y, conf) in enumerate(keypoints):
if conf > conf_threshold:
center = (int(x), int(y))
# ์™ธ๊ณฝ ํฐ์ƒ‰ ํ…Œ๋‘๋ฆฌ
cv2.circle(frame, center, keypoint_radius + 2, (255, 255, 255), -1, cv2.LINE_AA)
# ๋‚ด๋ถ€ ์ƒ‰์ƒ ์› (๋ฐ์€ ํ•˜๋Š˜์ƒ‰)
cv2.circle(frame, center, keypoint_radius, (255, 200, 0), -1, cv2.LINE_AA)
return frame
def _get_ellipse_kernel(radius: int) -> np.ndarray:
"""
์บ์‹œ๋œ ellipse ์ปค๋„ ๋ฐ˜ํ™˜ (morphological dilation์šฉ)
Args:
radius: ์ปค๋„ ๋ฐ˜์ง€๋ฆ„
Returns:
ellipse ์ปค๋„
"""
if radius not in _KERNEL_CACHE:
kernel_size = radius * 2 + 1
_KERNEL_CACHE[radius] = cv2.getStructuringElement(
cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)
)
return _KERNEL_CACHE[radius]
def draw_skeleton_vectorized(
frame: np.ndarray,
keypoints: np.ndarray,
conf_threshold: float = 0.5,
keypoint_radius: int = 4,
thickness: int = 2,
keypoint_mode: Literal['all', 'major'] = 'all',
use_body_part_colors: bool = True,
keypoint_color: Tuple[int, int, int] = (255, 200, 0),
border_color: Tuple[int, int, int] = (255, 255, 255)
) -> np.ndarray:
"""
์ตœ์ ํ™”๋œ skeleton ๋ Œ๋”๋ง
์ตœ์ ํ™” ์ „๋žต:
- cv2.polylines()๋กœ skeleton ์„  ์ผ๊ด„ ์ฒ˜๋ฆฌ (์ƒ‰์ƒ๋ณ„ ๊ทธ๋ฃนํ™”)
- ์ฃผ์š” keypoint๋งŒ ํ‘œ์‹œ ์˜ต์…˜์œผ๋กœ ๊ทธ๋ฆฌ๊ธฐ ํšŸ์ˆ˜ ๊ฐ์†Œ (17๊ฐœ -> 9๊ฐœ)
- Anti-aliasing ๋น„ํ™œ์„ฑํ™” ์˜ต์…˜ (cv2.LINE_AA -> cv2.LINE_8)
Note: morphological dilation์€ 4K ํ•ด์ƒ๋„์—์„œ ์ „์ฒด ์ด๋ฏธ์ง€ ๋งˆ์Šคํฌ ์ƒ์„ฑ์œผ๋กœ
์˜คํžˆ๋ ค ๋А๋ ค์ง€๋ฏ€๋กœ, keypoint ์›์€ ๊ธฐ์กด cv2.circle() ์œ ์ง€
Args:
frame: OpenCV ์ด๋ฏธ์ง€ (H, W, 3) BGR ํฌ๋งท
keypoints: (17, 3) numpy array - (x, y, conf)
conf_threshold: ์ตœ์†Œ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ (์ด ๊ฐ’ ์ดํ•˜๋Š” ๊ทธ๋ฆฌ์ง€ ์•Š์Œ)
keypoint_radius: ํ‚คํฌ์ธํŠธ ์›์˜ ๋ฐ˜์ง€๋ฆ„
thickness: skeleton ์„  ๋‘๊ป˜
keypoint_mode: 'all'=์ „์ฒด 17๊ฐœ, 'major'=์ฃผ์š” 9๊ฐœ๋งŒ ํ‘œ์‹œ
use_body_part_colors: True๋ฉด ์‹ ์ฒด ๋ถ€์œ„๋ณ„ ์ƒ‰์ƒ ์‚ฌ์šฉ
keypoint_color: keypoint ์› ์ƒ‰์ƒ (BGR)
border_color: keypoint ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ (BGR)
Returns:
frame: ์Šค์ผˆ๋ ˆํ†ค์ด ๋ Œ๋”๋ง๋œ ์ด๋ฏธ์ง€
"""
if keypoints.shape != (17, 3):
raise ValueError(f"Expected keypoints shape (17, 3), got {keypoints.shape}")
result = frame.copy()
# keypoint ๋ชจ๋“œ์— ๋”ฐ๋ฅธ ์ธ๋ฑ์Šค/skeleton ์„ ํƒ
if keypoint_mode == 'major':
kpt_indices = MAJOR_KEYPOINT_INDICES
skeleton = MAJOR_SKELETON
skeleton_parts = MAJOR_SKELETON_PART_MAPPING
else:
kpt_indices = list(range(17))
skeleton = COCO_SKELETON
skeleton_parts = SKELETON_PART_MAPPING
# ์œ ํšจํ•œ keypoints ํ•„ํ„ฐ๋ง (confidence > threshold)
valid_mask = keypoints[:, 2] > conf_threshold
if keypoint_mode == 'major':
# ์ฃผ์š” keypoint ์ธ๋ฑ์Šค๋งŒ ๊ณ ๋ ค
major_mask = np.zeros(17, dtype=bool)
major_mask[kpt_indices] = True
valid_mask = valid_mask & major_mask
valid_indices = np.where(valid_mask)[0]
if len(valid_indices) == 0:
return result
# 1. Skeleton ์„  ๊ทธ๋ฆฌ๊ธฐ (cv2.polylines ์‚ฌ์šฉ - ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ)
if use_body_part_colors:
# ์ƒ‰์ƒ๋ณ„๋กœ ์„  ๊ทธ๋ฃนํ™”
color_groups = {}
for i, (start_idx, end_idx) in enumerate(skeleton):
if valid_mask[start_idx] and valid_mask[end_idx]:
part_name = skeleton_parts[i]
color = BODY_PART_COLORS[part_name]
if color not in color_groups:
color_groups[color] = []
pt1 = (int(keypoints[start_idx, 0]), int(keypoints[start_idx, 1]))
pt2 = (int(keypoints[end_idx, 0]), int(keypoints[end_idx, 1]))
color_groups[color].append(np.array([pt1, pt2], dtype=np.int32))
# ์ƒ‰์ƒ๋ณ„๋กœ ์ผ๊ด„ ๊ทธ๋ฆฌ๊ธฐ
for color, lines in color_groups.items():
if lines:
cv2.polylines(result, lines, isClosed=False, color=color,
thickness=thickness, lineType=cv2.LINE_AA)
else:
# ๋‹จ์ผ ์ƒ‰์ƒ์œผ๋กœ ๋ชจ๋“  ์„  ๊ทธ๋ฆฌ๊ธฐ
lines = []
for start_idx, end_idx in skeleton:
if valid_mask[start_idx] and valid_mask[end_idx]:
pt1 = (int(keypoints[start_idx, 0]), int(keypoints[start_idx, 1]))
pt2 = (int(keypoints[end_idx, 0]), int(keypoints[end_idx, 1]))
lines.append(np.array([pt1, pt2], dtype=np.int32))
if lines:
cv2.polylines(result, lines, isClosed=False, color=(255, 255, 255),
thickness=thickness, lineType=cv2.LINE_AA)
# 2. Keypoint ์› ๊ทธ๋ฆฌ๊ธฐ (cv2.circle ์‚ฌ์šฉ - ๊ฐœ์ˆ˜๊ฐ€ ์ ์–ด ๋ฃจํ”„๊ฐ€ ํšจ์œจ์ )
for idx in valid_indices:
x, y = int(keypoints[idx, 0]), int(keypoints[idx, 1])
center = (x, y)
# ์™ธ๊ณฝ ํ…Œ๋‘๋ฆฌ
cv2.circle(result, center, keypoint_radius + 2, border_color, -1, cv2.LINE_AA)
# ๋‚ด๋ถ€ ์ƒ‰์ƒ ์›
cv2.circle(result, center, keypoint_radius, keypoint_color, -1, cv2.LINE_AA)
return result
def draw_prediction(
frame: np.ndarray,
prediction: str,
confidence: float,
bbox: Optional[Tuple[int, int, int, int]] = None,
fps: Optional[float] = None,
latency: Optional[float] = None,
position: str = 'top-left'
) -> np.ndarray:
"""
์˜ˆ์ธก ๊ฒฐ๊ณผ ์˜ค๋ฒ„๋ ˆ์ด ๋ Œ๋”๋ง
Args:
frame: OpenCV ์ด๋ฏธ์ง€
prediction: 'Fall' ๋˜๋Š” 'Non-Fall'
confidence: ์‹ ๋ขฐ๋„ (0.0-1.0)
bbox: (x1, y1, x2, y2) ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค (์„ ํƒ)
fps: FPS ๊ฐ’ (์„ ํƒ)
latency: Latency (ms) (์„ ํƒ)
position: ํ…์ŠคํŠธ ์œ„์น˜ ('top-left', 'top-right', 'bottom-left', 'bottom-right')
Returns:
frame: ๋ Œ๋”๋ง๋œ ์ด๋ฏธ์ง€
"""
frame = frame.copy()
h, w = frame.shape[:2]
# 1. ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ (์žˆ์„ ๊ฒฝ์šฐ)
if bbox is not None:
x1, y1, x2, y2 = bbox
pred_color = PREDICTION_COLORS.get(prediction, (255, 255, 255))
# ๋ฐ•์Šค ๋‘๊ป˜๋Š” Fall์ผ ๋•Œ ๋” ๋‘๊ป๊ฒŒ
box_thickness = 4 if prediction == 'Fall' else 2
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), pred_color, box_thickness)
# 2. ์˜ˆ์ธก ๋ผ๋ฒจ + ์‹ ๋ขฐ๋„ ํ…์ŠคํŠธ ์ค€๋น„
if confidence is not None:
pred_text = f"{prediction}: {confidence:.2%}"
else:
pred_text = f"{prediction}"
pred_color = PREDICTION_COLORS.get(prediction, (255, 255, 255))
# 3. FPS/Latency ํ…์ŠคํŠธ ์ค€๋น„ (์žˆ์„ ๊ฒฝ์šฐ)
info_texts = []
if fps is not None:
info_texts.append(f"FPS: {fps:.1f}")
if latency is not None:
info_texts.append(f"Latency: {latency:.1f}ms")
# 4. ํ…์ŠคํŠธ ์œ„์น˜ ๊ณ„์‚ฐ
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.8
font_thickness = 2
padding = 10
line_height = 35
# ์˜ˆ์ธก ํ…์ŠคํŠธ ํฌ๊ธฐ
(pred_w, pred_h), _ = cv2.getTextSize(pred_text, font, font_scale, font_thickness)
# ์œ„์น˜๋ณ„ ์ขŒํ‘œ ๊ณ„์‚ฐ
if position == 'top-left':
pred_x, pred_y = padding, padding + pred_h
elif position == 'top-right':
pred_x, pred_y = w - pred_w - padding, padding + pred_h
elif position == 'bottom-left':
pred_x, pred_y = padding, h - padding - (len(info_texts) * line_height) - 10
elif position == 'bottom-right':
pred_x, pred_y = w - pred_w - padding, h - padding - (len(info_texts) * line_height) - 10
else:
raise ValueError(f"Unknown position: {position}")
# 5. ๋ฐฐ๊ฒฝ ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ (๊ฐ€๋…์„ฑ ํ–ฅ์ƒ)
bg_x1 = pred_x - 5
bg_y1 = pred_y - pred_h - 5
bg_x2 = pred_x + pred_w + 5
bg_y2 = pred_y + 5
# ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์€ ๋ฐฐ๊ฒฝ
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.6, frame, 0.4, 0, frame)
# 6. ์˜ˆ์ธก ํ…์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ
cv2.putText(frame, pred_text, (pred_x, pred_y), font, font_scale, pred_color, font_thickness, cv2.LINE_AA)
# 7. FPS/Latency ์ •๋ณด ๊ทธ๋ฆฌ๊ธฐ (์žˆ์„ ๊ฒฝ์šฐ)
if info_texts:
info_y = pred_y + line_height
for info_text in info_texts:
(info_w, info_h), _ = cv2.getTextSize(info_text, font, font_scale, font_thickness)
# ๋ฐฐ๊ฒฝ ๋ฐ•์Šค
bg_x1 = pred_x - 5
bg_y1 = info_y - info_h - 5
bg_x2 = pred_x + info_w + 5
bg_y2 = info_y + 5
overlay = frame.copy()
cv2.rectangle(overlay, (bg_x1, bg_y1), (bg_x2, bg_y2), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.6, frame, 0.4, 0, frame)
# ํ…์ŠคํŠธ (ํฐ์ƒ‰)
cv2.putText(frame, info_text, (pred_x, info_y), font, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
info_y += line_height
return frame
def create_info_panel(
frame_width: int,
frame_height: int,
fps: float,
latency: float,
prediction: str,
confidence: float,
panel_height: int = 80,
position: str = 'top'
) -> np.ndarray:
"""
์ •๋ณด ํŒจ๋„ ์ƒ์„ฑ (์ƒ๋‹จ ๋˜๋Š” ํ•˜๋‹จ ์˜ค๋ฒ„๋ ˆ์ด)
Args:
frame_width: ํ”„๋ ˆ์ž„ ๋„ˆ๋น„
frame_height: ํ”„๋ ˆ์ž„ ๋†’์ด
fps: FPS ๊ฐ’
latency: Latency (ms)
prediction: 'Fall' ๋˜๋Š” 'Non-Fall'
confidence: ์‹ ๋ขฐ๋„ (0.0-1.0)
panel_height: ํŒจ๋„ ๋†’์ด
position: ํŒจ๋„ ์œ„์น˜ ('top' ๋˜๋Š” 'bottom')
Returns:
panel: ์ •๋ณด ํŒจ๋„ ์ด๋ฏธ์ง€ (panel_height, frame_width, 3)
"""
# ํŒจ๋„ ์ƒ์„ฑ (๊ฒ€์€ ๋ฐฐ๊ฒฝ)
panel = np.zeros((panel_height, frame_width, 3), dtype=np.uint8)
# ์˜ˆ์ธก ๊ฒฐ๊ณผ ์ƒ‰์ƒ
pred_color = PREDICTION_COLORS.get(prediction, (255, 255, 255))
# ํฐํŠธ ์„ค์ •
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.7
font_thickness = 2
# ํ…์ŠคํŠธ ์ค€๋น„
pred_text = f"{prediction}: {confidence:.1%}" if confidence is not None else f"{prediction}"
texts = [
(f"FPS: {fps:.1f}", (255, 255, 255)),
(f"Latency: {latency:.1f}ms", (255, 255, 255)),
(pred_text, pred_color),
]
# ํ…์ŠคํŠธ ๊ท ๋“ฑ ๋ฐฐ์น˜
section_width = frame_width // len(texts)
y_pos = panel_height // 2 + 10
for i, (text, color) in enumerate(texts):
# ํ…์ŠคํŠธ ํฌ๊ธฐ ๊ณ„์‚ฐ
(text_w, text_h), _ = cv2.getTextSize(text, font, font_scale, font_thickness)
# ์ค‘์•™ ์ •๋ ฌ
x_pos = (i * section_width) + (section_width - text_w) // 2
# ํ…์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ
cv2.putText(panel, text, (x_pos, y_pos), font, font_scale, color, font_thickness, cv2.LINE_AA)
# ๊ตฌ๋ถ„์„  ๊ทธ๋ฆฌ๊ธฐ
for i in range(1, len(texts)):
x_pos = i * section_width
cv2.line(panel, (x_pos, 10), (x_pos, panel_height - 10), (80, 80, 80), 1)
return panel
def add_info_panel_to_frame(
frame: np.ndarray,
fps: float,
latency: float,
prediction: str,
confidence: float,
panel_height: int = 80,
position: str = 'top'
) -> np.ndarray:
"""
ํ”„๋ ˆ์ž„์— ์ •๋ณด ํŒจ๋„ ์ถ”๊ฐ€
Args:
frame: ์›๋ณธ ํ”„๋ ˆ์ž„
fps: FPS ๊ฐ’
latency: Latency (ms)
prediction: 'Fall' ๋˜๋Š” 'Non-Fall'
confidence: ์‹ ๋ขฐ๋„
panel_height: ํŒจ๋„ ๋†’์ด
position: ํŒจ๋„ ์œ„์น˜ ('top' ๋˜๋Š” 'bottom')
Returns:
result: ํŒจ๋„์ด ์ถ”๊ฐ€๋œ ํ”„๋ ˆ์ž„
"""
h, w = frame.shape[:2]
# ์ •๋ณด ํŒจ๋„ ์ƒ์„ฑ
panel = create_info_panel(w, h, fps, latency, prediction, confidence, panel_height, position)
# ํŒจ๋„ ์œ„์น˜์— ๋”ฐ๋ผ ๊ฒฐํ•ฉ
if position == 'top':
result = np.vstack([panel, frame])
elif position == 'bottom':
result = np.vstack([frame, panel])
else:
raise ValueError(f"Unknown position: {position}. Use 'top' or 'bottom'.")
return result
def draw_fall_alert_overlay(
frame: np.ndarray,
alert_text: str = "FALL DETECTED!",
flash: bool = True
) -> np.ndarray:
"""
๋‚™์ƒ ๊ฒฝ๋ณด ์˜ค๋ฒ„๋ ˆ์ด ๊ทธ๋ฆฌ๊ธฐ (์ „์ฒด ํ™”๋ฉด ํ”Œ๋ž˜์‹œ ํšจ๊ณผ)
Args:
frame: ์›๋ณธ ํ”„๋ ˆ์ž„
alert_text: ๊ฒฝ๋ณด ํ…์ŠคํŠธ
flash: True๋ฉด ํ™”๋ฉด ์ „์ฒด์— ๋นจ๊ฐ„ ๋ฐ˜ํˆฌ๋ช… ์˜ค๋ฒ„๋ ˆ์ด ์ถ”๊ฐ€
Returns:
result: ๊ฒฝ๋ณด ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์ถ”๊ฐ€๋œ ํ”„๋ ˆ์ž„
"""
frame = frame.copy()
h, w = frame.shape[:2]
# 1. ํ”Œ๋ž˜์‹œ ํšจ๊ณผ (๋นจ๊ฐ„ ๋ฐ˜ํˆฌ๋ช… ์˜ค๋ฒ„๋ ˆ์ด)
if flash:
overlay = frame.copy()
cv2.rectangle(overlay, (0, 0), (w, h), (0, 0, 255), -1)
cv2.addWeighted(overlay, 0.3, frame, 0.7, 0, frame)
# 2. ์ค‘์•™์— ํฐ ๊ฒฝ๊ณ  ํ…์ŠคํŠธ
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 2.5
font_thickness = 8 # ๋‘๊บผ์šด ๊ตต๊ธฐ๋กœ ๋ณผ๋“œ์ฒด ํšจ๊ณผ
(text_w, text_h), _ = cv2.getTextSize(alert_text, font, font_scale, font_thickness)
text_x = (w - text_w) // 2
text_y = (h + text_h) // 2
# ํ…์ŠคํŠธ ๊ทธ๋ฆผ์ž (๊ฒ€์€์ƒ‰)
cv2.putText(frame, alert_text, (text_x + 3, text_y + 3), font, font_scale, (0, 0, 0), font_thickness + 2, cv2.LINE_AA)
# ํ…์ŠคํŠธ ๋ณธ๋ฌธ (ํฐ์ƒ‰)
cv2.putText(frame, alert_text, (text_x, text_y), font, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
return frame
def visualize_fall_simple(
frame: np.ndarray,
keypoints: Optional[np.ndarray] = None,
show_fall_text: bool = False,
keypoint_mode: Literal['all', 'major'] = 'all',
output_scale: float = 1.0
) -> np.ndarray:
"""
๊ฐ„์†Œํ™”๋œ ๋‚™์ƒ ๊ฐ์ง€ ์‹œ๊ฐํ™” (Pose skeleton + FALL DETECTED ํ…์ŠคํŠธ๋งŒ)
ํ‘œ์‹œ ํ•ญ๋ชฉ:
- Pose skeleton (์‹ ์ฒด ๋ถ€์œ„๋ณ„ ์ƒ‰์ƒ)
- FALL DETECTED ํ…์ŠคํŠธ (show_fall_text=True์ผ ๋•Œ)
์ œ๊ฑฐ๋œ ํ•ญ๋ชฉ:
- FPS/Latency ์ •๋ณด
- ์ •๋ณด ํŒจ๋„
- ๋นจ๊ฐ„ ํ”Œ๋ž˜์‹œ ์˜ค๋ฒ„๋ ˆ์ด
- ์‹ ๋ขฐ๋„ ํ‘œ์‹œ
Args:
frame: ์›๋ณธ ํ”„๋ ˆ์ž„
keypoints: (17, 3) pose keypoints (์„ ํƒ)
show_fall_text: True๋ฉด FALL DETECTED ํ…์ŠคํŠธ ํ‘œ์‹œ
keypoint_mode: 'all'=์ „์ฒด 17๊ฐœ, 'major'=์ฃผ์š” 9๊ฐœ๋งŒ ํ‘œ์‹œ
output_scale: ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์Šค์ผ€์ผ (0.5=50%, 1.0=100%)
Returns:
result: ์‹œ๊ฐํ™”๋œ ํ”„๋ ˆ์ž„
"""
# 1. ํ•ด์ƒ๋„ ์กฐ์ ˆ (output_scale < 1.0์ธ ๊ฒฝ์šฐ)
original_h, original_w = frame.shape[:2]
if output_scale < 1.0:
new_w = int(original_w * output_scale)
new_h = int(original_h * output_scale)
result = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
# keypoints ์ขŒํ‘œ๋„ ์Šค์ผ€์ผ ์กฐ์ •
if keypoints is not None:
keypoints = keypoints.copy()
keypoints[:, 0] *= output_scale # x ์ขŒํ‘œ
keypoints[:, 1] *= output_scale # y ์ขŒํ‘œ
else:
result = frame.copy()
# 2. ์Šค์ผˆ๋ ˆํ†ค ๊ทธ๋ฆฌ๊ธฐ
if keypoints is not None:
result = draw_skeleton_vectorized(
result, keypoints,
keypoint_mode=keypoint_mode,
use_body_part_colors=True
)
# 3. FALL DETECTED ํ…์ŠคํŠธ ํ‘œ์‹œ (ํ”Œ๋ž˜์‹œ ์—†์ด)
if show_fall_text:
h, w = result.shape[:2]
alert_text = "FALL DETECTED"
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 2.0
font_thickness = 6
(text_w, text_h), _ = cv2.getTextSize(alert_text, font, font_scale, font_thickness)
text_x = (w - text_w) // 2
text_y = 80 # ํ™”๋ฉด ์ƒ๋‹จ
# ํ…์ŠคํŠธ ๋ฐฐ๊ฒฝ (๋ฐ˜ํˆฌ๋ช… ๊ฒ€์€์ƒ‰)
bg_padding = 15
overlay = result.copy()
cv2.rectangle(
overlay,
(text_x - bg_padding, text_y - text_h - bg_padding),
(text_x + text_w + bg_padding, text_y + bg_padding),
(0, 0, 0),
-1
)
cv2.addWeighted(overlay, 0.6, result, 0.4, 0, result)
# ํ…์ŠคํŠธ ๊ทธ๋ฆผ์ž (๊ฒ€์€์ƒ‰)
cv2.putText(result, alert_text, (text_x + 2, text_y + 2),
font, font_scale, (0, 0, 0), font_thickness + 2, cv2.LINE_AA)
# ํ…์ŠคํŠธ ๋ณธ๋ฌธ (๋นจ๊ฐ„์ƒ‰)
cv2.putText(result, alert_text, (text_x, text_y),
font, font_scale, (0, 0, 255), font_thickness, cv2.LINE_AA)
return result
def visualize_fall_detection(
frame: np.ndarray,
keypoints: Optional[np.ndarray] = None,
prediction: str = 'Non-Fall',
confidence: float = 0.0,
bbox: Optional[Tuple[int, int, int, int]] = None,
fps: Optional[float] = None,
latency: Optional[float] = None,
show_skeleton: bool = True,
show_info_panel: bool = True,
show_alert: bool = False,
use_optimized: bool = True,
keypoint_mode: Literal['all', 'major'] = 'all',
output_scale: float = 1.0
) -> np.ndarray:
"""
๋‚™์ƒ ๊ฐ์ง€ ๊ฒฐ๊ณผ ์ข…ํ•ฉ ์‹œ๊ฐํ™” (All-in-one ํ•จ์ˆ˜)
Args:
frame: ์›๋ณธ ํ”„๋ ˆ์ž„
keypoints: (17, 3) pose keypoints (์„ ํƒ)
prediction: 'Fall' ๋˜๋Š” 'Non-Fall'
confidence: ์‹ ๋ขฐ๋„
bbox: ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค (์„ ํƒ)
fps: FPS ๊ฐ’ (์„ ํƒ)
latency: Latency (ms) (์„ ํƒ)
show_skeleton: True๋ฉด ์Šค์ผˆ๋ ˆํ†ค ๊ทธ๋ฆฌ๊ธฐ
show_info_panel: True๋ฉด ์ƒ๋‹จ์— ์ •๋ณด ํŒจ๋„ ์ถ”๊ฐ€
show_alert: True๋ฉด ๋‚™์ƒ ๊ฒฝ๋ณด ์˜ค๋ฒ„๋ ˆ์ด ์ถ”๊ฐ€ (prediction='Fall'์ผ ๋•Œ๋งŒ)
use_optimized: True๋ฉด ๋ฒกํ„ฐํ™”๋œ ๊ทธ๋ฆฌ๊ธฐ ํ•จ์ˆ˜ ์‚ฌ์šฉ (30๋ฐฐ ๋น ๋ฆ„)
keypoint_mode: 'all'=์ „์ฒด 17๊ฐœ, 'major'=์ฃผ์š” 9๊ฐœ๋งŒ ํ‘œ์‹œ
output_scale: ์ถœ๋ ฅ ํ•ด์ƒ๋„ ์Šค์ผ€์ผ (0.5=50%, 1.0=100%)
Returns:
result: ์‹œ๊ฐํ™”๋œ ํ”„๋ ˆ์ž„
"""
# 1. ํ•ด์ƒ๋„ ์กฐ์ ˆ (output_scale < 1.0์ธ ๊ฒฝ์šฐ)
original_h, original_w = frame.shape[:2]
if output_scale < 1.0:
new_w = int(original_w * output_scale)
new_h = int(original_h * output_scale)
result = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
# keypoints ์ขŒํ‘œ๋„ ์Šค์ผ€์ผ ์กฐ์ •
if keypoints is not None:
keypoints = keypoints.copy()
keypoints[:, 0] *= output_scale # x ์ขŒํ‘œ
keypoints[:, 1] *= output_scale # y ์ขŒํ‘œ
# bbox ์ขŒํ‘œ๋„ ์Šค์ผ€์ผ ์กฐ์ •
if bbox is not None:
bbox = tuple(int(v * output_scale) for v in bbox)
else:
result = frame.copy()
# 2. ์Šค์ผˆ๋ ˆํ†ค ๊ทธ๋ฆฌ๊ธฐ
if show_skeleton and keypoints is not None:
if use_optimized:
result = draw_skeleton_vectorized(
result, keypoints,
keypoint_mode=keypoint_mode,
use_body_part_colors=True
)
else:
result = draw_skeleton(result, keypoints, use_body_part_colors=True)
# 3. ์˜ˆ์ธก ๊ฒฐ๊ณผ ์˜ค๋ฒ„๋ ˆ์ด
if fps is not None or latency is not None:
result = draw_prediction(result, prediction, confidence, bbox, fps, latency, position='top-left')
# 4. ๋‚™์ƒ ๊ฒฝ๋ณด ์˜ค๋ฒ„๋ ˆ์ด (Fall์ด๊ณ  show_alert=True์ผ ๋•Œ๋งŒ)
if show_alert and prediction == 'Fall':
result = draw_fall_alert_overlay(result, alert_text="FALL DETECTED!", flash=True)
# 5. ์ •๋ณด ํŒจ๋„ ์ถ”๊ฐ€ (์„ ํƒ)
if show_info_panel and fps is not None and latency is not None:
result = add_info_panel_to_frame(result, fps, latency, prediction, confidence, position='bottom')
return result
if __name__ == '__main__':
import time
import argparse
parser = argparse.ArgumentParser(description='Visualization module test and benchmark')
parser.add_argument('--benchmark', action='store_true', help='Run performance benchmark')
parser.add_argument('--resolution', type=str, default='640x480',
help='Test resolution (default: 640x480, options: 640x480, 1920x1080, 3840x2160)')
parser.add_argument('--iterations', type=int, default=100, help='Benchmark iterations')
args = parser.parse_args()
# ํ•ด์ƒ๋„ ํŒŒ์‹ฑ
res_map = {
'640x480': (480, 640),
'1920x1080': (1080, 1920),
'3840x2160': (2160, 3840),
'4k': (2160, 3840),
'fhd': (1080, 1920),
'vga': (480, 640),
}
h, w = res_map.get(args.resolution.lower(), (480, 640))
print(f"Testing visualization module at {w}x{h}...")
# 1. ๋”๋ฏธ ํ”„๋ ˆ์ž„ ์ƒ์„ฑ
frame = np.zeros((h, w, 3), dtype=np.uint8)
frame[:, :] = (50, 50, 50)
# 2. ๋”๋ฏธ ํ‚คํฌ์ธํŠธ ์ƒ์„ฑ (ํ•ด์ƒ๋„์— ๋งž๊ฒŒ ์Šค์ผ€์ผ)
scale_x = w / 640
scale_y = h / 480
keypoints = np.array([
[320, 100, 0.9], # 0: nose
[310, 90, 0.9], # 1: left_eye
[330, 90, 0.9], # 2: right_eye
[300, 90, 0.8], # 3: left_ear
[340, 90, 0.8], # 4: right_ear
[300, 150, 0.95], # 5: left_shoulder
[340, 150, 0.95], # 6: right_shoulder
[280, 200, 0.9], # 7: left_elbow
[360, 200, 0.9], # 8: right_elbow
[270, 250, 0.85], # 9: left_wrist
[370, 250, 0.85], # 10: right_wrist
[300, 250, 0.95], # 11: left_hip
[340, 250, 0.95], # 12: right_hip
[300, 350, 0.9], # 13: left_knee
[340, 350, 0.9], # 14: right_knee
[300, 450, 0.85], # 15: left_ankle
[340, 450, 0.85], # 16: right_ankle
], dtype=np.float32)
keypoints[:, 0] *= scale_x
keypoints[:, 1] *= scale_y
if args.benchmark:
print("\n" + "=" * 70)
print("BENCHMARK: Visualization Performance Comparison")
print("=" * 70)
print(f"Resolution: {w}x{h}")
print(f"Iterations: {args.iterations}")
print("=" * 70)
# ๊ธฐ์กด draw_skeleton ๋ฒค์น˜๋งˆํฌ
print("\n[1] draw_skeleton (original - cv2.circle/line loops)")
times_original = []
for _ in range(args.iterations):
start = time.perf_counter()
_ = draw_skeleton(frame.copy(), keypoints, use_body_part_colors=True)
times_original.append((time.perf_counter() - start) * 1000)
avg_original = np.mean(times_original)
std_original = np.std(times_original)
print(f" Average: {avg_original:.2f}ms (+/- {std_original:.2f}ms)")
# ๋ฒกํ„ฐํ™” draw_skeleton_vectorized ๋ฒค์น˜๋งˆํฌ (all keypoints)
print("\n[2] draw_skeleton_vectorized (optimized - all keypoints)")
times_vectorized = []
for _ in range(args.iterations):
start = time.perf_counter()
_ = draw_skeleton_vectorized(frame.copy(), keypoints, keypoint_mode='all')
times_vectorized.append((time.perf_counter() - start) * 1000)
avg_vectorized = np.mean(times_vectorized)
std_vectorized = np.std(times_vectorized)
speedup_all = avg_original / avg_vectorized
print(f" Average: {avg_vectorized:.2f}ms (+/- {std_vectorized:.2f}ms)")
print(f" Speedup: {speedup_all:.1f}x faster")
# ๋ฒกํ„ฐํ™” draw_skeleton_vectorized ๋ฒค์น˜๋งˆํฌ (major keypoints)
print("\n[3] draw_skeleton_vectorized (optimized - major keypoints only)")
times_major = []
for _ in range(args.iterations):
start = time.perf_counter()
_ = draw_skeleton_vectorized(frame.copy(), keypoints, keypoint_mode='major')
times_major.append((time.perf_counter() - start) * 1000)
avg_major = np.mean(times_major)
std_major = np.std(times_major)
speedup_major = avg_original / avg_major
print(f" Average: {avg_major:.2f}ms (+/- {std_major:.2f}ms)")
print(f" Speedup: {speedup_major:.1f}x faster")
# ํ•ด์ƒ๋„ ์Šค์ผ€์ผ + ๋ฒกํ„ฐํ™” ๋ฒค์น˜๋งˆํฌ
if w > 640:
print("\n[4] draw_skeleton_vectorized + 50% scale")
times_scaled = []
for _ in range(args.iterations):
start = time.perf_counter()
result = visualize_fall_detection(
frame.copy(), keypoints,
prediction='Fall', confidence=0.9,
fps=30.0, latency=50.0,
use_optimized=True,
keypoint_mode='all',
output_scale=0.5
)
times_scaled.append((time.perf_counter() - start) * 1000)
avg_scaled = np.mean(times_scaled)
std_scaled = np.std(times_scaled)
print(f" Average: {avg_scaled:.2f}ms (+/- {std_scaled:.2f}ms)")
print(f" Output size: {result.shape[1]}x{result.shape[0]}")
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print(f"Original: {avg_original:.2f}ms")
print(f"Optimized: {avg_vectorized:.2f}ms ({speedup_all:.1f}x faster)")
print(f"Major only: {avg_major:.2f}ms ({speedup_major:.1f}x faster)")
target_met = avg_vectorized < 10.0
print(f"\nTarget (<10ms): {'MET' if target_met else 'NOT MET'}")
print("=" * 70)
else:
# ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ
print("\n1. Testing draw_skeleton (original)...")
result = draw_skeleton(frame.copy(), keypoints, use_body_part_colors=True)
print(f" Output shape: {result.shape}")
print("\n2. Testing draw_skeleton_vectorized (optimized)...")
result = draw_skeleton_vectorized(frame.copy(), keypoints, keypoint_mode='all')
print(f" Output shape: {result.shape}")
print("\n3. Testing draw_skeleton_vectorized (major only)...")
result = draw_skeleton_vectorized(frame.copy(), keypoints, keypoint_mode='major')
print(f" Output shape: {result.shape}")
print("\n4. Testing draw_prediction...")
result = draw_prediction(
frame.copy(),
prediction='Non-Fall',
confidence=0.95,
bbox=(int(270*scale_x), int(90*scale_y), int(370*scale_x), int(450*scale_y)),
fps=30.0,
latency=50.0
)
print(f" Output shape: {result.shape}")
print("\n5. Testing create_info_panel...")
panel = create_info_panel(w, h, fps=30.0, latency=50.0, prediction='Non-Fall', confidence=0.95)
print(f" Panel shape: {panel.shape}")
print("\n6. Testing visualize_fall_detection (optimized=True)...")
result = visualize_fall_detection(
frame=frame,
keypoints=keypoints,
prediction='Fall',
confidence=0.87,
fps=30.0,
latency=50.0,
show_skeleton=True,
show_info_panel=True,
show_alert=True,
use_optimized=True,
keypoint_mode='all'
)
print(f" Output shape: {result.shape}")
print("\n7. Testing visualize_fall_detection (output_scale=0.5)...")
result = visualize_fall_detection(
frame=frame,
keypoints=keypoints,
prediction='Non-Fall',
confidence=0.95,
fps=30.0,
latency=50.0,
show_skeleton=True,
show_info_panel=True,
use_optimized=True,
output_scale=0.5
)
print(f" Output shape: {result.shape}")
print("\nAll tests passed!")