beta: multi-person

This commit is contained in:
davidpagnon 2024-02-23 18:16:56 +01:00
parent b41bc933b7
commit 607865e3d4
3 changed files with 206 additions and 137 deletions

View File

@ -380,7 +380,7 @@ def triangulation(config=None):
triangulate_all(config_dict) triangulate_all(config_dict)
end = time.time() end = time.time()
logging.info(f'Triangulation took {end-start:.2f} s.') logging.info(f'\nTriangulation took {end-start:.2f} s.')
def filtering(config=None): def filtering(config=None):

View File

@ -91,12 +91,13 @@ def persons_combinations(json_files_framef):
def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_combinations, projection_matrices, tracked_keypoint_id, calib_params): def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_combinations, projection_matrices, tracked_keypoint_id, calib_params):
''' '''
At the same time, chooses the right person among the multiple ones found by - if single_person: Choose the right person among the multiple ones found by
OpenPose & excludes cameras with wrong 2d-pose estimation. OpenPose & excludes cameras with wrong 2d-pose estimation.
- else: Choose all the combination of cameras that give a reprojection error below a threshold
1. triangulate the tracked keypoint for all possible combinations of people, 1. triangulate the tracked keypoint for all possible combinations of people,
2. compute difference between reprojection & original openpose detection, 2. compute difference between reprojection & original openpose detection,
3. take combination with smallest difference. 3. take combination with smallest error OR all those below the error threshold
If error is too big, take off one or several of the cameras until err is If error is too big, take off one or several of the cameras until err is
lower than "max_err_px". lower than "max_err_px".
@ -108,10 +109,11 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
- tracked_keypoint_id: int - tracked_keypoint_id: int
OUTPUTS: OUTPUTS:
- error_min: float - errors_below_thresh: list of float
- persons_and_cameras_combination: array of ints - comb_errors_below_thresh: list of arrays of ints
''' '''
single_person = config.get('project').get('single_person')
error_threshold_tracking = config.get('personAssociation').get('reproj_error_threshold_association') error_threshold_tracking = config.get('personAssociation').get('reproj_error_threshold_association')
likelihood_threshold = config.get('personAssociation').get('likelihood_threshold_association') likelihood_threshold = config.get('personAssociation').get('likelihood_threshold_association')
min_cameras_for_triangulation = config.get('triangulation').get('min_cameras_for_triangulation') min_cameras_for_triangulation = config.get('triangulation').get('min_cameras_for_triangulation')
@ -121,6 +123,9 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
error_min = np.inf error_min = np.inf
nb_cams_off = 0 # cameras will be taken-off until the reprojection error is under threshold nb_cams_off = 0 # cameras will be taken-off until the reprojection error is under threshold
errors_below_thresh = []
comb_errors_below_thresh = []
Q_kpt = []
while error_min > error_threshold_tracking and n_cams - nb_cams_off >= min_cameras_for_triangulation: while error_min > error_threshold_tracking and n_cams - nb_cams_off >= min_cameras_for_triangulation:
# Try all persons combinations # Try all persons combinations
for combination in personsIDs_combinations: for combination in personsIDs_combinations:
@ -156,6 +161,7 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
# Try all subsets # Try all subsets
error_comb = [] error_comb = []
Q_comb = []
for comb in combinations_with_cams_off: for comb in combinations_with_cams_off:
# Filter x, y, likelihood, projection_matrices, with subset # Filter x, y, likelihood, projection_matrices, with subset
x_files_filt = [x_files[i] for i in range(len(comb)) if not np.isnan(comb[i])] x_files_filt = [x_files[i] for i in range(len(comb)) if not np.isnan(comb[i])]
@ -169,15 +175,15 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
calib_params_dist_filt = [calib_params['dist'][i] for i in range(len(comb)) if not np.isnan(comb[i])] calib_params_dist_filt = [calib_params['dist'][i] for i in range(len(comb)) if not np.isnan(comb[i])]
# Triangulate 2D points # Triangulate 2D points
Q_comb = weighted_triangulation(projection_matrices_filt, x_files_filt, y_files_filt, likelihood_files_filt) Q_comb.append(weighted_triangulation(projection_matrices_filt, x_files_filt, y_files_filt, likelihood_files_filt))
# Reprojection # Reprojection
if undistort_points: if undistort_points:
coords_2D_kpt_calc_filt = [cv2.projectPoints(np.array(Q_comb[:-1]), calib_params_R_filt[i], calib_params_T_filt[i], calib_params_K_filt[i], calib_params_dist_filt[i])[0] for i in range(n_cams-nb_cams_off)] coords_2D_kpt_calc_filt = [cv2.projectPoints(np.array(Q_comb[-1][:-1]), calib_params_R_filt[i], calib_params_T_filt[i], calib_params_K_filt[i], calib_params_dist_filt[i])[0] for i in range(n_cams-nb_cams_off)]
x_calc = [coords_2D_kpt_calc_filt[i][0,0,0] for i in range(n_cams-nb_cams_off)] x_calc = [coords_2D_kpt_calc_filt[i][0,0,0] for i in range(n_cams-nb_cams_off)]
y_calc = [coords_2D_kpt_calc_filt[i][0,0,1] for i in range(n_cams-nb_cams_off)] y_calc = [coords_2D_kpt_calc_filt[i][0,0,1] for i in range(n_cams-nb_cams_off)]
else: else:
x_calc, y_calc = reprojection(projection_matrices_filt, Q_comb) x_calc, y_calc = reprojection(projection_matrices_filt, Q_comb[-1])
# Reprojection error # Reprojection error
error_comb_per_cam = [] error_comb_per_cam = []
@ -187,15 +193,34 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
error_comb_per_cam.append( euclidean_distance(q_file, q_calc) ) error_comb_per_cam.append( euclidean_distance(q_file, q_calc) )
error_comb.append( np.mean(error_comb_per_cam) ) error_comb.append( np.mean(error_comb_per_cam) )
error_min = np.nanmin(error_comb) if single_person:
persons_and_cameras_combination = combinations_with_cams_off[np.argmin(error_comb)] error_min = np.nanmin(error_comb)
errors_below_thresh = [error_min]
if error_min < error_threshold_tracking: comb_errors_below_thresh = [combinations_with_cams_off[np.argmin(error_comb)]]
Q_kpt = [Q_comb[np.argmin(error_comb)]]
if errors_below_thresh[0] < error_threshold_tracking:
break
else:
errors_below_thresh += [e for e in error_comb if e<error_threshold_tracking]
comb_errors_below_thresh += [combinations_with_cams_off[error_comb.index(e)] for e in error_comb if e<error_threshold_tracking]
Q_kpt += [Q_comb[error_comb.index(e)] for e in error_comb if e<error_threshold_tracking]
print('\n', personsIDs_combinations)
print(errors_below_thresh)
print(comb_errors_below_thresh)
print(Q_kpt)
if not single_person:
# 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(
[personsIDs_combinations[i,j]==comb[j] for comb in comb_errors_below_thresh for j in range(len(comb))]
).any()])
if len(personsIDs_combinations) < len(errors_below_thresh):
break break
nb_cams_off += 1 nb_cams_off += 1
return error_min, persons_and_cameras_combination return errors_below_thresh, comb_errors_below_thresh, Q_kpt
def recap_tracking(config, error, nb_cams_excluded): def recap_tracking(config, error, nb_cams_excluded):
@ -307,7 +332,7 @@ def track_2d_all(config):
f_range = [[min([len(j) for j in json_files])] if frame_range==[] else frame_range][0] f_range = [[min([len(j) for j in json_files])] if frame_range==[] else frame_range][0]
n_cams = len(json_dirs_names) n_cams = len(json_dirs_names)
error_min_tot, cameras_off_tot = [], [] error_min_tot, cameras_off_tot = [], []
# Check that camera number is consistent between calibration file and pose folders # Check that camera number is consistent between calibration file and pose folders
if n_cams != len(P): if n_cams != len(P):
raise Exception(f'Error: The number of cameras is not consistent:\ raise Exception(f'Error: The number of cameras is not consistent:\
@ -315,32 +340,38 @@ def track_2d_all(config):
and {n_cams} cameras based on the number of pose folders.') and {n_cams} cameras based on the number of pose folders.')
for f in tqdm(range(*f_range)): for f in tqdm(range(*f_range)):
print(f'\nFrame {f}:')
json_files_f = [json_files[c][f] for c in range(n_cams)] json_files_f = [json_files[c][f] for c in range(n_cams)]
json_tracked_files_f = [json_tracked_files[c][f] for c in range(n_cams)] json_tracked_files_f = [json_tracked_files[c][f] for c in range(n_cams)]
# all possible combinations of persons # all possible combinations of persons
personsIDs_comb = persons_combinations(json_files_f) personsIDs_comb = persons_combinations(json_files_f)
# choose person of interest and exclude cameras with bad pose estimation # choose persons of interest and exclude cameras with bad pose estimation
error_min, persons_and_cameras_combination = best_persons_and_cameras_combination(config, json_files_f, personsIDs_comb, P, tracked_keypoint_id, calib_params) errors_below_thresh, comb_errors_below_thresh, Q_kpt = best_persons_and_cameras_combination(config, json_files_f, personsIDs_comb, P, tracked_keypoint_id, calib_params)
error_min_tot.append(error_min)
cameras_off_count = np.count_nonzero(np.isnan(persons_and_cameras_combination)) # reID persons across frames by checking the distance from one frame to another
##### TO DO
error_min_tot.append(np.mean(errors_below_thresh))
cameras_off_count = np.count_nonzero([np.isnan(comb) for comb in comb_errors_below_thresh]) / len(comb_errors_below_thresh)
print(cameras_off_count)
cameras_off_tot.append(cameras_off_count) cameras_off_tot.append(cameras_off_count)
# rewrite json files with only one person of interest # rewrite json files with a single or multiple persons of interest
for cam_nb, person_id in enumerate(persons_and_cameras_combination): for cam in range(n_cams):
with open(json_tracked_files_f[cam_nb], 'w') as json_tracked_f: with open(json_tracked_files_f[cam], 'w') as json_tracked_f:
with open(json_files_f[cam_nb], 'r') as json_f: with open(json_files_f[cam], 'r') as json_f:
js = json.load(json_f) js = json.load(json_f)
if not np.isnan(person_id): js_new = js.copy()
js['people'] = [js['people'][int(person_id)]] js_new['people'] = []
else: for new_comb in comb_errors_below_thresh:
js['people'] = [] if not np.isnan(new_comb[cam]):
json_tracked_f.write(json.dumps(js)) js_new['people'] += [js['people'][int(new_comb[cam])]]
else:
js_new['people'] += [{}]
json_tracked_f.write(json.dumps(js_new))
# recap message # recap message
recap_tracking(config, error_min_tot, cameras_off_tot) recap_tracking(config, error_min_tot, cameras_off_tot)

