FoundHand / utils.py
Chaerin5's picture
init
49f816b
raw
history blame
10.7 kB
import io
import cv2
import numpy as np
from PIL import Image
from skimage.transform import resize
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def draw_hand3d(keypoints):
# Define the connections between keypoints as tuples (start, end)
bones = [
((0, 1), 'red'), ((1, 2), 'green'), ((2, 3), 'blue'), ((3, 4), 'purple'),
((0, 5), 'orange'), ((5, 6), 'pink'), ((6, 7), 'brown'), ((7, 8), 'cyan'),
((0, 9), 'yellow'), ((9, 10), 'magenta'), ((10, 11), 'lime'), ((11, 12), 'blueviolet'),
((0, 13), 'olive'), ((13, 14), 'teal'), ((14, 15), 'crimson'), ((15, 16), 'cornsilk'),
((0, 17), 'aqua'), ((17, 18), 'silver'), ((18, 19), 'maroon'), ((19, 20), 'fuchsia')
]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot the bones
for bone, color in bones:
start_point = keypoints[bone[0], :]
end_point = keypoints[bone[1], :]
ax.plot([start_point[0], end_point[0]],
[start_point[1], end_point[1]],
[start_point[2], end_point[2]], color=color)
ax.scatter(keypoints[:, 0], keypoints[:, 1], keypoints[:, 2], color='gray', s=15)
# Set the aspect ratio to be equal
max_range = np.array([keypoints[:,0].max()-keypoints[:,0].min(),
keypoints[:,1].max()-keypoints[:,1].min(),
keypoints[:,2].max()-keypoints[:,2].min()]).max() / 2.0
mid_x = (keypoints[:,0].max()+keypoints[:,0].min()) * 0.5
mid_y = (keypoints[:,1].max()+keypoints[:,1].min()) * 0.5
mid_z = (keypoints[:,2].max()+keypoints[:,2].min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)
# Set labels for axes
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
def visualize_hand(joints, img):
# Define the connections between joints for drawing lines and their corresponding colors
connections = [
((0, 1), 'red'), ((1, 2), 'green'), ((2, 3), 'blue'), ((3, 4), 'purple'),
((0, 5), 'orange'), ((5, 6), 'pink'), ((6, 7), 'brown'), ((7, 8), 'cyan'),
((0, 9), 'yellow'), ((9, 10), 'magenta'), ((10, 11), 'lime'), ((11, 12), 'indigo'),
((0, 13), 'olive'), ((13, 14), 'teal'), ((14, 15), 'navy'), ((15, 16), 'gray'),
((0, 17), 'lavender'), ((17, 18), 'silver'), ((18, 19), 'maroon'), ((19, 20), 'fuchsia')
]
H, W, C = img.shape
# Create a figure and axis
plt.figure()
ax = plt.gca()
# Plot joints as points
ax.imshow(img)
ax.scatter(joints[:, 0], joints[:, 1], color='white', s=15)
# Plot lines connecting joints with different colors for each bone
for connection, color in connections:
joint1 = joints[connection[0]]
joint2 = joints[connection[1]]
ax.plot([joint1[0], joint2[0]], [joint1[1], joint2[1]], color=color)
ax.set_xlim([0, W])
ax.set_ylim([0, H])
ax.grid(False)
ax.set_axis_off()
ax.invert_yaxis()
plt.subplots_adjust(wspace=0.01)
plt.show()
def draw_hand_skeleton(joints, image_size, thickness=5):
# Create a blank white image
image = np.zeros((image_size[0], image_size[1]), dtype=np.uint8)
# Define the connections between joints
connections = [
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(0, 5),
(5, 6),
(6, 7),
(7, 8),
(0, 9),
(9, 10),
(10, 11),
(11, 12),
(0, 13),
(13, 14),
(14, 15),
(15, 16),
(0, 17),
(17, 18),
(18, 19),
(19, 20),
]
# Draw lines connecting joints
for connection in connections:
joint1 = joints[connection[0]].astype("int")
joint2 = joints[connection[1]].astype("int")
cv2.line(image, tuple(joint1), tuple(joint2), color=1, thickness=thickness)
return image
def draw_hand(joints, img):
# Define the connections between joints for drawing lines and their corresponding colors
connections = [
((0, 1), 'red'), ((1, 2), 'green'), ((2, 3), 'blue'), ((3, 4), 'purple'),
((0, 5), 'orange'), ((5, 6), 'pink'), ((6, 7), 'brown'), ((7, 8), 'cyan'),
((0, 9), 'yellow'), ((9, 10), 'magenta'), ((10, 11), 'lime'), ((11, 12), 'indigo'),
((0, 13), 'olive'), ((13, 14), 'teal'), ((14, 15), 'navy'), ((15, 16), 'gray'),
((0, 17), 'lavender'), ((17, 18), 'silver'), ((18, 19), 'maroon'), ((19, 20), 'fuchsia')
]
H, W, C = img.shape
# Create a figure and axis with the same size as the input image
fig, ax = plt.subplots(figsize=(W / 100, H / 100), dpi=100)
# Plot joints as points
ax.imshow(img)
ax.scatter(joints[:, 0], joints[:, 1], color='white', s=15)
# Plot lines connecting joints with different colors for each bone
for connection, color in connections:
joint1 = joints[connection[0]]
joint2 = joints[connection[1]]
ax.plot([joint1[0], joint2[0]], [joint1[1], joint2[1]], color=color)
ax.set_xlim([0, W])
ax.set_ylim([0, H])
ax.grid(False)
ax.set_axis_off()
ax.invert_yaxis()
plt.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0.01, hspace=0.01)
# Save the plot to a buffer
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
plt.close(fig) # Close the figure to free memory
# Load the image from the buffer into a PIL image and then into a numpy array
buf.seek(0)
img_arr = np.array(Image.open(buf))
return img_arr[..., :3]
def keypoint_heatmap(pts, size, var=1.0):
H, W = size
x = np.linspace(0, W - 1, W)
y = np.linspace(0, H - 1, H)
xv, yv = np.meshgrid(x, y)
grid = np.stack((xv, yv), axis=-1)
# Expanding dims for broadcasting subtraction between pts and every grid position
modes_exp = np.expand_dims(np.expand_dims(pts, axis=1), axis=1)
# Calculating squared difference
diff = grid - modes_exp
normal = np.exp(-np.sum(diff**2, axis=-1) / (2 * var)) / (
2.0 * np.pi * var
)
return normal
def check_keypoints_validity(keypoints, image_size):
H, W = image_size
# Check if x coordinates are valid: 0 < x < W
valid_x = (keypoints[:, 0] > 0) & (keypoints[:, 0] < W)
# Check if y coordinates are valid: 0 < y < H
valid_y = (keypoints[:, 1] > 0) & (keypoints[:, 1] < H)
# Combine the validity checks for both x and y
valid_keypoints = valid_x & valid_y
# Convert boolean array to integer (1 for True, 0 for False)
return valid_keypoints.astype(int)
def find_bounding_box(mask, margin=30):
"""Find the bounding box of a binary mask. Return None if the mask is empty."""
rows = np.any(mask, axis=1)
cols = np.any(mask, axis=0)
if not rows.any() or not cols.any(): # Mask is empty
return None
ymin, ymax = np.where(rows)[0][[0, -1]]
xmin, xmax = np.where(cols)[0][[0, -1]]
xmin -= margin
xmax += margin
ymin -= margin
ymax += margin
return xmin, ymin, xmax, ymax
def adjust_box_to_image(xmin, ymin, xmax, ymax, image_width, image_height):
"""Adjust the bounding box to fit within the image boundaries."""
box_width = xmax - xmin
box_height = ymax - ymin
# Determine the side length of the square (the larger of the two dimensions)
side_length = max(box_width, box_height)
# Adjust to maintain a square by expanding or contracting sides
xmin = max(0, xmin - (side_length - box_width) // 2)
xmax = xmin + side_length
ymin = max(0, ymin - (side_length - box_height) // 2)
ymax = ymin + side_length
# Ensure the box is still within the image boundaries after adjustments
if xmax > image_width:
shift = xmax - image_width
xmin -= shift
xmax -= shift
if ymax > image_height:
shift = ymax - image_height
ymin -= shift
ymax -= shift
# After shifting, double-check if any side is out-of-bounds and adjust if necessary
xmin = max(0, xmin)
ymin = max(0, ymin)
xmax = min(image_width, xmax)
ymax = min(image_height, ymax)
# It's possible the adjustments made the box not square (due to boundary constraints),
# so we might need to slightly adjust the size to keep it as square as possible
# This could involve a final adjustment based on the specific requirements,
# like reducing the side length to fit or deciding which dimension to prioritize.
return xmin, ymin, xmax, ymax
def scale_keypoint(keypoint, original_size, target_size):
"""Scale a keypoint based on the resizing of the image."""
keypoint_copy = keypoint.copy()
keypoint_copy[:, 0] *= target_size[0] / original_size[0]
keypoint_copy[:, 1] *= target_size[1] / original_size[1]
return keypoint_copy
def crop_and_adjust_image_and_annotations(image, hand_mask, obj_mask, hand_pose, intrinsics, target_size=(512, 512)):
# Find bounding boxes for each mask, handling potentially empty masks
xmin, ymin, xmax, ymax = find_bounding_box(hand_mask) if np.any(hand_mask) else None
# Adjust bounding box to fit within the image and be square
xmin, ymin, xmax, ymax = adjust_box_to_image(xmin, ymin, xmax, ymax, image.shape[1], image.shape[0])
# Crop the image and mask
# masked_hand_image = (image * np.maximum(hand_mask, obj_mask)[..., None].astype(float)).astype(np.uint8)
cropped_hand_image = image[ymin:ymax, xmin:xmax]
cropped_hand_mask = hand_mask[ymin:ymax, xmin:xmax].astype(np.uint8)
cropped_obj_mask = obj_mask[ymin:ymax, xmin:xmax].astype(np.uint8)
# Resize the image
resized_image = resize(cropped_hand_image, target_size, anti_aliasing=True)
resized_hand_mask = cv2.resize(cropped_hand_mask, dsize=target_size, interpolation=cv2.INTER_NEAREST)
resized_obj_mask = cv2.resize(cropped_obj_mask, dsize=target_size, interpolation=cv2.INTER_NEAREST)
# adjust and scale 2d keypoints
for hand_type, kps2d in hand_pose.items():
kps2d[:, 0] -= xmin
kps2d[:, 1] -= ymin
hand_pose[hand_type] = scale_keypoint(kps2d, (xmax - xmin, ymax - ymin), target_size)
# adjust instrinsics
resized_intrinsics= np.array(intrinsics, copy=True)
resized_intrinsics[0, 2] -= xmin
resized_intrinsics[1, 2] -= ymin
resized_intrinsics[0, :] *= target_size[0] / (xmax - xmin)
resized_intrinsics[1, :] *= target_size[1] / (ymax - ymin)
return (resized_image, resized_hand_mask, resized_obj_mask, hand_pose, resized_intrinsics)