Spaces:
Sleeping
Sleeping
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) | |