View File

@ -44,7 +44,7 @@ import cv2
import toml import toml
from tqdm import tqdm from tqdm import tqdm
from scipy import interpolate from scipy import interpolate
from collections import Counter from collections import Counter, OrderedDict
from anytree import RenderTree from anytree import RenderTree
from anytree.importer import DictImporter from anytree.importer import DictImporter
import logging import logging
@ -109,7 +109,7 @@ def interpolate_zeros_nans(col, *args):
return col_interp return col_interp
def make_trc(config, Q, keypoints_names, f_range): def make_trc(config, Q, keypoints_names, f_range, id_person=-1):
''' '''
Make Opensim compatible trc file from a dataframe with 3D coordinates Make Opensim compatible trc file from a dataframe with 3D coordinates
@ -126,7 +126,10 @@ def make_trc(config, Q, keypoints_names, f_range):
# Read config # Read config
project_dir = config.get('project').get('project_dir') project_dir = config.get('project').get('project_dir')
frame_rate = config.get('project').get('frame_rate') frame_rate = config.get('project').get('frame_rate')
seq_name = os.path.basename(os.path.realpath(project_dir)) if id_person == -1:
seq_name = f'{os.path.basename(os.path.realpath(project_dir))}'
else:
seq_name = f'{os.path.basename(os.path.realpath(project_dir))}_Participant{id_person+1}'
pose3d_dir = os.path.join(project_dir, 'pose-3d') pose3d_dir = os.path.join(project_dir, 'pose-3d')
trc_f = f'{seq_name}_{f_range[0]}-{f_range[1]}.trc' trc_f = f'{seq_name}_{f_range[0]}-{f_range[1]}.trc'
@ -181,7 +184,7 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl
calib_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file calib_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file
calib = toml.load(calib_file) calib = toml.load(calib_file)
cam_names = np.array([calib[c].get('name') for c in list(calib.keys())]) cam_names = np.array([calib[c].get('name') for c in list(calib.keys())])
cam_names = cam_names[list(cam_excluded_count.keys())] cam_names = cam_names[list(cam_excluded_count[0].keys())]
error_threshold_triangulation = config.get('triangulation').get('reproj_error_threshold_triangulation') error_threshold_triangulation = config.get('triangulation').get('reproj_error_threshold_triangulation')
likelihood_threshold = config.get('triangulation').get('likelihood_threshold_triangulation') likelihood_threshold = config.get('triangulation').get('likelihood_threshold_triangulation')
show_interp_indices = config.get('triangulation').get('show_interp_indices') show_interp_indices = config.get('triangulation').get('show_interp_indices')
@ -195,47 +198,53 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl
Dm = euclidean_distance(calib_cam1['translation'], [0,0,0]) Dm = euclidean_distance(calib_cam1['translation'], [0,0,0])
logging.info('') logging.info('')
for idx, name in enumerate(keypoints_names): nb_persons_to_detect = len(error)
mean_error_keypoint_px = np.around(error.iloc[:,idx].mean(), decimals=1) # RMS à la place? for n in range(nb_persons_to_detect):
mean_error_keypoint_m = np.around(mean_error_keypoint_px * Dm / fm, decimals=3) if nb_persons_to_detect > 1:
mean_cam_excluded_keypoint = np.around(nb_cams_excluded.iloc[:,idx].mean(), decimals=2) print(f'\n\nPARTICIPANT {n+1}\n')
logging.info(f'Mean reprojection error for {name} is {mean_error_keypoint_px} px (~ {mean_error_keypoint_m} m), reached with {mean_cam_excluded_keypoint} excluded cameras. ')
if show_interp_indices: for idx, name in enumerate(keypoints_names):
if interpolation_kind != 'none': mean_error_keypoint_px = np.around(error[n].iloc[:,idx].mean(), decimals=1) # RMS à la place?
if len(list(interp_frames[idx])) ==0: mean_error_keypoint_m = np.around(mean_error_keypoint_px * Dm / fm, decimals=3)
logging.info(f' No frames needed to be interpolated.') mean_cam_excluded_keypoint = np.around(nb_cams_excluded[n].iloc[:,idx].mean(), decimals=2)
else: logging.info(f'Mean reprojection error for {name} is {mean_error_keypoint_px} px (~ {mean_error_keypoint_m} m), reached with {mean_cam_excluded_keypoint} excluded cameras. ')
interp_str = str(interp_frames[idx]).replace(":", " to ").replace("'", "").replace("]", "").replace("[", "") if show_interp_indices:
logging.info(f' Frames {interp_str} were interpolated.') if interpolation_kind != 'none':
if len(list(non_interp_frames[idx]))>0: if len(list(interp_frames[n][idx])) ==0:
noninterp_str = str(non_interp_frames[idx]).replace(":", " to ").replace("'", "").replace("]", "").replace("[", "") logging.info(f' No frames needed to be interpolated.')
logging.info(f' Frames {non_interp_frames[idx]} could not be interpolated: consider adjusting thresholds.') else:
interp_str = str(interp_frames[n][idx]).replace(":", " to ").replace("'", "").replace("]", "").replace("[", "")
logging.info(f' Frames {interp_str} were interpolated.')
if len(list(non_interp_frames[n][idx]))>0:
noninterp_str = str(non_interp_frames[n][idx]).replace(":", " to ").replace("'", "").replace("]", "").replace("[", "")
logging.info(f' Frames {non_interp_frames[n][idx]} could not be interpolated: consider adjusting thresholds.')
else:
logging.info(f' No frames were interpolated because \'interpolation_kind\' was set to none. ')
mean_error_px = np.around(error[n]['mean'].mean(), decimals=1)
mean_error_mm = np.around(mean_error_px * Dm / fm *1000, decimals=1)
mean_cam_excluded = np.around(nb_cams_excluded[n]['mean'].mean(), decimals=2)
logging.info(f'\n--> Mean reprojection error for all points on all frames is {mean_error_px} px, which roughly corresponds to {mean_error_mm} mm. ')
logging.info(f'Cameras were excluded if likelihood was below {likelihood_threshold} and if the reprojection error was above {error_threshold_triangulation} px.')
logging.info(f'In average, {mean_cam_excluded} cameras had to be excluded to reach these thresholds.')
cam_excluded_count[n] = {i: v for i, v in zip(cam_names, cam_excluded_count[n].values())}
cam_excluded_count[n] = {i: cam_excluded_count[n][i] for i in sorted(cam_excluded_count[n].keys())}
str_cam_excluded_count = ''
for i, (k, v) in enumerate(cam_excluded_count[n].items()):
if i ==0:
str_cam_excluded_count += f'Camera {k} was excluded {int(np.round(v*100))}% of the time, '
elif i == len(cam_excluded_count[n])-1:
str_cam_excluded_count += f'and Camera {k}: {int(np.round(v*100))}%.'
else: else:
logging.info(f' No frames were interpolated because \'interpolation_kind\' was set to none. ') str_cam_excluded_count += f'Camera {k}: {int(np.round(v*100))}%, '
logging.info(str_cam_excluded_count)
logging.info(f'\n3D coordinates are stored at {trc_path[n]}.')
mean_error_px = np.around(error['mean'].mean(), decimals=1) logging.info(f'\n\nLimb swapping was {"handled" if handle_LR_swap else "not handled"}.')
mean_error_mm = np.around(mean_error_px * Dm / fm *1000, decimals=1)
mean_cam_excluded = np.around(nb_cams_excluded['mean'].mean(), decimals=2)
logging.info(f'\n--> Mean reprojection error for all points on all frames is {mean_error_px} px, which roughly corresponds to {mean_error_mm} mm. ')
logging.info(f'Cameras were excluded if likelihood was below {likelihood_threshold} and if the reprojection error was above {error_threshold_triangulation} px.')
logging.info(f'In average, {mean_cam_excluded} cameras had to be excluded to reach these thresholds.')
cam_excluded_count = {i: v for i, v in zip(cam_names, cam_excluded_count.values())}
str_cam_excluded_count = ''
for i, (k, v) in enumerate(cam_excluded_count.items()):
if i ==0:
str_cam_excluded_count += f'Camera {k} was excluded {int(np.round(v*100))}% of the time, '
elif i == len(cam_excluded_count)-1:
str_cam_excluded_count += f'and Camera {k}: {int(np.round(v*100))}%.'
else:
str_cam_excluded_count += f'Camera {k}: {int(np.round(v*100))}%, '
logging.info(str_cam_excluded_count)
logging.info(f'Limb swapping was {"handled" if handle_LR_swap else "not handled"}.')
logging.info(f'Lens distortions were {"taken into account" if undistort_points else "not taken into account"}.') logging.info(f'Lens distortions were {"taken into account" if undistort_points else "not taken into account"}.')
logging.info(f'\n3D coordinates are stored at {trc_path}.')
def triangulation_from_best_cameras(config, coords_2D_kpt, coords_2D_kpt_swapped, projection_matrices, calib_params): def triangulation_from_best_cameras(config, coords_2D_kpt, coords_2D_kpt_swapped, projection_matrices, calib_params):
''' '''
@ -481,7 +490,7 @@ def triangulation_from_best_cameras(config, coords_2D_kpt, coords_2D_kpt_swapped
return Q, error_min, nb_cams_excluded, id_excluded_cams return Q, error_min, nb_cams_excluded, id_excluded_cams
def extract_files_frame_f(json_tracked_files_f, keypoints_ids): def extract_files_frame_f(json_tracked_files_f, keypoints_ids, nb_persons_to_detect):
''' '''
Extract data from json files for frame f, Extract data from json files for frame f,
in the order of the body model hierarchy. in the order of the body model hierarchy.
@ -489,32 +498,34 @@ def extract_files_frame_f(json_tracked_files_f, keypoints_ids):
INPUTS: INPUTS:
- json_tracked_files_f: list of str. Paths of json_files for frame f. - json_tracked_files_f: list of str. Paths of json_files for frame f.
- keypoints_ids: list of int. Keypoints IDs in the order of the hierarchy. - keypoints_ids: list of int. Keypoints IDs in the order of the hierarchy.
- nb_persons_to_detect: int
OUTPUTS: OUTPUTS:
- x_files, y_files, likelihood_files: array: - x_files, y_files, likelihood_files: [[[list of coordinates] * n_cams ] * nb_persons_to_detect]
n_cams lists of n_keypoints lists of coordinates.
''' '''
n_cams = len(json_tracked_files_f) n_cams = len(json_tracked_files_f)
x_files, y_files, likelihood_files = [], [], [] x_files = [[] for n in range(nb_persons_to_detect)]
for cam_nb in range(n_cams): y_files = [[] for n in range(nb_persons_to_detect)]
x_files_cam, y_files_cam, likelihood_files_cam = [], [], [] likelihood_files = [[] for n in range(nb_persons_to_detect)]
with open(json_tracked_files_f[cam_nb], 'r') as json_f: for n in range(nb_persons_to_detect):
js = json.load(json_f) for cam_nb in range(n_cams):
for keypoint_id in keypoints_ids: x_files_cam, y_files_cam, likelihood_files_cam = [], [], []
try: with open(json_tracked_files_f[cam_nb], 'r') as json_f:
x_files_cam.append( js['people'][0]['pose_keypoints_2d'][keypoint_id*3] ) js = json.load(json_f)
y_files_cam.append( js['people'][0]['pose_keypoints_2d'][keypoint_id*3+1] ) for keypoint_id in keypoints_ids:
likelihood_files_cam.append( js['people'][0]['pose_keypoints_2d'][keypoint_id*3+2] ) try:
except: x_files_cam.append( js['people'][n]['pose_keypoints_2d'][keypoint_id*3] )
x_files_cam.append( np.nan ) y_files_cam.append( js['people'][n]['pose_keypoints_2d'][keypoint_id*3+1] )
y_files_cam.append( np.nan ) likelihood_files_cam.append( js['people'][n]['pose_keypoints_2d'][keypoint_id*3+2] )
likelihood_files_cam.append( np.nan ) except:
x_files_cam.append( np.nan )
x_files.append(x_files_cam) y_files_cam.append( np.nan )
y_files.append(y_files_cam) likelihood_files_cam.append( np.nan )
likelihood_files.append(likelihood_files_cam) x_files[n].append(x_files_cam)
y_files[n].append(y_files_cam)
likelihood_files[n].append(likelihood_files_cam)
x_files = np.array(x_files) x_files = np.array(x_files)
y_files = np.array(y_files) y_files = np.array(y_files)
@ -599,10 +610,12 @@ def triangulate_all(config):
json_files_names = [natural_sort(j) for j in json_files_names] json_files_names = [natural_sort(j) for j in json_files_names]
json_tracked_files = [[os.path.join(pose_dir, j_dir, j_file) for j_file in json_files_names[j]] for j, j_dir in enumerate(json_dirs_names)] json_tracked_files = [[os.path.join(pose_dir, j_dir, j_file) for j_file in json_files_names[j]] for j, j_dir in enumerate(json_dirs_names)]
# Triangulation # Prep triangulation
f_range = [[0,min([len(j) for j in json_files_names])] if frame_range==[] else frame_range][0] f_range = [[0,min([len(j) for j in json_files_names])] if frame_range==[] else frame_range][0]
frames_nb = f_range[1]-f_range[0] frames_nb = f_range[1]-f_range[0]
nb_persons_to_detect = max([len(json.load(open(json_fname))['people']) for json_fname in json_tracked_files[0]])
n_cams = len(json_dirs_names) n_cams = len(json_dirs_names)
# Check that camera number is consistent between calibration file and pose folders # Check that camera number is consistent between calibration file and pose folders
@ -610,79 +623,104 @@ def triangulate_all(config):
raise Exception(f'Error: The number of cameras is not consistent:\ raise Exception(f'Error: The number of cameras is not consistent:\
Found {len(P)} cameras in the calibration file,\ Found {len(P)} cameras in the calibration file,\
and {n_cams} cameras based on the number of pose folders.') and {n_cams} cameras based on the number of pose folders.')
# Triangulation
Q_tot, error_tot, nb_cams_excluded_tot,id_excluded_cams_tot = [], [], [], [] Q_tot, error_tot, nb_cams_excluded_tot,id_excluded_cams_tot = [], [], [], []
for f in tqdm(range(*f_range)): for f in tqdm(range(*f_range)):
# Get x,y,likelihood values from files # Get x,y,likelihood values from files
json_tracked_files_f = [json_tracked_files[c][f] for c in range(n_cams)] json_tracked_files_f = [json_tracked_files[c][f] for c in range(n_cams)]
# print(json_tracked_files_f) # print(json_tracked_files_f)
x_files, y_files, likelihood_files = extract_files_frame_f(json_tracked_files_f, keypoints_ids) x_files, y_files, likelihood_files = extract_files_frame_f(json_tracked_files_f, keypoints_ids, nb_persons_to_detect)
# [[[list of coordinates] * n_cams ] * nb_persons_to_detect]
# vs. [[list of coordinates] * n_cams ]
# undistort points # undistort points
if undistort_points: if undistort_points:
points = [np.array(tuple(zip(x_files[i],y_files[i]))).reshape(-1, 1, 2).astype('float32') for i in range(n_cams)] for n in range(nb_persons_to_detect):
undistorted_points = [cv2.undistortPoints(points[i], calib_params['K'][i], calib_params['dist'][i], None, calib_params['optim_K'][i]) for i in range(n_cams)] points = [np.array(tuple(zip(x_files[n][i],y_files[n][i]))).reshape(-1, 1, 2).astype('float32') for i in range(n_cams)]
x_files = np.array([[u[i][0][0] for i in range(len(u))] for u in undistorted_points]) undistorted_points = [cv2.undistortPoints(points[i], calib_params['K'][i], calib_params['dist'][i], None, calib_params['optim_K'][i]) for i in range(n_cams)]
y_files = np.array([[u[i][0][1] for i in range(len(u))] for u in undistorted_points]) x_files[n] = np.array([[u[i][0][0] for i in range(len(u))] for u in undistorted_points])
# This is good for slight distortion. For fishey camera, the model does not work anymore. See there for an example https://github.com/lambdaloop/aniposelib/blob/d03b485c4e178d7cff076e9fe1ac36837db49158/aniposelib/cameras.py#L301 y_files[n] = np.array([[u[i][0][1] for i in range(len(u))] for u in undistorted_points])
# This is good for slight distortion. For fisheye camera, the model does not work anymore. See there for an example https://github.com/lambdaloop/aniposelib/blob/d03b485c4e178d7cff076e9fe1ac36837db49158/aniposelib/cameras.py#L301
# Replace likelihood by 0 if under likelihood_threshold # Replace likelihood by 0 if under likelihood_threshold
with np.errstate(invalid='ignore'): with np.errstate(invalid='ignore'):
x_files[likelihood_files<likelihood_threshold] = np.nan for n in range(nb_persons_to_detect):
y_files[likelihood_files<likelihood_threshold] = np.nan x_files[n][likelihood_files[n] < likelihood_threshold] = np.nan
likelihood_files[likelihood_files<likelihood_threshold] = np.nan y_files[n][likelihood_files[n] < likelihood_threshold] = np.nan
likelihood_files[n][likelihood_files[n] < likelihood_threshold] = np.nan
Q, error, nb_cams_excluded, id_excluded_cams = [], [], [], [] Q = [[] for n in range(nb_persons_to_detect)]
for keypoint_idx in keypoints_idx: error = [[] for n in range(nb_persons_to_detect)]
# Triangulate cameras with min reprojection error nb_cams_excluded = [[] for n in range(nb_persons_to_detect)]
# print('\n', keypoints_names[keypoint_idx]) id_excluded_cams = [[] for n in range(nb_persons_to_detect)]
coords_2D_kpt = np.array( (x_files[:, keypoint_idx], y_files[:, keypoint_idx], likelihood_files[:, keypoint_idx]) ) for n in range(nb_persons_to_detect):
coords_2D_kpt_swapped = np.array(( x_files[:, keypoints_idx_swapped[keypoint_idx]], y_files[:, keypoints_idx_swapped[keypoint_idx]], likelihood_files[:, keypoints_idx_swapped[keypoint_idx]] )) for keypoint_idx in keypoints_idx:
# Triangulate cameras with min reprojection error
# print('\n', keypoints_names[keypoint_idx])
coords_2D_kpt = np.array( (x_files[n][:, keypoint_idx], y_files[n][:, keypoint_idx], likelihood_files[n][:, keypoint_idx]) )
coords_2D_kpt_swapped = np.array(( x_files[n][:, keypoints_idx_swapped[keypoint_idx]], y_files[n][:, keypoints_idx_swapped[keypoint_idx]], likelihood_files[n][:, keypoints_idx_swapped[keypoint_idx]] ))
Q_kpt, error_kpt, nb_cams_excluded_kpt, id_excluded_cams_kpt = triangulation_from_best_cameras(config, coords_2D_kpt, coords_2D_kpt_swapped, P, calib_params) # P has been modified if undistort_points=True Q_kpt, error_kpt, nb_cams_excluded_kpt, id_excluded_cams_kpt = triangulation_from_best_cameras(config, coords_2D_kpt, coords_2D_kpt_swapped, P, calib_params) # P has been modified if undistort_points=True
Q.append(Q_kpt) Q[n].append(Q_kpt)
error.append(error_kpt) error[n].append(error_kpt)
nb_cams_excluded.append(nb_cams_excluded_kpt) nb_cams_excluded[n].append(nb_cams_excluded_kpt)
id_excluded_cams.append(id_excluded_cams_kpt) id_excluded_cams[n].append(id_excluded_cams_kpt)
# Add triangulated points, errors and excluded cameras to pandas dataframes # Add triangulated points, errors and excluded cameras to pandas dataframes
Q_tot.append(np.concatenate(Q)) Q_tot.append([np.concatenate(Q[n]) for n in range(nb_persons_to_detect)])
error_tot.append(error) error_tot.append([error[n] for n in range(nb_persons_to_detect)])
nb_cams_excluded_tot.append(nb_cams_excluded) nb_cams_excluded_tot.append([nb_cams_excluded[n] for n in range(nb_persons_to_detect)])
id_excluded_cams = [item for sublist in id_excluded_cams for item in sublist] 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) id_excluded_cams_tot.append(id_excluded_cams)
Q_tot = pd.DataFrame(Q_tot) 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) 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) 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)]
id_excluded_cams_tot = [item for sublist in id_excluded_cams_tot for item in sublist] for n in range(nb_persons_to_detect):
cam_excluded_count = dict(Counter(id_excluded_cams_tot)) error_tot[n]['mean'] = error_tot[n].mean(axis = 1)
cam_excluded_count.update((x, y/keypoints_nb/frames_nb) for x, y in cam_excluded_count.items()) nb_cams_excluded_tot[n]['mean'] = nb_cams_excluded_tot[n].mean(axis = 1)
error_tot['mean'] = error_tot.mean(axis = 1) # Delete participants with less than 4 valid triangulated frames
nb_cams_excluded_tot['mean'] = nb_cams_excluded_tot.mean(axis = 1) # for each person, for each keypoint, frames to interpolate
zero_nan_frames = [np.where( Q_tot[n].iloc[:,::3].T.eq(0) | ~np.isfinite(Q_tot[n].iloc[:,::3].T) ) for n in range(nb_persons_to_detect)]
zero_nan_frames_per_kpt = [[zero_nan_frames[n][1][np.where(zero_nan_frames[n][0]==k)[0]] for k in range(keypoints_nb)] for n in range(nb_persons_to_detect)]
non_nan_nb_first_kpt = [frames_nb - len(zero_nan_frames_per_kpt[n][0]) for n in range(nb_persons_to_detect)]
deleted_person_id = [n for n in range(len(non_nan_nb_first_kpt)) if non_nan_nb_first_kpt[n]<4]
# Optionally, for each keypoint, show indices of frames that should be interpolated Q_tot = [Q_tot[n] for n in range(len(Q_tot)) if n not in deleted_person_id]
error_tot = [error_tot[n] for n in range(len(error_tot)) if n not in deleted_person_id]
nb_cams_excluded_tot = [nb_cams_excluded_tot[n] for n in range(len(nb_cams_excluded_tot)) if n not in deleted_person_id]
id_excluded_cams_tot = [id_excluded_cams_tot[n] for n in range(len(id_excluded_cams_tot)) if n not in deleted_person_id]
nb_persons_to_detect = len(Q_tot)
# IDs of excluded cameras
# id_excluded_cams_tot = [np.concatenate([id_excluded_cams_tot[f][k] for f in range(frames_nb)]) for k in range(keypoints_nb)]
id_excluded_cams_tot = [np.hstack(np.hstack(np.array(id_excluded_cams_tot[n]))) for n in range(nb_persons_to_detect)]
cam_excluded_count = [dict(Counter(k)) for k in id_excluded_cams_tot]
[cam_excluded_count[n].update((x, y/frames_nb/keypoints_nb) for x, y in cam_excluded_count[n].items()) for n in range(nb_persons_to_detect)]
# Optionally, for each person, for each keypoint, show indices of frames that should be interpolated
if show_interp_indices: if show_interp_indices:
zero_nan_frames = np.where( Q_tot.iloc[:,::3].T.eq(0) | ~np.isfinite(Q_tot.iloc[:,::3].T) ) gaps = [[np.where(np.diff(zero_nan_frames_per_kpt[n][k]) > 1)[0] + 1 for k in range(keypoints_nb)] for n in range(nb_persons_to_detect)]
zero_nan_frames_per_kpt = [zero_nan_frames[1][np.where(zero_nan_frames[0]==k)[0]] for k in range(keypoints_nb)] sequences = [[np.split(zero_nan_frames_per_kpt[n][k], gaps[n][k]) for k in range(keypoints_nb)] for n in range(nb_persons_to_detect)]
gaps = [np.where(np.diff(zero_nan_frames_per_kpt[k]) > 1)[0] + 1 for k in range(keypoints_nb)] interp_frames = [[[f'{seq[0]}:{seq[-1]}' for seq in seq_kpt if len(seq)<=interp_gap_smaller_than and len(seq)>0] for seq_kpt in sequences[n]] for n in range(nb_persons_to_detect)]
sequences = [np.split(zero_nan_frames_per_kpt[k], gaps[k]) for k in range(keypoints_nb)] non_interp_frames = [[[f'{seq[0]}:{seq[-1]}' for seq in seq_kpt if len(seq)>interp_gap_smaller_than] for seq_kpt in sequences[n]] for n in range(nb_persons_to_detect)]
interp_frames = [[f'{seq[0]}:{seq[-1]+1}' for seq in seq_kpt if len(seq)<=interp_gap_smaller_than and len(seq)>0] for seq_kpt in sequences]
non_interp_frames = [[f'{seq[0]}:{seq[-1]+1}' for seq in seq_kpt if len(seq)>interp_gap_smaller_than] for seq_kpt in sequences]
else: else:
interp_frames = None interp_frames = None
non_interp_frames = [] non_interp_frames = []
# Interpolate missing values # Interpolate missing values
if interpolation_kind != 'none': if interpolation_kind != 'none':
Q_tot = Q_tot.apply(interpolate_zeros_nans, axis=0, args = [interp_gap_smaller_than, interpolation_kind]) for n in range(nb_persons_to_detect):
Q_tot[n].apply(interpolate_zeros_nans, axis=0, args = [interp_gap_smaller_than, interpolation_kind])
# Q_tot.replace(np.nan, 0, inplace=True) # Q_tot.replace(np.nan, 0, inplace=True)
# Create TRC file # Create TRC file
trc_path = make_trc(config, Q_tot, keypoints_names, f_range) trc_paths = [make_trc(config, Q_tot[n], keypoints_names, f_range, id_person=n) for n in range(len(Q_tot))]
# Recap message # Recap message
recap_triangulate(config, error_tot, nb_cams_excluded_tot, keypoints_names, cam_excluded_count, interp_frames, non_interp_frames, trc_path) recap_triangulate(config, error_tot, nb_cams_excluded_tot, keypoints_names, cam_excluded_count, interp_frames, non_interp_frames, trc_paths)