diff --git a/Sports2D/Demo/Config_demo.toml b/Sports2D/Demo/Config_demo.toml index 95f06fc..0d087a7 100644 --- a/Sports2D/Demo/Config_demo.toml +++ b/Sports2D/Demo/Config_demo.toml @@ -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'] diff --git a/Sports2D/Utilities/common.py b/Sports2D/Utilities/common.py index 48fdbd3..a9e5b62 100644 --- a/Sports2D/Utilities/common.py +++ b/Sports2D/Utilities/common.py @@ -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 \ No newline at end of file diff --git a/Sports2D/process.py b/Sports2D/process.py index 636d301..0e4abca 100644 --- a/Sports2D/process.py +++ b/Sports2D/process.py @@ -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 @@ -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 @@ -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): @@ -532,7 +549,7 @@ 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) @@ -540,7 +557,7 @@ def draw_bounding_box(img, X, Y, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], 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. @@ -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. @@ -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): @@ -648,22 +665,22 @@ 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) @@ -671,7 +688,7 @@ def draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, keypoints_ 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. @@ -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. @@ -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) @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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: