From 66df6bbd7c095401b8a66136c7a77b53258c3fd3 Mon Sep 17 00:00:00 2001 From: davidpagnon Date: Mon, 4 Mar 2024 18:46:56 +0100 Subject: [PATCH] small improvements on multi-person detection --- .../Utilities/json_display_without_img.py | 2 +- Pose2Sim/calibration.py | 23 ++++++----- Pose2Sim/personAssociation.py | 41 +++++++++++++++++++ Pose2Sim/triangulation.py | 12 +++--- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/Pose2Sim/Utilities/json_display_without_img.py b/Pose2Sim/Utilities/json_display_without_img.py index f3eaf00..437e45d 100644 --- a/Pose2Sim/Utilities/json_display_without_img.py +++ b/Pose2Sim/Utilities/json_display_without_img.py @@ -118,7 +118,7 @@ def json_display_without_img_func(**args): scat.set_offsets(np.c_[X[frame], image_height-Y[frame]]) scat.set_array(CONF[frame]) if save == True or save=='True' or save == '1': - output_name = os.path.join(output_img_folder, f'{os.path.basename(output_img_folder)}_{frame}.png') + output_name = os.path.join(output_img_folder, f'{os.path.basename(output_img_folder)}_{str(frame).zfill(5)}.png') plt.savefig(output_name) return scat, diff --git a/Pose2Sim/calibration.py b/Pose2Sim/calibration.py index 0bfd675..96830c2 100644 --- a/Pose2Sim/calibration.py +++ b/Pose2Sim/calibration.py @@ -319,14 +319,15 @@ def read_intrinsic_yml(intrinsic_path): N.B. : Size is calculated as twice the position of the optical center. Please correct in the .toml file if needed. ''' intrinsic_yml = cv2.FileStorage(intrinsic_path, cv2.FILE_STORAGE_READ) - N = intrinsic_yml.getNode('names').size() - S, D, K = [], [], [] - for i in range(N): + cam_number = intrinsic_yml.getNode('names').size() + N, S, D, K = [], [], [], [] + for i in range(cam_number): name = intrinsic_yml.getNode('names').at(i).string() + N.append(name) K.append(intrinsic_yml.getNode(f'K_{name}').mat()) D.append(intrinsic_yml.getNode(f'dist_{name}').mat().flatten()[:-1]) S.append([K[i][0,2]*2, K[i][1,2]*2]) - return S, K, D + return N, S, K, D def read_extrinsic_yml(extrinsic_path): @@ -337,13 +338,14 @@ def read_extrinsic_yml(extrinsic_path): - T (extrinsic translation) ''' extrinsic_yml = cv2.FileStorage(extrinsic_path, cv2.FILE_STORAGE_READ) - N = extrinsic_yml.getNode('names').size() - R, T = [], [] - for i in range(N): + cam_number = extrinsic_yml.getNode('names').size() + N, R, T = [], [], [] + for i in range(cam_number): name = extrinsic_yml.getNode('names').at(i).string() + N.append(name) R.append(extrinsic_yml.getNode(f'R_{name}').mat().flatten()) # R_1 pour Rodrigues, Rot_1 pour matrice T.append(extrinsic_yml.getNode(f'T_{name}').mat().flatten()) - return R, T + return N, R, T def calib_easymocap_fun(files_to_convert_paths, binning_factor=1): @@ -365,9 +367,8 @@ def calib_easymocap_fun(files_to_convert_paths, binning_factor=1): ''' extrinsic_path, intrinsic_path = files_to_convert_paths - S, K, D = read_intrinsic_yml(intrinsic_path) - R, T = read_extrinsic_yml(extrinsic_path) - C = np.array(range(len(S))) + C, S, K, D = read_intrinsic_yml(intrinsic_path) + _, R, T = read_extrinsic_yml(extrinsic_path) ret = [np.nan]*len(C) return ret, C, S, D, K, R, T diff --git a/Pose2Sim/personAssociation.py b/Pose2Sim/personAssociation.py index 91b8491..6a13043 100644 --- a/Pose2Sim/personAssociation.py +++ b/Pose2Sim/personAssociation.py @@ -59,6 +59,18 @@ __status__ = "Development" ## FUNCTIONS +def common_items_in_list(list1, list2): + ''' + Do two lists have any items in common at the same index? + Returns True or False + ''' + + for i, j in enumerate(list1): + if j == list2[i]: + return True + return False + + def min_with_single_indices(L, T): ''' Let L be a list (size s) with T associated tuple indices (size s). @@ -121,6 +133,8 @@ def sort_people(Q_kpt_old, Q_kpt, nb_persons_to_detect): ''' # Generate possible person correspondences across frames + if len(Q_kpt_old) < len(Q_kpt): + Q_kpt_old = np.concatenate((Q_kpt_old, [[0., 0., 0., 1.]]*(len(Q_kpt)-len(Q_kpt_old)))) personsIDs_comb = sorted(list(it.product(range(len(Q_kpt_old)),range(len(Q_kpt))))) # Compute distance between persons from one frame to another frame_by_frame_dist = [] @@ -287,6 +301,27 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c # print(comb_errors_below_thresh) # print(Q_kpt) if multi_person: + # sort combinations by error magnitude + errors_below_thresh_sorted = sorted(errors_below_thresh) + sorted_idx = np.array([errors_below_thresh.index(e) for e in errors_below_thresh_sorted]) + comb_errors_below_thresh = np.array(comb_errors_below_thresh)[sorted_idx] + Q_kpt = np.array(Q_kpt)[sorted_idx] + # remove combinations with indices used several times for the same person + comb_errors_below_thresh = [c.tolist() for c in comb_errors_below_thresh] + comb = comb_errors_below_thresh.copy() + comb_ok = np.array([comb[0]]) + for i, c1 in enumerate(comb): + idx_ok = np.array([not(common_items_in_list(c1, c2)) for c2 in comb[1:]]) + try: + comb = np.array(comb[1:])[idx_ok] + comb_ok = np.concatenate((comb_ok, [comb[0]])) + except: + break + sorted_pruned_idx = [comb_errors_below_thresh.index(c.tolist()) for c in comb_ok] + errors_below_thresh = np.array(errors_below_thresh_sorted)[sorted_pruned_idx] + comb_errors_below_thresh = np.array(comb_errors_below_thresh)[sorted_pruned_idx] + Q_kpt = Q_kpt[sorted_pruned_idx] + # Remove indices already used for a person personsIDs_combinations = np.array([personsIDs_combinations[i] for i in range(len(personsIDs_combinations)) if not np.array( @@ -365,6 +400,7 @@ def track_2d_all(config): # Read config project_dir = config.get('project').get('project_dir') session_dir = os.path.realpath(os.path.join(project_dir, '..', '..')) + multi_person = config.get('project').get('multi_person') pose_model = config.get('pose').get('pose_model') tracked_keypoint = config.get('personAssociation').get('tracked_keypoint') frame_range = config.get('project').get('frame_range') @@ -378,6 +414,11 @@ def track_2d_all(config): pose_dir = os.path.join(project_dir, 'pose') poseTracked_dir = os.path.join(project_dir, 'pose-associated') + if multi_person: + logging.info('\nMulti-person analysis selected. Note that you can set this option to false for faster runtime if you only need the main person in the scene.') + else: + logging.info('\nSingle-person analysis selected.') + # projection matrix from toml calibration file P = computeP(calib_file, undistort=undistort_points) calib_params = retrieve_calib_params(calib_file) diff --git a/Pose2Sim/triangulation.py b/Pose2Sim/triangulation.py index 405598d..3611d87 100644 --- a/Pose2Sim/triangulation.py +++ b/Pose2Sim/triangulation.py @@ -669,7 +669,7 @@ def triangulate_all(config): # Triangulation Q_tot, error_tot, nb_cams_excluded_tot,id_excluded_cams_tot = [], [], [], [] - for f in tqdm(range(*f_range)): + for f in tqdm(range(frames_nb)): # Get x,y,likelihood values from files json_tracked_files_f = [json_tracked_files[c][f] for c in range(n_cams)] # print(json_tracked_files_f) @@ -718,10 +718,10 @@ def triangulate_all(config): id_excluded_cams = [[id_excluded_cams[n][k] for k in range(keypoints_nb)] for n in range(nb_persons_to_detect)] id_excluded_cams_tot.append(id_excluded_cams) - Q_tot = [pd.DataFrame([Q_tot[f][n] for f in range(*f_range)]) for n in range(nb_persons_to_detect)] - error_tot = [pd.DataFrame([error_tot[f][n] for f in range(*f_range)]) for n in range(nb_persons_to_detect)] - nb_cams_excluded_tot = [pd.DataFrame([nb_cams_excluded_tot[f][n] for f in range(*f_range)]) for n in range(nb_persons_to_detect)] - id_excluded_cams_tot = [pd.DataFrame([id_excluded_cams_tot[f][n] for f in range(*f_range)]) for n in range(nb_persons_to_detect)] + Q_tot = [pd.DataFrame([Q_tot[f][n] for f in range(frames_nb)]) for n in range(nb_persons_to_detect)] + error_tot = [pd.DataFrame([error_tot[f][n] for f in range(frames_nb)]) for n in range(nb_persons_to_detect)] + nb_cams_excluded_tot = [pd.DataFrame([nb_cams_excluded_tot[f][n] for f in range(frames_nb)]) for n in range(nb_persons_to_detect)] + id_excluded_cams_tot = [pd.DataFrame([id_excluded_cams_tot[f][n] for f in range(frames_nb)]) for n in range(nb_persons_to_detect)] for n in range(nb_persons_to_detect): error_tot[n]['mean'] = error_tot[n].mean(axis = 1) @@ -769,7 +769,7 @@ def triangulate_all(config): trc_paths = [make_trc(config, Q_tot[n], keypoints_names, f_range, id_person=n) for n in range(len(Q_tot))] # Reorder TRC files - if multi_person and reorder_trc: + if multi_person and reorder_trc and len(trc_paths)>1: trc_id = retrieve_right_trc_order(trc_paths) [os.rename(t, t+'.old') for t in trc_paths] [os.rename(t+'.old', trc_paths[i]) for i, t in zip(trc_id,trc_paths)]