Spaces:
Sleeping
Sleeping
| """ | |
| 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!") | |