-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 03eabdc
Showing
17 changed files
with
2,330 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
**/__pycache__/ | ||
*.pyc | ||
*.egg-info/ | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
BSD 3-Clause License | ||
|
||
Copyright (c) 2022, perfanalytics | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
3. Neither the name of the copyright holder nor the names of its | ||
contributors may be used to endorse or promote products derived from | ||
this software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Right click in folder parent to Sports2D -> Open PowerShell here | ||
|
||
conda create -n Sports2D python[version='<3.11,>=3.7'] | ||
|
||
pip install ipython toml numpy pandas scipy anytree opencv-python mediapipe PyQt5 | ||
ipython | ||
|
||
from Sports2D import Sports2D | ||
Sports2D.detect_pose('Sports2D\Demo\Config_demo.toml') | ||
Sports2D.compute_angles('Sports2D\Demo\Config_demo.toml') | ||
|
||
|
||
Copy, edit, and if you like, rename your Config_demo.toml file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
############################################################################### | ||
## SPORTS2D PROJECT PARAMETERS ## | ||
############################################################################### | ||
|
||
# Configure your project parameters here | ||
|
||
|
||
[project] | ||
video_dir = '' # BETWEEN SINGLE QUOTES! # If empty, project dir is current dir | ||
video_file = 'demo.mp4' | ||
frame_rate = 30 #Hz | ||
|
||
|
||
[pose] | ||
pose_algo = 'BLAZEPOSE' # 'OPENPOSE' or 'BLAZEPOSE' | ||
# OpenPose is more accurate and supports multi-person detection, but needs to be installed separately | ||
# Coming soon: 'deeplabcut', 'alphapose' | ||
|
||
[pose.BLAZEPOSE] | ||
# 0,1,2. 2 is slightly slower but more accurate | ||
model_complexity = 2 | ||
|
||
[pose.OPENPOSE] | ||
# Install OpenPose from https://github.com/CMU-Perceptual-Computing-Lab/openpose | ||
#BODY_25 is standard, BODY_25B is more accurate but requires downloading the model from | ||
# https://github.com/CMU-Perceptual-Computing-Lab/openpose_train/blob/master/experimental_models/README.md | ||
openpose_model = 'BODY_25' | ||
# Installation path of openpose (between single quotes) | ||
openpose_path = 'D:\softs\openpose-1.6.0-binaries-win64-gpu-flir-3d_recommended\openpose' | ||
|
||
|
||
[compute_angles] | ||
joint_angles = ['rankle', 'lankle', 'rknee', 'lknee', 'rhip', 'lhip', 'rshoulder', 'lshoulder', 'relbow', 'lelbow'] | ||
# select among ['rankle', 'lankle', 'rknee', 'lknee', 'rhip', 'lhip', 'rshoulder', 'lshoulder', 'relbow', 'lelbow'] | ||
segment_angles = ['rfoot', 'lfoot', 'rshank', 'lshank', 'rthigh', 'lthigh', 'trunk', 'rarm', 'larm', 'rforearm', 'lforearm'] | ||
# select among ['rfoot', 'lfoot', 'rshank', 'lshank', 'rthigh', 'lthigh', 'trunk', 'rarm', 'larm', 'rforearm', 'lforearm'] | ||
|
||
|
||
|
||
# ADVANCED CONFIGURATION | ||
|
||
[pose_advanced] # only for OPENPOSE | ||
load_pose = true # else proceed to detection | ||
save_vid = true | ||
save_img = true | ||
interp_gap_smaller_than = 5 # do not interpolate bigger gaps | ||
show_plots = false | ||
filter = true | ||
filter_type = 'butterworth' # butterworth, gaussian, LOESS, median | ||
[pose_advanced.butterworth] | ||
order = 4 | ||
cut_off_frequency = 6 # Hz | ||
[pose_advanced.gaussian] | ||
sigma_kernel = 1 #px | ||
[pose_advanced.loess] | ||
nb_values_used = 5 # = fraction of data used * nb frames | ||
[pose_advanced.median] | ||
kernel_size = 3 | ||
|
||
|
||
[compute_angles_advanced] # for OPENPOSE and BLAZEPOSE | ||
show_plots = false | ||
filter = true | ||
filter_type = 'butterworth' # butterworth, gaussian, LOESS, median | ||
[compute_angles_advanced.butterworth] | ||
order = 4 | ||
cut_off_frequency = 6 # Hz | ||
[compute_angles_advanced.gaussian] | ||
sigma_kernel = 1 #px | ||
[compute_angles_advanced.loess] | ||
nb_values_used = 5 # = fraction of data used * nb frames | ||
[compute_angles_advanced.median] | ||
kernel_size = 3 |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
|
||
|
||
''' | ||
############################################################## | ||
## SPORTS2D ## | ||
############################################################## | ||
This repository provides a workflow to compute 2D markerless | ||
joint and segment angles from videos. | ||
These angles can be plotted and processed with any | ||
spreadsheet software or programming language. | ||
This is a headless version, but apps will be released | ||
for Windows, Linux, MacOS, as well as Android and iOS. | ||
Mobile versions will only support exploratory joint detection | ||
from BlazePose, hence less accurately and tunable. | ||
If you need to detect several persons and want more accurate results, | ||
you can install and use OpenPose: | ||
https://github.com/CMU-Perceptual-Computing-Lab/openpose | ||
----- | ||
Sports2D installation: | ||
----- | ||
Optional: | ||
- Install Miniconda | ||
- Open a Anaconda Prompt and type: | ||
`conda create -n Sports2D python>=3.7` | ||
`conda activate Sports2D` | ||
pip install | ||
- Open a python prompt and type `pip install sports2d` | ||
- `pip show sports2d` | ||
- Adjust your settings (in particular video path) in `Config_demo.toml` | ||
- ```from Sports2D import Sports2D | ||
Sports2D.detect_pose('Sports2D\Demo\Config_demo.toml') | ||
Sports2D.compute_angles('Sports2D\Demo\Config_demo.toml')``` | ||
----- | ||
/!\ Warning /!\ | ||
----- | ||
- The angle estimation is only as good as the pose estimation algorithm, i.e., it is not perfect. | ||
- It will only lead to acceptable results if the persons move in the 2D plane (sagittal plane). | ||
- The persons need to be filmed as perpendicularly as possible from their side. | ||
If you need research-grade markerless joint kinematics, consider using several cameras, | ||
and constraining angles to a biomechanically accurate model. See Pose2Sim for example: | ||
https://github.com/perfanalytics/pose2sim | ||
----- | ||
Pose detection: | ||
----- | ||
Detect joint centers from a video with OpenPose or BlazePose. | ||
Save a 2D csv position file per person, and optionally json files, image files, and video files. | ||
If OpenPose is used, multiple persons can be consistently detected across frames. | ||
Interpolates sequences of missing data if they are less than N frames long. | ||
Optionally filters results with Butterworth, gaussian, median, or loess filter. | ||
Optionally displays figures. | ||
If BlazePose is used, only one person can be detected. | ||
No interpolation nor filtering options available. Not plotting available. | ||
----- | ||
Angle computation: | ||
----- | ||
Compute joint and segment angles from csv position files. | ||
Automatically adjust angles when person switches to face the other way. | ||
Save a 2D csv angle file per person. | ||
Optionally filters results with Butterworth, gaussian, median, or loess filter. | ||
Optionally displays figures. | ||
Joint angle conventions: | ||
- Ankle dorsiflexion: Between heel and big toe, and ankle and knee | ||
- Knee flexion: Between hip, knee, and ankle | ||
- Hip flexion: Between knee, hip, and shoulder | ||
- Shoulder flexion: Between hip, shoulder, and elbow | ||
- Elbow flexion: Between wrist, elbow, and shoulder | ||
Segment angle conventions: | ||
Angles are measured anticlockwise between the horizontal and the segment. | ||
- Foot: Between heel and big toe | ||
- Shank: Between ankle and knee | ||
- Thigh: Between hip and knee | ||
- Arm: Between shoulder and elbow | ||
- Forearm: Between elbow and wrist | ||
- Trunk: Between hip midpoint and shoulder midpoint | ||
----- | ||
To-do list: | ||
----- | ||
- GUI applications for all platforms (with Kivy: https://kivy.org/) | ||
- Pose refinement: click and move badly estimated 2D points (cf DeepLabCut: https://www.youtube.com/watch?v=bEuBKB7eqmk) | ||
- Include OpenPose in Sports2D (dockerize it cf https://github.com/stanfordnmbl/mobile-gaitlab/blob/master/demo/Dockerfile) | ||
- Constrain points to OpenSim skeletal model for better angle estimation (cf Pose2Sim but in 2D https://github.com/perfanalytics/pose2sim) | ||
''' | ||
|
||
|
||
## INIT | ||
import toml | ||
import os | ||
from pathlib import Path | ||
import time | ||
import logging, logging.handlers | ||
|
||
with open(Path('logs.txt'), 'a+') as log_f: pass | ||
logging.basicConfig(format='%(message)s', level=logging.INFO, | ||
handlers = [logging.handlers.TimedRotatingFileHandler(Path('logs.txt'), when='D', interval=7), logging.StreamHandler()]) | ||
|
||
|
||
## AUTHORSHIP INFORMATION | ||
__author__ = "David Pagnon" | ||
__copyright__ = "Copyright 2023, Sports2D" | ||
__credits__ = ["David Pagnon"] | ||
__license__ = "BSD 3-Clause License" | ||
__version__ = "0.1" | ||
__maintainer__ = "David Pagnon" | ||
__email__ = "[email protected]" | ||
__status__ = "Development" | ||
|
||
|
||
## FUNCTIONS | ||
def read_config_file(config): | ||
''' | ||
Read configation file. | ||
''' | ||
|
||
config_dict = toml.load(config) | ||
return config_dict | ||
|
||
|
||
def base_params(config_dict): | ||
''' | ||
Retrieve sequence name and frames to be analyzed. | ||
''' | ||
|
||
video_dir = Path(config_dict.get('project').get('video_dir')).resolve() | ||
if video_dir == '': video_dir = os.getcwd() | ||
video_file = Path(config_dict.get('project').get('video_file')) | ||
frame_rate = config_dict.get('project').get('frame_rate') | ||
|
||
return video_dir, video_file, frame_rate | ||
|
||
|
||
def detect_pose(config='Config_demo.toml'): | ||
''' | ||
Compute 2D pose from video. | ||
Save 2D csv file, and optionally json files, image files, and video file. | ||
Optionally interpolates missing data, filters them, and displays figures. | ||
''' | ||
|
||
from Sports2D.detect_pose import detect_pose_fun | ||
|
||
config_dict = read_config_file(config) | ||
_, video_file, _ = base_params(config_dict) | ||
|
||
logging.info("\n\n---------------------------------------------------------------------") | ||
logging.info(f"Detect pose for video {video_file}") | ||
logging.info("---------------------------------------------------------------------") | ||
start = time.time() | ||
|
||
detect_pose_fun(config_dict) | ||
|
||
end = time.time() | ||
logging.info(f'Pose detection took {end-start:.2f} s.') | ||
|
||
|
||
def compute_angles(config='Config_demo.toml'): | ||
''' | ||
Compute joint and segment angles from 2D points coordinates in csv file. | ||
Save csv file. | ||
Optionally interpolates missing data, filters them, and displays figures. | ||
''' | ||
|
||
from Sports2D.compute_angles import compute_angles_fun | ||
|
||
config_dict = read_config_file(config) | ||
_, video_file, _ = base_params(config_dict) | ||
joint_angles = config_dict.get('compute_angles').get('joint_angles') | ||
segment_angles = config_dict.get('compute_angles').get('segment_angles') | ||
|
||
logging.info("\n\n---------------------------------------------------------------------") | ||
logging.info(f"Compute angles for video {video_file} ") | ||
logging.info(f"for {joint_angles}") | ||
logging.info(f"and {segment_angles}.") | ||
logging.info("---------------------------------------------------------------------") | ||
start = time.time() | ||
|
||
compute_angles_fun(config_dict) | ||
|
||
end = time.time() | ||
logging.info(f'Joint and segment computation took {end-start:.2f} s.') | ||
|
||
|
||
|
Oops, something went wrong.