From 4bd5106acbb1875419da14706bcfa476b953f1fb Mon Sep 17 00:00:00 2001 From: davidpagnon Date: Fri, 22 Sep 2023 11:41:36 +0200 Subject: [PATCH] convert easymocap calibration files --- Pose2Sim/Demo/User/Config.toml | 3 +- Pose2Sim/Demo/User/test.toml | 7 +- Pose2Sim/Empty_project/User/Config.toml | 7 +- ..._to_toml.py => calib_easymocap_to_toml.py} | 16 ++-- ...l_to_yml.py => calib_toml_to_easymocap.py} | 28 +++--- Pose2Sim/calibration.py | 89 ++++++++++++++++--- README.md | 27 +++--- 7 files changed, 131 insertions(+), 46 deletions(-) rename Pose2Sim/Utilities/{calib_yml_to_toml.py => calib_easymocap_to_toml.py} (90%) rename Pose2Sim/Utilities/{calib_toml_to_yml.py => calib_toml_to_easymocap.py} (81%) diff --git a/Pose2Sim/Demo/User/Config.toml b/Pose2Sim/Demo/User/Config.toml index 578746e..ae14479 100644 --- a/Pose2Sim/Demo/User/Config.toml +++ b/Pose2Sim/Demo/User/Config.toml @@ -35,12 +35,13 @@ openpose_path = '' # only checked if OpenPose is used calibration_type = 'convert' # 'convert' or 'calculate' [calibration.convert] - convert_from = 'qualisys' # 'qualisys', 'optitrack', vicon', 'opencap', or 'biocv' + 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 parameters needed [calibration.convert.opencap] # No parameters needed + [calibration.convert.easymocap] # No parameters needed [calibration.convert.biocv] # No parameters needed diff --git a/Pose2Sim/Demo/User/test.toml b/Pose2Sim/Demo/User/test.toml index b16057e..15d4462 100644 --- a/Pose2Sim/Demo/User/test.toml +++ b/Pose2Sim/Demo/User/test.toml @@ -35,9 +35,14 @@ openpose_path = '' # only checked if OpenPose is used calibration_type = 'convert' # 'convert' or 'calculate' [calibration.convert] - convert_from = 'qualisys' # 'qualisys', 'optitrack', or 'vicon' + 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 parameters needed + [calibration.convert.opencap] # No parameters needed + [calibration.convert.easymocap] # No parameters needed + [calibration.convert.biocv] # No parameters needed [calibration.calculate] diff --git a/Pose2Sim/Empty_project/User/Config.toml b/Pose2Sim/Empty_project/User/Config.toml index 5a8f8a7..4d44e98 100644 --- a/Pose2Sim/Empty_project/User/Config.toml +++ b/Pose2Sim/Empty_project/User/Config.toml @@ -35,9 +35,14 @@ openpose_path = '' # only checked if OpenPose is used calibration_type = 'convert' # 'convert' or 'calculate' [calibration.convert] - convert_from = 'qualisys' # 'qualisys', 'optitrack', or 'vicon' + 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 parameters needed + [calibration.convert.opencap] # No parameters needed + [calibration.convert.easymocap] # No parameters needed + [calibration.convert.biocv] # No parameters needed [calibration.calculate] diff --git a/Pose2Sim/Utilities/calib_yml_to_toml.py b/Pose2Sim/Utilities/calib_easymocap_to_toml.py similarity index 90% rename from Pose2Sim/Utilities/calib_yml_to_toml.py rename to Pose2Sim/Utilities/calib_easymocap_to_toml.py index a7eb6e1..eb39b2f 100644 --- a/Pose2Sim/Utilities/calib_yml_to_toml.py +++ b/Pose2Sim/Utilities/calib_easymocap_to_toml.py @@ -4,7 +4,7 @@ ''' ################################################## - ## YML CALIBRATION TO TOML CALIBRATION ## + ## EASYMOCAP CALIBRATION TO TOML CALIBRATION ## ################################################## Converts OpenCV intrinsic and extrinsic .yml calibration files @@ -14,9 +14,9 @@ Please correct in the resulting .toml file if needed. Take your image size as a reference. Usage: - import calib_yml_to_toml; calib_yml_to_toml.calib_yml_to_toml_func(r'', r'') - OR python -m calib_yml_to_toml -i intrinsic_yml_file -e extrinsic_yml_file - OR python -m calib_yml_to_toml -i intrinsic_yml_file -e extrinsic_yml_file -o output_toml_file + import calib_easymocap_to_toml; calib_yml_to_toml.calib_easymocap_to_toml_func(r'', r'') + OR python -m calib_easymocap_to_toml -i intrinsic_yml_file -e extrinsic_yml_file + OR python -m calib_easymocap_to_toml -i intrinsic_yml_file -e extrinsic_yml_file -o output_toml_file ''' @@ -113,7 +113,7 @@ def toml_write(toml_path, C, S, D, K, R, T): cal_f.write(meta) -def calib_yml_to_toml_func(*args): +def calib_easymocap_to_toml_func(*args): ''' Converts OpenCV intrinsic and extrinsic .yml calibration files to an OpenCV .toml calibration file @@ -146,10 +146,10 @@ def calib_yml_to_toml_func(*args): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('-i', '--intrinsic_file', required = True, help='OpenCV intrinsic .yml calibration file') - parser.add_argument('-e', '--extrinsic_file', required = True, help='OpenCV extrinsic .yml calibration file') + parser.add_argument('-i', '--intrinsic_file', required = True, help='EasyMocap intrinsic .yml calibration file') + parser.add_argument('-e', '--extrinsic_file', required = True, help='EasyMocap extrinsic .yml calibration file') parser.add_argument('-t', '--toml_file', required=False, help='OpenCV .toml output calibration file') args = vars(parser.parse_args()) - calib_yml_to_toml_func(args) + calib_easymocap_to_toml_func(args) diff --git a/Pose2Sim/Utilities/calib_toml_to_yml.py b/Pose2Sim/Utilities/calib_toml_to_easymocap.py similarity index 81% rename from Pose2Sim/Utilities/calib_toml_to_yml.py rename to Pose2Sim/Utilities/calib_toml_to_easymocap.py index 9edb004..02e4a12 100644 --- a/Pose2Sim/Utilities/calib_toml_to_yml.py +++ b/Pose2Sim/Utilities/calib_toml_to_easymocap.py @@ -4,16 +4,16 @@ ''' ################################################## - ## YML CALIBRATION TO TOML CALIBRATION ## + ## EASYMOCAP CALIBRATION TO TOML CALIBRATION ## ################################################## - Convert an OpenCV .toml calibration file - to OpenCV intrinsic and extrinsic .yml calibration files + Convert a Pose2Sim .toml calibration file + to EasyMocap intrinsic and extrinsic .yml calibration files Usage: - from Pose2Sim.Utilities import calib_toml_to_yml; calib_toml_to_yml.calib_toml_to_yml_func(r'') - OR python -m calib_yml_to_toml -t input_toml_file - OR python -m calib_yml_to_toml -t input_toml_file -i intrinsic_yml_file -e extrinsic_yml_file + from Pose2Sim.Utilities import calib_toml_to_easymocap; calib_toml_to_easymocap.calib_toml_to_easymocap_func(r'') + OR python -m calib_easymocap_to_toml -t input_toml_file + OR python -m calib_easymocap_to_toml -t input_toml_file -i intrinsic_yml_file -e extrinsic_yml_file ''' ## INIT @@ -61,6 +61,7 @@ def read_toml(toml_path): return C, S, D, K, R, T + def write_intrinsic_yml(intrinsic_yml_path, C, D, K): ''' Writes an OpenCV .yml intrinsic calibration file @@ -109,15 +110,16 @@ def write_extrinsic_yml(extrinsic_yml_path, C, R, T): extrinsic_file.release() -def calib_toml_to_yml_func(*args): + +def calib_toml_to_easymocap_func(*args): ''' - Convert an OpenCV .toml calibration file - to OpenCV intrinsic and extrinsic .yml calibration files + Convert a Pose2Sim .toml calibration file + to EasyMocap intrinsic and extrinsic .yml calibration files Usage: - import calib_toml_to_yml; calib_toml_to_yml.calib_toml_to_yml_func(r'') - OR python -m calib_toml_to_yml -t input_toml_file - OR python -m calib_toml_to_yml -t input_toml_file -i intrinsic_yml_file -e extrinsic_yml_file + from Pose2Sim.Utilities import calib_toml_to_easymocap; calib_toml_to_easymocap.calib_toml_to_easymocap_func(r'') + OR python -m calib_easymocap_to_toml -t input_toml_file + OR python -m calib_easymocap_to_toml -t input_toml_file -i intrinsic_yml_file -e extrinsic_yml_file ''' try: @@ -147,5 +149,5 @@ if __name__ == '__main__': parser.add_argument('-e', '--extrinsic_yml_file', required = False, help='OpenCV extrinsic .yml calibration file') args = vars(parser.parse_args()) - calib_toml_to_yml_func(args) + calib_toml_to_easymocap_func(args) diff --git a/Pose2Sim/calibration.py b/Pose2Sim/calibration.py index 03a744c..811ea71 100644 --- a/Pose2Sim/calibration.py +++ b/Pose2Sim/calibration.py @@ -321,12 +321,77 @@ def read_vicon(vicon_path): return ret, C, S, D, K, R, T -def calib_biocv_fun(file_to_convert_paths, binning_factor=1): +def read_intrinsic_yml(intrinsic_path): + ''' + Reads an intrinsic .yml calibration file + Returns 3 lists of size N (N=number of cameras): + - S (image size) + - K (intrinsic parameters) + - D (distorsion) + + N.B. : Size is calculated as twice the position of the optical center. Please correct in the .toml file if needed. + ''' + intrinsic_yml = cv2.FileStorage(intrinsic_path, cv2.FILE_STORAGE_READ) + N = intrinsic_yml.getNode('names').size() + S, D, K = [], [], [] + for i in range(N): + name = intrinsic_yml.getNode('names').at(i).string() + K.append(intrinsic_yml.getNode(f'K_{name}').mat()) + D.append(intrinsic_yml.getNode(f'dist_{name}').mat().flatten()[:-1]) + S.append([K[i][0,2]*2, K[i][1,2]*2]) + return S, K, D + + +def read_extrinsic_yml(extrinsic_path): + ''' + Reads an intrinsic .yml calibration file + Returns 3 lists of size N (N=number of cameras): + - R (extrinsic rotation, Rodrigues vector) + - T (extrinsic translation) + ''' + extrinsic_yml = cv2.FileStorage(extrinsic_path, cv2.FILE_STORAGE_READ) + N = extrinsic_yml.getNode('names').size() + R, T = [], [] + for i in range(N): + name = extrinsic_yml.getNode('names').at(i).string() + R.append(extrinsic_yml.getNode(f'R_{name}').mat().flatten()) # R_1 pour Rodrigues, Rot_1 pour matrice + T.append(extrinsic_yml.getNode(f'T_{name}').mat().flatten()) + return R, T + + +def calib_easymocap_fun(files_to_convert_paths, binning_factor=1): + ''' + Reads EasyMocap .yml calibration files + + INPUTS: + - files_to_convert_paths: paths of the intri.yml and extri.yml calibration files to convert + - binning_factor: always 1 with easymocap calibration + + OUTPUTS: + - ret: residual reprojection error in _mm_: list of floats + - C: camera name: list of strings + - S: image size: list of list of floats + - D: distorsion: list of arrays of floats + - K: intrinsic parameters: list of 3x3 arrays of floats + - R: extrinsic rotation: list of arrays of floats + - T: extrinsic translation: list of arrays of floats + ''' + + extrinsic_path, intrinsic_path = files_to_convert_paths + S, K, D = read_intrinsic_yml(intrinsic_path) + R, T = read_extrinsic_yml(extrinsic_path) + C = np.array(range(len(S)))+1 + ret = [np.nan]*len(C) + + return ret, C, S, D, K, R, T + + +def calib_biocv_fun(files_to_convert_paths, binning_factor=1): ''' Convert bioCV calibration files. INPUTS: - - file_to_convert_path: path of the calibration files to convert (no extension) + - files_to_convert_paths: paths of the calibration files to convert (no extension) - binning_factor: always 1 with biocv calibration OUTPUTS: @@ -339,10 +404,10 @@ def calib_biocv_fun(file_to_convert_paths, binning_factor=1): - T: extrinsic translation: list of arrays of floats ''' - logging.info(f'Converting {[os.path.basename(f) for f in file_to_convert_paths]} to .toml calibration file...') + logging.info(f'Converting {[os.path.basename(f) for f in files_to_convert_paths]} to .toml calibration file...') ret, C, S, D, K, R, T = [], [], [], [], [], [], [] - for i, f_path in enumerate(file_to_convert_paths): + for i, f_path in enumerate(files_to_convert_paths): with open(f_path) as f: calib_data = f.read().split('\n') ret += [np.nan] @@ -357,7 +422,7 @@ def calib_biocv_fun(file_to_convert_paths, binning_factor=1): return ret, C, S, D, K, R, T -def calib_opencap_fun(file_to_convert_paths, binning_factor=1): +def calib_opencap_fun(files_to_convert_paths, binning_factor=1): ''' Convert OpenCap calibration files. @@ -366,8 +431,8 @@ def calib_opencap_fun(file_to_convert_paths, binning_factor=1): T is good the way it is. INPUTS: - - file_to_convert_path: path of the .pickle calibration files to convert - - binning_factor: always 1 with biocv calibration + - files_to_convert_paths: paths of the .pickle calibration files to convert + - binning_factor: always 1 with opencap calibration OUTPUTS: - ret: residual reprojection error in _mm_: list of floats @@ -379,10 +444,10 @@ def calib_opencap_fun(file_to_convert_paths, binning_factor=1): - T: extrinsic translation: list of arrays of floats ''' - logging.info(f'Converting {[os.path.basename(f) for f in file_to_convert_paths]} to .toml calibration file...') + logging.info(f'Converting {[os.path.basename(f) for f in files_to_convert_paths]} to .toml calibration file...') ret, C, S, D, K, R, T = [], [], [], [], [], [], [] - for i, f_path in enumerate(file_to_convert_paths): + for i, f_path in enumerate(files_to_convert_paths): with open(f_path, 'rb') as f_pickle: calib_data = pickle.load(f_pickle) ret += [np.nan] @@ -393,7 +458,7 @@ def calib_opencap_fun(file_to_convert_paths, binning_factor=1): R_cam = calib_data['rotation'] T_cam = calib_data['translation'].squeeze() - # Rotate cameras by Pi/2 around x in world frame + # Rotate cameras by Pi/2 around x in world frame -> could have just switched some columns in matrix # camera frame to world frame R_w, T_w = RT_qca2cv(R_cam, T_cam) # x_rotate -Pi/2 and z_rotate Pi @@ -1161,6 +1226,9 @@ def calibrate_cams_all(config): elif convert_filetype=='opencap': # all files with .pickle extension file_to_convert_path = glob.glob(os.path.join(calib_dir, '*.pickle')) binning_factor = 1 + elif convert_filetype=='easymocap': #intri.yml and intri.yml + file_to_convert_path = glob.glob(os.path.join(calib_dir, '*.yml')) + binning_factor = 1 elif convert_filetype=='biocv': # all files without extension list_dir = os.listdir(calib_dir) list_dir_noext = [os.path.splitext(f)[0] for f in list_dir if os.path.splitext(f)[1]==''] @@ -1198,6 +1266,7 @@ def calibrate_cams_all(config): 'convert_optitrack': calib_optitrack_fun, 'convert_vicon': calib_vicon_fun, 'convert_opencap': calib_opencap_fun, + 'convert_easymocap': calib_easymocap_fun, 'convert_biocv': calib_biocv_fun, 'calculate_board': calib_board_fun, 'calculate_points': calib_points_fun diff --git a/README.md b/README.md index 6c0c93a..fcf0b49 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you can only use one single camera and don't mind losing some accuracy, pleas 3. [With DeepLabCut](#with-deeplabcut) 4. [With AlphaPose](#with-alphapose) 3. [Camera calibration](#camera-calibration) - 1. [Convert from Qualisys, Optitrack, Vicon, OpenCap, or bioCV](#convert-from-qualisys-optitrack-vicon-opencap-or-biocv) + 1. [Convert from Qualisys, Optitrack, Vicon, OpenCap, EasyMocap, or bioCV](#convert-from-qualisys-optitrack-vicon-opencap-easymocap--or-biocv) 2. [Calculate from scratch](#calculate-from-scratch) 4. [Camera synchronization](#camera-synchronization) 5. [Tracking, Triangulating, Filtering](#tracking-triangulating-filtering) @@ -241,23 +241,26 @@ N.B.: Markers are not needed in Pose2Sim and were used here for validation > _**Convert a preexisting calibration file, or calculate intrinsic and extrinsic parameters from scratch.**_ \ > _**N.B.:**_ You can visualize camera calibration in 3D with my (experimental) [Maya-Mocap tool](https://github.com/davidpagnon/Maya-Mocap). -### Convert from Qualisys, Optitrack, Vicon, OpenCap, or bioCV +### Convert from Qualisys, Optitrack, Vicon, OpenCap, EasyMocap, or bioCV If you already have a calibration file, set `calibration_type` type to `convert` in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. -- **From Qualisys:** +- **From [Qualisys](https://www.qualisys.com):** - Export calibration to `.qca.txt` within QTM. - Copy it in the `calibration` Pose2Sim folder. - set `convert_from` to 'qualisys' in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. Change `binning_factor` to 2 if you film in 540p. -- **From Optitrack:** Exporting calibration will be available in Motive 3.2. In the meantime: +- **From [Optitrack](https://optitrack.com/):** Exporting calibration will be available in Motive 3.2. In the meantime: - Calculate intrinsics with a board (see next section). - Use their C++ API [to retrieve extrinsic properties](https://docs.optitrack.com/developer-tools/motive-api/motive-api-function-reference#tt_cameraxlocation). Translation can be copied as is in your `Calib.toml` file, but TT_CameraOrientationMatrix first needs to be [converted to a Rodrigues vector](https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga61585db663d9da06b68e70cfbf6a1eac) with OpenCV. See instructions [here](https://github.com/perfanalytics/pose2sim/issues/28). -- **From Vicon:** +- **From [Vicon](http://www.vicon.com/Software/Nexus):** - Copy your `.xcp` Vicon calibration file to the Pose2Sim `calibration` folder. - set `convert_from` to 'vicon' in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. No other setting is needed. -- **From OpenCap:** +- **From [OpenCap](https://www.opencap.ai/):** - Copy your `.pickle` OpenCap calibration files to the Pose2Sim `calibration` folder. - set `convert_from` to 'opencap' in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. No other setting is needed. -- **From bioCV:** +- **From [EasyMocap](https://github.com/zju3dv/EasyMocap/):** + - Copy your `intri.yml` and `extri.yml` files to the Pose2Sim `calibration` folder. + - set `convert_from` to 'easymocap' in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. No other setting is needed. +- **From [bioCV](https://github.com/camera-mc-dev/.github/blob/main/profile/mocapPipe.md):** - Copy your bioCV calibration files (no extension) to the Pose2Sim `calibration` folder. - set `convert_from` to 'biocv' in your [User\Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Empty_project/User/Config.toml) file. No other setting is needed. @@ -728,11 +731,11 @@ Converts a Qualisys .qca.txt calibration file to the Pose2Sim .toml calibration [calib_toml_to_qca.py](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Utilities/calib_toml_to_qca.py) Converts a Pose2Sim .toml calibration file (e.g., from a checkerboard) to a Qualisys .qca.txt calibration file. -[calib_yml_to_toml.py](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Utilities/calib_yml_to_toml.py) -Converts OpenCV intrinsic and extrinsic .yml calibration files to an OpenCV .toml calibration file. +[calib_easymocap_to_toml.py](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Utilities/calib_easymocap_to_toml.py) +Converts EasyMocap intrinsic and extrinsic .yml calibration files to an OpenCV .toml calibration file. -[calib_toml_to_yml.py](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Utilities/calib_toml_to_yml.py) -Converts an OpenCV .toml calibration file to OpenCV intrinsic and extrinsic .yml calibration files. +[calib_easymocap_to_yml.py](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Utilities/calib_toml_to_easymocap.py) +Converts an OpenCV .toml calibration file to EasyMocap intrinsic and extrinsic .yml calibration files. @@ -840,13 +843,13 @@ If you want to contribute to Pose2Sim, please follow [this guide](https://docs.g > - [x] **Calibration:** Convert [Optitrack](https://optitrack.com/) extrinsic calibration file. > - [x] **Calibration:** Convert [Vicon](http://www.vicon.com/Software/Nexus) .xcp calibration file. > - [x] **Calibration:** Convert [OpenCap](https://www.opencap.ai/) .pickle calibration files. +> - [x] **Calibration:** Convert [EasyMocap](https://github.com/zju3dv/EasyMocap/) .yml calibration files. > - [x] **Calibration:** Convert [bioCV](https://github.com/camera-mc-dev/.github/blob/main/profile/mocapPipe.md) calibration files. > - [x] **Calibration:** Easier and clearer calibration procedure: separate intrinsic and extrinsic parameter calculation, edit corner detection if some are wrongly detected (or not visible). > - [x] **Calibration:** Possibility to evaluate extrinsic parameters from cues on scene. > - [ ] **Calibration:** Once object points have been detected or clicked once, track them for live calibration of moving cameras. Propose to click again when they are lost. > - [ ] **Calibration:** Fine-tune calibration with bundle adjustment. > - [ ] **Calibration:** Support ChArUco board detection (see [there](https://mecaruco2.readthedocs.io/en/latest/notebooks_rst/Aruco/sandbox/ludovic/aruco_calibration_rotation.html)). -> - [ ] **Calibration:** Convert bioCV (University of Bath) and OpenCap calibrations. > - [ ] **Calibration:** Calculate calibration with points rather than board. (1) SBA calibration with wand (cf [Argus](https://argus.web.unc.edu), see converter [here](https://github.com/backyardbiomech/DLCconverterDLT/blob/master/DLTcameraPosition.py)). Set world reference frame in the end. > - [ ] **Calibration:** Alternatively, self-calibrate with [OpenPose keypoints](https://ietresearch.onlinelibrary.wiley.com/doi/full/10.1049/cvi2.12130). Set world reference frame in the end.