File size: 5,698 Bytes
cca6a52 cfbaa51 cca6a52 3cd94cf cca6a52 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 cca6a52 1ca9b28 cca6a52 1ca9b28 3cd94cf 1ca9b28 3cd94cf 1ca9b28 cca6a52 1ca9b28 3cd94cf 1ca9b28 cca6a52 1ca9b28 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
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:
# Unpack bounding box and calculate shoulder dimensions
bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bounding_box
bbox_delta_y = abs(bbox_y2 - bbox_y1)
# Keypoints for shoulders
left_shoulder = keypoints[CocoKeypoints.LEFT_SHOULDER.value]
right_shoulder = keypoints[CocoKeypoints.RIGHT_SHOULDER.value]
# Midpoints calculation
shoulder_center = np.array([(left_shoulder[0] + right_shoulder[0]) / 2,
(left_shoulder[1] + right_shoulder[1]) / 2])
# Calculate chest center by applying directional adjustment separately for x and y
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 dimensions (85% of shoulder width, 40% height)
chest_dx = bbox_delta_y * 0.8
chest_dy = bbox_delta_y * 1.75
# Calculate region coordinates
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
# Clamp to frame boundaries
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))
# Check validity
if x2 <= x1 or y2 <= y1:
return None
# Adjusted parameters
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
# Filter out None values and get recent frames
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
# Convert to numpy array (preserve floating-point precision)
history_array = np.array(valid_history, dtype=np.float32)
# Exponential weights (stronger emphasis on recent frames)
weights = np.exp(np.linspace(1, 3, len(history_array)))
weights /= weights.sum()
try:
# Calculate weighted average in float space
cx, cy, cw, ch = np.average(history_array, axis=0, weights=weights)
# Convert to rectangle coordinates (still floating point)
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)
# Only round to integers after all calculations
x1, y1, x2, y2 = map(round, [x1, y1, x2, y2])
# Validate rectangle
if x2 <= x1 or y2 <= y1:
return None
return (
(x1 + x2) // 2, # cx
(y1 + y2) // 2, # cy
x2 - x1, # cw
y2 - y1 # ch
)
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)
# Draw rectangle and center
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
|