|
|
import cv2 |
|
|
import numpy as np |
|
|
from CPR_Module.Common.keypoints import CocoKeypoints |
|
|
from CPR_Module.Common.logging_config import cpr_logger |
|
|
|
|
|
class ChestInitializer: |
|
|
"""Handles chest point detection with validations in estimation.""" |
|
|
|
|
|
def __init__(self): |
|
|
self.chest_params = None |
|
|
self.chest_params_history = [] |
|
|
self.expected_chest_params = None |
|
|
|
|
|
def estimate_chest_region(self, keypoints, bounding_box, frame_width, frame_height): |
|
|
"""Estimate and validate chest region. Returns (cx, cy, cw, ch) or None.""" |
|
|
try: |
|
|
|
|
|
bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bounding_box |
|
|
bbox_delta_y = abs(bbox_y2 - bbox_y1) |
|
|
|
|
|
|
|
|
left_shoulder = keypoints[CocoKeypoints.LEFT_SHOULDER.value] |
|
|
right_shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value] |
|
|
|
|
|
|
|
|
shoulder_center = np.array([(left_shoulder[0] + right_shoulder[0]) / 2, |
|
|
(left_shoulder[1] + right_shoulder[1]) / 2]) |
|
|
|
|
|
|
|
|
chest_center_from_shoulder_x = shoulder_center[0] - 0.3 * bbox_delta_y |
|
|
chest_center_from_shoulder_y = shoulder_center[1] - 0.1 * bbox_delta_y |
|
|
chest_center_from_shoulder = np.array([chest_center_from_shoulder_x, chest_center_from_shoulder_y]) |
|
|
|
|
|
|
|
|
chest_dx = bbox_delta_y * 0.8 |
|
|
chest_dy = bbox_delta_y * 1.75 |
|
|
|
|
|
|
|
|
x1 = chest_center_from_shoulder[0] - chest_dx / 2 |
|
|
y1 = chest_center_from_shoulder[1] - chest_dy / 2 |
|
|
x2 = chest_center_from_shoulder[0] + chest_dx / 2 |
|
|
y2 = chest_center_from_shoulder[1] + chest_dy / 2 |
|
|
|
|
|
|
|
|
x1 = max(0, min(x1, frame_width - 1)) |
|
|
y1 = max(0, min(y1, frame_height - 1)) |
|
|
x2 = max(0, min(x2, frame_width - 1)) |
|
|
y2 = max(0, min(y2, frame_height - 1)) |
|
|
|
|
|
|
|
|
if x2 <= x1 or y2 <= y1: |
|
|
return None |
|
|
|
|
|
|
|
|
cx = (x1 + x2) / 2 |
|
|
cy = (y1 + y2) / 2 |
|
|
cw = x2 - x1 |
|
|
ch = y2 - y1 |
|
|
|
|
|
return (cx, cy, cw, ch) |
|
|
|
|
|
except (IndexError, TypeError, ValueError) as e: |
|
|
print(f"Chest estimation error: {e}") |
|
|
return None |
|
|
|
|
|
def estimate_chest_region_weighted_avg(self, frame_width, frame_height, window_size=60, min_samples=3): |
|
|
""" |
|
|
Calculate stabilized chest parameters using weighted averaging with boundary checks. |
|
|
|
|
|
Args: |
|
|
self.chest_params_history: List of recent chest parameters [(cx, cy, cw, ch), ...] |
|
|
frame_width: Width of the video frame |
|
|
frame_height: Height of the video frame |
|
|
window_size: Number of recent frames to consider (default: 5) |
|
|
min_samples: Minimum valid samples required (default: 3) |
|
|
|
|
|
Returns: |
|
|
Tuple of (cx, cy, cw, ch) as integers within frame boundaries, |
|
|
or None if insufficient data or invalid rectangle |
|
|
""" |
|
|
if not self.chest_params_history: |
|
|
return None |
|
|
|
|
|
|
|
|
valid_history = [h for h in self.chest_params_history[-window_size:] if h is not None] |
|
|
|
|
|
if len(valid_history) < min_samples: |
|
|
return None |
|
|
|
|
|
|
|
|
history_array = np.array(valid_history, dtype=np.float32) |
|
|
|
|
|
|
|
|
weights = np.exp(np.linspace(1, 3, len(history_array))) |
|
|
weights /= weights.sum() |
|
|
|
|
|
try: |
|
|
|
|
|
cx, cy, cw, ch = np.average(history_array, axis=0, weights=weights) |
|
|
|
|
|
|
|
|
x1 = max(0.0, cx - cw/2) |
|
|
y1 = max(0.0, cy - ch/2) |
|
|
x2 = min(float(frame_width - 1), cx + cw/2) |
|
|
y2 = min(float(frame_height - 1), cy + ch/2) |
|
|
|
|
|
|
|
|
x1, y1, x2, y2 = map(round, [x1, y1, x2, y2]) |
|
|
|
|
|
|
|
|
if x2 <= x1 or y2 <= y1: |
|
|
return None |
|
|
|
|
|
return ( |
|
|
(x1 + x2) // 2, |
|
|
(y1 + y2) // 2, |
|
|
x2 - x1, |
|
|
y2 - y1 |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Chest region estimation error: {e}") |
|
|
return None |
|
|
|
|
|
def draw_expected_chest_region(self, frame): |
|
|
"""Draws the chest region without validation.""" |
|
|
if self.expected_chest_params is None: |
|
|
return frame |
|
|
|
|
|
cx, cy, cw, ch = self.expected_chest_params |
|
|
x1 = int(cx - cw / 2) |
|
|
y1 = int(cy - ch / 2) |
|
|
x2 = int(cx + cw / 2) |
|
|
y2 = int(cy + ch / 2) |
|
|
|
|
|
|
|
|
cv2.rectangle(frame, (x1, y1), (x2, y2), (128, 128, 0), 5) |
|
|
|
|
|
cv2.circle(frame, (int(cx), int(cy)), 8, (128, 128, 0), -1) |
|
|
|
|
|
cv2.putText(frame, "EXPECTED CHEST", (x1, max(10, y1 - 5)), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (128, 128, 0), 2) |
|
|
|
|
|
return frame |
|
|
|