Pose Module
Pose Detection Module
Author: Nathan Forsyth
Pose
Class for displaying and retrieving data for poses recognized by the PoseDetector module.
Attributes:
| Name | Type | Description |
|---|---|---|
landmarks |
list[tuple[int, int, int]]
|
List of landmarks using 3D pixel coordinates. |
normalized_landmarks |
list[tuple[float, float, float]]
|
List of the original normalized landmark tuples provided by MediaPipe. |
world_landmarks |
list[tuple[float, float, float]]
|
List of world landmark tuples in meters when MediaPipe provides them. |
box |
BoundingBox
|
Bounding box of the pose. |
center |
tuple[int, int]
|
2D pixel coordinates of the bounding box center. |
flip |
bool
|
When True, left/right landmark shortcuts are swapped for mirrored images. |
image |
ndarray
|
Image used by the PoseDetector. Used as the default image to display the drawn pose. |
connection_style |
ConnectionStyle
|
Style settings for the pose connection lines. |
landmark_style |
LandmarkStyle
|
Style settings for the pose landmarks. |
Source code in src/pumpkinpipe/pose.py
class Pose:
"""
Class for displaying and retrieving data for poses recognized by the PoseDetector module.
:ivar landmarks: List of landmarks using 3D pixel coordinates.
:ivar normalized_landmarks: List of the original normalized landmark tuples provided by MediaPipe.
:ivar world_landmarks: List of world landmark tuples in meters when MediaPipe provides them.
:ivar box: Bounding box of the pose.
:ivar center: 2D pixel coordinates of the bounding box center.
:ivar flip: When True, left/right landmark shortcuts are swapped for mirrored images.
:ivar image: Image used by the PoseDetector. Used as the default image to display the drawn pose.
:ivar connection_style: Style settings for the pose connection lines.
:ivar landmark_style: Style settings for the pose landmarks.
"""
_CONNECTIONS = PoseLandmarksConnections.POSE_LANDMARKS
_NOSE_ID = 0
_LEFT_SHOULDER_ID = 11
_RIGHT_SHOULDER_ID = 12
_LEFT_ELBOW_ID = 13
_RIGHT_ELBOW_ID = 14
_LEFT_WRIST_ID = 15
_RIGHT_WRIST_ID = 16
_LEFT_HIP_ID = 23
_RIGHT_HIP_ID = 24
_LEFT_KNEE_ID = 25
_RIGHT_KNEE_ID = 26
_LEFT_ANKLE_ID = 27
_RIGHT_ANKLE_ID = 28
def __init__(
self,
landmarks: list[tuple[int, int, int]],
normalized_landmarks: list[tuple[float, float, float]],
world_landmarks: list[tuple[float, float, float]],
box: BoundingBox,
image: np.ndarray,
flip: bool = True,
):
"""
Initialize the pose.
:param landmarks: The (x, y, z) pixel coordinates of landmarks.
:param normalized_landmarks: The normalized landmark tuples provided by MediaPipe.
:param world_landmarks: The world landmark tuples provided by MediaPipe.
:param box: The bounding box of the pose.
:param image: The image that was used to detect the pose.
:param flip: When True, swap left/right shortcuts for mirrored images.
"""
self.landmarks: list[tuple[int, int, int]] = landmarks
self.normalized_landmarks: list[tuple[float, float, float]] = normalized_landmarks
self.world_landmarks: list[tuple[float, float, float]] = world_landmarks
self.flip: bool = flip
self.nose: tuple[int, int, int] = self.landmarks[Pose._NOSE_ID]
left_shoulder_id, right_shoulder_id = self._side_ids(Pose._LEFT_SHOULDER_ID, Pose._RIGHT_SHOULDER_ID)
left_elbow_id, right_elbow_id = self._side_ids(Pose._LEFT_ELBOW_ID, Pose._RIGHT_ELBOW_ID)
left_wrist_id, right_wrist_id = self._side_ids(Pose._LEFT_WRIST_ID, Pose._RIGHT_WRIST_ID)
left_hip_id, right_hip_id = self._side_ids(Pose._LEFT_HIP_ID, Pose._RIGHT_HIP_ID)
left_knee_id, right_knee_id = self._side_ids(Pose._LEFT_KNEE_ID, Pose._RIGHT_KNEE_ID)
left_ankle_id, right_ankle_id = self._side_ids(Pose._LEFT_ANKLE_ID, Pose._RIGHT_ANKLE_ID)
self.left_shoulder: tuple[int, int, int] = self.landmarks[left_shoulder_id]
self.right_shoulder: tuple[int, int, int] = self.landmarks[right_shoulder_id]
self.left_elbow: tuple[int, int, int] = self.landmarks[left_elbow_id]
self.right_elbow: tuple[int, int, int] = self.landmarks[right_elbow_id]
self.left_wrist: tuple[int, int, int] = self.landmarks[left_wrist_id]
self.right_wrist: tuple[int, int, int] = self.landmarks[right_wrist_id]
self.left_hip: tuple[int, int, int] = self.landmarks[left_hip_id]
self.right_hip: tuple[int, int, int] = self.landmarks[right_hip_id]
self.left_knee: tuple[int, int, int] = self.landmarks[left_knee_id]
self.right_knee: tuple[int, int, int] = self.landmarks[right_knee_id]
self.left_ankle: tuple[int, int, int] = self.landmarks[left_ankle_id]
self.right_ankle: tuple[int, int, int] = self.landmarks[right_ankle_id]
self.box: BoundingBox = box
self.center: tuple[int, int] = self.box.center
self.image: np.ndarray = image
self.connection_style: ConnectionStyle = ConnectionStyle()
self.landmark_style: LandmarkStyle = LandmarkStyle(fill=(0, 255, 0))
def _side_ids(self, left_id: int, right_id: int) -> tuple[int, int]:
if self.flip:
return right_id, left_id
return left_id, right_id
def draw(self, image: np.ndarray | None = None):
"""
Draws the pose skeleton on the specified image.
:param image: The target image for the drawing. If None, it will draw on the pose's image.
"""
if image is None:
image = self.image
for connection in Pose._CONNECTIONS:
start_x, start_y, _ = self.landmarks[connection.start]
end_x, end_y, _ = self.landmarks[connection.end]
cv2.line(
image,
(start_x, start_y),
(end_x, end_y),
self.connection_style.stroke,
self.connection_style.thickness,
)
for x, y, _ in self.landmarks:
cv2.circle(image, (x, y), self.landmark_style.radius, self.landmark_style.fill, -1)
cv2.circle(
image,
(x, y),
self.landmark_style.radius,
self.landmark_style.stroke,
self.landmark_style.thickness,
)
def debug(
self,
image: np.ndarray | None = None,
skeleton: bool = True,
bounding_box: bool = True,
center: bool = True,
landmark_count: bool = True,
key_points: bool = True,
):
"""
Draws the requested debug information. Defaults to all debug information.
:param image: The image to draw on. If None, the pose will draw on its own image.
:param skeleton: When True, draw the pose landmarks and connections.
:param bounding_box: When True, draw the outer bounding box of the pose.
:param center: When True, draw and label the bounding box center.
:param landmark_count: When True, display the number of landmarks found.
:param key_points: When True, label common pose landmarks.
"""
if image is None:
image = self.image
debug_font = cv2.FONT_HERSHEY_PLAIN
debug_text_size = 1
debug_thickness = 1
if skeleton:
self.draw(image)
if bounding_box:
self.box.draw_corners(image, length=25, thickness=5, stroke=(0, 0, 0))
self.box.draw_corners(image, length=24, thickness=4, stroke=(255, 255, 255))
if landmark_count:
stack_text(
image,
[f"Pose landmarks: {len(self.landmarks)}"],
self.box.origin,
debug_font,
debug_text_size,
debug_thickness,
(255, 255, 255),
HAlign.LEFT,
VAlign.BOTTOM,
)
if key_points:
labels = [
("nose", self.nose),
("L shoulder", self.left_shoulder),
("R shoulder", self.right_shoulder),
("L wrist", self.left_wrist),
("R wrist", self.right_wrist),
("L hip", self.left_hip),
("R hip", self.right_hip),
("L ankle", self.left_ankle),
("R ankle", self.right_ankle),
]
for label, point in labels:
x, y, _ = point
stack_text(
image,
[label],
(x, y),
debug_font,
debug_text_size,
debug_thickness,
(0, 255, 0),
HAlign.CENTER,
VAlign.BOTTOM,
0,
)
if center:
cv2.circle(image, self.center, 7, (0, 255, 0), -1)
cv2.circle(image, self.center, 7, (0, 0, 0), 2)
stack_text(
image,
[f"Center: ({self.center[0]}, {self.center[1]})"],
self.center,
debug_font,
debug_text_size * 1.5,
debug_thickness * 2,
(0, 255, 0),
HAlign.CENTER,
VAlign.BOTTOM,
0,
)
def set_connection_style(
self,
stroke: tuple[int, int, int] | list[int] | None = None,
thickness: float | None = None,
):
"""
Modifies the style of the pose connections when drawn.
:param stroke: The BGR color of the connections.
:param thickness: The thickness of the connector lines in pixels.
"""
if stroke is not None:
self.connection_style.stroke = stroke
if thickness is not None:
self.connection_style.thickness = int(thickness)
def set_landmarks_style(
self,
fill: tuple[int, int, int] | list[int] | None = None,
stroke: tuple[int, int, int] | list[int] | None = None,
radius: float | None = None,
thickness: float | None = None,
):
"""
Modifies the style of the pose landmarks when drawn.
:param fill: The BGR color of the landmarks.
:param stroke: The BGR color of the landmark outlines.
:param radius: Radius of the landmarks.
:param thickness: Thickness of the landmark outline.
"""
if fill is not None:
self.landmark_style.fill = fill
if stroke is not None:
self.landmark_style.stroke = stroke
if radius is not None:
self.landmark_style.radius = int(radius)
if thickness is not None:
self.landmark_style.thickness = int(thickness)
__init__
__init__(landmarks: list[tuple[int, int, int]], normalized_landmarks: list[tuple[float, float, float]], world_landmarks: list[tuple[float, float, float]], box: BoundingBox, image: ndarray, flip: bool = True)
Initialize the pose.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
landmarks
|
list[tuple[int, int, int]]
|
The (x, y, z) pixel coordinates of landmarks. |
required |
normalized_landmarks
|
list[tuple[float, float, float]]
|
The normalized landmark tuples provided by MediaPipe. |
required |
world_landmarks
|
list[tuple[float, float, float]]
|
The world landmark tuples provided by MediaPipe. |
required |
box
|
BoundingBox
|
The bounding box of the pose. |
required |
image
|
ndarray
|
The image that was used to detect the pose. |
required |
flip
|
bool
|
When True, swap left/right shortcuts for mirrored images. |
True
|
Source code in src/pumpkinpipe/pose.py
def __init__(
self,
landmarks: list[tuple[int, int, int]],
normalized_landmarks: list[tuple[float, float, float]],
world_landmarks: list[tuple[float, float, float]],
box: BoundingBox,
image: np.ndarray,
flip: bool = True,
):
"""
Initialize the pose.
:param landmarks: The (x, y, z) pixel coordinates of landmarks.
:param normalized_landmarks: The normalized landmark tuples provided by MediaPipe.
:param world_landmarks: The world landmark tuples provided by MediaPipe.
:param box: The bounding box of the pose.
:param image: The image that was used to detect the pose.
:param flip: When True, swap left/right shortcuts for mirrored images.
"""
self.landmarks: list[tuple[int, int, int]] = landmarks
self.normalized_landmarks: list[tuple[float, float, float]] = normalized_landmarks
self.world_landmarks: list[tuple[float, float, float]] = world_landmarks
self.flip: bool = flip
self.nose: tuple[int, int, int] = self.landmarks[Pose._NOSE_ID]
left_shoulder_id, right_shoulder_id = self._side_ids(Pose._LEFT_SHOULDER_ID, Pose._RIGHT_SHOULDER_ID)
left_elbow_id, right_elbow_id = self._side_ids(Pose._LEFT_ELBOW_ID, Pose._RIGHT_ELBOW_ID)
left_wrist_id, right_wrist_id = self._side_ids(Pose._LEFT_WRIST_ID, Pose._RIGHT_WRIST_ID)
left_hip_id, right_hip_id = self._side_ids(Pose._LEFT_HIP_ID, Pose._RIGHT_HIP_ID)
left_knee_id, right_knee_id = self._side_ids(Pose._LEFT_KNEE_ID, Pose._RIGHT_KNEE_ID)
left_ankle_id, right_ankle_id = self._side_ids(Pose._LEFT_ANKLE_ID, Pose._RIGHT_ANKLE_ID)
self.left_shoulder: tuple[int, int, int] = self.landmarks[left_shoulder_id]
self.right_shoulder: tuple[int, int, int] = self.landmarks[right_shoulder_id]
self.left_elbow: tuple[int, int, int] = self.landmarks[left_elbow_id]
self.right_elbow: tuple[int, int, int] = self.landmarks[right_elbow_id]
self.left_wrist: tuple[int, int, int] = self.landmarks[left_wrist_id]
self.right_wrist: tuple[int, int, int] = self.landmarks[right_wrist_id]
self.left_hip: tuple[int, int, int] = self.landmarks[left_hip_id]
self.right_hip: tuple[int, int, int] = self.landmarks[right_hip_id]
self.left_knee: tuple[int, int, int] = self.landmarks[left_knee_id]
self.right_knee: tuple[int, int, int] = self.landmarks[right_knee_id]
self.left_ankle: tuple[int, int, int] = self.landmarks[left_ankle_id]
self.right_ankle: tuple[int, int, int] = self.landmarks[right_ankle_id]
self.box: BoundingBox = box
self.center: tuple[int, int] = self.box.center
self.image: np.ndarray = image
self.connection_style: ConnectionStyle = ConnectionStyle()
self.landmark_style: LandmarkStyle = LandmarkStyle(fill=(0, 255, 0))
debug
debug(image: ndarray | None = None, skeleton: bool = True, bounding_box: bool = True, center: bool = True, landmark_count: bool = True, key_points: bool = True)
Draws the requested debug information. Defaults to all debug information.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
ndarray | None
|
The image to draw on. If None, the pose will draw on its own image. |
None
|
skeleton
|
bool
|
When True, draw the pose landmarks and connections. |
True
|
bounding_box
|
bool
|
When True, draw the outer bounding box of the pose. |
True
|
center
|
bool
|
When True, draw and label the bounding box center. |
True
|
landmark_count
|
bool
|
When True, display the number of landmarks found. |
True
|
key_points
|
bool
|
When True, label common pose landmarks. |
True
|
Source code in src/pumpkinpipe/pose.py
def debug(
self,
image: np.ndarray | None = None,
skeleton: bool = True,
bounding_box: bool = True,
center: bool = True,
landmark_count: bool = True,
key_points: bool = True,
):
"""
Draws the requested debug information. Defaults to all debug information.
:param image: The image to draw on. If None, the pose will draw on its own image.
:param skeleton: When True, draw the pose landmarks and connections.
:param bounding_box: When True, draw the outer bounding box of the pose.
:param center: When True, draw and label the bounding box center.
:param landmark_count: When True, display the number of landmarks found.
:param key_points: When True, label common pose landmarks.
"""
if image is None:
image = self.image
debug_font = cv2.FONT_HERSHEY_PLAIN
debug_text_size = 1
debug_thickness = 1
if skeleton:
self.draw(image)
if bounding_box:
self.box.draw_corners(image, length=25, thickness=5, stroke=(0, 0, 0))
self.box.draw_corners(image, length=24, thickness=4, stroke=(255, 255, 255))
if landmark_count:
stack_text(
image,
[f"Pose landmarks: {len(self.landmarks)}"],
self.box.origin,
debug_font,
debug_text_size,
debug_thickness,
(255, 255, 255),
HAlign.LEFT,
VAlign.BOTTOM,
)
if key_points:
labels = [
("nose", self.nose),
("L shoulder", self.left_shoulder),
("R shoulder", self.right_shoulder),
("L wrist", self.left_wrist),
("R wrist", self.right_wrist),
("L hip", self.left_hip),
("R hip", self.right_hip),
("L ankle", self.left_ankle),
("R ankle", self.right_ankle),
]
for label, point in labels:
x, y, _ = point
stack_text(
image,
[label],
(x, y),
debug_font,
debug_text_size,
debug_thickness,
(0, 255, 0),
HAlign.CENTER,
VAlign.BOTTOM,
0,
)
if center:
cv2.circle(image, self.center, 7, (0, 255, 0), -1)
cv2.circle(image, self.center, 7, (0, 0, 0), 2)
stack_text(
image,
[f"Center: ({self.center[0]}, {self.center[1]})"],
self.center,
debug_font,
debug_text_size * 1.5,
debug_thickness * 2,
(0, 255, 0),
HAlign.CENTER,
VAlign.BOTTOM,
0,
)
draw
draw(image: ndarray | None = None)
Draws the pose skeleton on the specified image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
ndarray | None
|
The target image for the drawing. If None, it will draw on the pose's image. |
None
|
Source code in src/pumpkinpipe/pose.py
def draw(self, image: np.ndarray | None = None):
"""
Draws the pose skeleton on the specified image.
:param image: The target image for the drawing. If None, it will draw on the pose's image.
"""
if image is None:
image = self.image
for connection in Pose._CONNECTIONS:
start_x, start_y, _ = self.landmarks[connection.start]
end_x, end_y, _ = self.landmarks[connection.end]
cv2.line(
image,
(start_x, start_y),
(end_x, end_y),
self.connection_style.stroke,
self.connection_style.thickness,
)
for x, y, _ in self.landmarks:
cv2.circle(image, (x, y), self.landmark_style.radius, self.landmark_style.fill, -1)
cv2.circle(
image,
(x, y),
self.landmark_style.radius,
self.landmark_style.stroke,
self.landmark_style.thickness,
)
set_connection_style
set_connection_style(stroke: tuple[int, int, int] | list[int] | None = None, thickness: float | None = None)
Modifies the style of the pose connections when drawn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stroke
|
tuple[int, int, int] | list[int] | None
|
The BGR color of the connections. |
None
|
thickness
|
float | None
|
The thickness of the connector lines in pixels. |
None
|
Source code in src/pumpkinpipe/pose.py
def set_connection_style(
self,
stroke: tuple[int, int, int] | list[int] | None = None,
thickness: float | None = None,
):
"""
Modifies the style of the pose connections when drawn.
:param stroke: The BGR color of the connections.
:param thickness: The thickness of the connector lines in pixels.
"""
if stroke is not None:
self.connection_style.stroke = stroke
if thickness is not None:
self.connection_style.thickness = int(thickness)
set_landmarks_style
set_landmarks_style(fill: tuple[int, int, int] | list[int] | None = None, stroke: tuple[int, int, int] | list[int] | None = None, radius: float | None = None, thickness: float | None = None)
Modifies the style of the pose landmarks when drawn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fill
|
tuple[int, int, int] | list[int] | None
|
The BGR color of the landmarks. |
None
|
stroke
|
tuple[int, int, int] | list[int] | None
|
The BGR color of the landmark outlines. |
None
|
radius
|
float | None
|
Radius of the landmarks. |
None
|
thickness
|
float | None
|
Thickness of the landmark outline. |
None
|
Source code in src/pumpkinpipe/pose.py
def set_landmarks_style(
self,
fill: tuple[int, int, int] | list[int] | None = None,
stroke: tuple[int, int, int] | list[int] | None = None,
radius: float | None = None,
thickness: float | None = None,
):
"""
Modifies the style of the pose landmarks when drawn.
:param fill: The BGR color of the landmarks.
:param stroke: The BGR color of the landmark outlines.
:param radius: Radius of the landmarks.
:param thickness: Thickness of the landmark outline.
"""
if fill is not None:
self.landmark_style.fill = fill
if stroke is not None:
self.landmark_style.stroke = stroke
if radius is not None:
self.landmark_style.radius = int(radius)
if thickness is not None:
self.landmark_style.thickness = int(thickness)
PoseDetector
Class for setting up MediaPipe and finding pose data.
Attributes:
| Name | Type | Description |
|---|---|---|
timestamp_ms |
Variable for tracking time between frames. |
|
frame_rate |
Desired frame rate. Set to 30. |
|
landmarker |
Pipeline to detect pose landmarks. |
Source code in src/pumpkinpipe/pose.py
class PoseDetector:
"""
Class for setting up MediaPipe and finding pose data.
:ivar timestamp_ms: Variable for tracking time between frames.
:ivar frame_rate: Desired frame rate. Set to 30.
:ivar landmarker: Pipeline to detect pose landmarks.
"""
def __init__(self, max_poses: int = 1):
"""
Initialize the pose detector.
:param max_poses: The maximum number of poses for the detector to try and process.
"""
with get_model_path("pose_landmarker_full.task") as model_path:
options = vision.PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path=model_path),
num_poses=max_poses,
running_mode=vision.RunningMode.VIDEO,
)
self.landmarker = vision.PoseLandmarker.create_from_options(options)
self.timestamp_ms = 0
self.frame_rate = 30
def find_poses(self, image: np.ndarray, flip: bool = True) -> list[Pose]:
"""
Detect poses and add them to a list.
:param image: The image to detect poses in.
:param flip: When True, swap left/right shortcuts for mirrored images.
:return: A list of detected poses.
"""
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
height, width, _ = image.shape
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_rgb)
result = self.landmarker.detect_for_video(mp_image, self.timestamp_ms)
self.timestamp_ms += int(1000 / self.frame_rate)
if not result.pose_landmarks:
return []
poses: list[Pose] = []
world_landmark_sets = result.pose_world_landmarks or [[] for _ in result.pose_landmarks]
for landmarks, world_landmarks in zip(result.pose_landmarks, world_landmark_sets):
pixel_landmarks: list[tuple[int, int, int]] = []
normalized_landmarks: list[tuple[float, float, float]] = []
pose_world_landmarks: list[tuple[float, float, float]] = []
x_list: list[int] = []
y_list: list[int] = []
for lm in landmarks:
x = int(lm.x * width)
y = int(lm.y * height)
z = int(lm.z * width)
normalized_landmarks.append((lm.x, lm.y, lm.z))
pixel_landmarks.append((x, y, z))
x_list.append(x)
y_list.append(y)
for lm in world_landmarks:
pose_world_landmarks.append((lm.x, lm.y, lm.z))
bounding_box = BoundingBox(
(min(x_list) - 10, min(y_list) - 10),
(max(x_list) + 10, max(y_list) + 10),
)
poses.append(Pose(pixel_landmarks, normalized_landmarks, pose_world_landmarks, bounding_box, image, flip))
return poses
__init__
__init__(max_poses: int = 1)
Initialize the pose detector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_poses
|
int
|
The maximum number of poses for the detector to try and process. |
1
|
Source code in src/pumpkinpipe/pose.py
def __init__(self, max_poses: int = 1):
"""
Initialize the pose detector.
:param max_poses: The maximum number of poses for the detector to try and process.
"""
with get_model_path("pose_landmarker_full.task") as model_path:
options = vision.PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path=model_path),
num_poses=max_poses,
running_mode=vision.RunningMode.VIDEO,
)
self.landmarker = vision.PoseLandmarker.create_from_options(options)
self.timestamp_ms = 0
self.frame_rate = 30
find_poses
find_poses(image: ndarray, flip: bool = True) -> list[Pose]
Detect poses and add them to a list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image
|
ndarray
|
The image to detect poses in. |
required |
flip
|
bool
|
When True, swap left/right shortcuts for mirrored images. |
True
|
Returns:
| Type | Description |
|---|---|
list[Pose]
|
A list of detected poses. |
Source code in src/pumpkinpipe/pose.py
def find_poses(self, image: np.ndarray, flip: bool = True) -> list[Pose]:
"""
Detect poses and add them to a list.
:param image: The image to detect poses in.
:param flip: When True, swap left/right shortcuts for mirrored images.
:return: A list of detected poses.
"""
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
height, width, _ = image.shape
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_rgb)
result = self.landmarker.detect_for_video(mp_image, self.timestamp_ms)
self.timestamp_ms += int(1000 / self.frame_rate)
if not result.pose_landmarks:
return []
poses: list[Pose] = []
world_landmark_sets = result.pose_world_landmarks or [[] for _ in result.pose_landmarks]
for landmarks, world_landmarks in zip(result.pose_landmarks, world_landmark_sets):
pixel_landmarks: list[tuple[int, int, int]] = []
normalized_landmarks: list[tuple[float, float, float]] = []
pose_world_landmarks: list[tuple[float, float, float]] = []
x_list: list[int] = []
y_list: list[int] = []
for lm in landmarks:
x = int(lm.x * width)
y = int(lm.y * height)
z = int(lm.z * width)
normalized_landmarks.append((lm.x, lm.y, lm.z))
pixel_landmarks.append((x, y, z))
x_list.append(x)
y_list.append(y)
for lm in world_landmarks:
pose_world_landmarks.append((lm.x, lm.y, lm.z))
bounding_box = BoundingBox(
(min(x_list) - 10, min(y_list) - 10),
(max(x_list) + 10, max(y_list) + 10),
)
poses.append(Pose(pixel_landmarks, normalized_landmarks, pose_world_landmarks, bounding_box, image, flip))
return poses
main
main()
Test script for the Pose Detection module.
Source code in src/pumpkinpipe/pose.py
def main():
"""
Test script for the Pose Detection module.
"""
cap = cv2.VideoCapture(0)
pose_detector = PoseDetector()
while True:
success, img = cap.read()
if not success:
break
img = cv2.flip(img, 1)
poses = pose_detector.find_poses(img)
for pose in poses:
pose.debug()
cv2.imshow("Image", img)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
if cv2.getWindowProperty("Image", cv2.WND_PROP_VISIBLE) < 1:
break
cap.release()
cv2.destroyAllWindows()