Loading imgutils/pose/__init__.py +2 −2 Original line number Diff line number Diff line from .dwpose import dwpose_estimate from .format import OP18KeyPointSet, OP18_BODY_MAX, OP18_BODY_MIN, OP18_FACE_MAX, OP18_FACE_MIN, OP18_FEET_MAX, \ OP18_FEET_MIN, OP18_LEFT_FOOT_MAX, OP18_LEFT_FOOT_MIN, OP18_LEFT_HAND_MAX, OP18_LEFT_HAND_MIN, \ from .format import OP18KeyPointSet, OP18_BODY_MAX, OP18_BODY_MIN, OP18_FACE_MAX, OP18_FACE_MIN, \ OP18_LEFT_FOOT_MAX, OP18_LEFT_FOOT_MIN, OP18_LEFT_HAND_MAX, OP18_LEFT_HAND_MIN, \ OP18_RIGHT_FOOT_MAX, OP18_RIGHT_FOOT_MIN, OP18_RIGHT_HAND_MAX, OP18_RIGHT_HAND_MIN, OpenPose18 from .visual import op18_visualize imgutils/pose/dwpose.py +9 −7 Original line number Diff line number Diff line Loading @@ -242,7 +242,7 @@ def _get_warp_matrix(center: np.ndarray, scale: np.ndarray, rot: float, src, dst = src.astype(np.float32), dst.astype(np.float32) if inv: src, dst = dst, src src, dst = dst, src # pragma: no cover warp_mat = cv2.getAffineTransform(src, dst) return warp_mat Loading Loading @@ -325,7 +325,8 @@ def _get_simcc_maximum(simcc_x: np.ndarray, simcc_y: np.ndarray) -> Tuple[np.nda return locs, vals def _output_decode(simcc_x: np.ndarray, simcc_y: np.ndarray, simcc_split_ratio: float) -> Tuple[np.ndarray, np.ndarray]: def _output_decode(simcc_x: np.ndarray, simcc_y: np.ndarray, simcc_split_ratio: float) \ -> Tuple[np.ndarray, np.ndarray]: """Modulate simcc distribution with Gaussian. Args: Loading Loading @@ -397,7 +398,8 @@ def dwpose_estimate(image: ImageTyping, auto_detect: bool = True, np_image = np.array(image) if auto_detect: if out_bboxes is not None: warnings.warn(f'Auto detection enabled, value of out_bboxes will be ignored: {out_bboxes!r}.') warnings.warn('Out bboxes provided, auto detection will be disabled.') else: out_bboxes = [ (x0, y0, x1, y1) for (x0, y0, x1, y1), _, _ in detect_person(image, **(person_detect_cfgs or {})) Loading imgutils/pose/format.py +50 −15 Original line number Diff line number Diff line Loading @@ -4,17 +4,12 @@ import numpy as np OP18_BODY_MIN = 0 OP18_BODY_MAX = 17 OP18_FEET_MIN = 18 OP18_FEET_MAX = 23 OP18_LEFT_FOOT_MIN = 18 OP18_LEFT_FOOT_MAX = 20 OP18_RIGHT_FOOT_MIN = 21 OP18_RIGHT_FOOT_MAX = 23 OP18_FACE_MIN = 24 OP18_FACE_MAX = 91 OP18_LEFT_HAND_MIN = 92 OP18_LEFT_HAND_MAX = 112 OP18_RIGHT_HAND_MIN = 113 Loading @@ -23,6 +18,15 @@ OP18_RIGHT_HAND_MAX = 135 @unique class OpenPose18(IntEnum): """ Enumeration class representing the OpenPose 18 keypoint indices. The enumeration provides symbolic names for the keypoint indices, making it more readable and maintainable when accessing specific keypoints in the OP18 keypoint set. The keypoint indices are categorized into different body parts such as nose, neck, shoulders, elbows, wrists, hips, knees, ankles, eyes, ears, feet, and hands. """ NOSE = 0 NECK = 1 RIGHT_SHOULDER = 2 Loading Loading @@ -51,51 +55,82 @@ class OpenPose18(IntEnum): class OP18KeyPointSet: def __init__(self, all_): """ Class representing a set of keypoints detected by the OpenPose 18 (OP18) model. This class provides convenient properties to access keypoints for different body parts, including the body, left foot, right foot, face, left hand, and right hand. :param all_: NumPy array containing the coordinates and confidence scores of all keypoints. :type all_: np.ndarray """ def __init__(self, all_: np.ndarray): self.all: np.ndarray = all_ @property def body(self): """Property representing the keypoints for the body.""" return self.all[OP18_BODY_MIN:OP18_BODY_MAX + 1] @property def feet(self): return self.all[OP18_FEET_MIN:OP18_FEET_MAX + 1] @property def left_foot(self): """Property representing the keypoints for the left foot.""" return self.all[OP18_LEFT_FOOT_MIN:OP18_LEFT_FOOT_MAX + 1] @property def right_foot(self): """Property representing the keypoints for the right foot.""" return self.all[OP18_RIGHT_FOOT_MIN:OP18_RIGHT_FOOT_MAX + 1] @property def face(self): """Property representing the keypoints for the face.""" return self.all[OP18_FACE_MIN:OP18_FACE_MAX + 1] @property def left_hand(self): """Property representing the keypoints for the left hand.""" return self.all[OP18_LEFT_HAND_MIN:OP18_LEFT_HAND_MAX + 1] @property def right_hand(self): """Property representing the keypoints for the right hand.""" return self.all[OP18_RIGHT_HAND_MIN:OP18_RIGHT_HAND_MAX + 1] def __mul__(self, multiplier): """ Multiply the coordinates of all keypoints by a scalar multiplier. :param multiplier: The scalar multiplier. :type multiplier: Union[float, int] :return: New OP18KeyPointSet with scaled coordinates. :rtype: OP18KeyPointSet :raises TypeError: If the type of the multiplier is not float or int. """ if isinstance(multiplier, (float, int)): new_all = self.all.copy() new_all[:, 0] *= multiplier new_all[:, 1] *= multiplier return OP18KeyPointSet(new_all) else: raise TypeError(f'Invalid type of multiplier - {multiplier!r}.') raise TypeError(f'Invalid type of multiplier - {multiplier!r}') def __truediv__(self, divisor): """ Divide the coordinates of all keypoints by a scalar divisor. :param divisor: The scalar divisor. :type divisor: Union[float, int] :return: New OP18KeyPointSet with scaled coordinates. :rtype: OP18KeyPointSet :raises TypeError: If the type of the divisor is not float or int. """ if isinstance(divisor, (float, int)): new_all = self.all.copy() new_all[:, 0] /= divisor new_all[:, 1] /= divisor return OP18KeyPointSet(new_all) return self * (1.0 / divisor) else: raise TypeError(f'Invalid type of divisor - {divisor!r}.') imgutils/pose/visual.py +29 −0 Original line number Diff line number Diff line Loading @@ -79,6 +79,35 @@ def _op18_face(keypoints: OP18KeyPointSet, draw: ImageDraw.ImageDraw, threshold: def op18_visualize(image: ImageTyping, keypoints_list: List[OP18KeyPointSet], threshold: float = 0.3, min_edge_size: Optional[int] = 512, draw_body: bool = True, draw_hands: bool = True, draw_feet: bool = True, draw_face: bool = True) -> Image.Image: """ Visualize the keypoint information of animated characters using the OP18 model on an image. This function takes an input image and a list of OP18 keypoint sets for animated characters, and visualizes the keypoint information on the image. It supports drawing the body, hands, feet, and face of the characters based on the provided keypoint sets. :param image: The input image to visualize. :type image: ImageTyping :param keypoints_list: List of OP18KeyPointSet objects containing keypoint information for characters. :type keypoints_list: List[OP18KeyPointSet] :param threshold: Keypoint detection threshold. Keypoints with a confidence score below this threshold will not be drawn. :type threshold: float, optional :param min_edge_size: Minimum size for the shorter edge of the output image. If the original image is larger, it will be resized. :type min_edge_size: int, optional :param draw_body: If True, draw lines connecting keypoints for the body. Default is True. :type draw_body: bool :param draw_hands: If True, draw lines connecting keypoints for the hands. Default is True. :type draw_hands: bool :param draw_feet: If True, draw lines connecting keypoints for the feet. Default is True. :type draw_feet: bool :param draw_face: If True, draw ellipses around facial keypoints. Default is True. :type draw_face: bool :return: The image with visualized keypoint information. :rtype: Image.Image """ image = load_image(image, force_background='white', mode='RGB') if min_edge_size is not None and min(image.width, image.height) > min_edge_size: r = min(image.width, image.height) / min_edge_size Loading test/pose/conftest.py 0 → 100644 +44 −0 Original line number Diff line number Diff line import numpy as np import pytest from test.testings import get_testfile @pytest.fixture() def img_file_2girls(): return get_testfile('pose', '2girls.png') @pytest.fixture() def pose_data_2girls_0(): return np.load(get_testfile('pose', '2girls_pose_0.npy')) @pytest.fixture() def pose_data_2girls_1(): return np.load(get_testfile('pose', '2girls_pose_1.npy')) @pytest.fixture() def img_file_halfbody(): return get_testfile('pose', 'halfbody.png') @pytest.fixture() def pose_data_halfbody(): return np.load(get_testfile('pose', 'halfbody_pose.npy')) @pytest.fixture() def img_file_rin(): return get_testfile('pose', 'tohsaka_rin.png') @pytest.fixture() def pose_data_rin(): return np.load(get_testfile('pose', 'tohsaka_rin_pose.npy')) @pytest.fixture() def pose_data_nad_rin(): return np.load(get_testfile('pose', 'tohsaka_rin_nad_pose.npy')) Loading
imgutils/pose/__init__.py +2 −2 Original line number Diff line number Diff line from .dwpose import dwpose_estimate from .format import OP18KeyPointSet, OP18_BODY_MAX, OP18_BODY_MIN, OP18_FACE_MAX, OP18_FACE_MIN, OP18_FEET_MAX, \ OP18_FEET_MIN, OP18_LEFT_FOOT_MAX, OP18_LEFT_FOOT_MIN, OP18_LEFT_HAND_MAX, OP18_LEFT_HAND_MIN, \ from .format import OP18KeyPointSet, OP18_BODY_MAX, OP18_BODY_MIN, OP18_FACE_MAX, OP18_FACE_MIN, \ OP18_LEFT_FOOT_MAX, OP18_LEFT_FOOT_MIN, OP18_LEFT_HAND_MAX, OP18_LEFT_HAND_MIN, \ OP18_RIGHT_FOOT_MAX, OP18_RIGHT_FOOT_MIN, OP18_RIGHT_HAND_MAX, OP18_RIGHT_HAND_MIN, OpenPose18 from .visual import op18_visualize
imgutils/pose/dwpose.py +9 −7 Original line number Diff line number Diff line Loading @@ -242,7 +242,7 @@ def _get_warp_matrix(center: np.ndarray, scale: np.ndarray, rot: float, src, dst = src.astype(np.float32), dst.astype(np.float32) if inv: src, dst = dst, src src, dst = dst, src # pragma: no cover warp_mat = cv2.getAffineTransform(src, dst) return warp_mat Loading Loading @@ -325,7 +325,8 @@ def _get_simcc_maximum(simcc_x: np.ndarray, simcc_y: np.ndarray) -> Tuple[np.nda return locs, vals def _output_decode(simcc_x: np.ndarray, simcc_y: np.ndarray, simcc_split_ratio: float) -> Tuple[np.ndarray, np.ndarray]: def _output_decode(simcc_x: np.ndarray, simcc_y: np.ndarray, simcc_split_ratio: float) \ -> Tuple[np.ndarray, np.ndarray]: """Modulate simcc distribution with Gaussian. Args: Loading Loading @@ -397,7 +398,8 @@ def dwpose_estimate(image: ImageTyping, auto_detect: bool = True, np_image = np.array(image) if auto_detect: if out_bboxes is not None: warnings.warn(f'Auto detection enabled, value of out_bboxes will be ignored: {out_bboxes!r}.') warnings.warn('Out bboxes provided, auto detection will be disabled.') else: out_bboxes = [ (x0, y0, x1, y1) for (x0, y0, x1, y1), _, _ in detect_person(image, **(person_detect_cfgs or {})) Loading
imgutils/pose/format.py +50 −15 Original line number Diff line number Diff line Loading @@ -4,17 +4,12 @@ import numpy as np OP18_BODY_MIN = 0 OP18_BODY_MAX = 17 OP18_FEET_MIN = 18 OP18_FEET_MAX = 23 OP18_LEFT_FOOT_MIN = 18 OP18_LEFT_FOOT_MAX = 20 OP18_RIGHT_FOOT_MIN = 21 OP18_RIGHT_FOOT_MAX = 23 OP18_FACE_MIN = 24 OP18_FACE_MAX = 91 OP18_LEFT_HAND_MIN = 92 OP18_LEFT_HAND_MAX = 112 OP18_RIGHT_HAND_MIN = 113 Loading @@ -23,6 +18,15 @@ OP18_RIGHT_HAND_MAX = 135 @unique class OpenPose18(IntEnum): """ Enumeration class representing the OpenPose 18 keypoint indices. The enumeration provides symbolic names for the keypoint indices, making it more readable and maintainable when accessing specific keypoints in the OP18 keypoint set. The keypoint indices are categorized into different body parts such as nose, neck, shoulders, elbows, wrists, hips, knees, ankles, eyes, ears, feet, and hands. """ NOSE = 0 NECK = 1 RIGHT_SHOULDER = 2 Loading Loading @@ -51,51 +55,82 @@ class OpenPose18(IntEnum): class OP18KeyPointSet: def __init__(self, all_): """ Class representing a set of keypoints detected by the OpenPose 18 (OP18) model. This class provides convenient properties to access keypoints for different body parts, including the body, left foot, right foot, face, left hand, and right hand. :param all_: NumPy array containing the coordinates and confidence scores of all keypoints. :type all_: np.ndarray """ def __init__(self, all_: np.ndarray): self.all: np.ndarray = all_ @property def body(self): """Property representing the keypoints for the body.""" return self.all[OP18_BODY_MIN:OP18_BODY_MAX + 1] @property def feet(self): return self.all[OP18_FEET_MIN:OP18_FEET_MAX + 1] @property def left_foot(self): """Property representing the keypoints for the left foot.""" return self.all[OP18_LEFT_FOOT_MIN:OP18_LEFT_FOOT_MAX + 1] @property def right_foot(self): """Property representing the keypoints for the right foot.""" return self.all[OP18_RIGHT_FOOT_MIN:OP18_RIGHT_FOOT_MAX + 1] @property def face(self): """Property representing the keypoints for the face.""" return self.all[OP18_FACE_MIN:OP18_FACE_MAX + 1] @property def left_hand(self): """Property representing the keypoints for the left hand.""" return self.all[OP18_LEFT_HAND_MIN:OP18_LEFT_HAND_MAX + 1] @property def right_hand(self): """Property representing the keypoints for the right hand.""" return self.all[OP18_RIGHT_HAND_MIN:OP18_RIGHT_HAND_MAX + 1] def __mul__(self, multiplier): """ Multiply the coordinates of all keypoints by a scalar multiplier. :param multiplier: The scalar multiplier. :type multiplier: Union[float, int] :return: New OP18KeyPointSet with scaled coordinates. :rtype: OP18KeyPointSet :raises TypeError: If the type of the multiplier is not float or int. """ if isinstance(multiplier, (float, int)): new_all = self.all.copy() new_all[:, 0] *= multiplier new_all[:, 1] *= multiplier return OP18KeyPointSet(new_all) else: raise TypeError(f'Invalid type of multiplier - {multiplier!r}.') raise TypeError(f'Invalid type of multiplier - {multiplier!r}') def __truediv__(self, divisor): """ Divide the coordinates of all keypoints by a scalar divisor. :param divisor: The scalar divisor. :type divisor: Union[float, int] :return: New OP18KeyPointSet with scaled coordinates. :rtype: OP18KeyPointSet :raises TypeError: If the type of the divisor is not float or int. """ if isinstance(divisor, (float, int)): new_all = self.all.copy() new_all[:, 0] /= divisor new_all[:, 1] /= divisor return OP18KeyPointSet(new_all) return self * (1.0 / divisor) else: raise TypeError(f'Invalid type of divisor - {divisor!r}.')
imgutils/pose/visual.py +29 −0 Original line number Diff line number Diff line Loading @@ -79,6 +79,35 @@ def _op18_face(keypoints: OP18KeyPointSet, draw: ImageDraw.ImageDraw, threshold: def op18_visualize(image: ImageTyping, keypoints_list: List[OP18KeyPointSet], threshold: float = 0.3, min_edge_size: Optional[int] = 512, draw_body: bool = True, draw_hands: bool = True, draw_feet: bool = True, draw_face: bool = True) -> Image.Image: """ Visualize the keypoint information of animated characters using the OP18 model on an image. This function takes an input image and a list of OP18 keypoint sets for animated characters, and visualizes the keypoint information on the image. It supports drawing the body, hands, feet, and face of the characters based on the provided keypoint sets. :param image: The input image to visualize. :type image: ImageTyping :param keypoints_list: List of OP18KeyPointSet objects containing keypoint information for characters. :type keypoints_list: List[OP18KeyPointSet] :param threshold: Keypoint detection threshold. Keypoints with a confidence score below this threshold will not be drawn. :type threshold: float, optional :param min_edge_size: Minimum size for the shorter edge of the output image. If the original image is larger, it will be resized. :type min_edge_size: int, optional :param draw_body: If True, draw lines connecting keypoints for the body. Default is True. :type draw_body: bool :param draw_hands: If True, draw lines connecting keypoints for the hands. Default is True. :type draw_hands: bool :param draw_feet: If True, draw lines connecting keypoints for the feet. Default is True. :type draw_feet: bool :param draw_face: If True, draw ellipses around facial keypoints. Default is True. :type draw_face: bool :return: The image with visualized keypoint information. :rtype: Image.Image """ image = load_image(image, force_background='white', mode='RGB') if min_edge_size is not None and min(image.width, image.height) > min_edge_size: r = min(image.width, image.height) / min_edge_size Loading
test/pose/conftest.py 0 → 100644 +44 −0 Original line number Diff line number Diff line import numpy as np import pytest from test.testings import get_testfile @pytest.fixture() def img_file_2girls(): return get_testfile('pose', '2girls.png') @pytest.fixture() def pose_data_2girls_0(): return np.load(get_testfile('pose', '2girls_pose_0.npy')) @pytest.fixture() def pose_data_2girls_1(): return np.load(get_testfile('pose', '2girls_pose_1.npy')) @pytest.fixture() def img_file_halfbody(): return get_testfile('pose', 'halfbody.png') @pytest.fixture() def pose_data_halfbody(): return np.load(get_testfile('pose', 'halfbody_pose.npy')) @pytest.fixture() def img_file_rin(): return get_testfile('pose', 'tohsaka_rin.png') @pytest.fixture() def pose_data_rin(): return np.load(get_testfile('pose', 'tohsaka_rin_pose.npy')) @pytest.fixture() def pose_data_nad_rin(): return np.load(get_testfile('pose', 'tohsaka_rin_nad_pose.npy'))