Commit 05038751 authored by narugo1992's avatar narugo1992
Browse files

dev(narugo): add unittest for dwpose

parent 87daa6c7
Loading
Loading
Loading
Loading
+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
+9 −7
Original line number Diff line number Diff line
@@ -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
@@ -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:
@@ -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 {}))
+50 −15
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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}.')
+29 −0
Original line number Diff line number Diff line
@@ -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

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