Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sports2D/Demo/Config_demo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ keypoint_number_threshold = 0.3 # Person will be ignored if the number of good k

[angles]
display_angle_values_on = ['body','list'] # 'body', 'list', ['body', 'list'], []. Display angle values on the body, as a list in the upper left of the image, both, or do not display them.
fontSize = 0.3
fontSize = 'auto' # 'auto' or your preference

# Select joint angles among
# ['Right ankle', 'Left ankle', 'Right knee', 'Left knee', 'Right hip', 'Left hip', 'Right shoulder', 'Left shoulder', 'Right elbow', 'Left elbow', 'Right wrist', 'Left wrist']
Expand Down
2 changes: 1 addition & 1 deletion Sports2D/Utilities/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,4 @@ def euclidean_distance(q1, q2):

euc_dist = np.sqrt(np.sum( [d**2 for d in dist]))

return euc_dist
return euc_dist
80 changes: 50 additions & 30 deletions Sports2D/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@
(125, 0, 0), (0, 125, 0), (0, 0, 125), (125, 125, 0), (125, 0, 125), (0, 125, 125),
(255, 125, 125), (125, 255, 125), (125, 125, 255), (255, 255, 125), (255, 125, 255), (125, 255, 255), (125, 125, 125),
(255, 0, 125), (255, 125, 0), (0, 125, 255), (0, 255, 125), (125, 0, 255), (125, 255, 0), (0, 255, 0)]
thickness = 1


## AUTHORSHIP INFORMATION
Expand Down Expand Up @@ -479,8 +478,26 @@ def sort_people_rtmlib(pose_tracker, keypoints, scores):

return sorted_keypoints, sorted_scores

def dynamic_fontSize(width, height, base_fontSize=0.3, base_dimension=1768):
'''
Dynamically adjust font size according to the max dimension (width or height).
'''
# Calculate scale
scale = max(width, height) / base_dimension

def draw_dotted_line(img, start, direction, length, color=(0, 255, 0), gap=7, dot_length=3, thickness=thickness):
# Adjust font size with a fixed scale factor of 1.2 for better readability
adjusted_fontSize = base_fontSize * scale * 1.2

# Ensure font size is within the allowed range but, not used parameters for boundaries
return max(min(adjusted_fontSize, 1), 0.2)

def dynamic_thickness(fontSize, scale_factor=5):
'''
Dynamically adjust the thickness of the lines according to the font size.
'''
return int(fontSize * scale_factor) # 5 is a scale factor to make sure the thickness is at least 1 (0.2 * 5 = 1)

def draw_dotted_line(img, start, direction, length, color=(0, 255, 0), gap=7, dot_length=3, thickness=1):
'''
Draw a dotted line with on a cv2 image

Expand All @@ -501,7 +518,7 @@ def draw_dotted_line(img, start, direction, length, color=(0, 255, 0), gap=7, do
for i in range(0, length, gap):
line_start = start + direction * i
line_end = line_start + direction * dot_length
cv2.line(img, tuple(line_start.astype(int)), tuple(line_end.astype(int)), color, thickness)
cv2.line(img, tuple(line_start.astype(int)), tuple(line_end.astype(int)), color, thickness+1 if thickness<2 else thickness) # +1 to make sure the line is more visible when the thickness is too thin (only for lines not for characters)


def draw_bounding_box(img, X, Y, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], fontSize=0.3, thickness=1):
Expand Down Expand Up @@ -532,15 +549,15 @@ def draw_bounding_box(img, X, Y, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)],
if y_max > img.shape[0]: y_max = img.shape[0]

# Draw rectangles
cv2.rectangle(img, (x_min-25, y_min-25), (x_max+25, y_max+25), color, thickness)
cv2.rectangle(img, (x_min-25, y_min-25), (x_max+25, y_max+25), color, thickness+1 if thickness<2 else thickness)

# Write person ID
cv2.putText(img, str(i), (x_min-30, y_min-30), cv2.FONT_HERSHEY_SIMPLEX, fontSize+1, color, 2, cv2.LINE_AA)

return img


def draw_skel(img, X, Y, model, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)]):
def draw_skel(img, X, Y, model, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], thickness=1):
'''
Draws keypoints and skeleton for each person.
Skeletons have a different color for each person.
Expand Down Expand Up @@ -569,14 +586,14 @@ def draw_skel(img, X, Y, model, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)]):
c = next(color_cycle)
if not np.isnan(x).all():
[cv2.line(img,
(int(x[n[0]]), int(y[n[0]])), (int(x[n[1]]), int(y[n[1]])), c, thickness)
(int(x[n[0]]), int(y[n[0]])), (int(x[n[1]]), int(y[n[1]])), c, thickness+1 if thickness<2 else thickness)
for n in node_pairs
if not (np.isnan(x[n[0]]) or np.isnan(y[n[0]]) or np.isnan(x[n[1]]) or np.isnan(y[n[1]]))]

