Faster and more robust multi-person analysis (#85)

* tests synchro

* draft

* further draft

* affinity ok

* proposals okay, need to incorporate in Pose2Sim+tests

* will transfer sorting across frames in triangulation in next commit

* Lasts tests need to be done but seems to work pretty well

* should all work smoothly

* update readme

* last checks

* fixed linting issues

* getting tired of being forgetful
This commit is contained in:
David PAGNON 2024-03-31 01:40:38 +01:00 committed by GitHub
parent 5ddef52185
commit 19efec2723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1821 additions and 1338 deletions

View File

@ -287,9 +287,6 @@ def synchronization(config=None):
start = time.time()
currentDateAndTime = datetime.now()
project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
seq_name = os.path.basename(project_dir)
frame_range = config_dict.get('project').get('frame_range')
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
logging.info("\n\n---------------------------------------------------------------------")
logging.info("Camera synchronization")

View File

@ -18,9 +18,8 @@
[project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
nb_persons_to_detect = 2 # checked only if multi_person is selected
frame_rate = 120 # fps
multi_person = false # If false, only the main person in scene is analyzed.
frame_rate = 60 # fps
frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
@ -31,6 +30,26 @@ exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<
# Take heart, calibration is not that complicated once you get the hang of it!
[pose]
pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
#With mediapipe: BLAZEPOSE.
#With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
#With deeplabcut: CUSTOM. See example at the end of the file.
# What follows has not been implemented yet
overwrite_pose = false
openpose_path = '' # only checked if OpenPose is used
[synchronization]
display_corr = true # true or false (lowercase)
reset_sync = true # Recalculate synchronization even if already done
# id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# limit synchronization search (to the beginning or to the end of the capture for example)
[calibration]
calibration_type = 'convert' # 'convert' or 'calculate'
@ -90,30 +109,17 @@ calibration_type = 'convert' # 'convert' or 'calculate'
# Coming soon!
[pose]
pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
#With mediapipe: BLAZEPOSE.
#With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
#With deeplabcut: CUSTOM. See example at the end of the file.
# What follows has not been implemented yet
overwrite_pose = false
openpose_path = '' # only checked if OpenPose is used
[synchronization]
# COMING SOON!
reset_sync = true # Recalculate synchronization even if already done
speed_kind = 'y' # 'y' showed best performance.
id_kpt = [10] # number from keypoint name in skeleton.py. RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
[personAssociation]
tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
reproj_error_threshold_association = 20 # px
likelihood_threshold_association = 0.3
likelihood_threshold_association = 0.3
[personAssociation.single_person]
reproj_error_threshold_association = 20 # px
tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
[personAssociation.multi_person]
reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
min_affinity = 0.2 # affinity below which a correspondence is ignored
[triangulation]

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,38 +139,39 @@
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
## Only works on BODY_25 and BODY_25B models
# participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials)
# participant_mass = 70.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,38 +139,39 @@
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
## Only works on BODY_25 and BODY_25B models
# participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials)
# participant_mass = 70.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -139,40 +137,41 @@
[filtering]
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
display_figures = true # true or false (lowercase)
display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
## Only works on BODY_25 and BODY_25B models
# participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials)
# participant_mass = 70.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@ display_figures = true # true or false (lowercase)
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,38 +139,39 @@
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
# participant_height = [1.21, 1.72] # m # float if single person, list of float if multi-person (same order as the Static trials)
# participant_mass = [25.0, 70.0] # kg
## Only works on BODY_25 and BODY_25B models
# participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials)
# participant_mass = 70.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,38 +139,39 @@
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
[markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
## Only works on BODY_25 and BODY_25B models
participant_height = 1.21 # m # float if single person, list of float if multi-person (same order as the Static trials)
participant_mass = 25.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@ participant_mass = 25.0 # kg
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
# [project]
# multi_person = false # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
# nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
# multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# # tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
# [triangulation]
# reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,38 +139,39 @@
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
[markerAugmentation]
# ## Only works on BODY_25 and BODY_25B models
## Only works on BODY_25 and BODY_25B models
participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials)
participant_mass = 70.0 # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@ participant_mass = 70.0 # kg
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -16,84 +16,22 @@
# [filtering.butterworth] and set cut_off_frequency = 10, etc.
[project]
multi_person = true # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
nb_persons_to_detect = 2 # checked only if multi_person is selected
# frame_rate = 60 # FPS
multi_person = true # If false, only the main person in scene is analyzed.
# frame_rate = 60 # fps
# frame_range = [] # For example [10,300], or [] for all frames
## N.B.: If you want a time range instead, use frame_range = time_range * frame_rate
## For example if you want to analyze from 0.1 to 2 seconds with a 60 fps frame rate,
## frame_range = [0.1, 2.0]*frame_rate = [6, 120]
# exclude_from_batch = [] # List of trials to be excluded from batch analysis, ['<participant_dir/trial_dir>', 'etc'].
# e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P00_Participant/S00_P00_T01_BalancingTrial']
## Take heart, calibration is not that complicated once you get the hang of it!
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', or 'biocv'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
## Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
## Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
## 'board' should be large enough to be detected when laid on the floor. Not recommended.
## 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
## 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# calculate_extrinsics = true # true or false (lowercase)
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
## list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
## in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
## Coming soon!
# [pose]
# pose_framework = 'openpose' # 'openpose', 'mediapipe', 'alphapose', 'deeplabcut'
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII,
# pose_model = 'BODY_25B' #With openpose: BODY_25B, BODY_25, BODY_135, COCO, MPII
# #With mediapipe: BLAZEPOSE.
# #With alphapose: HALPE_26, HALPE_68, HALPE_136, COCO_133.
# #With deeplabcut: CUSTOM. See example at the end of the file.
@ -103,33 +41,93 @@ nb_persons_to_detect = 2 # checked only if multi_person is selected
# [synchronization]
## COMING SOON!
# display_corr = true # true or false (lowercase)
# reset_sync = true # Recalculate synchronization even if already done
# frames = [2850,3490] # Frames to use for synchronization, should point to a moment with fast motion.
# cut_off_frequency = 10 # cut-off frequency for a 4th order low-pass Butterworth filter
## Vertical speeds (on X, Y, or Z axis, or 2D speeds)
# speed_kind = 'y' # 'x', 'y', 'z', or '2D'
# vmax = 20 # px/s
# cam1_nb = 4
# cam2_nb = 3
# id_kpt = [9,10] # Pour plus tard aller chercher numéro depuis keypoint name dans skeleton.py. 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# weights_kpt = [1,1] # Pris en compte uniquement si on a plusieurs keypoints
# # id_kpt = [10] # keypoint ID, to be found in skeleton.py. Example RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
# # weights_kpt = [1] # Only taken into account if you have several keypoints (Currently only one keypoint is supported).
# sync_frame_range = [] # For example [0,150], or [] for all frames (default)
# # limit synchronization search (to the beginning or to the end of the capture for example)
# [calibration]
# calibration_type = 'convert' # 'convert' or 'calculate'
# [calibration.convert]
# convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', 'easymocap', 'biocv', 'anipose', or 'freemocap'
# [calibration.convert.qualisys]
# binning_factor = 1 # Usually 1, except when filming in 540p where it usually is 2
# [calibration.convert.optitrack] # See readme for instructions
# [calibration.convert.vicon] # No parameter needed
# [calibration.convert.opencap] # No parameter needed
# [calibration.convert.easymocap] # No parameter needed
# [calibration.convert.biocv] # No parameter needed
# [calibration.convert.anipose] # No parameter needed
# [calibration.convert.freemocap] # No parameter needed
# [calibration.calculate]
# # Camera properties, theoretically need to be calculated only once in a camera lifetime
# [calibration.calculate.intrinsics]
# overwrite_intrinsics = false # overwrite (or not) if they have already been calculated?
# show_detection_intrinsics = true # true or false (lowercase)
# intrinsics_extension = 'jpg' # any video or image extension
# extract_every_N_sec = 1 # if video, extract frames every N seconds (can be <1 )
# intrinsics_corners_nb = [4,7]
# intrinsics_square_size = 60 # mm
# # Camera placements, need to be done before every session
# [calibration.calculate.extrinsics]
# calculate_extrinsics = true # true or false (lowercase)
# extrinsics_method = 'scene' # 'board', 'scene', 'keypoints'
# # 'board' should be large enough to be detected when laid on the floor. Not recommended.
# # 'scene' involves manually clicking any point of know coordinates on scene. Usually more accurate if points are spread out.
# # 'keypoints' uses automatic pose estimation of a person freely walking and waving arms in the scene. Slighlty less accurate, requires synchronized cameras.
# moving_cameras = false # Not implemented yet
# [calibration.calculate.extrinsics.board]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# extrinsics_corners_nb = [4,7] # [H,W] rather than [w,h]
# extrinsics_square_size = 60 # mm # [h,w] if square is actually a rectangle
# [calibration.calculate.extrinsics.scene]
# show_reprojection_error = true # true or false (lowercase)
# extrinsics_extension = 'png' # any video or image extension
# # list of 3D coordinates to be manually labelled on images. Can also be a 2 dimensional plane.
# # in m -> unlike for intrinsics, NOT in mm!
# object_coords_3d = [[-2.0, 0.3, 0.0],
# [-2.0 , 0.0, 0.0],
# [-2.0, 0.0, 0.05],
# [-2.0, -0.3 , 0.0],
# [0.0, 0.3, 0.0],
# [0.0, 0.0, 0.0],
# [0.0, 0.0, 0.05],
# [0.0, -0.3, 0.0]]
# [calibration.calculate.extrinsics.keypoints]
# # Coming soon!
# [personAssociation]
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
## and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# reproj_error_threshold_association = 20 # px
# likelihood_threshold_association = 0.05
# likelihood_threshold_association = 0.3
# [personAssociation.single_person]
# reproj_error_threshold_association = 20 # px
# tracked_keypoint = 'Neck' # If the neck is not detected by the pose_model, check skeleton.py
# # and choose a stable point for tracking the person of interest (e.g., 'right_shoulder' with BLAZEPOSE)
# [personAssociation.multi_person]
# reconstruction_error_threshold = 0.1 # 0.1 = 10 cm
# min_affinity = 0.2 # affinity below which a correspondence is ignored
[triangulation]
reorder_trc = true # only checked if multi_person analysis
reorder_trc = false # only checked if multi_person analysis
# reproj_error_threshold_triangulation = 15 # px
# likelihood_threshold_triangulation= 0.05
# likelihood_threshold_triangulation= 0.3
# min_cameras_for_triangulation = 2
# interpolation = 'cubic' #linear, slinear, quadratic, cubic, or none
## 'none' if you don't want to interpolate missing points
# # 'none' if you don't want to interpolate missing points
# interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps
# show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated
# handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower
@ -141,22 +139,22 @@ reorder_trc = true # only checked if multi_person analysis
# type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed
# display_figures = false # true or false (lowercase)
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
## How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
# [filtering.butterworth]
# order = 4
# cut_off_frequency = 6 # Hz
# [filtering.kalman]
# # How much more do you trust triangulation results (measurements), than previous data (process assuming constant acceleration)?
# trust_ratio = 100 # = measurement_trust/process_trust ~= process_noise/measurement_noise
# smooth = true # should be true, unless you need real-time filtering
# [filtering.butterworth_on_speed]
# order = 4
# cut_off_frequency = 10 # Hz
# [filtering.gaussian]
# sigma_kernel = 2 #px
# [filtering.LOESS]
# nb_values_used = 30 # = fraction of data used * nb frames
# [filtering.median]
# kernel_size = 9
[markerAugmentation]
@ -167,12 +165,13 @@ participant_mass = [25.0, 70.0] # kg
# [opensim]
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
## CUSTOM skeleton, if you trained your own DeepLabCut model for example.
## Make sure the node ids correspond to the column numbers of the 2D pose file, starting from zero.
##
@ -188,65 +187,65 @@ participant_mass = [25.0, 70.0] # kg
# name = "CHip"
# id = "None"
# [[pose.CUSTOM.children]]
# id = 12
# name = "RHip"
# id = 12
# [[pose.CUSTOM.children.children]]
# id = 14
# name = "RKnee"
# id = 14
# [[pose.CUSTOM.children.children.children]]
# id = 16
# name = "RAnkle"
# id = 16
# [[pose.CUSTOM.children.children.children.children]]
# id = 22
# name = "RBigToe"
# id = 22
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 23
# name = "RSmallToe"
# id = 23
# [[pose.CUSTOM.children.children.children.children]]
# id = 24
# name = "RHeel"
# id = 24
# [[pose.CUSTOM.children]]
# id = 11
# name = "LHip"
# id = 11
# [[pose.CUSTOM.children.children]]
# id = 13
# name = "LKnee"
# id = 13
# [[pose.CUSTOM.children.children.children]]
# id = 15
# name = "LAnkle"
# id = 15
# [[pose.CUSTOM.children.children.children.children]]
# id = 19
# name = "LBigToe"
# id = 19
# [[pose.CUSTOM.children.children.children.children.children]]
# id = 20
# name = "LSmallToe"
# id = 20
# [[pose.CUSTOM.children.children.children.children]]
# id = 21
# name = "LHeel"
# id = 21
# [[pose.CUSTOM.children]]
# id = 17
# name = "Neck"
# id = 17
# [[pose.CUSTOM.children.children]]
# id = 18
# name = "Head"
# id = 18
# [[pose.CUSTOM.children.children.children]]
# id = 0
# name = "Nose"
# id = 0
# [[pose.CUSTOM.children.children]]
# id = 6
# name = "RShoulder"
# id = 6
# [[pose.CUSTOM.children.children.children]]
# id = 8
# name = "RElbow"
# id = 8
# [[pose.CUSTOM.children.children.children.children]]
# id = 10
# name = "RWrist"
# id = 10
# [[pose.CUSTOM.children.children]]
# id = 5
# name = "LShoulder"
# id = 5
# [[pose.CUSTOM.children.children.children]]
# id = 7
# name = "LElbow"
# id = 7
# [[pose.CUSTOM.children.children.children.children]]
# id = 9
# name = "LWrist"
# id = 9

View File

@ -15,7 +15,7 @@
Usage:
python -m json_display_without_img -j json_folder -W 1920 -H 1080
python -m json_display_without_img -j json_folder -o output_img_folder -d True -s True -W 1920 -H 1080 - 30
python -m json_display_without_img -j json_folder -o output_img_folder -d True -s True -W 1920 -H 1080 -f 30
import json_display_without_img; json_display_without_img.json_display_without_img_func(json_folder=r'<json_folder>', image_width=1920, image_height = 1080)
'''
@ -60,7 +60,7 @@ def json_display_without_img_func(**args):
Usage:
json_display_without_img -j json_folder -W 1920 -H 1080
json_display_without_img -j json_folder -o output_img_folder -d True -s True -W 1920 -H 1080
json_display_without_img -j json_folder -o output_img_folder -d True -s True -W 1920 -H 1080 -f 30
import json_display_without_img; json_display_without_img.json_display_without_img_func(json_folder=r'<json_folder>', image_width=1920, image_height = 1080)
'''

View File

@ -114,10 +114,10 @@ def interpolate_nans(col, kind):
def plot_time_lagged_cross_corr(camx, camy, ax):
pearson_r = [camx.corr(camy.shift(lag)) for lag in range(-2*fps, 2*fps)] # lag -2 sec à +2 sec
offset = int(np.floor(len(pearson_r)*2)-np.argmax(pearson_r))
offset = int(np.floor(len(pearson_r)/2)-np.argmax(pearson_r))
max_corr = np.max(pearson_r)
ax.plot(list(range(-2*fps, 2*fps)), pearson_r)
ax.axvline(np.ceil(len(pearson_r)*2)-2*fps,color='k',linestyle='--')
ax.axvline(np.ceil(len(pearson_r)/2)-2*fps,color='k',linestyle='--')
ax.axvline(np.argmax(pearson_r)-2*fps,color='r',linestyle='--',label='Peak synchrony')
plt.annotate(f'Max correlation={np.round(max_corr,2)}', xy=(0.05, 0.9), xycoords='axes fraction')
ax.set(title=f'Offset = {offset} frames', xlabel='Offset (frames)',ylabel='Pearson r')
@ -156,7 +156,6 @@ with open(os.path.join(pose_dir, 'coords'), 'wb') as fp:
#############################
# Vitesse verticale
df_speed = []
for i in range(len(json_dirs)):
@ -199,6 +198,41 @@ else:
raise ValueError('wrong values for id_kpt or weights_kpt')
# camx = df_speed[1][16]
# camy = df_speed[2][16]
# camx = df_speed[1][10]
# camy = df_speed[2][10]
# camx = df_speed[1].sum(axis=1)
# camy = df_speed[2].sum(axis=1)
# camx.plot()
# camy.plot()
# plt.show()
for i in range(25):
df_coords[1].iloc[:,i*2+1].plot(label='1')
df_coords[2].iloc[:,i*2+1].plot(label='2')
plt.title(i)
plt.legend()
plt.show()
for i in range(25):
df_speed[1].iloc[:,i].plot(label='1')
df_speed[2].iloc[:,i].plot(label='2')
plt.title(i)
plt.legend()
plt.show()
for i in range(4):
abs(df_speed[i]).sum(axis=1).plot(label=i)
plt.legend()
plt.show()
df_speed[0].plot() # --> remove janky points
plt.show()
f, ax = plt.subplots(2,1)
# speed
camx.plot(ax=ax[0], label = f'cam {cam1_nb}')

View File

@ -12,6 +12,7 @@ Functions shared between modules, and other utilities
## INIT
import toml
import json
import numpy as np
import re
import cv2
@ -37,6 +38,58 @@ __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 bounding_boxes(js_file, margin_percent=0.1, around='extremities'):
'''
Compute the bounding boxes of the people in the json file.
Either around the extremities (with a margin)
or around the center of the person (with a margin).
INPUTS:
- js_file: json file
- margin_percent: margin around the person
- around: 'extremities' or 'center'
OUTPUT:
- bounding_boxes: list of bounding boxes [x_min, y_min, x_max, y_max]
'''
bounding_boxes = []
with open(js_file, 'r') as json_f:
js = json.load(json_f)
for people in range(len(js['people'])):
if len(js['people'][people]['pose_keypoints_2d']) < 3: continue
else:
x = js['people'][people]['pose_keypoints_2d'][0::3]
y = js['people'][people]['pose_keypoints_2d'][1::3]
x_min, x_max = min(x), max(x)
y_min, y_max = min(y), max(y)
if around == 'extremities':
dx = (x_max - x_min) * margin_percent
dy = (y_max - y_min) * margin_percent
bounding_boxes.append([x_min-dx, y_min-dy, x_max+dx, y_max+dy])
elif around == 'center':
x_mean, y_mean = np.mean(x), np.mean(y)
x_size = (x_max - x_min) * (1 + margin_percent)
y_size = (y_max - y_min) * (1 + margin_percent)
bounding_boxes.append([x_mean - x_size/2, y_mean - y_size/2, x_mean + x_size/2, y_mean + y_size/2])
return bounding_boxes
def retrieve_calib_params(calib_file):
'''
Compute projection matrices from toml calibration file.
@ -48,6 +101,7 @@ def retrieve_calib_params(calib_file):
- S: (h,w) vectors as list of 2x1 arrays
- K: intrinsic matrices as list of 3x3 arrays
- dist: distortion vectors as list of 4x1 arrays
- inv_K: inverse intrinsic matrices as list of 3x3 arrays
- optim_K: intrinsic matrices for undistorting points as list of 3x3 arrays
- R: rotation rodrigue vectors as list of 3x1 arrays
- T: translation vectors as list of 3x1 arrays
@ -55,16 +109,18 @@ def retrieve_calib_params(calib_file):
calib = toml.load(calib_file)
S, K, dist, optim_K, R, T = [], [], [], [], [], []
S, K, dist, optim_K, inv_K, R, R_mat, T = [], [], [], [], [], [], [], []
for c, cam in enumerate(calib.keys()):
if cam != 'metadata':
S.append(np.array(calib[cam]['size']))
K.append(np.array(calib[cam]['matrix']))
dist.append(np.array(calib[cam]['distortions']))
optim_K.append(cv2.getOptimalNewCameraMatrix(K[c], dist[c], [int(s) for s in S[c]], 1, [int(s) for s in S[c]])[0])
inv_K.append(np.linalg.inv(K[c]))
R.append(np.array(calib[cam]['rotation']))
R_mat.append(cv2.Rodrigues(R[c])[0])
T.append(np.array(calib[cam]['translation']))
calib_params = {'S': S, 'K': K, 'dist': dist, 'optim_K': optim_K, 'R': R, 'T': T}
calib_params = {'S': S, 'K': K, 'dist': dist, 'inv_K': inv_K, 'optim_K': optim_K, 'R': R, 'R_mat': R_mat, 'T': T}
return calib_params

View File

@ -8,13 +8,17 @@
###########################################################################
Openpose detects all people in the field of view.
Which is the one of interest?
- multi_person = false: Which is the one of interest?
- multi_person = true: How to triangulate the same persons across views?
How to associate them across time frames? Done in the
triangulation stage.
This module tries all possible triangulations of a chosen anatomical
point. If "multi_person" mode is not used, it chooses the person for
whom the reprojection error is smallest. Otherwise, it selects all
persons with a reprojection error smaller than a threshold, and then
associates them across time frames by minimizing the displacement speed.
If multi_person = false, this module tries all possible triangulations of a chosen
anatomical point, and chooses the person for whom the reprojection error is smallest.
If multi_person = true, it computes the distance between epipolar lines (camera to
keypoint lines) for all persons detected in all views, and selects the best correspondences.
The computation of the affinity matrix from the distance is inspired from the EasyMocap approach.
INPUTS:
- a calibration file (.toml extension)
@ -58,97 +62,6 @@ __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).
Select the smallest values of L, considering that
the next smallest value cannot have the same numbers
in the associated tuple as any of the previous ones.
Example:
L = [ 20, 27, 51, 33, 43, 23, 37, 24, 4, 68, 84, 3 ]
T = list(it.product(range(2),range(3)))
= [(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3),(2,0),(2,1),(2,2),(2,3)]
- 1st smallest value: 3 with tuple (2,3), index 11
- 2nd smallest value when excluding indices (2,.) and (.,3), i.e. [(0,0),(0,1),(0,2),X,(1,0),(1,1),(1,2),X,X,X,X,X]:
20 with tuple (0,0), index 0
- 3rd smallest value when excluding [X,X,X,X,X,(1,1),(1,2),X,X,X,X,X]:
23 with tuple (1,1), index 5
INPUTS:
- L: list (size s)
- T: T associated tuple indices (size s)
OUTPUTS:
- minL: list of smallest values of L, considering constraints on tuple indices
- argminL: list of indices of smallest values of L
- T_minL: list of tuples associated with smallest values of L
'''
minL = [np.min(L)]
argminL = [np.argmin(L)]
T_minL = [T[argminL[0]]]
mask_tokeep = np.array([True for t in T])
i=0
while mask_tokeep.any()==True:
mask_tokeep = mask_tokeep & np.array([t[0]!=T_minL[i][0] and t[1]!=T_minL[i][1] for t in T])
if mask_tokeep.any()==True:
indicesL_tokeep = np.where(mask_tokeep)[0]
minL += [np.min(np.array(L)[indicesL_tokeep])]
argminL += [indicesL_tokeep[np.argmin(np.array(L)[indicesL_tokeep])]]
T_minL += (T[argminL[i+1]],)
i+=1
return minL, argminL, T_minL
def sort_people(Q_kpt_old, Q_kpt):
'''
Associate persons across frames
Persons' indices are sometimes swapped when changing frame
A person is associated to another in the next frame when they are at a small distance
INPUTS:
- Q_kpt_old: list of arrays of 3D coordinates [X, Y, Z, 1.] for the previous frame
- Q_kpt: idem Q_kpt_old, for current frame
OUTPUT:
- Q_kpt: array with reordered persons
- personsIDs_sorted: index of reordered persons
'''
# 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 = []
for comb in personsIDs_comb:
frame_by_frame_dist += [euclidean_distance(Q_kpt_old[comb[0]][:3],Q_kpt[comb[1]][:3])]
# sort correspondences by distance
_, index_best_comb, _ = min_with_single_indices(frame_by_frame_dist, personsIDs_comb)
index_best_comb.sort()
personsIDs_sorted = np.array(personsIDs_comb)[index_best_comb][:,1]
# rearrange persons
Q_kpt = np.array(Q_kpt)[personsIDs_sorted]
return Q_kpt, personsIDs_sorted
def persons_combinations(json_files_framef):
'''
Find all possible combinations of detected persons' ids.
@ -179,10 +92,63 @@ def persons_combinations(json_files_framef):
return personsIDs_comb
def triangulate_comb(comb, coords, P_all, calib_params, config):
'''
Triangulate 2D points and compute reprojection error for a combination of cameras.
INPUTS:
- comb: list of ints: combination of persons' ids for each camera
- coords: array: x, y, likelihood for each camera
- P_all: list of arrays: projection matrices for each camera
- calib_params: dict: calibration parameters
- config: dictionary from Config.toml file
OUTPUTS:
- error_comb: float: reprojection error
- comb: list of ints: combination of persons' ids for each camera
- Q_comb: array: 3D coordinates of the triangulated point
'''
undistort_points = config.get('triangulation').get('undistort_points')
likelihood_threshold = config.get('personAssociation').get('likelihood_threshold_association')
# Replace likelihood by 0. if under likelihood_threshold
coords[:,2][coords[:,2] < likelihood_threshold] = 0.
comb[coords[:,2] == 0.] = np.nan
# Filter coords and projection_matrices containing nans
coords_filt = [coords[i] for i in range(len(comb)) if not np.isnan(comb[i])]
projection_matrices_filt = [P_all[i] for i in range(len(comb)) if not np.isnan(comb[i])]
if undistort_points:
calib_params_R_filt = [calib_params['R'][i] for i in range(len(comb)) if not np.isnan(comb[i])]
calib_params_T_filt = [calib_params['T'][i] for i in range(len(comb)) if not np.isnan(comb[i])]
calib_params_K_filt = [calib_params['K'][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
x_files_filt, y_files_filt, likelihood_files_filt = np.array(coords_filt).T
Q_comb = weighted_triangulation(projection_matrices_filt, x_files_filt, y_files_filt, likelihood_files_filt)
# Reprojection
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(len(Q_comb))]
x_calc = [coords_2D_kpt_calc_filt[i][0,0,0] for i in range(len(Q_comb))]
y_calc = [coords_2D_kpt_calc_filt[i][0,0,1] for i in range(len(Q_comb))]
else:
x_calc, y_calc = reprojection(projection_matrices_filt, Q_comb)
# Reprojection error
error_comb_per_cam = []
for cam in range(len(x_calc)):
q_file = (x_files_filt[cam], y_files_filt[cam])
q_calc = (x_calc[cam], y_calc[cam])
error_comb_per_cam.append( euclidean_distance(q_file, q_calc) )
error_comb = np.mean(error_comb_per_cam)
return error_comb, comb, Q_comb
def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_combinations, projection_matrices, tracked_keypoint_id, calib_params):
'''
- if multi_person: Choose all the combination of cameras that give a reprojection error below a threshold
- else: Chooses the right person among the multiple ones found by
Chooses the right person among the multiple ones found by
OpenPose & excludes cameras with wrong 2d-pose estimation.
1. triangulate the tracked keypoint for all possible combinations of people,
@ -203,9 +169,7 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
- comb_errors_below_thresh: list of arrays of ints
'''
multi_person = config.get('project').get('multi_person')
nb_persons_to_detect = config.get('project').get('nb_persons_to_detect')
error_threshold_tracking = config.get('personAssociation').get('reproj_error_threshold_association')
error_threshold_tracking = config.get('personAssociation').get('single_person').get('reproj_error_threshold_association')
likelihood_threshold = config.get('personAssociation').get('likelihood_threshold_association')
min_cameras_for_triangulation = config.get('triangulation').get('min_cameras_for_triangulation')
undistort_points = config.get('triangulation').get('undistort_points')
@ -219,29 +183,22 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
while error_min > error_threshold_tracking and n_cams - nb_cams_off >= min_cameras_for_triangulation:
# Try all persons combinations
for combination in personsIDs_combinations:
# Get x,y,likelihood values from files
x_files, y_files,likelihood_files = [], [], []
# Get coords from files
coords = []
for index_cam, person_nb in enumerate(combination):
with open(json_files_framef[index_cam], 'r') as json_f:
js = json.load(json_f)
try:
x_files.append( js['people'][int(person_nb)]['pose_keypoints_2d'][tracked_keypoint_id*3] )
y_files.append( js['people'][int(person_nb)]['pose_keypoints_2d'][tracked_keypoint_id*3+1] )
likelihood_files.append( js['people'][int(person_nb)]['pose_keypoints_2d'][tracked_keypoint_id*3+2] )
except:
x_files.append(np.nan)
y_files.append(np.nan)
likelihood_files.append(np.nan)
try:
js = read_json(json_files_framef[index_cam])
coords.append(js[int(person_nb)][tracked_keypoint_id*3:tracked_keypoint_id*3+3])
except:
coords.append([np.nan, np.nan, np.nan])
coords = np.array(coords)
# undistort points
if undistort_points:
points = np.array(tuple(zip(x_files,y_files))).reshape(-1, 1, 2).astype('float32')
points = np.array(coords)[:,None,:2]
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)]
x_files = np.array([[u[i][0][0] for i in range(len(u))] for u in undistorted_points]).squeeze()
y_files = np.array([[u[i][0][1] for i in range(len(u))] for u in undistorted_points]).squeeze()
# Replace likelihood by 0. if under likelihood_threshold
likelihood_files = [0. if lik < likelihood_threshold else lik for lik in likelihood_files]
coords[:,0] = np.array([[u[i][0][0] for i in range(len(u))] for u in undistorted_points]).squeeze()
coords[:,1] = np.array([[u[i][0][1] for i in range(len(u))] for u in undistorted_points]).squeeze()
# For each persons combination, create subsets with "nb_cams_off" cameras excluded
id_cams_off = list(it.combinations(range(len(combination)), nb_cams_off))
@ -250,91 +207,338 @@ def best_persons_and_cameras_combination(config, json_files_framef, personsIDs_c
combinations_with_cams_off[i,id] = np.nan
# Try all subsets
error_comb = []
Q_comb = []
error_comb_all, comb_all, Q_comb_all = [], [], []
for comb in combinations_with_cams_off:
# 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])]
y_files_filt = [y_files[i] for i in range(len(comb)) if not np.isnan(comb[i])]
likelihood_files_filt = [likelihood_files[i] for i in range(len(comb)) if not np.isnan(comb[i])]
projection_matrices_filt = [projection_matrices[i] for i in range(len(comb)) if not np.isnan(comb[i])]
if undistort_points:
calib_params_R_filt = [calib_params['R'][i] for i in range(len(comb)) if not np.isnan(comb[i])]
calib_params_T_filt = [calib_params['T'][i] for i in range(len(comb)) if not np.isnan(comb[i])]
calib_params_K_filt = [calib_params['K'][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])]
error_comb, comb, Q_comb = triangulate_comb(comb, coords, projection_matrices, calib_params, config)
error_comb_all.append(error_comb)
comb_all.append(comb)
Q_comb_all.append(Q_comb)
# Triangulate 2D points
Q_comb.append(weighted_triangulation(projection_matrices_filt, x_files_filt, y_files_filt, likelihood_files_filt))
# Reprojection
if undistort_points:
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)]
y_calc = [coords_2D_kpt_calc_filt[i][0,0,1] for i in range(n_cams-nb_cams_off)]
else:
x_calc, y_calc = reprojection(projection_matrices_filt, Q_comb[-1])
# Reprojection error
error_comb_per_cam = []
for cam in range(len(x_calc)):
q_file = (x_files_filt[cam], y_files_filt[cam])
q_calc = (x_calc[cam], y_calc[cam])
error_comb_per_cam.append( euclidean_distance(q_file, q_calc) )
error_comb.append( np.mean(error_comb_per_cam) )
if multi_person:
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]
else:
error_min = np.nanmin(error_comb)
errors_below_thresh = [error_min]
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
if multi_person:
if len(errors_below_thresh)>0:
# 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 = [i for i, x in enumerate(comb_errors_below_thresh) for c in comb_ok if np.array_equal(x,c,equal_nan=True)]
errors_below_thresh = np.array(errors_below_thresh_sorted)[sorted_pruned_idx].tolist()
comb_errors_below_thresh = np.array(comb_errors_below_thresh)[sorted_pruned_idx].tolist()
Q_kpt = Q_kpt[sorted_pruned_idx].tolist()
# 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(errors_below_thresh) >= len(personsIDs_combinations) or len(errors_below_thresh) >= nb_persons_to_detect:
errors_below_thresh = errors_below_thresh[:nb_persons_to_detect]
comb_errors_below_thresh = comb_errors_below_thresh[:nb_persons_to_detect]
Q_kpt = Q_kpt[:nb_persons_to_detect]
error_min = np.nanmin(error_comb_all)
comb_error_min = [comb_all[np.argmin(error_comb_all)]]
Q_kpt = [Q_comb_all[np.argmin(error_comb_all)]]
if error_min < error_threshold_tracking:
break
nb_cams_off += 1
return errors_below_thresh, comb_errors_below_thresh, Q_kpt
return error_min, comb_error_min, Q_kpt
def recap_tracking(config, error, nb_cams_excluded):
def read_json(js_file):
'''
Read OpenPose json file
'''
with open(js_file, 'r') as json_f:
js = json.load(json_f)
json_data = []
for people in range(len(js['people'])):
if len(js['people'][people]['pose_keypoints_2d']) < 3: continue
else:
json_data.append(js['people'][people]['pose_keypoints_2d'])
return json_data
def compute_rays(json_coord, calib_params, cam_id):
'''
Plucker coordinates of rays from camera to each joint of a person
Plucker coordinates: camera to keypoint line direction (size 3)
moment: origin ^ line (size 3)
additionally, confidence
INPUTS:
- json_coord: x, y, likelihood for a person seen from a camera (list of 3*joint_nb)
- calib_params: calibration parameters from retrieve_calib_params('calib.toml')
- cam_id: camera id (int)
OUTPUT:
- plucker: array. nb joints * (6 plucker coordinates + 1 likelihood)
'''
x = json_coord[0::3]
y = json_coord[1::3]
likelihood = json_coord[2::3]
inv_K = calib_params['inv_K'][cam_id]
R_mat = calib_params['R_mat'][cam_id]
T = calib_params['T'][cam_id]
cam_center = -R_mat.T @ T
plucker = []
for i in range(len(x)):
q = np.array([x[i], y[i], 1])
norm_Q = R_mat.T @ (inv_K @ q -T)
line = norm_Q - cam_center
norm_line = line/np.linalg.norm(line)
moment = np.cross(cam_center, norm_line)
plucker.append(np.concatenate([norm_line, moment, [likelihood[i]]]))
return np.array(plucker)
def broadcast_line_to_line_distance(p0, p1):
'''
Compute the distance between two lines in 3D space.
see: https://faculty.sites.iastate.edu/jia/files/inline-files/plucker-coordinates.pdf
p0 = (l0,m0), p1 = (l1,m1)
dist = | (l0,m0) * (l1,m1) | / || l0 x l1 ||
(l0,m0) * (l1,m1) = l0 @ m1 + m0 @ l1 (reciprocal product)
No need to divide by the norm of the cross product of the directions, since we
don't need the actual distance but whether the lines are close to intersecting or not
=> dist = | (l0,m0) * (l1,m1) |
INPUTS:
- p0: array(nb_persons_detected * 1 * nb_joints * 7 coordinates)
- p1: array(1 * nb_persons_detected * nb_joints * 7 coordinates)
OUTPUT:
- dist: distances between the two lines (not normalized).
array(nb_persons_0 * nb_persons_1 * nb_joints)
'''
product = np.sum(p0[..., :3] * p1[..., 3:6], axis=-1) + np.sum(p1[..., :3] * p0[..., 3:6], axis=-1)
dist = np.abs(product)
return dist
def compute_affinity(all_json_data_f, calib_params, cum_persons_per_view, reconstruction_error_threshold=0.1):
'''
Compute the affinity between all the people in the different views.
The affinity is defined as 1 - distance/max_distance, with distance the
distance between epipolar lines in each view (reciprocal product of Plucker
coordinates).
Another approach would be to project one epipolar line onto the other camera
plane and compute the line to point distance, but it is more computationally
intensive (simple dot product vs. projection and distance calculation).
INPUTS:
- all_json_data_f: list of json data. For frame f, nb_views*nb_persons*(x,y,likelihood)*nb_joints
- calib_params: calibration parameters from retrieve_calib_params('calib.toml')
- cum_persons_per_view: cumulative number of persons per view
- reconstruction_error_threshold: maximum distance between epipolar lines to consider a match
OUTPUT:
- affinity: affinity matrix between all the people in the different views.
(nb_views*nb_persons_per_view * nb_views*nb_persons_per_view)
'''
# Compute plucker coordinates for all keypoints for each person in each view
# pluckers_f: dims=(camera, person, joint, 7 coordinates)
pluckers_f = []
for cam_id, json_cam in enumerate(all_json_data_f):
pluckers = []
for json_coord in json_cam:
plucker = compute_rays(json_coord, calib_params, cam_id) # LIMIT TO 15 JOINTS? json_coord[:15*3]
pluckers.append(plucker)
pluckers = np.array(pluckers)
pluckers_f.append(pluckers)
# Compute affinity matrix
distance = np.zeros((cum_persons_per_view[-1], cum_persons_per_view[-1])) + 2*reconstruction_error_threshold
for compared_cam0, compared_cam1 in it.combinations(range(len(all_json_data_f)), 2):
# skip when no detection for a camera
if cum_persons_per_view[compared_cam0] == cum_persons_per_view[compared_cam0+1] \
or cum_persons_per_view[compared_cam1] == cum_persons_per_view[compared_cam1 +1]:
continue
# compute distance
p0 = pluckers_f[compared_cam0][:,None] # add coordinate on second dimension
p1 = pluckers_f[compared_cam1][None,:] # add coordinate on first dimension
dist = broadcast_line_to_line_distance(p0, p1)
likelihood = np.sqrt(p0[..., -1] * p1[..., -1])
mean_weighted_dist = np.sum(dist*likelihood, axis=-1)/(1e-5 + likelihood.sum(axis=-1)) # array(nb_persons_0 * nb_persons_1)
# populate distance matrix
distance[cum_persons_per_view[compared_cam0]:cum_persons_per_view[compared_cam0+1], \
cum_persons_per_view[compared_cam1]:cum_persons_per_view[compared_cam1+1]] \
= mean_weighted_dist
distance[cum_persons_per_view[compared_cam1]:cum_persons_per_view[compared_cam1+1], \
cum_persons_per_view[compared_cam0]:cum_persons_per_view[compared_cam0+1]] \
= mean_weighted_dist.T
# compute affinity matrix and clamp it to zero when distance > reconstruction_error_threshold
distance[distance > reconstruction_error_threshold] = reconstruction_error_threshold
affinity = 1 - distance / reconstruction_error_threshold
return affinity
def circular_constraint(cum_persons_per_view):
'''
A person can be matched only with themselves in the same view, and with any
person from other views
INPUT:
- cum_persons_per_view: cumulative number of persons per view
OUTPUT:
- circ_constraint: circular constraint matrix
'''
circ_constraint = np.identity(cum_persons_per_view[-1])
for i in range(len(cum_persons_per_view)-1):
circ_constraint[cum_persons_per_view[i]:cum_persons_per_view[i+1], cum_persons_per_view[i+1]:cum_persons_per_view[-1]] = 1
circ_constraint[cum_persons_per_view[i+1]:cum_persons_per_view[-1], cum_persons_per_view[i]:cum_persons_per_view[i+1]] = 1
return circ_constraint
def SVT(matrix, threshold):
'''
Find a low-rank approximation of the matrix using Singular Value Thresholding.
INPUTS:
- matrix: matrix to decompose
- threshold: threshold for singular values
OUTPUT:
- matrix_thresh: low-rank approximation of the matrix
'''
U, s, Vt = np.linalg.svd(matrix) # decompose matrix
s_thresh = np.maximum(s - threshold, 0) # set smallest singular values to zero
matrix_thresh = U @ np.diag(s_thresh) @ Vt # recompose matrix
return matrix_thresh
def matchSVT(affinity, cum_persons_per_view, circ_constraint, max_iter = 20, w_rank = 50, tol = 1e-4, w_sparse=0.1):
'''
Find low-rank approximation of 'affinity' while satisfying the circular constraint.
INPUTS:
- affinity: affinity matrix between all the people in the different views
- cum_persons_per_view: cumulative number of persons per view
- circ_constraint: circular constraint matrix
- max_iter: maximum number of iterations
- w_rank: threshold for singular values
- tol: tolerance for convergence
- w_sparse: regularization parameter
OUTPUT:
- new_aff: low-rank approximation of the affinity matrix
'''
new_aff = affinity.copy()
N = new_aff.shape[0]
index_diag = np.arange(N)
new_aff[index_diag, index_diag] = 0.
# new_aff = (new_aff + new_aff.T)/2 # symmetric by construction
Y = np.zeros_like(new_aff) # Initial deviation matrix / residual ()
W = w_sparse - new_aff # Initial sparse matrix / regularization (prevent overfitting)
mu = 64 # initial step size
for iter in range(max_iter):
new_aff0 = new_aff.copy()
Q = new_aff + Y*1.0/mu
Q = SVT(Q,w_rank/mu)
new_aff = Q - (W + Y)/mu
# Project X onto dimGroups
for i in range(len(cum_persons_per_view) - 1):
ind1, ind2 = cum_persons_per_view[i], cum_persons_per_view[i + 1]
new_aff[ind1:ind2, ind1:ind2] = 0
# Reset diagonal elements to one and ensure X is within valid range [0, 1]
new_aff[index_diag, index_diag] = 1.
new_aff[new_aff < 0] = 0
new_aff[new_aff > 1] = 1
# Enforce circular constraint
new_aff = new_aff * circ_constraint
new_aff = (new_aff + new_aff.T) / 2 # kept just in case X loses its symmetry during optimization
Y = Y + mu * (new_aff - Q)
# Compute convergence criteria: break if new_aff is close enough to Q and no evolution anymore
pRes = np.linalg.norm(new_aff - Q) / N # primal residual (diff between new_aff and SVT result)
dRes = mu * np.linalg.norm(new_aff - new_aff0) / N # dual residual (diff between new_aff and previous new_aff)
if pRes < tol and dRes < tol:
break
if pRes > 10 * dRes: mu = 2 * mu
elif dRes > 10 * pRes: mu = mu / 2
iter +=1
return new_aff
def person_index_per_cam(affinity, cum_persons_per_view, min_cameras_for_triangulation):
'''
For each detected person, gives their index for each camera
INPUTS:
- affinity: affinity matrix between all the people in the different views
- min_cameras_for_triangulation: exclude proposals if less than N cameras see them
OUTPUT:
- proposals: 2D array: n_persons * n_cams
'''
# index of the max affinity for each group (-1 if no detection)
proposals = []
for row in range(affinity.shape[0]):
proposal_row = []
for cam in range(len(cum_persons_per_view)-1):
id_persons_per_view = affinity[row, cum_persons_per_view[cam]:cum_persons_per_view[cam+1]]
proposal_row += [np.argmax(id_persons_per_view) if (len(id_persons_per_view)>0 and max(id_persons_per_view)>0) else -1]
proposals.append(proposal_row)
proposals = np.array(proposals, dtype=float)
# remove duplicates and order
proposals, nb_detections = np.unique(proposals, axis=0, return_counts=True)
proposals = proposals[np.argsort(nb_detections)[::-1]]
# remove row if any value is the same in previous rows at same index (nan!=nan so nan ignored)
proposals[proposals==-1] = np.nan
mask = np.ones(proposals.shape[0], dtype=bool)
for i in range(1, len(proposals)):
mask[i] = ~np.any(proposals[i] == proposals[:i], axis=0).any()
proposals = proposals[mask]
# remove identifications if less than N cameras see them
nb_cams_per_person = [np.count_nonzero(~np.isnan(p)) for p in proposals]
proposals = np.array([p for (n,p) in zip(nb_cams_per_person, proposals) if n >= min_cameras_for_triangulation])
return proposals
def rewrite_json_files(json_tracked_files_f, json_files_f, proposals, n_cams):
'''
Write new json files with correct association of people across cameras.
INPUTS:
- json_tracked_files_f: list of strings: json files to write
- json_files_f: list of strings: json files to read
- proposals: 2D array: n_persons * n_cams
- n_cams: int: number of cameras
OUTPUT:
- json files with correct association of people across cameras
'''
for cam in range(n_cams):
with open(json_tracked_files_f[cam], 'w') as json_tracked_f:
with open(json_files_f[cam], 'r') as json_f:
js = json.load(json_f)
js_new = js.copy()
js_new['people'] = []
for new_comb in proposals:
if not np.isnan(new_comb[cam]):
js_new['people'] += [js['people'][int(new_comb[cam])]]
else:
js_new['people'] += [{}]
json_tracked_f.write(json.dumps(js_new))
def recap_tracking(config, error=0, nb_cams_excluded=0):
'''
Print a message giving statistics on reprojection errors (in pixel and in m)
as well as the number of cameras that had to be excluded to reach threshold
@ -352,27 +556,39 @@ def recap_tracking(config, error, nb_cams_excluded):
# Read config
project_dir = config.get('project').get('project_dir')
session_dir = os.path.realpath(os.path.join(project_dir, '..', '..'))
tracked_keypoint = config.get('personAssociation').get('tracked_keypoint')
error_threshold_tracking = config.get('personAssociation').get('reproj_error_threshold_association')
multi_person = config.get('project').get('multi_person')
likelihood_threshold_association = config.get('personAssociation').get('likelihood_threshold_association')
tracked_keypoint = config.get('personAssociation').get('single_person').get('tracked_keypoint')
error_threshold_tracking = config.get('personAssociation').get('single_person').get('reproj_error_threshold_association')
reconstruction_error_threshold = config.get('personAssociation').get('multi_person').get('reconstruction_error_threshold')
min_affinity = config.get('personAssociation').get('multi_person').get('min_affinity')
poseTracked_dir = os.path.join(project_dir, 'pose-associated')
calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if 'calib' in c.lower()][0]
calib_file = glob.glob(os.path.join(calib_dir, '*.toml'))[0] # lastly created calibration file
# Error
mean_error_px = np.around(np.mean(error), decimals=1)
if not multi_person:
logging.info('\nSingle-person analysis selected.')
# Error
mean_error_px = np.around(np.mean(error), decimals=1)
calib = toml.load(calib_file)
calib_cam1 = calib[list(calib.keys())[0]]
fm = calib_cam1['matrix'][0][0]
Dm = euclidean_distance(calib_cam1['translation'], [0,0,0])
mean_error_mm = np.around(mean_error_px * Dm / fm * 1000, decimals=1)
calib = toml.load(calib_file)
calib_cam1 = calib[list(calib.keys())[0]]
fm = calib_cam1['matrix'][0][0]
Dm = euclidean_distance(calib_cam1['translation'], [0,0,0])
mean_error_mm = np.around(mean_error_px * Dm / fm * 1000, decimals=1)
# Excluded cameras
mean_cam_off_count = np.around(np.mean(nb_cams_excluded), decimals=2)
# Excluded cameras
mean_cam_off_count = np.around(np.mean(nb_cams_excluded), decimals=2)
# Recap
logging.info(f'\n--> Mean reprojection error for {tracked_keypoint} point on all frames is {mean_error_px} px, which roughly corresponds to {mean_error_mm} mm. ')
logging.info(f'--> In average, {mean_cam_off_count} cameras had to be excluded to reach the demanded {error_threshold_tracking} px error threshold after excluding points with likelihood below {likelihood_threshold_association}.')
else:
logging.info('\nMulti-person analysis selected.')
logging.info(f'\n--> A person was reconstructed if the lines from cameras to their keypoints intersected within {reconstruction_error_threshold} m and if the calculated affinity stayed below {min_affinity} after excluding points with likelihood below {likelihood_threshold_association}.')
logging.info(f'--> Beware that people were sorted across cameras, but not across frames. This will be done in the triangulation stage.')
# Recap
logging.info(f'\n--> Mean reprojection error for {tracked_keypoint} point on all frames is {mean_error_px} px, which roughly corresponds to {mean_error_mm} mm. ')
logging.info(f'--> In average, {mean_cam_off_count} cameras had to be excluded to reach the demanded {error_threshold_tracking} px error threshold.')
logging.info(f'\nTracked json files are stored in {os.path.realpath(poseTracked_dir)}.')
@ -401,7 +617,11 @@ def track_2d_all(config):
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')
tracked_keypoint = config.get('personAssociation').get('single_person').get('tracked_keypoint')
likelihood_threshold = config.get('personAssociation').get('likelihood_threshold_association')
min_cameras_for_triangulation = config.get('triangulation').get('min_cameras_for_triangulation')
reconstruction_error_threshold = config.get('personAssociation').get('multi_person').get('reconstruction_error_threshold')
min_affinity = config.get('personAssociation').get('multi_person').get('min_affinity')
frame_range = config.get('project').get('frame_range')
undistort_points = config.get('triangulation').get('undistort_points')
@ -414,12 +634,12 @@ def track_2d_all(config):
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.')
logging.info('\nMulti-person analysis selected. Note that you can set this option to false 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)
P_all = computeP(calib_file, undistort=undistort_points)
calib_params = retrieve_calib_params(calib_file)
# selection of tracked keypoint id
@ -448,15 +668,14 @@ def track_2d_all(config):
except: pass
json_tracked_files = [[os.path.join(poseTracked_dir, j_dir, j_file) for j_file in json_files_names[j]] for j, j_dir in enumerate(json_dirs_names)]
# person's tracking
f_range = [[min([len(j) for j in json_files])] if frame_range==[] else frame_range][0]
n_cams = len(json_dirs_names)
error_min_tot, cameras_off_tot = [], []
# Check that camera number is consistent between calibration file and pose folders
if n_cams != len(P):
if n_cams != len(P_all):
raise Exception(f'Error: The number of cameras is not consistent:\
Found {len(P)} cameras in the calibration file,\
Found {len(P_all)} cameras in the calibration file,\
and {n_cams} cameras based on the number of pose folders.')
Q_kpt = [np.array([0., 0., 0., 1.])]
@ -464,35 +683,40 @@ def track_2d_all(config):
# print(f'\nFrame {f}:')
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)]
# all possible combinations of persons
personsIDs_comb = persons_combinations(json_files_f)
# choose persons of interest and exclude cameras with bad pose estimation
Q_kpt_old = Q_kpt
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)
# reID persons across frames by checking the distance from one frame to another
Q_kpt, personsIDs_sorted = sort_people(Q_kpt_old, Q_kpt)
errors_below_thresh = np.array(errors_below_thresh)[personsIDs_sorted]
comb_errors_below_thresh = np.array(comb_errors_below_thresh)[personsIDs_sorted]
if not multi_person:
# all possible combinations of persons
personsIDs_comb = persons_combinations(json_files_f)
# choose persons of interest and exclude cameras with bad pose estimation
error_proposals, proposals, Q_kpt = best_persons_and_cameras_combination(config, json_files_f, personsIDs_comb, P_all, tracked_keypoint_id, calib_params)
error_min_tot.append(np.mean(error_proposals))
cameras_off_count = np.count_nonzero([np.isnan(comb) for comb in proposals]) / len(proposals)
cameras_off_tot.append(cameras_off_count)
else:
# read data
all_json_data_f = []
for js_file in json_files_f:
all_json_data_f.append(read_json(js_file))
#TODO: remove people with average likelihood < 0.3, no full torso, less than 12 joints... (cf filter2d in dataset/base.py L498)
# obtain proposals after computing affinity between all the people in the different views
persons_per_view = [0] + [len(j) for j in all_json_data_f]
cum_persons_per_view = np.cumsum(persons_per_view)
affinity = compute_affinity(all_json_data_f, calib_params, cum_persons_per_view, reconstruction_error_threshold=reconstruction_error_threshold)
circ_constraint = circular_constraint(cum_persons_per_view)
affinity = affinity * circ_constraint
#TODO: affinity without hand, face, feet (cf ray.py L31)
affinity = matchSVT(affinity, cum_persons_per_view, circ_constraint, max_iter = 20, w_rank = 50, tol = 1e-4, w_sparse=0.1)
affinity[affinity<min_affinity] = 0
proposals = person_index_per_cam(affinity, cum_persons_per_view, min_cameras_for_triangulation)
# rewrite json files with a single or multiple persons of interest
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)
cameras_off_tot.append(cameras_off_count)
for cam in range(n_cams):
with open(json_tracked_files_f[cam], 'w') as json_tracked_f:
with open(json_files_f[cam], 'r') as json_f:
js = json.load(json_f)
js_new = js.copy()
js_new['people'] = []
for new_comb in comb_errors_below_thresh:
if not np.isnan(new_comb[cam]):
js_new['people'] += [js['people'][int(new_comb[cam])]]
else:
js_new['people'] += [{}]
json_tracked_f.write(json.dumps(js_new))
rewrite_json_files(json_tracked_files_f, json_files_f, proposals, n_cams)
# recap message
recap_tracking(config, error_min_tot, cameras_off_tot)

