| """
|
| ISL Sign Language Translation - TechMatrix Solvers Initiative
|
| Utility functions for pose processing and visualization
|
| Developed by: TechMatrix Solvers Team
|
| """
|
|
|
| import numpy as np
|
| import math
|
| import cv2
|
| import matplotlib
|
| from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
| from matplotlib.figure import Figure
|
| import matplotlib.pyplot as plt
|
| import copy
|
| import seaborn as sns
|
|
|
|
|
| def pad_image_corner(img, stride, pad_value):
|
| """
|
| Pad image to ensure dimensions are divisible by stride
|
|
|
| Args:
|
| img: Input image array
|
| stride: Stride value for padding calculation
|
| pad_value: Value to use for padding
|
| """
|
| h, w = img.shape[:2]
|
|
|
| pad = [0, 0, 0, 0]
|
| pad[2] = 0 if (h % stride == 0) else stride - (h % stride)
|
| pad[3] = 0 if (w % stride == 0) else stride - (w % stride)
|
|
|
| img_padded = img
|
|
|
|
|
| if pad[0] > 0:
|
| pad_up = np.tile(img_padded[0:1, :, :] * 0 + pad_value, (pad[0], 1, 1))
|
| img_padded = np.concatenate((pad_up, img_padded), axis=0)
|
|
|
| if pad[1] > 0:
|
| pad_left = np.tile(img_padded[:, 0:1, :] * 0 + pad_value, (1, pad[1], 1))
|
| img_padded = np.concatenate((pad_left, img_padded), axis=1)
|
|
|
| if pad[2] > 0:
|
| pad_down = np.tile(img_padded[-2:-1, :, :] * 0 + pad_value, (pad[2], 1, 1))
|
| img_padded = np.concatenate((img_padded, pad_down), axis=0)
|
|
|
| if pad[3] > 0:
|
| pad_right = np.tile(img_padded[:, -2:-1, :] * 0 + pad_value, (1, pad[3], 1))
|
| img_padded = np.concatenate((img_padded, pad_right), axis=1)
|
|
|
| return img_padded, pad
|
|
|
|
|
| def transfer_model_weights(model, model_weights):
|
| """
|
| Transfer weights from caffe model to pytorch model format
|
|
|
| Args:
|
| model: PyTorch model
|
| model_weights: Dictionary of weights from caffe model
|
| """
|
| transferred_weights = {}
|
| for weights_name in model.state_dict().keys():
|
| if len(weights_name.split('.')) > 4:
|
| transferred_weights[weights_name] = model_weights['.'.join(
|
| weights_name.split('.')[3:])]
|
| else:
|
| transferred_weights[weights_name] = model_weights['.'.join(
|
| weights_name.split('.')[1:])]
|
| return transferred_weights
|
|
|
|
|
| def draw_body_pose_visualization(canvas, candidate, subset, model_type='body25'):
|
| """
|
| Draw body pose keypoints and connections on image
|
|
|
| Args:
|
| canvas: Image to draw on
|
| candidate: Detected keypoint candidates
|
| subset: Valid keypoint connections
|
| model_type: Type of pose model ('body25' or 'coco')
|
| """
|
| stick_width = 4
|
|
|
| if model_type == 'body25':
|
| limb_sequence = [
|
| [1,0],[1,2],[2,3],[3,4],[1,5],[5,6],[6,7],[1,8],[8,9],[9,10],
|
| [10,11],[8,12],[12,13],[13,14],[0,15],[0,16],[15,17],[16,18],
|
| [11,24],[11,22],[14,21],[14,19],[22,23],[19,20]
|
| ]
|
| num_joints = 25
|
| else:
|
| limb_sequence = [
|
| [1, 2], [1, 5], [2, 3], [3, 4], [5, 6], [6, 7], [1, 8], [8, 9],
|
| [9, 10], [1, 11], [11, 12], [12, 13], [1, 0], [0, 14], [14, 16],
|
| [0, 15], [15, 17], [2, 16], [5, 17]
|
| ]
|
| num_joints = 18
|
|
|
|
|
| colors = [
|
| [255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0],
|
| [85, 255, 0], [0, 255, 0], [0, 255, 85], [0, 255, 170], [0, 255, 255],
|
| [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], [170, 0, 255],
|
| [255, 0, 255], [255, 0, 170], [255, 0, 85], [255,255,0], [255,255,85],
|
| [255,255,170], [255,255,255], [170,255,255], [85,255,255], [0,255,255]
|
| ]
|
|
|
|
|
| for i in range(num_joints):
|
| for n in range(len(subset)):
|
| index = int(subset[n][i])
|
| if index == -1:
|
| continue
|
| x, y = candidate[index][0:2]
|
| cv2.circle(canvas, (int(x), int(y)), 4, colors[i], thickness=-1)
|
|
|
|
|
| for i in range(num_joints - 1):
|
| for n in range(len(subset)):
|
| index = subset[n][np.array(limb_sequence[i])]
|
| if -1 in index:
|
| continue
|
| current_canvas = canvas.copy()
|
| Y = candidate[index.astype(int), 0]
|
| X = candidate[index.astype(int), 1]
|
| mean_x = np.mean(X)
|
| mean_y = np.mean(Y)
|
| length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5
|
| angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1]))
|
| polygon = cv2.ellipse2Poly((int(mean_y), int(mean_x)),
|
| (int(length / 2), stick_width),
|
| int(angle), 0, 360, 1)
|
| cv2.fillConvexPoly(current_canvas, polygon, colors[i])
|
| canvas = cv2.addWeighted(canvas, 0.4, current_canvas, 0.6, 0)
|
|
|
| return canvas
|
|
|
|
|
| def extract_body_pose_data(candidate, subset, model_type='body25'):
|
| """
|
| Extract body pose data without drawing
|
|
|
| Returns:
|
| tuple: (keypoint_circles, limb_sticks) data for further processing
|
| """
|
| stick_width = 4
|
|
|
| if model_type == 'body25':
|
| limb_sequence = [
|
| [1,0],[1,2],[2,3],[3,4],[1,5],[5,6],[6,7],[1,8],[8,9],[9,10],
|
| [10,11],[8,12],[12,13],[13,14],[0,15],[0,16],[15,17],[16,18],
|
| [11,24],[11,22],[14,21],[14,19],[22,23],[19,20]
|
| ]
|
| num_joints = 25
|
| else:
|
| limb_sequence = [
|
| [1, 2], [1, 5], [2, 3], [3, 4], [5, 6], [6, 7], [1, 8], [8, 9],
|
| [9, 10], [1, 11], [11, 12], [12, 13], [1, 0], [0, 14], [14, 16],
|
| [0, 15], [15, 17], [2, 16], [5, 17]
|
| ]
|
| num_joints = 18
|
|
|
|
|
| keypoint_circles = []
|
| for i in range(num_joints):
|
| for n in range(len(subset)):
|
| index = int(subset[n][i])
|
| if index == -1:
|
| continue
|
| x, y = candidate[index][0:2]
|
| keypoint_circles.append((x, y))
|
|
|
|
|
| limb_sticks = []
|
| for i in range(num_joints - 1):
|
| for n in range(len(subset)):
|
| index = subset[n][np.array(limb_sequence[i])]
|
| if -1 in index:
|
| continue
|
| Y = candidate[index.astype(int), 0]
|
| X = candidate[index.astype(int), 1]
|
| mean_x = np.mean(X)
|
| mean_y = np.mean(Y)
|
| length = ((X[0] - X[1]) ** 2 + (Y[0] - Y[1]) ** 2) ** 0.5
|
| angle = math.degrees(math.atan2(X[0] - X[1], Y[0] - Y[1]))
|
| limb_sticks.append((mean_y, mean_x, angle, length))
|
|
|
| return keypoint_circles, limb_sticks
|
|
|
|
|
| def draw_hand_pose_visualization(canvas, all_hand_peaks, show_numbers=False):
|
| """
|
| Draw hand pose keypoints and connections
|
|
|
| Args:
|
| canvas: Image to draw on
|
| all_hand_peaks: Detected hand keypoints for both hands
|
| show_numbers: Whether to show keypoint numbers
|
| """
|
| edges = [
|
| [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]
|
| ]
|
|
|
| fig = Figure(figsize=plt.figaspect(canvas))
|
| fig.subplots_adjust(0, 0, 1, 1)
|
| bg = FigureCanvas(fig)
|
| ax = fig.subplots()
|
| ax.axis('off')
|
| ax.imshow(canvas)
|
|
|
| width, height = ax.figure.get_size_inches() * ax.figure.get_dpi()
|
|
|
| for peaks in all_hand_peaks:
|
| for ie, e in enumerate(edges):
|
| if np.sum(np.all(peaks[e], axis=1) == 0) == 0:
|
| x1, y1 = peaks[e[0]]
|
| x2, y2 = peaks[e[1]]
|
| ax.plot([x1, x2], [y1, y2],
|
| color=matplotlib.colors.hsv_to_rgb([ie/float(len(edges)), 1.0, 1.0]))
|
|
|
| for i, keypoint in enumerate(peaks):
|
| x, y = keypoint
|
| ax.plot(x, y, 'r.')
|
| if show_numbers:
|
| ax.text(x, y, str(i))
|
|
|
| bg.draw()
|
| canvas = np.fromstring(bg.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3)
|
| return canvas
|
|
|
|
|
| def extract_hand_pose_data(all_hand_peaks, show_numbers=False):
|
| """
|
| Extract hand pose data without drawing
|
|
|
| Returns:
|
| tuple: (hand_edges, hand_peaks) data for further processing
|
| """
|
| edges = [
|
| [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]
|
| ]
|
|
|
| export_edges = [[], []]
|
| export_peaks = [[], []]
|
|
|
| for idx, peaks in enumerate(all_hand_peaks):
|
| for ie, e in enumerate(edges):
|
| if np.sum(np.all(peaks[e], axis=1) == 0) == 0:
|
| x1, y1 = peaks[e[0]]
|
| x2, y2 = peaks[e[1]]
|
| export_edges[idx].append((ie, (x1, y1), (x2, y2)))
|
|
|
| for i, keypoint in enumerate(peaks):
|
| x, y = keypoint
|
| export_peaks[idx].append((x, y, str(i)))
|
|
|
| return export_edges, export_peaks
|
|
|
|
|
| def detect_hand_regions(candidate, subset, original_image):
|
| """
|
| Detect hand regions based on body pose keypoints
|
|
|
| Args:
|
| candidate: Body pose candidates
|
| subset: Valid body pose connections
|
| original_image: Original input image
|
|
|
| Returns:
|
| List of detected hand regions [x, y, width, is_left_hand]
|
| """
|
| ratio_wrist_elbow = 0.33
|
| detection_results = []
|
|
|
| image_height, image_width = original_image.shape[0:2]
|
|
|
| for person in subset.astype(int):
|
|
|
| has_left_hand = np.sum(person[[5, 6, 7]] == -1) == 0
|
| has_right_hand = np.sum(person[[2, 3, 4]] == -1) == 0
|
|
|
| if not (has_left_hand or has_right_hand):
|
| continue
|
|
|
| hands = []
|
|
|
|
|
| if has_left_hand:
|
| left_shoulder_idx, left_elbow_idx, left_wrist_idx = person[[5, 6, 7]]
|
| x1, y1 = candidate[left_shoulder_idx][:2]
|
| x2, y2 = candidate[left_elbow_idx][:2]
|
| x3, y3 = candidate[left_wrist_idx][:2]
|
| hands.append([x1, y1, x2, y2, x3, y3, True])
|
|
|
|
|
| if has_right_hand:
|
| right_shoulder_idx, right_elbow_idx, right_wrist_idx = person[[2, 3, 4]]
|
| x1, y1 = candidate[right_shoulder_idx][:2]
|
| x2, y2 = candidate[right_elbow_idx][:2]
|
| x3, y3 = candidate[right_wrist_idx][:2]
|
| hands.append([x1, y1, x2, y2, x3, y3, False])
|
|
|
| for x1, y1, x2, y2, x3, y3, is_left in hands:
|
|
|
| x = x3 + ratio_wrist_elbow * (x3 - x2)
|
| y = y3 + ratio_wrist_elbow * (y3 - y2)
|
|
|
| distance_wrist_elbow = math.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2)
|
| distance_elbow_shoulder = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
| width = 1.5 * max(distance_wrist_elbow, 0.9 * distance_elbow_shoulder)
|
|
|
|
|
| x -= width / 2
|
| y -= width / 2
|
|
|
|
|
| x = max(0, x)
|
| y = max(0, y)
|
|
|
| width1 = width if x + width <= image_width else image_width - x
|
| width2 = width if y + width <= image_height else image_height - y
|
| width = min(width1, width2)
|
|
|
|
|
| if width >= 20:
|
| detection_results.append([int(x), int(y), int(width), is_left])
|
|
|
| return detection_results
|
|
|
|
|
| def render_stick_model(original_img, keypoint_circles, limb_sticks, hand_edges, hand_peaks):
|
| """
|
| Render complete stick model with body and hand poses
|
|
|
| Args:
|
| original_img: Original image
|
| keypoint_circles: Body keypoint coordinates
|
| limb_sticks: Body limb stick data
|
| hand_edges: Hand connection data
|
| hand_peaks: Hand keypoint data
|
| """
|
| canvas = copy.deepcopy(original_img)
|
|
|
| colors = [
|
| [255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0],
|
| [85, 255, 0], [0, 255, 0], [0, 255, 85], [0, 255, 170], [0, 255, 255],
|
| [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], [170, 0, 255],
|
| [255, 0, 255], [255, 0, 170], [255, 0, 85], [255,255,0], [255,255,85],
|
| [255,255,170], [255,255,255], [170,255,255], [85,255,255], [0,255,255]
|
| ]
|
| stick_width = 4
|
|
|
|
|
| for idx, (mean_x, mean_y, angle, length) in enumerate(limb_sticks):
|
| current_canvas = canvas.copy()
|
| polygon = cv2.ellipse2Poly(
|
| (int(mean_x), int(mean_y)),
|
| (int(length / 2), stick_width),
|
| int(angle), 0, 360, 1
|
| )
|
| cv2.fillConvexPoly(current_canvas, polygon, colors[idx])
|
| canvas = cv2.addWeighted(canvas, 0.4, current_canvas, 0.6, 0)
|
|
|
|
|
| for idx, (x, y) in enumerate(keypoint_circles):
|
| cv2.circle(canvas, (int(x), int(y)), 4, colors[idx], thickness=-1)
|
|
|
|
|
| fig = Figure(figsize=plt.figaspect(canvas))
|
| fig.subplots_adjust(0, 0, 1, 1)
|
| ax = fig.subplots()
|
| ax.axis('off')
|
| ax.imshow(canvas)
|
|
|
| edges = [
|
| [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]
|
| ]
|
|
|
| for hand_edge_set in hand_edges:
|
| for (ie, (x1, y1), (x2, y2)) in hand_edge_set:
|
| ax.plot([x1, x2], [y1, y2],
|
| color=matplotlib.colors.hsv_to_rgb([ie/float(len(edges)), 1.0, 1.0]))
|
|
|
| for hand_peak_set in hand_peaks:
|
| for (x, y, text) in hand_peak_set:
|
| ax.plot(x, y, 'r.')
|
|
|
|
|
| bg = FigureCanvas(fig)
|
| bg.draw()
|
|
|
| width, height = fig.get_size_inches() * fig.get_dpi()
|
| buf = bg.buffer_rgba()
|
| canvas = np.frombuffer(buf, dtype=np.uint8).reshape(int(height), int(width), 4)
|
| canvas = canvas[:, :, :3]
|
|
|
| plt.close(fig)
|
| return cv2.resize(canvas, (math.ceil(width), math.ceil(height)))
|
|
|
|
|
| def create_bar_plot_visualization(image, predictions, title, orig_img):
|
| """
|
| Create bar plot visualization below the image
|
|
|
| Args:
|
| image: Input image
|
| predictions: Dictionary of prediction probabilities
|
| title: Plot title
|
| orig_img: Original image for sizing
|
| """
|
|
|
| if not predictions or len(predictions) == 0:
|
|
|
| fig, ax = plt.subplots(figsize=(orig_img.shape[1]/100, orig_img.shape[0]/200), dpi=100)
|
| ax.text(0.5, 0.5, 'No Predictions Available',
|
| horizontalalignment='center', verticalalignment='center',
|
| transform=ax.transAxes, fontsize=14)
|
| ax.set_title(title)
|
| ax.set_xlim(0, 1)
|
| ax.set_ylim(0, 1)
|
| ax.set_xticks([])
|
| ax.set_yticks([])
|
| else:
|
| fig, ax = plt.subplots(figsize=(orig_img.shape[1]/100, orig_img.shape[0]/200), dpi=100)
|
| plt.title(title)
|
|
|
|
|
| labels = list(predictions.keys())
|
| probabilities = list(predictions.values())
|
|
|
|
|
| sns.barplot(x=labels, y=probabilities, ax=ax)
|
|
|
| fig.canvas.draw()
|
|
|
|
|
| plot_image = np.array(fig.canvas.renderer.buffer_rgba())[:, :, :3]
|
| plt.close(fig)
|
|
|
|
|
| combined_image = np.vstack((image, cv2.resize(plot_image, (image.shape[1], plot_image.shape[0]))))
|
|
|
| return combined_image
|
|
|
|
|
| def add_bottom_padding(image, pad_value, pad_height):
|
| """
|
| Add padding to the bottom of an image
|
|
|
| Args:
|
| image: Input image
|
| pad_value: Color value for padding (tuple or int)
|
| pad_height: Height of padding to add
|
| """
|
| height, width, channels = image.shape
|
| padding = np.zeros((pad_height, width, channels), dtype=image.dtype)
|
| padding[:, :, :] = pad_value
|
|
|
| return np.vstack((image, padding))
|
|
|
|
|
| def find_array_maximum(array):
|
| """
|
| Get maximum index of 2D array
|
|
|
| Args:
|
| array: 2D numpy array
|
|
|
| Returns:
|
| tuple: (row_index, col_index) of maximum value
|
| """
|
| array_index = array.argmax(1)
|
| array_value = array.max(1)
|
| i = array_value.argmax()
|
| j = array_index[i]
|
| return i, j |