return img


def draw_keypts(img, X, Y, scores, cmap_str='RdYlGn'):
def draw_keypts(img, X, Y, scores, cmap_str='RdYlGn', thickness=1):
'''
Draws keypoints and skeleton for each person.
Keypoints' colors depend on their score.
Expand Down Expand Up @@ -628,17 +645,17 @@ def draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, keypoints_
- img: image with angles
'''

color_cycle = it.cycle(colors)
color_cycle = it.cycle(colors)
for person_id, (X,Y,angles, X_flipped) in enumerate(zip(valid_X, valid_Y, valid_angles, valid_X_flipped)):
c = next(color_cycle)
if not np.isnan(X).all():
# person label
if 'list' in display_angle_values_on:
person_label_position = (int(10 + fontSize*150/0.3*person_id), int(fontSize*50))
cv2.putText(img, f'person {person_id}', person_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (255,255,255), thickness+1, cv2.LINE_AA)
cv2.putText(img, f'person {person_id}', person_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, c, thickness, cv2.LINE_AA)
cv2.putText(img, f'person {person_id}', person_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (255,255,255), thickness*3, cv2.LINE_AA) # The outline of the font is three times of the thickness of font(It seems better to see the font)
cv2.putText(img, f'person {person_id}', person_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, c, thickness, cv2.LINE_AA)

# angle lines, names and values
# angle lines, names and values
ang_label_line = 1
for k, ang in enumerate(angles):
if not np.isnan(ang):
Expand All @@ -648,30 +665,30 @@ def draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, keypoints_
ang_coords = np.array([[X[keypoints_ids[keypoints_names.index(kpt)]], Y[keypoints_ids[keypoints_names.index(kpt)]]] for kpt in ang_params[0] if kpt in keypoints_names])
X_flipped_coords = [X_flipped[keypoints_ids[keypoints_names.index(kpt)]] for kpt in ang_params[0] if kpt in keypoints_names]
flip = -1 if any(x_flipped < 0 for x_flipped in X_flipped_coords) else 1
flip = 1 if ang_name in ['pelvis', 'trunk', 'shoulders'] else flip
flip = 1 if ang_name in ['pelvis', 'trunk', 'shoulders'] else flip
right_angle = True if ang_params[2]==90 else False

# Draw angle
if 'body' in display_angle_values_on:
if 'body' in display_angle_values_on:
if len(ang_coords) == 2: # segment angle
app_point, vec = draw_segment_angle(img, ang_coords, flip)
app_point, vec = draw_segment_angle(img, ang_coords, flip, thickness=thickness)
write_angle_on_body(img, ang, app_point, vec, np.array([1,0]), dist=20, color=(255,255,255), fontSize=fontSize, thickness=thickness)