View File

@ -1,18 +1,10 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import signal
from scipy import interpolate
import json
import os
import fnmatch
import pickle as pk
import re
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
#########################################
## Synchronize cameras ##
## SYNCHRONIZE CAMERAS ##
#########################################
Steps undergone in this script
@ -25,64 +17,90 @@ import re
'''
############
# FUNCTIONS#
############
## INIT
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import signal
from scipy import interpolate
import json
import os
import fnmatch
import pickle as pk
import re
def convert_json2csv(json_dir):
"""
from Pose2Sim.filtering import loess_filter_1d
## AUTHORSHIP INFORMATION
__author__ = "HunMin Kim, David Pagnon"
__copyright__ = "Copyright 2021, Pose2Sim"
__credits__ = ["David Pagnon"]
__license__ = "BSD 3-Clause License"
__version__ = '0.7'
__maintainer__ = "David Pagnon"
__email__ = "contact@david-pagnon.com"
__status__ = "Development"
# FUNCTIONS
def convert_json2pandas(json_dir):
'''
Convert JSON files in a directory to a pandas DataFrame.
Args:
json_dir (str): The directory path containing the JSON files.
INPUTS:
- json_dir: str. The directory path containing the JSON files.
OUTPUT:
- df_json_coords: dataframe. Extracted coordinates in a pandas dataframe.
'''
Returns:
pandas.DataFrame: A DataFrame containing the coordinates extracted from the JSON files.
"""
json_files_names = fnmatch.filter(os.listdir(os.path.join(json_dir)), '*.json') # modified ( 'json' to '*.json' )
json_files_names.sort(key=lambda name: int(re.search(r'(\d+)_keypoints\.json', name).group(1)))
json_files_names.sort(key=lambda name: int(re.search(r'(\d+)\.json', name).group(1)))
json_files_path = [os.path.join(json_dir, j_f) for j_f in json_files_names]
json_coords = []
for i, j_p in enumerate(json_files_path):
# if i in range(frames)
with open(j_p) as j_f:
try:
json_data = json.load(j_f)['people'][0]['pose_keypoints_2d']
except:
print(f'No person found in {os.path.basename(json_dir)}, frame {i}')
json_data = [0]*75
json_coords.append(json_data)
with open(j_p) as j_f:
try:
json_data = json.load(j_f)['people'][0]['pose_keypoints_2d']
except:
print(f'No person found in {os.path.basename(json_dir)}, frame {i}')
json_data = [0]*75
json_coords.append(json_data)
df_json_coords = pd.DataFrame(json_coords)
return df_json_coords
def drop_col(df, col_nb):
"""
'''
Drops every nth column from a DataFrame.
Parameters:
df (pandas.DataFrame): The DataFrame from which columns will be dropped.
col_nb (int): The column number to drop.
INPUTS:
- df: dataframe. The DataFrame from which columns will be dropped.
- col_nb: int. The column number to drop.
Returns:
pandas.DataFrame: The DataFrame with dropped columns.
"""
OUTPUT:
- dataframe: DataFrame with dropped columns.
'''
idx_col = list(range(col_nb-1, df.shape[1], col_nb))
df_dropped = df.drop(idx_col, axis=1)
df_dropped.columns = range(df_dropped.columns.size)
return df_dropped
def speed_vert(df, axis='y'):
"""
'''
Calculate the vertical speed of a DataFrame along a specified axis.
Parameters:
df (DataFrame): The input DataFrame.
axis (str): The axis along which to calculate the speed. Default is 'y'.
- df: dataframe. DataFrame of 2D coordinates.
- axis (str): The axis along which to calculate the speed. Default is 'y'.
OUTPUT:
- DataFrame: The DataFrame containing the vertical speed values.
'''
Returns:
DataFrame: The DataFrame containing the vertical speed values.
"""
axis_dict = {'x':0, 'y':1, 'z':2}
df_diff = df.diff()
df_diff = df_diff.fillna(df_diff.iloc[1]*2)
@ -91,45 +109,58 @@ def speed_vert(df, axis='y'):
return df_vert_speed
def interpolate_nans(col, kind):
def speed_2D(df):
'''
Calculate the 2D speed of a DataFrame.
INPUTS:
- df: dataframe. DataFrame of 2D coordinates.
OUTPUT:
- DataFrame: The DataFrame containing the 2D speed values.
'''
df_diff = df.diff()
df_diff = df_diff.fillna(df_diff.iloc[1]*2)
df_2Dspeed = pd.DataFrame([np.sqrt(df_diff.loc[:,2*k]*2 + df_diff.loc[:,2*k+1]*2) for k in range(int(df_diff.shape[1]*2))]).T
return df_2Dspeed
def interpolate_zeros_nans(col, kind):
'''
Interpolate missing points (of value nan)
INPUTS
- col pandas column of coordinates
- kind 'linear', 'slinear', 'quadratic', 'cubic'. Default 'cubic'
- col: pandas column of coordinates
- kind: 'linear', 'slinear', 'quadratic', 'cubic'. Default 'cubic'
OUTPUT
- col_interp interpolated pandas column
- col_interp: interpolated pandas column
'''
idx = col.index
idx_good = np.where(np.isfinite(col))[0] #index of non zeros
if len(idx_good) == 10: return col
# idx_notgood = np.delete(np.arange(len(col)), idx_good)
if not kind: # 'linear', 'slinear', 'quadratic', 'cubic'
f_interp = interpolate.interp1d(idx_good, col[idx_good], kind='cubic', bounds_error=False)
else:
f_interp = interpolate.interp1d(idx_good, col[idx_good], kind=kind, bounds_error=False) # modified
col_interp = np.where(np.isfinite(col), col, f_interp(idx)) #replace nans with interpolated values
col_interp = np.where(np.isfinite(col_interp), col_interp, np.nanmean(col_interp)) #replace remaining nans
return col_interp #, idx_notgood
mask = ~(np.isnan(col) | col.eq(0)) # true where nans or zeros
idx_good = np.where(mask)[0]
try:
f_interp = interpolate.interp1d(idx_good, col[idx_good], kind=kind, bounds_error=False)
col_interp = np.where(mask, col, f_interp(col.index))
return col_interp
except:
print('No good values to interpolate')
return col
def find_highest_wrist_position(df_coords, wrist_index):
"""
'''
Find the frame with the highest wrist position in a list of coordinate DataFrames.
Highest wrist position frame use for finding the fastest frame.
Args:
df_coords (list): List of coordinate DataFrames.
wrist_index (int): The index of the wrist in the keypoint list.
INPUT:
- df_coords (list): List of coordinate DataFrames.
- wrist_index (int): The index of the wrist in the keypoint list.
Returns:
list: The index of the frame with the highest wrist position.
"""
OUTPUT:
- list: The index of the frame with the highest wrist position.
'''
start_frames = []
min_y_coords = []
@ -149,20 +180,22 @@ def find_highest_wrist_position(df_coords, wrist_index):
return start_frames, min_y_coords
def find_motion_end(df_coords, wrist_index, start_frame, lowest_y, fps):
"""
'''
Find the frame where hands down movement ends.
Hands down movement is defined as the time when the wrist moves down from the highest position.
Args:
df_coord (DataFrame): The coordinate DataFrame of the reference camera.
wrist_index (int): The index of the wrist in the keypoint list.
start_frame (int): The frame where the hands down movement starts.
fps (int): The frame rate of the cameras in Hz.
INPUT:
- df_coord (DataFrame): The coordinate DataFrame of the reference camera.
- wrist_index (int): The index of the wrist in the keypoint list.
- start_frame (int): The frame where the hands down movement starts.
- fps (int): The frame rate of the cameras in Hz.
OUTPUT:
- int: The index of the frame where hands down movement ends.
'''
Returns:
int: The index of the frame where hands down movement ends.
"""
y_col_index = wrist_index * 2 + 1
wrist_y_values = df_coords.iloc[:, y_col_index].values # wrist y-coordinates
highest_y_value = lowest_y
@ -181,20 +214,21 @@ def find_motion_end(df_coords, wrist_index, start_frame, lowest_y, fps):
return time
def find_fastest_frame(df_speed_list):
"""
'''
Find the frame with the highest speed in a list of speed DataFrames.
Fastest frame should locate in after highest wrist position frame.
Args:
df_speed_list (list): List of speed DataFrames.
df_speed (DataFrame): The speed DataFrame of the reference camera.
fps (int): The frame rate of the cameras in Hz.
lag_time (float): The time lag in seconds.
INPUT:
- df_speed_list (list): List of speed DataFrames.
- df_speed (DataFrame): The speed DataFrame of the reference camera.
- fps (int): The frame rate of the cameras in Hz.
- lag_time (float): The time lag in seconds.
Returns:
int: The index of the frame with the highest speed.
"""
OUTPUT:
- int: The index of the frame with the highest speed.
'''
for speed_series in df_speed_list:
max_speed = speed_series.abs().max()
@ -205,31 +239,25 @@ def find_fastest_frame(df_speed_list):
return max_speed_index, max_speed
def plot_time_lagged_cross_corr(camx, camy, ax, fps, lag_time, camx_max_speed_index, camy_max_speed_index):
"""
def plot_time_lagged_cross_corr(camx, camy, ax, fps, lag_time):
'''
Calculate and plot the max correlation between two cameras with a time lag.
How it works:
1. Reference camera is camx and the other is camy. (Reference camera should record last. If not, the offset will be positive.)
2. The initial shift alppied to camy to match camx is calculated.
3. Additionally shift camy by max_lag frames to find the max correlation.
Args:
camx (pandas.Series): The speed series of the reference camera.
camy (pandas.Series): The speed series of the other camera.
ax (matplotlib.axes.Axes): The axes to plot the correlation.
fps (int): The frame rate of the cameras in Hz.
lag_time (float): The time lag in seconds.
camx_max_speed_index (int): The index of the frame with the highest speed in camx.
camy_max_speed_index (int): The index of the frame with the highest speed in camy.
INPUT:
- camx: pd.Series. Speed series of the reference camera.
- camy: pd.Series). Speed series of the other camera.
- ax: plt.axis. Plot correlation on second axis.
- fps: int. Framerate of the cameras in Hz.
- lag_time: float. Time lag in seconds.
Returns:
int: The offset value to apply to synchronize the cameras.
float: The maximum correlation value.
"""
# Initial shift of camy to match camx
# initial_shift = -(camy_max_speed_index - camx_max_speed_index) + fps
# camy = camy.shift(initial_shift).dropna()
OUTPUT:
- offset: int. Offset value to apply to synchronize the cameras.
- max_corr: float. Maximum correlation value.
'''
max_lag = int(fps * lag_time)
pearson_r = []
@ -238,7 +266,6 @@ def plot_time_lagged_cross_corr(camx, camy, ax, fps, lag_time, camx_max_speed_in
for lag in lags:
if lag < 0:
shifted_camy = camy.shift(lag).dropna() # shift the camy segment by lag
corr = camx.corr(shifted_camy) # calculate the correlation between the camx segment and the shifted camy segment
elif lag == 0:
corr = camx.corr(camy)
@ -265,19 +292,19 @@ def plot_time_lagged_cross_corr(camx, camy, ax, fps, lag_time, camx_max_speed_in
def apply_offset(offset, json_dirs, reset_sync, cam1_nb, cam2_nb):
"""
'''
Apply the offset to synchronize the cameras.
Offset is always applied to the second camera.
Offset would be always negative if the first camera is the last to start recording.
Delete the camy json files from initial frame to offset frame.
Args:
offset (int): The offset value to apply to synchronize the cameras.
json_dirs (list): List of directories containing the JSON files for each camera.
reset_sync (bool): Whether to reset the synchronization by deleting the .del files.
cam1_nb (int): The number of the reference camera.
cam2_nb (int): The number of the other camera.
"""
INPUT:
- offset (int): The offset value to apply to synchronize the cameras.
- json_dirs (list): List of directories containing the JSON files for each camera.
- reset_sync (bool): Whether to reset the synchronization by deleting the .del files.
- cam1_nb (int): The number of the reference camera.
- cam2_nb (int): The number of the other camera.
'''
if offset == 0:
print(f"Cams {cam1_nb} and {cam2_nb} are already synchronized. No offset applied.")
@ -300,59 +327,60 @@ def apply_offset(offset, json_dirs, reset_sync, cam1_nb, cam2_nb):
os.rename(os.path.join(json_dir_to_offset, json_files[i]), os.path.join(json_dir_to_offset, json_files[i] + '.del'))
#################
# Main Function #
#################
def synchronize_cams_all(config_dict):
'''
#############
# CONSTANTS #
#############
'''
# get parameters from Config.toml
project_dir = config_dict.get('project').get('project_dir')
pose_dir = os.path.realpath(os.path.join(project_dir, 'pose'))
fps = config_dict.get('project').get('frame_rate') # frame rate of the cameras (Hz)
reset_sync = config_dict.get('synchronization').get('reset_sync') # Start synchronization over each time it is run
id_kpt = 4
weights_kpt = 1
filter_order = 4
filter_cutoff = 6
vmax = 20 # px/s
# Vertical speeds (on 'Y')
speed_kind = config_dict.get('synchronization').get('speed_kind') # this maybe fixed in the future
id_kpt = config_dict.get('synchronization').get('id_kpt') # get the numbers from the keypoint names in skeleton.py: 'RWrist' BLAZEPOSE 16, BODY_25B 10, BODY_25 4 ; 'LWrist' BLAZEPOSE 15, BODY_25B 9, BODY_25 7
weights_kpt = config_dict.get('synchronization').get('weights_kpt') # only considered if there are multiple keypoints.
######################################
# 0. CONVERTING JSON FILES TO PANDAS #
######################################
# Also filter, and then save the filtered data
# List json files
pose_listdirs_names = next(os.walk(pose_dir))[1]
pose_listdirs_names.sort(key=lambda name: int(re.search(r'(\d+)', name).group(1)))
json_dirs_names = [k for k in pose_listdirs_names if 'json' in k]
json_dirs = [os.path.join(pose_dir, j_d) for j_d in json_dirs_names] # list of json directories in pose_dir
cam_nb = len(json_dirs)
# keypoints coordinates
# Extract, interpolate, and filter keypoint coordinates
df_coords = []
b, a = signal.butter(filter_order/2, filter_cutoff/(fps/2), 'low', analog = False)
for i, json_dir in enumerate(json_dirs):
df_coords.append(convert_json2csv(json_dir))
df_coords.append(convert_json2pandas(json_dir))
df_coords[i] = drop_col(df_coords[i],3) # drop likelihood
df_coords[i] = df_coords[i].apply(interpolate_zeros_nans, axis=0, args = ['cubic'])
df_coords[i] = df_coords[i].apply(loess_filter_1d, axis=0, args = [30])
df_coords[i] = pd.DataFrame(signal.filtfilt(b, a, df_coords[i], axis=0))
## To save it and reopen it if needed
# Save keypoint coordinates to pickle
with open(os.path.join(pose_dir, 'coords'), 'wb') as fp:
pk.dump(df_coords, fp)
with open(os.path.join(pose_dir, 'coords'), 'rb') as fp:
df_coords = pk.load(fp)
# with open(os.path.join(pose_dir, 'coords'), 'rb') as fp:
# df_coords = pk.load(fp)
#############################
# 1. COMPUTING SPEEDS #
#############################
# Vitesse verticale
# Compute vertical speed
df_speed = []
for i in range(len(json_dirs)):
if speed_kind == 'y':
df_speed.append(speed_vert(df_coords[i]))
for i in range(cam_nb):
df_speed.append(speed_vert(df_coords[i]))
# df_speed[i] = df_speed[i].where(abs(df_speed[i])<vmax, other=np.nan) # replaces by nan if jumps in speed
# df_speed[i] = df_speed[i].apply(interpolate_nans, axis=0, args = ['cubic'])
# Frame with maximum of the sum of absolute speeds
max_speed_frame = []
for i in range(cam_nb):
max_speed_frame += [np.argmax(abs(df_speed[i].sum(axis=1)))]
#############################################
# 2. PLOTTING PAIRED CORRELATIONS OF SPEEDS #
@ -368,26 +396,31 @@ def synchronize_cams_all(config_dict):
lowest_frames, lowest_y_coords = find_highest_wrist_position(df_coords, id_kpt)
# set reference camera
ref_cam_nb = 0
nb_frames_per_cam = [len(d) for d in df_speed]
ref_cam_id = nb_frames_per_cam.index(min(nb_frames_per_cam))
max_speeds = []
for cam_nb in range(1, len(json_dirs)):
cam_list = list(range(cam_nb))
cam_list.pop(ref_cam_id)
for cam_id in cam_list:
# find the highest wrist position for each camera
camx_start_frame = lowest_frames[ref_cam_nb]
camy_start_frame = lowest_frames[cam_nb]
camx_start_frame = lowest_frames[ref_cam_id]
camy_start_frame = lowest_frames[cam_id]
camx_lowest_y = lowest_y_coords[ref_cam_nb]
camy_lowest_y = lowest_y_coords[cam_nb]
camx_lowest_y = lowest_y_coords[ref_cam_id]
camy_lowest_y = lowest_y_coords[cam_id]
camx_time = find_motion_end(df_coords[ref_cam_nb], id_kpt[0], camx_start_frame, camx_lowest_y, fps)
camy_time = find_motion_end(df_coords[cam_nb], id_kpt[0], camy_start_frame, camy_lowest_y, fps)
camx_time = find_motion_end(df_coords[ref_cam_id], id_kpt[0], camx_start_frame, camx_lowest_y, fps)
camy_time = find_motion_end(df_coords[cam_id], id_kpt[0], camy_start_frame, camy_lowest_y, fps)
camx_end_frame = camx_start_frame + int(camx_time * fps)
camy_end_frame = camy_start_frame + int(camy_time * fps)
camx_segment = df_speed[ref_cam_nb].iloc[camx_start_frame:camx_end_frame+1, id_kpt[0]]
camy_segment = df_speed[cam_nb].iloc[camy_start_frame:camy_end_frame+1, id_kpt[0]]
camx_segment = df_speed[ref_cam_id].iloc[camx_start_frame:camx_end_frame+1, id_kpt[0]]
camy_segment = df_speed[cam_id].iloc[camy_start_frame:camy_end_frame+1, id_kpt[0]]
# Find the fastest speed and the frame
camx_max_speed_index, camx_max_speed = find_fastest_frame([camx_segment])
@ -410,15 +443,15 @@ def synchronize_cams_all(config_dict):
camy_end_frame = camy_max_speed_index + (fps) * (lag_time)
if len(id_kpt) == 1 and id_kpt[0] != 'all':
camx = df_speed[ref_cam_nb].iloc[camx_start_frame:camx_end_frame+1, id_kpt[0]]
camy = df_speed[cam_nb].iloc[camy_start_frame:camy_end_frame+1, id_kpt[0]]
camx = df_speed[ref_cam_id].iloc[camx_start_frame:camx_end_frame+1, id_kpt[0]]
camy = df_speed[cam_id].iloc[camy_start_frame:camy_end_frame+1, id_kpt[0]]
elif id_kpt == ['all']:
camx = df_speed[ref_cam_nb].iloc[camx_start_frame:camx_end_frame+1].sum(axis=1)
camy = df_speed[cam_nb].iloc[camy_start_frame:camy_end_frame+1].sum(axis=1)
camx = df_speed[ref_cam_id].iloc[camx_start_frame:camx_end_frame+1].sum(axis=1)
camy = df_speed[cam_id].iloc[camy_start_frame:camy_end_frame+1].sum(axis=1)
elif len(id_kpt) == 1 and len(id_kpt) == len(weights_kpt):
dict_id_weights = {i:w for i, w in zip(id_kpt, weights_kpt)}
camx = df_speed[ref_cam_nb] @ pd.Series(dict_id_weights).reindex(df_speed[ref_cam_nb].columns, fill_value=0)
camy = df_speed[cam_nb] @ pd.Series(dict_id_weights).reindex(df_speed[cam_nb].columns, fill_value=0)
camx = df_speed[ref_cam_id] @ pd.Series(dict_id_weights).reindex(df_speed[ref_cam_id].columns, fill_value=0)
camy = df_speed[cam_id] @ pd.Series(dict_id_weights).reindex(df_speed[cam_id].columns, fill_value=0)
camx = camx.iloc[camx_start_frame:camx_end_frame+1]
camy = camy.iloc[camy_start_frame:camy_end_frame+1]
else:
@ -431,8 +464,8 @@ def synchronize_cams_all(config_dict):
f, ax = plt.subplots(2,1)
# speed
camx.plot(ax=ax[0], label = f'cam {ref_cam_nb+1}')
camy.plot(ax=ax[0], label = f'cam {cam_nb+1}')
camx.plot(ax=ax[0], label = f'cam {ref_cam_id+1}')
camy.plot(ax=ax[0], label = f'cam {cam_id+1}')
ax[0].set(xlabel='Frame',ylabel='Speed (pxframe)')
ax[0].legend()
@ -440,9 +473,9 @@ def synchronize_cams_all(config_dict):
offset, max_corr = plot_time_lagged_cross_corr(camx, camy, ax[1], fps, lag_time, camx_max_speed_index, camy_max_speed_index)
f.tight_layout()
plt.show()
print(f'Using number{id_kpt} keypoint, synchronized camera {ref_cam_nb+1} and camera {cam_nb+1}, with an offset of {offset} and a max correlation of {max_corr}.')
print(f'Using number{id_kpt} keypoint, synchronized camera {ref_cam_id+1} and camera {cam_id+1}, with an offset of {offset} and a max correlation of {max_corr}.')
# apply offset
apply_offset(offset, json_dirs, reset_sync, ref_cam_nb, cam_nb)
apply_offset(offset, json_dirs, reset_sync, ref_cam_id, cam_id)

