FoundHand / utils.py
Chaerin5's picture
init
49f816b
Raw
History Blame Contribute Delete
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)