else: # joint angle
app_point, vec1, vec2 = draw_joint_angle(img, ang_coords, flip, right_angle)
app_point, vec1, vec2 = draw_joint_angle(img, ang_coords, flip, right_angle, thickness=thickness)
write_angle_on_body(img, ang, app_point, vec1, vec2, dist=40, color=(0,255,0), fontSize=fontSize, thickness=thickness)

# Write angle as a list on image with progress bar
if 'list' in display_angle_values_on:
if len(ang_coords) == 2: # segment angle
if 'list' in display_angle_values_on:
if len(ang_coords) == 2: # segment angle
ang_label_line = write_angle_as_list(img, ang, ang_name, person_label_position, ang_label_line, color = (255,255,255), fontSize=fontSize, thickness=thickness)
else:
ang_label_line = write_angle_as_list(img, ang, ang_name, person_label_position, ang_label_line, color = (0,255,0), fontSize=fontSize, thickness=thickness)

return img


def draw_segment_angle(img, ang_coords, flip):
def draw_segment_angle(img, ang_coords, flip, thickness=1):
'''
Draw a segment angle on the image.

Expand All @@ -694,15 +711,15 @@ def draw_segment_angle(img, ang_coords, flip):
if (segment_direction==0).all():
return app_point, np.array([0,0])
unit_segment_direction = segment_direction/np.linalg.norm(segment_direction)
cv2.line(img, app_point, np.int32(app_point+unit_segment_direction*20), (255,255,255), thickness)
cv2.line(img, app_point, np.int32(app_point+unit_segment_direction*20), (255,255,255), thickness+1 if thickness<2 else thickness)

# horizontal line
cv2.line(img, app_point, (np.int32(app_point[0])+flip*20, np.int32(app_point[1])), (255,255,255), thickness)
cv2.line(img, app_point, (np.int32(app_point[0])+flip*20, np.int32(app_point[1])), (255,255,255), thickness+1 if thickness<2 else thickness)

return app_point, unit_segment_direction


def draw_joint_angle(img, ang_coords, flip, right_angle):
def draw_joint_angle(img, ang_coords, flip, right_angle, thickness=1):
'''
Draw a joint angle on the image.

Expand Down Expand Up @@ -733,7 +750,7 @@ def draw_joint_angle(img, ang_coords, flip, right_angle):

# segment line
unit_segment_direction = segment_direction/np.linalg.norm(segment_direction)
cv2.line(img, app_point, np.int32(app_point+unit_segment_direction*40), (0,255,0), thickness)
cv2.line(img, app_point, np.int32(app_point+unit_segment_direction*40), (0,255,0), thickness+1 if thickness<2 else thickness)

# parent segment dotted line
unit_parentsegment_direction = parentsegment_direction/np.linalg.norm(parentsegment_direction)
Expand All @@ -745,7 +762,7 @@ def draw_joint_angle(img, ang_coords, flip, right_angle):
if abs(end_angle - start_angle) > 180:
if end_angle > start_angle: start_angle += 360
else: end_angle += 360
cv2.ellipse(img, app_point, (20, 20), 0, start_angle, end_angle, (0, 255, 0), thickness)
cv2.ellipse(img, app_point, (20, 20), 0, start_angle, end_angle, (0, 255, 0), thickness+1 if thickness<2 else thickness)

return app_point, unit_segment_direction, unit_parentsegment_direction

Expand All @@ -772,7 +789,7 @@ def write_angle_on_body(img, ang, app_point, vec1, vec2, dist=40, color=(255,255
return
unit_vec_sum = vec_sum/np.linalg.norm(vec_sum)
text_position = np.int32(app_point + unit_vec_sum*dist)
cv2.putText(img, f'{ang:.1f}', text_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,0,0), thickness+1, cv2.LINE_AA)
cv2.putText(img, f'{ang:.1f}', text_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0,0,0), thickness*3, cv2.LINE_AA)
cv2.putText(img, f'{ang:.1f}', text_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, color, thickness, cv2.LINE_AA)