View File

@ -18,7 +18,8 @@ cameras are removed than a predefined minimum, triangulation is skipped for
the point and this frame. In the end, missing values are interpolated.
In case of multiple subjects detection, make sure you first run the
personAssociation module.
personAssociation module. It will then associate people across frames by
measuring the frame-by-frame distance between them.
INPUTS:
- a calibration file (.toml extension)
@ -108,6 +109,96 @@ def interpolate_zeros_nans(col, *args):
return col_interp
def min_with_single_indices(L, T):
'''
Let L be a list (size s) with T associated tuple indices (size s).
Select the smallest values of L, considering that
the next smallest value cannot have the same numbers
in the associated tuple as any of the previous ones.
Example:
L = [ 20, 27, 51, 33, 43, 23, 37, 24, 4, 68, 84, 3 ]
T = list(it.product(range(2),range(3)))
= [(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3),(2,0),(2,1),(2,2),(2,3)]
- 1st smallest value: 3 with tuple (2,3), index 11
- 2nd smallest value when excluding indices (2,.) and (.,3), i.e. [(0,0),(0,1),(0,2),X,(1,0),(1,1),(1,2),X,X,X,X,X]:
20 with tuple (0,0), index 0
- 3rd smallest value when excluding [X,X,X,X,X,(1,1),(1,2),X,X,X,X,X]:
23 with tuple (1,1), index 5
INPUTS:
- L: list (size s)
- T: T associated tuple indices (size s)
OUTPUTS:
- minL: list of smallest values of L, considering constraints on tuple indices
- argminL: list of indices of smallest values of L
- T_minL: list of tuples associated with smallest values of L
'''
minL = [np.nanmin(L)]
argminL = [np.nanargmin(L)]
T_minL = [T[argminL[0]]]
mask_tokeep = np.array([True for t in T])
i=0
while mask_tokeep.any()==True:
mask_tokeep = mask_tokeep & np.array([t[0]!=T_minL[i][0] and t[1]!=T_minL[i][1] for t in T])
if mask_tokeep.any()==True:
indicesL_tokeep = np.where(mask_tokeep)[0]
minL += [np.nanmin(np.array(L)[indicesL_tokeep]) if not np.isnan(np.array(L)[indicesL_tokeep]).all() else np.nan]
argminL += [indicesL_tokeep[np.nanargmin(np.array(L)[indicesL_tokeep])] if not np.isnan(minL[-1]) else indicesL_tokeep[0]]
T_minL += (T[argminL[i+1]],)
i+=1
return np.array(minL), np.array(argminL), np.array(T_minL)
def sort_people(Q_kpt_old, Q_kpt):
'''
Associate persons across frames
Persons' indices are sometimes swapped when changing frame
A person is associated to another in the next frame when they are at a small distance
INPUTS:
- Q_kpt_old: list of arrays of 3D coordinates [X, Y, Z, 1.] for the previous frame
- Q_kpt: idem Q_kpt_old, for current frame
OUTPUT:
- Q_kpt_new: array with reordered persons
- personsIDs_sorted: index of reordered persons
'''
# 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 = []
for comb in personsIDs_comb:
frame_by_frame_dist += [euclidean_distance(Q_kpt_old[comb[0]][:3],Q_kpt[comb[1]][:3])]
# sort correspondences by distance
minL, _, associated_tuples = min_with_single_indices(frame_by_frame_dist, personsIDs_comb)
# print('Distances :', minL)
# associate 3D points to same index across frames, nan if no correspondence
Q_kpt_new, personsIDs_sorted = [], []
for i in range(len(Q_kpt_old)):
id_in_old = associated_tuples[:,1][associated_tuples[:,0] == i].tolist()
# print('id_in_old ', i, id_in_old)
if len(id_in_old) > 0:
personsIDs_sorted += id_in_old
Q_kpt_new += [Q_kpt[id_in_old[0]]]
else:
personsIDs_sorted += [-1]
Q_kpt_new += [Q_kpt_old[i]]
return Q_kpt_new, personsIDs_sorted, associated_tuples
def make_trc(config, Q, keypoints_names, f_range, id_person=-1):
'''
Make Opensim compatible trc file from a dataframe with 3D coordinates
@ -267,7 +358,7 @@ def recap_triangulate(config, error, nb_cams_excluded, keypoints_names, cam_excl
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())}
cam_excluded_count[n] = {k: v for k, v in sorted(cam_excluded_count[n].items(), key=lambda item: item[1])[::-1]}
str_cam_excluded_count = ''
for i, (k, v) in enumerate(cam_excluded_count[n].items()):
if i ==0:
@ -584,7 +675,7 @@ def triangulate_all(config):
INPUTS:
- a calibration file (.toml extension)
- json files for each camera with only one person of interest
- json files for each camera with indices matching the detected persons
- a Config.toml file
- a skeleton model
@ -655,9 +746,7 @@ def triangulate_all(config):
# Prep triangulation
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]
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)
# Check that camera number is consistent between calibration file and pose folders
@ -667,8 +756,14 @@ def triangulate_all(config):
and {n_cams} cameras based on the number of pose folders.')
# Triangulation
Q = [[[np.nan]*3]*keypoints_nb for n in range(nb_persons_to_detect)]
Q_old = [[[np.nan]*3]*keypoints_nb for n in range(nb_persons_to_detect)]
error = [[] for n in range(nb_persons_to_detect)]
nb_cams_excluded = [[] for n in range(nb_persons_to_detect)]
id_excluded_cams = [[] for n in range(nb_persons_to_detect)]
Q_tot, error_tot, nb_cams_excluded_tot,id_excluded_cams_tot = [], [], [], []
for f in tqdm(range(frames_nb)):
# print(f'\nFrame {f}:')
# 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)
@ -692,12 +787,19 @@ def triangulate_all(config):
y_files[n][likelihood_files[n] < likelihood_threshold] = np.nan
likelihood_files[n][likelihood_files[n] < likelihood_threshold] = np.nan
# Q_old = Q except when it has nan, otherwise it takes the Q_old value
nan_mask = np.isnan(Q)
Q_old = np.where(nan_mask, Q_old, Q)
error_old, nb_cams_excluded_old, id_excluded_cams_old = error.copy(), nb_cams_excluded.copy(), id_excluded_cams.copy()
Q = [[] for n in range(nb_persons_to_detect)]
error = [[] for n in range(nb_persons_to_detect)]
nb_cams_excluded = [[] for n in range(nb_persons_to_detect)]
id_excluded_cams = [[] for n in range(nb_persons_to_detect)]
for n in range(nb_persons_to_detect):
for keypoint_idx in keypoints_idx:
# keypoints_nb = 2
# for keypoint_idx in range(2):
# 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]) )
@ -710,6 +812,30 @@ def triangulate_all(config):
nb_cams_excluded[n].append(nb_cams_excluded_kpt)
id_excluded_cams[n].append(id_excluded_cams_kpt)
if multi_person:
# reID persons across frames by checking the distance from one frame to another
# print('Q before ordering ', np.array(Q)[:,:2])
if f !=0:
Q, personsIDs_sorted, associated_tuples = sort_people(Q_old, Q)
# print('Q after ordering ', personsIDs_sorted, associated_tuples, np.array(Q)[:,:2])
error_sorted, nb_cams_excluded_sorted, id_excluded_cams_sorted = [], [], []
for i in range(len(Q)):
id_in_old = associated_tuples[:,1][associated_tuples[:,0] == i].tolist()
if len(id_in_old) > 0:
personsIDs_sorted += id_in_old
error_sorted += [error[id_in_old[0]]]
nb_cams_excluded_sorted += [nb_cams_excluded[id_in_old[0]]]
id_excluded_cams_sorted += [id_excluded_cams[id_in_old[0]]]
else:
personsIDs_sorted += [-1]
error_sorted += [error[i]]
nb_cams_excluded_sorted += [nb_cams_excluded[i]]
id_excluded_cams_sorted += [id_excluded_cams[i]]
error, nb_cams_excluded, id_excluded_cams = error_sorted, nb_cams_excluded_sorted, id_excluded_cams_sorted
# TODO: if distance > threshold, new person
# Add triangulated points, errors and excluded cameras to pandas dataframes
Q_tot.append([np.concatenate(Q[n]) for n in range(nb_persons_to_detect)])
error_tot.append([error[n] for n in range(nb_persons_to_detect)])
@ -717,6 +843,13 @@ 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)
# fill values for if a person that was not initially detected has entered the frame
Q_tot = [list(tpl) for tpl in zip(*it.zip_longest(*Q_tot, fillvalue=[np.nan]*keypoints_nb*3))]
error_tot = [list(tpl) for tpl in zip(*it.zip_longest(*error_tot, fillvalue=[np.nan]*keypoints_nb*3))]
nb_cams_excluded_tot = [list(tpl) for tpl in zip(*it.zip_longest(*nb_cams_excluded_tot, fillvalue=[np.nan]*keypoints_nb*3))]
id_excluded_cams_tot = [list(tpl) for tpl in zip(*it.zip_longest(*id_excluded_cams_tot, fillvalue=[np.nan]*keypoints_nb*3))]
# dataframes for each person
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)]

View File

@ -17,7 +17,7 @@
##### N.B:. Please set undistort_points and handle_LR_swap to false for now since it currently leads to inaccuracies. I'll try to fix it soon.
> **_News_: Version 0.7:**\
> **Multi-person analysis is now supported!**\
> **Multi-person analysis is now supported!** Latest version is 100 times faster that the one before, and also more robust.\
> Team sports, combat sports, and ballroom dancing can now take advantage of Pose2Sim full potential.\
> **Other recently added features**: Automatic batch processing, Marker augmentation, Blender visualization.
<!-- Incidentally, right/left limb swapping is now handled, which is useful if few cameras are used;\
@ -307,27 +307,34 @@ All AlphaPose models are supported (HALPE_26, HALPE_68, HALPE_136, COCO_133, COC
> _**Cameras need to be synchronized, so that 2D points correspond to the same position across cameras.**_\
***N.B.:** Skip this step if your cameras are already synchronized.*
Open an Anaconda prompt or a terminal in a `Session`, `Participant`, or `Trial` folder.\
Type `ipython`.
``` python
from Pose2Sim import Pose2Sim
Pose2Sim.synchronization()
```
Reference camera (usally cam1) should start record at last between whole cameras.\
Set fps, id_kpt, weight_kpt, reset_sync in Config.toml.\
**How to get perfect sync point**
1. Set cameras position where they can see person wrist clearly.
1. Set cameras position where they can see `id_kpt` (default: `RWrist`) clearly.
2. Press record button, and what pressed last time to be reference camera.
3. Walk to proper location( See 1 ).
4. Raise your hands.
5. Downward your hand fastly.
Check printed output. If results are not satisfying, try and release the constraints in the [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/S00_Demo_Session/Config.toml) file.
Alternatively, use a flashlight or a clap to synchronize them. GoPro cameras can also be synchronized with a timecode, by GPS (outdoors) or with a remote control (slightly less reliable).
</br>
## Camera calibration
> _**Calculate camera intrinsic properties and extrinsic locations and positions.\
> Convert a preexisting calibration file, or calculate intrinsic and extrinsic parameters from scratch.**_ \
> Convert a preexisting calibration file, or calculate intrinsic and extrinsic parameters from scratch.**_
Open an Anaconda prompt or a terminal in a `Session`, `Participant`, or `Trial` folder.\
Type `ipython`.