Expand All @@ -797,9 +814,9 @@ def write_angle_as_list(img, ang, ang_name, person_label_position, ang_label_lin
# angle names and values
ang_label_position = (person_label_position[0], person_label_position[1]+int((ang_label_line)*40*fontSize))
ang_value_position = (ang_label_position[0]+int(250*fontSize), ang_label_position[1])
cv2.putText(img, f'{ang_name}:', ang_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), thickness+1, cv2.LINE_AA)
cv2.putText(img, f'{ang_name}:', ang_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), thickness*3, cv2.LINE_AA)
cv2.putText(img, f'{ang_name}:', ang_label_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, color, thickness, cv2.LINE_AA)
cv2.putText(img, f'{ang:.1f}', ang_value_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), thickness+1, cv2.LINE_AA)
cv2.putText(img, f'{ang:.1f}', ang_value_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), thickness*3, cv2.LINE_AA)
cv2.putText(img, f'{ang:.1f}', ang_value_position, cv2.FONT_HERSHEY_SIMPLEX, fontSize, color, thickness, cv2.LINE_AA)

# progress bar
Expand Down Expand Up @@ -1043,7 +1060,6 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
angle_names = [angle_name.lower() for angle_name in angle_names]
display_angle_values_on = config_dict.get('angles').get('display_angle_values_on')
fontSize = config_dict.get('angles').get('fontSize')
thickness = 1 if fontSize < 0.8 else 2
flip_left_right = config_dict.get('angles').get('flip_left_right')

# Post-processing settings
Expand Down Expand Up @@ -1108,6 +1124,10 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
cv2.namedWindow(f'{video_file} Sports2D', cv2.WINDOW_NORMAL + cv2.WINDOW_KEEPRATIO)
cv2.setWindowProperty(f'{video_file} Sports2D', cv2.WND_PROP_ASPECT_RATIO, cv2.WINDOW_FULLSCREEN)

# Set up front size and thickness
if isinstance(fontSize, str) and fontSize.lower() == 'auto': # I add isinstance to avoid errors when running with user's preferences (fontSize is a float)
fontSize = dynamic_fontSize(cam_width, cam_height)
thickness = dynamic_thickness(fontSize)

# Set up pose tracker
tracking_rtmlib = True if (tracking_mode == 'rtmlib' and tracking) else False
Expand Down Expand Up @@ -1144,7 +1164,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
all_frames_angles.append([])
continue
else:
cv2.putText(frame, f"Press 'q' to quit", (cam_width-int(400*fontSize), cam_height-20), cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (255,255,255), thickness+1, cv2.LINE_AA)
cv2.putText(frame, f"Press 'q' to quit", (cam_width-int(400*fontSize), cam_height-20), cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (255,255,255), thickness*3, cv2.LINE_AA)
cv2.putText(frame, f"Press 'q' to quit", (cam_width-int(400*fontSize), cam_height-20), cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (0,0,255), thickness, cv2.LINE_AA)

# Detect poses
Expand Down Expand Up @@ -1199,8 +1219,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
if show_realtime_results or save_vid or save_img:
img = frame.copy()
img = draw_bounding_box(img, valid_X, valid_Y, colors=colors, fontSize=fontSize, thickness=thickness)
img = draw_keypts(img, valid_X, valid_Y, scores, cmap_str='RdYlGn')
img = draw_skel(img, valid_X, valid_Y, model, colors=colors)
img = draw_keypts(img, valid_X, valid_Y, scores, cmap_str='RdYlGn', thickness=thickness)
img = draw_skel(img, valid_X, valid_Y, model, colors=colors, thickness=thickness)
img = draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, keypoints_ids, keypoints_names, angle_names, display_angle_values_on=display_angle_values_on, colors=colors, fontSize=fontSize, thickness=thickness)

if show_realtime_results:
Expand Down