sports2d 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Sports2D/Demo/Config_demo.toml +40 -22
- Sports2D/Sports2D.py +39 -13
- Sports2D/Utilities/common.py +477 -20
- Sports2D/Utilities/skeletons.py +7 -8
- Sports2D/Utilities/tests.py +3 -3
- Sports2D/process.py +162 -326
- {sports2d-0.6.1.dist-info → sports2d-0.6.3.dist-info}/METADATA +306 -182
- sports2d-0.6.3.dist-info/RECORD +16 -0
- sports2d-0.6.1.dist-info/RECORD +0 -16
- {sports2d-0.6.1.dist-info → sports2d-0.6.3.dist-info}/LICENSE +0 -0
- {sports2d-0.6.1.dist-info → sports2d-0.6.3.dist-info}/WHEEL +0 -0
- {sports2d-0.6.1.dist-info → sports2d-0.6.3.dist-info}/entry_points.txt +0 -0
- {sports2d-0.6.1.dist-info → sports2d-0.6.3.dist-info}/top_level.txt +0 -0
Sports2D/process.py
CHANGED
|
@@ -60,7 +60,7 @@ from functools import partial
|
|
|
60
60
|
from datetime import datetime
|
|
61
61
|
import itertools as it
|
|
62
62
|
from tqdm import tqdm
|
|
63
|
-
from anytree import RenderTree
|
|
63
|
+
from anytree import RenderTree
|
|
64
64
|
|
|
65
65
|
import numpy as np
|
|
66
66
|
import pandas as pd
|
|
@@ -68,6 +68,7 @@ import cv2
|
|
|
68
68
|
import matplotlib as mpl
|
|
69
69
|
import matplotlib.pyplot as plt
|
|
70
70
|
from rtmlib import PoseTracker, BodyWithFeet, Wholebody, Body, Custom
|
|
71
|
+
from deep_sort_realtime.deepsort_tracker import DeepSort
|
|
71
72
|
|
|
72
73
|
from Sports2D.Utilities import filter
|
|
73
74
|
from Sports2D.Utilities.common import *
|
|
@@ -337,161 +338,6 @@ def compute_angle(ang_name, person_X_flipped, person_Y, angle_dict, keypoints_id
|
|
|
337
338
|
return ang
|
|
338
339
|
|
|
339
340
|
|
|
340
|
-
def min_with_single_indices(L, T):
|
|
341
|
-
'''
|
|
342
|
-
Let L be a list (size s) with T associated tuple indices (size s).
|
|
343
|
-
Select the smallest values of L, considering that
|
|
344
|
-
the next smallest value cannot have the same numbers
|
|
345
|
-
in the associated tuple as any of the previous ones.
|
|
346
|
-
|
|
347
|
-
Example:
|
|
348
|
-
L = [ 20, 27, 51, 33, 43, 23, 37, 24, 4, 68, 84, 3 ]
|
|
349
|
-
T = list(it.product(range(2),range(3)))
|
|
350
|
-
= [(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3),(2,0),(2,1),(2,2),(2,3)]
|
|
351
|
-
|
|
352
|
-
- 1st smallest value: 3 with tuple (2,3), index 11
|
|
353
|
-
- 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]:
|
|
354
|
-
20 with tuple (0,0), index 0
|
|
355
|
-
- 3rd smallest value when excluding [X,X,X,X,X,(1,1),(1,2),X,X,X,X,X]:
|
|
356
|
-
23 with tuple (1,1), index 5
|
|
357
|
-
|
|
358
|
-
INPUTS:
|
|
359
|
-
- L: list (size s)
|
|
360
|
-
- T: T associated tuple indices (size s)
|
|
361
|
-
|
|
362
|
-
OUTPUTS:
|
|
363
|
-
- minL: list of smallest values of L, considering constraints on tuple indices
|
|
364
|
-
- argminL: list of indices of smallest values of L (indices of best combinations)
|
|
365
|
-
- T_minL: list of tuples associated with smallest values of L
|
|
366
|
-
'''
|
|
367
|
-
|
|
368
|
-
minL = [np.nanmin(L)]
|
|
369
|
-
argminL = [np.nanargmin(L)]
|
|
370
|
-
T_minL = [T[argminL[0]]]
|
|
371
|
-
|
|
372
|
-
mask_tokeep = np.array([True for t in T])
|
|
373
|
-
i=0
|
|
374
|
-
while mask_tokeep.any()==True:
|
|
375
|
-
mask_tokeep = mask_tokeep & np.array([t[0]!=T_minL[i][0] and t[1]!=T_minL[i][1] for t in T])
|
|
376
|
-
if mask_tokeep.any()==True:
|
|
377
|
-
indicesL_tokeep = np.where(mask_tokeep)[0]
|
|
378
|
-
minL += [np.nanmin(np.array(L)[indicesL_tokeep]) if not np.isnan(np.array(L)[indicesL_tokeep]).all() else np.nan]
|
|
379
|
-
argminL += [indicesL_tokeep[np.nanargmin(np.array(L)[indicesL_tokeep])] if not np.isnan(minL[-1]) else indicesL_tokeep[0]]
|
|
380
|
-
T_minL += (T[argminL[i+1]],)
|
|
381
|
-
i+=1
|
|
382
|
-
|
|
383
|
-
return np.array(minL), np.array(argminL), np.array(T_minL)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def pad_shape(arr, target_len, fill_value=np.nan):
|
|
387
|
-
'''
|
|
388
|
-
Pads an array to the target length with specified fill values
|
|
389
|
-
|
|
390
|
-
INPUTS:
|
|
391
|
-
- arr: Input array to be padded.
|
|
392
|
-
- target_len: The target length of the first dimension after padding.
|
|
393
|
-
- fill_value: The value to use for padding (default: np.nan).
|
|
394
|
-
|
|
395
|
-
OUTPUTS:
|
|
396
|
-
- Padded array with shape (target_len, ...) matching the input dimensions.
|
|
397
|
-
'''
|
|
398
|
-
|
|
399
|
-
if len(arr) < target_len:
|
|
400
|
-
pad_shape = (target_len - len(arr),) + arr.shape[1:]
|
|
401
|
-
padding = np.full(pad_shape, fill_value)
|
|
402
|
-
return np.concatenate((arr, padding))
|
|
403
|
-
|
|
404
|
-
return arr
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def sort_people_sports2d(keyptpre, keypt, scores=None):
|
|
408
|
-
'''
|
|
409
|
-
Associate persons across frames (Sports2D method)
|
|
410
|
-
Persons' indices are sometimes swapped when changing frame
|
|
411
|
-
A person is associated to another in the next frame when they are at a small distance
|
|
412
|
-
|
|
413
|
-
N.B.: Requires min_with_single_indices and euclidian_distance function (see common.py)
|
|
414
|
-
|
|
415
|
-
INPUTS:
|
|
416
|
-
- keyptpre: (K, L, M) array of 2D coordinates for K persons in the previous frame, L keypoints, M 2D coordinates
|
|
417
|
-
- keypt: idem keyptpre, for current frame
|
|
418
|
-
- score: (K, L) array of confidence scores for K persons, L keypoints (optional)
|
|
419
|
-
|
|
420
|
-
OUTPUTS:
|
|
421
|
-
- sorted_prev_keypoints: array with reordered persons with values of previous frame if current is empty
|
|
422
|
-
- sorted_keypoints: array with reordered persons --> if scores is not None
|
|
423
|
-
- sorted_scores: array with reordered scores --> if scores is not None
|
|
424
|
-
- associated_tuples: list of tuples with correspondences between persons across frames --> if scores is None (for Pose2Sim.triangulation())
|
|
425
|
-
'''
|
|
426
|
-
|
|
427
|
-
# Generate possible person correspondences across frames
|
|
428
|
-
max_len = max(len(keyptpre), len(keypt))
|
|
429
|
-
keyptpre = pad_shape(keyptpre, max_len, fill_value=np.nan)
|
|
430
|
-
keypt = pad_shape(keypt, max_len, fill_value=np.nan)
|
|
431
|
-
if scores is not None:
|
|
432
|
-
scores = pad_shape(scores, max_len, fill_value=np.nan)
|
|
433
|
-
|
|
434
|
-
# Compute distance between persons from one frame to another
|
|
435
|
-
personsIDs_comb = sorted(list(it.product(range(len(keyptpre)), range(len(keypt)))))
|
|
436
|
-
frame_by_frame_dist = [euclidean_distance(keyptpre[comb[0]],keypt[comb[1]]) for comb in personsIDs_comb]
|
|
437
|
-
frame_by_frame_dist = np.mean(frame_by_frame_dist, axis=1)
|
|
438
|
-
|
|
439
|
-
# Sort correspondences by distance
|
|
440
|
-
_, _, associated_tuples = min_with_single_indices(frame_by_frame_dist, personsIDs_comb)
|
|
441
|
-
|
|
442
|
-
# Associate points to same index across frames, nan if no correspondence
|
|
443
|
-
sorted_keypoints = []
|
|
444
|
-
for i in range(len(keyptpre)):
|
|
445
|
-
id_in_old = associated_tuples[:,1][associated_tuples[:,0] == i].tolist()
|
|
446
|
-
if len(id_in_old) > 0: sorted_keypoints += [keypt[id_in_old[0]]]
|
|
447
|
-
else: sorted_keypoints += [keypt[i]]
|
|
448
|
-
sorted_keypoints = np.array(sorted_keypoints)
|
|
449
|
-
|
|
450
|
-
if scores is not None:
|
|
451
|
-
sorted_scores = []
|
|
452
|
-
for i in range(len(keyptpre)):
|
|
453
|
-
id_in_old = associated_tuples[:,1][associated_tuples[:,0] == i].tolist()
|
|
454
|
-
if len(id_in_old) > 0: sorted_scores += [scores[id_in_old[0]]]
|
|
455
|
-
else: sorted_scores += [scores[i]]
|
|
456
|
-
sorted_scores = np.array(sorted_scores)
|
|
457
|
-
|
|
458
|
-
# Keep track of previous values even when missing for more than one frame
|
|
459
|
-
sorted_prev_keypoints = np.where(np.isnan(sorted_keypoints) & ~np.isnan(keyptpre), keyptpre, sorted_keypoints)
|
|
460
|
-
|
|
461
|
-
if scores is not None:
|
|
462
|
-
return sorted_prev_keypoints, sorted_keypoints, sorted_scores
|
|
463
|
-
else: # For Pose2Sim.triangulation()
|
|
464
|
-
return sorted_keypoints, associated_tuples
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
def sort_people_rtmlib(pose_tracker, keypoints, scores):
|
|
468
|
-
'''
|
|
469
|
-
Associate persons across frames (RTMLib method)
|
|
470
|
-
|
|
471
|
-
INPUTS:
|
|
472
|
-
- pose_tracker: PoseTracker. The initialized RTMLib pose tracker object
|
|
473
|
-
- keypoints: array of shape K, L, M with K the number of detected persons,
|
|
474
|
-
L the number of detected keypoints, M their 2D coordinates
|
|
475
|
-
- scores: array of shape K, L with K the number of detected persons,
|
|
476
|
-
L the confidence of detected keypoints
|
|
477
|
-
|
|
478
|
-
OUTPUT:
|
|
479
|
-
- sorted_keypoints: array with reordered persons
|
|
480
|
-
- sorted_scores: array with reordered scores
|
|
481
|
-
'''
|
|
482
|
-
|
|
483
|
-
try:
|
|
484
|
-
desired_size = max(pose_tracker.track_ids_last_frame)+1
|
|
485
|
-
sorted_keypoints = np.full((desired_size, keypoints.shape[1], 2), np.nan)
|
|
486
|
-
sorted_keypoints[pose_tracker.track_ids_last_frame] = keypoints[:len(pose_tracker.track_ids_last_frame), :, :]
|
|
487
|
-
sorted_scores = np.full((desired_size, scores.shape[1]), np.nan)
|
|
488
|
-
sorted_scores[pose_tracker.track_ids_last_frame] = scores[:len(pose_tracker.track_ids_last_frame), :]
|
|
489
|
-
except:
|
|
490
|
-
sorted_keypoints, sorted_scores = keypoints, scores
|
|
491
|
-
|
|
492
|
-
return sorted_keypoints, sorted_scores
|
|
493
|
-
|
|
494
|
-
|
|
495
341
|
def draw_dotted_line(img, start, direction, length, color=(0, 255, 0), gap=7, dot_length=3, thickness=thickness):
|
|
496
342
|
'''
|
|
497
343
|
Draw a dotted line with on a cv2 image
|
|
@@ -516,109 +362,6 @@ def draw_dotted_line(img, start, direction, length, color=(0, 255, 0), gap=7, do
|
|
|
516
362
|
cv2.line(img, tuple(line_start.astype(int)), tuple(line_end.astype(int)), color, thickness)
|
|
517
363
|
|
|
518
364
|
|
|
519
|
-
def draw_bounding_box(img, X, Y, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], fontSize=0.3, thickness=1):
|
|
520
|
-
'''
|
|
521
|
-
Draw bounding boxes and person ID around list of lists of X and Y coordinates.
|
|
522
|
-
Bounding boxes have a different color for each person.
|
|
523
|
-
|
|
524
|
-
INPUTS:
|
|
525
|
-
- img: opencv image
|
|
526
|
-
- X: list of list of x coordinates
|
|
527
|
-
- Y: list of list of y coordinates
|
|
528
|
-
- colors: list of colors to cycle through
|
|
529
|
-
|
|
530
|
-
OUTPUT:
|
|
531
|
-
- img: image with rectangles and person IDs
|
|
532
|
-
'''
|
|
533
|
-
|
|
534
|
-
color_cycle = it.cycle(colors)
|
|
535
|
-
|
|
536
|
-
for i,(x,y) in enumerate(zip(X,Y)):
|
|
537
|
-
color = next(color_cycle)
|
|
538
|
-
if not np.isnan(x).all():
|
|
539
|
-
x_min, y_min = np.nanmin(x).astype(int), np.nanmin(y).astype(int)
|
|
540
|
-
x_max, y_max = np.nanmax(x).astype(int), np.nanmax(y).astype(int)
|
|
541
|
-
if x_min < 0: x_min = 0
|
|
542
|
-
if x_max > img.shape[1]: x_max = img.shape[1]
|
|
543
|
-
if y_min < 0: y_min = 0
|
|
544
|
-
if y_max > img.shape[0]: y_max = img.shape[0]
|
|
545
|
-
|
|
546
|
-
# Draw rectangles
|
|
547
|
-
cv2.rectangle(img, (x_min-25, y_min-25), (x_max+25, y_max+25), color, thickness)
|
|
548
|
-
|
|
549
|
-
# Write person ID
|
|
550
|
-
cv2.putText(img, str(i), (x_min-30, y_min-30), cv2.FONT_HERSHEY_SIMPLEX, fontSize+1, color, 2, cv2.LINE_AA)
|
|
551
|
-
|
|
552
|
-
return img
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
def draw_skel(img, X, Y, model, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)]):
|
|
556
|
-
'''
|
|
557
|
-
Draws keypoints and skeleton for each person.
|
|
558
|
-
Skeletons have a different color for each person.
|
|
559
|
-
|
|
560
|
-
INPUTS:
|
|
561
|
-
- img: opencv image
|
|
562
|
-
- X: list of list of x coordinates
|
|
563
|
-
- Y: list of list of y coordinates
|
|
564
|
-
- model: skeleton model (from skeletons.py)
|
|
565
|
-
- colors: list of colors to cycle through
|
|
566
|
-
|
|
567
|
-
OUTPUT:
|
|
568
|
-
- img: image with keypoints and skeleton
|
|
569
|
-
'''
|
|
570
|
-
|
|
571
|
-
# Get (unique) pairs between which to draw a line
|
|
572
|
-
node_pairs = []
|
|
573
|
-
for data_i in PreOrderIter(model.root, filter_=lambda node: node.is_leaf):
|
|
574
|
-
node_branches = [node_i.id for node_i in data_i.path]
|
|
575
|
-
node_pairs += [[node_branches[i],node_branches[i+1]] for i in range(len(node_branches)-1)]
|
|
576
|
-
node_pairs = [list(x) for x in set(tuple(x) for x in node_pairs)]
|
|
577
|
-
|
|
578
|
-
# Draw lines
|
|
579
|
-
color_cycle = it.cycle(colors)
|
|
580
|
-
for (x,y) in zip(X,Y):
|
|
581
|
-
c = next(color_cycle)
|
|
582
|
-
if not np.isnan(x).all():
|
|
583
|
-
[cv2.line(img,
|
|
584
|
-
(int(x[n[0]]), int(y[n[0]])), (int(x[n[1]]), int(y[n[1]])), c, thickness)
|
|
585
|
-
for n in node_pairs
|
|
586
|
-
if not None in n and not (np.isnan(x[n[0]]) or np.isnan(y[n[0]]) or np.isnan(x[n[1]]) or np.isnan(y[n[1]]))] # IF NOT NONE
|
|
587
|
-
|
|
588
|
-
return img
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
def draw_keypts(img, X, Y, scores, cmap_str='RdYlGn'):
|
|
592
|
-
'''
|
|
593
|
-
Draws keypoints and skeleton for each person.
|
|
594
|
-
Keypoints' colors depend on their score.
|
|
595
|
-
|
|
596
|
-
INPUTS:
|
|
597
|
-
- img: opencv image
|
|
598
|
-
- X: list of list of x coordinates
|
|
599
|
-
- Y: list of list of y coordinates
|
|
600
|
-
- scores: list of list of scores
|
|
601
|
-
- cmap_str: colormap name
|
|
602
|
-
|
|
603
|
-
OUTPUT:
|
|
604
|
-
- img: image with keypoints and skeleton
|
|
605
|
-
'''
|
|
606
|
-
|
|
607
|
-
scores = np.where(np.isnan(scores), 0, scores)
|
|
608
|
-
# scores = (scores - 0.4) / (1-0.4) # to get a red color for scores lower than 0.4
|
|
609
|
-
scores = np.where(scores>0.99, 0.99, scores)
|
|
610
|
-
scores = np.where(scores<0, 0, scores)
|
|
611
|
-
|
|
612
|
-
cmap = plt.get_cmap(cmap_str)
|
|
613
|
-
for (x,y,s) in zip(X,Y,scores):
|
|
614
|
-
c_k = np.array(cmap(s))[:,:-1]*255
|
|
615
|
-
[cv2.circle(img, (int(x[i]), int(y[i])), thickness+4, c_k[i][::-1], -1)
|
|
616
|
-
for i in range(len(x))
|
|
617
|
-
if not (np.isnan(x[i]) or np.isnan(y[i]))]
|
|
618
|
-
|
|
619
|
-
return img
|
|
620
|
-
|
|
621
|
-
|
|
622
365
|
def draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, keypoints_ids, keypoints_names, angle_names, display_angle_values_on= ['body', 'list'], colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], fontSize=0.3, thickness=1):
|
|
623
366
|
'''
|
|
624
367
|
Draw angles on the image.
|
|
@@ -1044,9 +787,9 @@ def get_personID_with_highest_scores(all_frames_scores):
|
|
|
1044
787
|
return person_id
|
|
1045
788
|
|
|
1046
789
|
|
|
1047
|
-
def compute_floor_line(trc_data, keypoint_names = ['LBigToe', 'RBigToe'], toe_speed_below =
|
|
790
|
+
def compute_floor_line(trc_data, keypoint_names = ['LBigToe', 'RBigToe'], toe_speed_below = 7, tot_speed_above=2.0):
|
|
1048
791
|
'''
|
|
1049
|
-
Compute the floor line equation and
|
|
792
|
+
Compute the floor line equation, angle, and direction
|
|
1050
793
|
from the feet keypoints when they have zero speed.
|
|
1051
794
|
|
|
1052
795
|
N.B.: Y coordinates point downwards
|
|
@@ -1059,6 +802,7 @@ def compute_floor_line(trc_data, keypoint_names = ['LBigToe', 'RBigToe'], toe_sp
|
|
|
1059
802
|
OUTPUT:
|
|
1060
803
|
- angle: float. The angle of the floor line in radians
|
|
1061
804
|
- xy_origin: list. The origin of the floor line
|
|
805
|
+
- gait_direction: float. Left if < 0, 'right' otherwise
|
|
1062
806
|
'''
|
|
1063
807
|
|
|
1064
808
|
# Remove frames where the person is mostly not moving (outlier)
|
|
@@ -1067,36 +811,45 @@ def compute_floor_line(trc_data, keypoint_names = ['LBigToe', 'RBigToe'], toe_sp
|
|
|
1067
811
|
|
|
1068
812
|
# Retrieve zero-speed coordinates for the foot
|
|
1069
813
|
low_speeds_X, low_speeds_Y = [], []
|
|
814
|
+
gait_direction_val = []
|
|
1070
815
|
for kpt in keypoint_names:
|
|
1071
816
|
speeds = np.linalg.norm(trc_data[kpt].diff(), axis=1)
|
|
1072
|
-
|
|
817
|
+
|
|
1073
818
|
low_speed_frames = trc_data[speeds<toe_speed_below].index
|
|
1074
819
|
low_speeds_coords = trc_data[kpt].loc[low_speed_frames]
|
|
1075
820
|
low_speeds_coords = low_speeds_coords[low_speeds_coords!=0]
|
|
1076
821
|
|
|
1077
|
-
|
|
822
|
+
low_speeds_X_kpt = low_speeds_coords.iloc[:,0].tolist()
|
|
823
|
+
low_speeds_X += low_speeds_X_kpt
|
|
1078
824
|
low_speeds_Y += low_speeds_coords.iloc[:,1].tolist()
|
|
1079
825
|
|
|
826
|
+
# gait direction (between [-1,1])
|
|
827
|
+
X_trend_val = np.polyfit(range(len(low_speeds_X_kpt)), low_speeds_X_kpt, 1)[0]
|
|
828
|
+
gait_direction_kpt = X_trend_val * len(low_speeds_X_kpt) / (np.max(low_speeds_X_kpt) - np.min(low_speeds_X_kpt))
|
|
829
|
+
gait_direction_val.append(gait_direction_kpt)
|
|
830
|
+
|
|
1080
831
|
# Fit a line to the zero-speed coordinates
|
|
1081
832
|
floor_line = np.polyfit(low_speeds_X, low_speeds_Y, 1) # (slope, intercept)
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
# Compute the angle of the floor line in degrees
|
|
1085
|
-
angle = -np.arctan(floor_line[0])
|
|
833
|
+
angle = -np.arctan(floor_line[0]) # angle of the floor line in degrees
|
|
834
|
+
xy_origin = [0, floor_line[1]] # origin of the floor line
|
|
1086
835
|
|
|
1087
|
-
|
|
836
|
+
# Gait direction
|
|
837
|
+
gait_direction = np.mean(gait_direction_val)
|
|
838
|
+
|
|
839
|
+
return angle, xy_origin, gait_direction
|
|
1088
840
|
|
|
1089
841
|
|
|
1090
|
-
def convert_px_to_meters(Q_coords_kpt,
|
|
842
|
+
def convert_px_to_meters(Q_coords_kpt, px_to_m_person_height_m, height_px, cx, cy, floor_angle, visible_side='none'):
|
|
1091
843
|
'''
|
|
1092
844
|
Convert pixel coordinates to meters.
|
|
1093
845
|
|
|
1094
846
|
INPUTS:
|
|
1095
847
|
- Q_coords_kpt: pd.DataFrame. The xyz coordinates of a keypoint in pixels, with z filled with zeros
|
|
1096
|
-
-
|
|
848
|
+
- px_to_m_person_height_m: float. The height of the person in meters
|
|
1097
849
|
- height_px: float. The height of the person in pixels
|
|
1098
850
|
- cx, cy: float. The origin of the image in pixels
|
|
1099
851
|
- floor_angle: float. The angle of the floor in radians
|
|
852
|
+
- visible_side: str. The side of the person that is visible ('right', 'left', 'front', 'back', 'none')
|
|
1100
853
|
|
|
1101
854
|
OUTPUT:
|
|
1102
855
|
- Q_coords_kpt_m: pd.DataFrame. The XYZ coordinates of a keypoint in meters
|
|
@@ -1105,10 +858,17 @@ def convert_px_to_meters(Q_coords_kpt, person_height_m, height_px, cx, cy, floor
|
|
|
1105
858
|
u = Q_coords_kpt.iloc[:,0]
|
|
1106
859
|
v = Q_coords_kpt.iloc[:,1]
|
|
1107
860
|
|
|
1108
|
-
X =
|
|
1109
|
-
Y = -
|
|
861
|
+
X = px_to_m_person_height_m / height_px * ((u-cx) + (v-cy)*np.sin(floor_angle))
|
|
862
|
+
Y = - px_to_m_person_height_m / height_px * np.cos(floor_angle) * (v-cy - np.tan(floor_angle)*(u-cx))
|
|
863
|
+
|
|
864
|
+
if 'marker_Z_positions' in globals() and visible_side!='none':
|
|
865
|
+
marker_name = Q_coords_kpt.columns[0]
|
|
866
|
+
Z = X.copy()
|
|
867
|
+
Z[:] = marker_Z_positions[visible_side][marker_name]
|
|
868
|
+
else:
|
|
869
|
+
Z = np.zeros_like(X)
|
|
1110
870
|
|
|
1111
|
-
Q_coords_kpt_m = pd.DataFrame(np.array([X, Y,
|
|
871
|
+
Q_coords_kpt_m = pd.DataFrame(np.array([X, Y, Z]).T, columns=Q_coords_kpt.columns)
|
|
1112
872
|
|
|
1113
873
|
return Q_coords_kpt_m
|
|
1114
874
|
|
|
@@ -1159,11 +919,13 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1159
919
|
|
|
1160
920
|
# Base parameters
|
|
1161
921
|
video_dir = Path(config_dict.get('project').get('video_dir'))
|
|
1162
|
-
|
|
922
|
+
px_to_m_from_person_id = int(config_dict.get('project').get('px_to_m_from_person_id'))
|
|
923
|
+
px_to_m_person_height_m = config_dict.get('project').get('px_to_m_person_height')
|
|
924
|
+
visible_side = config_dict.get('project').get('visible_side')
|
|
1163
925
|
# Pose from file
|
|
1164
|
-
|
|
1165
|
-
if
|
|
1166
|
-
else:
|
|
926
|
+
load_trc_px = config_dict.get('project').get('load_trc_px')
|
|
927
|
+
if load_trc_px == '': load_trc_px = None
|
|
928
|
+
else: load_trc_px = Path(load_trc_px).resolve()
|
|
1167
929
|
compare = config_dict.get('project').get('compare')
|
|
1168
930
|
# Webcam settings
|
|
1169
931
|
webcam_id = config_dict.get('project').get('webcam_id')
|
|
@@ -1184,18 +946,28 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1184
946
|
mode = config_dict.get('pose').get('mode')
|
|
1185
947
|
det_frequency = config_dict.get('pose').get('det_frequency')
|
|
1186
948
|
tracking_mode = config_dict.get('pose').get('tracking_mode')
|
|
949
|
+
if tracking_mode == 'deepsort':
|
|
950
|
+
deepsort_params = config_dict.get('pose').get('deepsort_params')
|
|
951
|
+
try:
|
|
952
|
+
deepsort_params = ast.literal_eval(deepsort_params)
|
|
953
|
+
except: # if within single quotes instead of double quotes when run with sports2d --mode """{dictionary}"""
|
|
954
|
+
deepsort_params = deepsort_params.strip("'").replace('\n', '').replace(" ", "").replace(",", '", "').replace(":", '":"').replace("{", '{"').replace("}", '"}').replace('":"/',':/').replace('":"\\',':\\')
|
|
955
|
+
deepsort_params = re.sub(r'"\[([^"]+)",\s?"([^"]+)\]"', r'[\1,\2]', deepsort_params) # changes "[640", "640]" to [640,640]
|
|
956
|
+
deepsort_params = json.loads(deepsort_params)
|
|
957
|
+
deepsort_tracker = DeepSort(**deepsort_params)
|
|
958
|
+
deepsort_tracker.tracker.tracks.clear()
|
|
1187
959
|
backend = config_dict.get('pose').get('backend')
|
|
1188
960
|
device = config_dict.get('pose').get('device')
|
|
1189
961
|
|
|
1190
962
|
# Pixel to meters conversion
|
|
1191
963
|
to_meters = config_dict.get('px_to_meters_conversion').get('to_meters')
|
|
964
|
+
make_c3d = config_dict.get('px_to_meters_conversion').get('make_c3d')
|
|
1192
965
|
save_calib = config_dict.get('px_to_meters_conversion').get('save_calib')
|
|
1193
966
|
# Calibration from file
|
|
1194
967
|
calib_file = config_dict.get('px_to_meters_conversion').get('calib_file')
|
|
1195
968
|
if calib_file == '': calib_file = None
|
|
1196
969
|
else: calib_file = Path(calib_file).resolve()
|
|
1197
970
|
# Calibration from person height
|
|
1198
|
-
calib_on_person_id = int(config_dict.get('px_to_meters_conversion').get('calib_on_person_id'))
|
|
1199
971
|
floor_angle = config_dict.get('px_to_meters_conversion').get('floor_angle') # 'auto' or float
|
|
1200
972
|
floor_angle = np.radians(float(floor_angle)) if floor_angle != 'auto' else floor_angle
|
|
1201
973
|
xy_origin = config_dict.get('px_to_meters_conversion').get('xy_origin') # ['auto'] or [x, y]
|
|
@@ -1239,9 +1011,21 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1239
1011
|
gaussian_filter_kernel, loess_filter_kernel, median_filter_kernel]
|
|
1240
1012
|
|
|
1241
1013
|
# Inverse kinematics settings
|
|
1242
|
-
do_ik = config_dict.get('
|
|
1243
|
-
|
|
1244
|
-
|
|
1014
|
+
do_ik = config_dict.get('kinematics').get('do_ik')
|
|
1015
|
+
use_augmentation = config_dict.get('kinematics').get('use_augmentation')
|
|
1016
|
+
use_contacts_muscles = config_dict.get('kinematics').get('use_contacts_muscles')
|
|
1017
|
+
|
|
1018
|
+
osim_setup_path = config_dict.get('kinematics').get('osim_setup_path')
|
|
1019
|
+
right_left_symmetry = config_dict.get('kinematics').get('right_left_symmetry')
|
|
1020
|
+
default_height = config_dict.get('kinematics').get('default_height')
|
|
1021
|
+
remove_scaling_setup = config_dict.get('kinematics').get('remove_individual_scaling_setup')
|
|
1022
|
+
remove_ik_setup = config_dict.get('kinematics').get('remove_individual_ik_setup')
|
|
1023
|
+
fastest_frames_to_remove_percent = config_dict.get('kinematics').get('fastest_frames_to_remove_percent')
|
|
1024
|
+
large_hip_knee_angles = config_dict.get('kinematics').get('large_hip_knee_angles')
|
|
1025
|
+
trimmed_extrema_percent = config_dict.get('kinematics').get('trimmed_extrema_percent')
|
|
1026
|
+
close_to_zero_speed = config_dict.get('kinematics').get('close_to_zero_speed_m')
|
|
1027
|
+
|
|
1028
|
+
if do_ik: from Pose2Sim import Pose2Sim
|
|
1245
1029
|
|
|
1246
1030
|
# Create output directories
|
|
1247
1031
|
if video_file == "webcam":
|
|
@@ -1321,15 +1105,15 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1321
1105
|
logging.warning("\nInvalid mode. Must be 'lightweight', 'balanced', 'performance', or '''{dictionary}''' of parameters within triple quotes. Make sure input_sizes are within square brackets.")
|
|
1322
1106
|
logging.warning('Using the default "balanced" mode.')
|
|
1323
1107
|
mode = 'balanced'
|
|
1324
|
-
|
|
1325
1108
|
|
|
1109
|
+
|
|
1326
1110
|
# Skip pose estimation or set it up:
|
|
1327
|
-
if
|
|
1328
|
-
if not '_px' in str(
|
|
1329
|
-
logging.error(f'\n{
|
|
1330
|
-
logging.info(f'\nUsing a pose file instead of running pose estimation and tracking: {
|
|
1111
|
+
if load_trc_px:
|
|
1112
|
+
if not '_px' in str(load_trc_px):
|
|
1113
|
+
logging.error(f'\n{load_trc_px} file needs to be in px, not in meters.')
|
|
1114
|
+
logging.info(f'\nUsing a pose file instead of running pose estimation and tracking: {load_trc_px}.')
|
|
1331
1115
|
# Load pose file in px
|
|
1332
|
-
Q_coords, _, _, keypoints_names, _ = read_trc(
|
|
1116
|
+
Q_coords, _, _, keypoints_names, _ = read_trc(load_trc_px)
|
|
1333
1117
|
keypoints_ids = [i for i in range(len(keypoints_names))]
|
|
1334
1118
|
keypoints_all, scores_all = load_pose_file(Q_coords)
|
|
1335
1119
|
for pre, _, node in RenderTree(model_name):
|
|
@@ -1341,12 +1125,21 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1341
1125
|
keypoints_ids = [node.id for _, _, node in RenderTree(pose_model) if node.id!=None]
|
|
1342
1126
|
keypoints_names = [node.name for _, _, node in RenderTree(pose_model) if node.id!=None]
|
|
1343
1127
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1128
|
+
# Set up pose tracker
|
|
1129
|
+
try:
|
|
1130
|
+
pose_tracker = setup_pose_tracker(ModelClass, det_frequency, mode, False, backend, device)
|
|
1131
|
+
except:
|
|
1132
|
+
logging.error('Error: Pose estimation failed. Check in Config.toml that pose_model and mode are valid.')
|
|
1133
|
+
raise ValueError('Error: Pose estimation failed. Check in Config.toml that pose_model and mode are valid.')
|
|
1134
|
+
|
|
1135
|
+
if tracking_mode not in ['deepsort', 'sports2d']:
|
|
1136
|
+
logging.warning(f"Tracking mode {tracking_mode} not recognized. Using sports2d method.")
|
|
1137
|
+
tracking_mode = 'sports2d'
|
|
1346
1138
|
logging.info(f'\nPose tracking set up for "{pose_model_name}" model.')
|
|
1347
1139
|
logging.info(f'Mode: {mode}.\n')
|
|
1348
|
-
logging.info(f'Persons are detected every {det_frequency} frames and tracked inbetween. Multi-person is {"" if multiperson else "not "}selected.')
|
|
1349
|
-
logging.info(f
|
|
1140
|
+
logging.info(f'Persons are detected every {det_frequency} frames and tracked inbetween. Multi-person is {"" if multiperson else "not "}selected. Tracking is done with {tracking_mode}.')
|
|
1141
|
+
if tracking_mode == 'deepsort': logging.info(f'Deepsort parameters: {deepsort_params}.')
|
|
1142
|
+
logging.info(f"{keypoint_likelihood_threshold=}, {average_likelihood_threshold=}, {keypoint_number_threshold=}")
|
|
1350
1143
|
|
|
1351
1144
|
if flip_left_right:
|
|
1352
1145
|
try:
|
|
@@ -1375,7 +1168,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1375
1168
|
frame_count = 0
|
|
1376
1169
|
while cap.isOpened():
|
|
1377
1170
|
# Skip to the starting frame
|
|
1378
|
-
if frame_count < frame_range[0] and not
|
|
1171
|
+
if frame_count < frame_range[0] and not load_trc_px:
|
|
1379
1172
|
cap.read()
|
|
1380
1173
|
frame_count += 1
|
|
1381
1174
|
continue
|
|
@@ -1383,25 +1176,25 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1383
1176
|
for frame_nb in frame_iterator:
|
|
1384
1177
|
start_time = datetime.now()
|
|
1385
1178
|
success, frame = cap.read()
|
|
1179
|
+
frame_count += 1
|
|
1386
1180
|
|
|
1387
1181
|
# If frame not grabbed
|
|
1388
1182
|
if not success:
|
|
1389
|
-
logging.warning(f"Failed to grab frame {frame_count}.")
|
|
1183
|
+
logging.warning(f"Failed to grab frame {frame_count-1}.")
|
|
1390
1184
|
if save_pose:
|
|
1391
1185
|
all_frames_X.append([])
|
|
1392
1186
|
all_frames_Y.append([])
|
|
1393
1187
|
all_frames_scores.append([])
|
|
1394
1188
|
if save_angles:
|
|
1395
1189
|
all_frames_angles.append([])
|
|
1396
|
-
frame_count += 1
|
|
1397
1190
|
continue
|
|
1398
1191
|
else:
|
|
1399
1192
|
cv2.putText(frame, f"Press 'q' to quit", (cam_width-int(400*fontSize), cam_height-20), cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (255,255,255), thickness+1, cv2.LINE_AA)
|
|
1400
1193
|
cv2.putText(frame, f"Press 'q' to quit", (cam_width-int(400*fontSize), cam_height-20), cv2.FONT_HERSHEY_SIMPLEX, fontSize+0.2, (0,0,255), thickness, cv2.LINE_AA)
|
|
1401
|
-
|
|
1194
|
+
|
|
1402
1195
|
|
|
1403
1196
|
# Retrieve pose or Estimate pose and track people
|
|
1404
|
-
if
|
|
1197
|
+
if load_trc_px:
|
|
1405
1198
|
if frame_nb >= len(keypoints_all):
|
|
1406
1199
|
break
|
|
1407
1200
|
keypoints = keypoints_all[frame_nb]
|
|
@@ -1409,19 +1202,20 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1409
1202
|
else:
|
|
1410
1203
|
# Detect poses
|
|
1411
1204
|
keypoints, scores = pose_tracker(frame)
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1205
|
+
|
|
1206
|
+
# Track poses across frames
|
|
1207
|
+
if tracking_mode == 'deepsort':
|
|
1208
|
+
keypoints, scores = sort_people_deepsort(keypoints, scores, deepsort_tracker, frame, frame_count)
|
|
1209
|
+
if tracking_mode == 'sports2d':
|
|
1416
1210
|
if 'prev_keypoints' not in locals(): prev_keypoints = keypoints
|
|
1417
1211
|
prev_keypoints, keypoints, scores = sort_people_sports2d(prev_keypoints, keypoints, scores=scores)
|
|
1418
|
-
|
|
1212
|
+
|
|
1419
1213
|
|
|
1420
1214
|
# Process coordinates and compute angles
|
|
1421
1215
|
valid_X, valid_Y, valid_scores = [], [], []
|
|
1422
1216
|
valid_X_flipped, valid_angles = [], []
|
|
1423
1217
|
for person_idx in range(len(keypoints)):
|
|
1424
|
-
if
|
|
1218
|
+
if load_trc_px:
|
|
1425
1219
|
person_X = keypoints[person_idx][:,0]
|
|
1426
1220
|
person_Y = keypoints[person_idx][:,1]
|
|
1427
1221
|
person_scores = scores[person_idx]
|
|
@@ -1478,7 +1272,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1478
1272
|
img = frame.copy()
|
|
1479
1273
|
img = draw_bounding_box(img, valid_X, valid_Y, colors=colors, fontSize=fontSize, thickness=thickness)
|
|
1480
1274
|
img = draw_keypts(img, valid_X, valid_Y, valid_scores, cmap_str='RdYlGn')
|
|
1481
|
-
img = draw_skel(img, valid_X, valid_Y, pose_model
|
|
1275
|
+
img = draw_skel(img, valid_X, valid_Y, pose_model)
|
|
1482
1276
|
if calculate_angles:
|
|
1483
1277
|
img = draw_angles(img, valid_X, valid_Y, valid_angles, valid_X_flipped, new_keypoints_ids, new_keypoints_names, angle_names, display_angle_values_on=display_angle_values_on, colors=colors, fontSize=fontSize, thickness=thickness)
|
|
1484
1278
|
|
|
@@ -1530,8 +1324,8 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1530
1324
|
frame_range = [0,frame_count] if video_file == 'webcam' else frame_range
|
|
1531
1325
|
all_frames_time = pd.Series(np.linspace(frame_range[0]/fps, frame_range[1]/fps, frame_count+1), name='time')
|
|
1532
1326
|
if not multiperson:
|
|
1533
|
-
|
|
1534
|
-
detected_persons = [
|
|
1327
|
+
px_to_m_from_person_id = get_personID_with_highest_scores(all_frames_scores)
|
|
1328
|
+
detected_persons = [px_to_m_from_person_id]
|
|
1535
1329
|
else:
|
|
1536
1330
|
detected_persons = range(all_frames_X_homog.shape[1])
|
|
1537
1331
|
|
|
@@ -1604,7 +1398,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1604
1398
|
# Build TRC file
|
|
1605
1399
|
trc_data_i = trc_data_from_XYZtime(all_frames_X_person_filt, all_frames_Y_person_filt, all_frames_Z_homog, all_frames_time)
|
|
1606
1400
|
trc_data.append(trc_data_i)
|
|
1607
|
-
if not
|
|
1401
|
+
if not load_trc_px:
|
|
1608
1402
|
make_trc_with_trc_data(trc_data_i, str(pose_path_person), fps=fps)
|
|
1609
1403
|
logging.info(f'Pose in pixels saved to {pose_path_person.resolve()}.')
|
|
1610
1404
|
|
|
@@ -1619,9 +1413,9 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1619
1413
|
# Convert px to meters
|
|
1620
1414
|
if to_meters:
|
|
1621
1415
|
logging.info('\nConverting pose to meters:')
|
|
1622
|
-
if
|
|
1623
|
-
logging.warning(f'Person #{
|
|
1624
|
-
|
|
1416
|
+
if px_to_m_from_person_id>=len(trc_data):
|
|
1417
|
+
logging.warning(f'Person #{px_to_m_from_person_id} not detected in the video. Calibrating on person #0 instead.')
|
|
1418
|
+
px_to_m_from_person_id = 0
|
|
1625
1419
|
if calib_file:
|
|
1626
1420
|
logging.info(f'Using calibration file to convert coordinates in meters: {calib_file}.')
|
|
1627
1421
|
calib_params_dict = retrieve_calib_params(calib_file)
|
|
@@ -1630,43 +1424,68 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1630
1424
|
else:
|
|
1631
1425
|
# Compute calibration parameters
|
|
1632
1426
|
if not multiperson:
|
|
1633
|
-
selected_person_id =
|
|
1634
|
-
|
|
1635
|
-
height_px = compute_height(trc_data[
|
|
1427
|
+
selected_person_id = px_to_m_from_person_id
|
|
1428
|
+
px_to_m_from_person_id = 0
|
|
1429
|
+
height_px = compute_height(trc_data[px_to_m_from_person_id].iloc[:,1:], keypoints_names,
|
|
1636
1430
|
fastest_frames_to_remove_percent=fastest_frames_to_remove_percent, close_to_zero_speed=close_to_zero_speed_px, large_hip_knee_angles=large_hip_knee_angles, trimmed_extrema_percent=trimmed_extrema_percent)
|
|
1637
1431
|
|
|
1432
|
+
toe_speed_below = 1 # m/s (below which the foot is considered to be stationary)
|
|
1433
|
+
px_per_m = height_px/px_to_m_person_height_m
|
|
1434
|
+
toe_speed_below_px_frame = toe_speed_below * px_per_m / fps
|
|
1638
1435
|
if floor_angle == 'auto' or xy_origin == 'auto':
|
|
1639
1436
|
# estimated from the line formed by the toes when they are on the ground (where speed = 0)
|
|
1640
1437
|
try:
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
floor_angle_estim, xy_origin_estim = compute_floor_line(trc_data[calib_on_person_id], keypoint_names=['LBigToe', 'RBigToe'], toe_speed_below=toe_speed_below_px_frame)
|
|
1646
|
-
except: # no feet points
|
|
1647
|
-
floor_angle_estim, xy_origin_estim = compute_floor_line(trc_data[calib_on_person_id], keypoint_names=['LAnkle', 'RAnkle'], toe_speed_below=toe_speed_below_px_frame)
|
|
1438
|
+
if all(key in trc_data[px_to_m_from_person_id] for key in ['LBigToe', 'RBigToe']):
|
|
1439
|
+
floor_angle_estim, xy_origin_estim, _ = compute_floor_line(trc_data[px_to_m_from_person_id], keypoint_names=['LBigToe', 'RBigToe'], toe_speed_below=toe_speed_below_px_frame)
|
|
1440
|
+
else:
|
|
1441
|
+
floor_angle_estim, xy_origin_estim, _ = compute_floor_line(trc_data[px_to_m_from_person_id], keypoint_names=['LAnkle', 'RAnkle'], toe_speed_below=toe_speed_below_px_frame)
|
|
1648
1442
|
xy_origin_estim[0] = xy_origin_estim[0]-0.13
|
|
1649
1443
|
logging.warning(f'The RBigToe and LBigToe are missing from your model. Using ankles - 13 cm to compute the floor line.')
|
|
1650
1444
|
except:
|
|
1651
1445
|
floor_angle_estim = 0
|
|
1652
1446
|
xy_origin_estim = cam_width/2, cam_height/2
|
|
1653
|
-
logging.warning(f'Could not estimate the floor angle and xy_origin. Make sure that the full body is visible. Using floor angle = 0° and xy_origin = [{cam_width/2}, {cam_height/2}].')
|
|
1447
|
+
logging.warning(f'Could not estimate the floor angle and xy_origin for person {px_to_m_from_person_id}. Make sure that the full body is visible. Using floor angle = 0° and xy_origin = [{cam_width/2}, {cam_height/2}].')
|
|
1654
1448
|
if not floor_angle == 'auto':
|
|
1655
1449
|
floor_angle_estim = floor_angle
|
|
1656
1450
|
if xy_origin == 'auto':
|
|
1657
1451
|
cx, cy = xy_origin_estim
|
|
1658
1452
|
else:
|
|
1659
1453
|
cx, cy = xy_origin
|
|
1660
|
-
logging.info(f'Using height of person #{
|
|
1454
|
+
logging.info(f'Using height of person #{px_to_m_from_person_id} ({px_to_m_person_height_m}m) to convert coordinates in meters. '
|
|
1661
1455
|
f'Floor angle: {np.degrees(floor_angle_estim) if not floor_angle=="auto" else f"auto (estimation: {round(np.degrees(floor_angle_estim),2)}°)"}, '
|
|
1662
1456
|
f'xy_origin: {xy_origin if not xy_origin=="auto" else f"auto (estimation: {[round(c) for c in xy_origin_estim]})"}.')
|
|
1663
1457
|
|
|
1664
1458
|
# Coordinates in m
|
|
1665
1459
|
for i in range(len(trc_data)):
|
|
1460
|
+
# print(i)
|
|
1666
1461
|
if not np.array(trc_data[i].iloc[:,1:] ==0).all():
|
|
1667
|
-
|
|
1462
|
+
# Automatically determine visible side
|
|
1463
|
+
visible_side_i = visible_side[i] if len(visible_side)>i else 'auto' # set to 'auto' if list too short
|
|
1464
|
+
|
|
1465
|
+
# Set to 'front' if slope of X values between [-5,5]
|
|
1466
|
+
if visible_side_i == 'auto':
|
|
1467
|
+
try:
|
|
1468
|
+
if all(key in trc_data[i] for key in ['LBigToe', 'RBigToe']):
|
|
1469
|
+
_, _, gait_direction = compute_floor_line(trc_data[i], keypoint_names=['LBigToe', 'RBigToe'], toe_speed_below=toe_speed_below_px_frame)
|
|
1470
|
+
else:
|
|
1471
|
+
_, _, gait_direction = compute_floor_line(trc_data[i], keypoint_names=['LAnkle', 'RAnkle'], toe_speed_below=toe_speed_below_px_frame)
|
|
1472
|
+
logging.warning(f'The RBigToe and LBigToe are missing from your model. Gait direction will be determined from the ankle points.')
|
|
1473
|
+
visible_side_i = 'right' if gait_direction > 0.6 \
|
|
1474
|
+
else 'left' if gait_direction < -0.6 \
|
|
1475
|
+
else 'front'
|
|
1476
|
+
except:
|
|
1477
|
+
visible_side_i = 'none'
|
|
1478
|
+
logging.warning(f'Could not automatically find gait direction for person {i}. Please set visible_side to "front", "back", "left", or "right" for this person. Setting to "none".')
|
|
1479
|
+
|
|
1480
|
+
# skip if none
|
|
1481
|
+
if visible_side_i == 'none':
|
|
1482
|
+
logging.info(f'Skipping because "visible_side" is set to none for person {i}.')
|
|
1483
|
+
continue
|
|
1484
|
+
|
|
1485
|
+
# Convert to meters
|
|
1486
|
+
trc_data_m_i = pd.concat([convert_px_to_meters(trc_data[i][kpt_name], px_to_m_person_height_m, height_px, cx, cy, -floor_angle_estim, visible_side=visible_side_i) for kpt_name in keypoints_names], axis=1)
|
|
1668
1487
|
trc_data_m_i.insert(0, 't', all_frames_time)
|
|
1669
|
-
trc_data_unfiltered_m_i = pd.concat([convert_px_to_meters(trc_data_unfiltered[i][kpt_name],
|
|
1488
|
+
trc_data_unfiltered_m_i = pd.concat([convert_px_to_meters(trc_data_unfiltered[i][kpt_name], px_to_m_person_height_m, height_px, cx, cy, -floor_angle_estim) for kpt_name in keypoints_names], axis=1)
|
|
1670
1489
|
trc_data_unfiltered_m_i.insert(0, 't', all_frames_time)
|
|
1671
1490
|
|
|
1672
1491
|
if to_meters and show_plots:
|
|
@@ -1676,7 +1495,9 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1676
1495
|
idx_path = selected_person_id if not multiperson and not calib_file else i
|
|
1677
1496
|
pose_path_person_m_i = (pose_output_path.parent / (pose_output_path_m.stem + f'_person{idx_path:02d}.trc'))
|
|
1678
1497
|
make_trc_with_trc_data(trc_data_m_i, pose_path_person_m_i, fps=fps)
|
|
1679
|
-
|
|
1498
|
+
if make_c3d:
|
|
1499
|
+
c3d_path = convert_to_c3d(pose_path_person_m_i)
|
|
1500
|
+
logging.info(f'Person {idx_path}: Pose in meters saved to {pose_path_person_m_i.resolve()}. {"Also saved in c3d format." if make_c3d else ""}')
|
|
1680
1501
|
|
|
1681
1502
|
|
|
1682
1503
|
|
|
@@ -1694,7 +1515,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1694
1515
|
|
|
1695
1516
|
|
|
1696
1517
|
# z = 3.0 # distance between the camera and the person. Required in the calibration file but simplified in the equations
|
|
1697
|
-
# f = height_px /
|
|
1518
|
+
# f = height_px / px_to_m_person_height_m * z
|
|
1698
1519
|
|
|
1699
1520
|
|
|
1700
1521
|
# # Name
|
|
@@ -1728,7 +1549,7 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1728
1549
|
|
|
1729
1550
|
# Post-processing angles
|
|
1730
1551
|
if save_angles and calculate_angles:
|
|
1731
|
-
logging.info('\nPost-processing angles:')
|
|
1552
|
+
logging.info('\nPost-processing angles (without inverse kinematics):')
|
|
1732
1553
|
all_frames_angles = make_homogeneous(all_frames_angles)
|
|
1733
1554
|
|
|
1734
1555
|
# unwrap angles
|
|
@@ -1805,3 +1626,18 @@ def process_fun(config_dict, video_file, time_range, frame_rate, result_dir):
|
|
|
1805
1626
|
if show_plots:
|
|
1806
1627
|
all_frames_angles_person.insert(0, 't', all_frames_time)
|
|
1807
1628
|
angle_plots(all_frames_angles_person, angle_data, i) # i = current person
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
# # Run scaling and inverse kinematics
|
|
1632
|
+
# if save_angles and calculate_angles and do_ik:
|
|
1633
|
+
# logging.info('\nPost-processing angles (with inverse kinematics):')
|
|
1634
|
+
# if not to_meters:
|
|
1635
|
+
# logging.error('IK requires positions in meters rather than in pixels. Set to_meters to True.')
|
|
1636
|
+
# raise ValueError('IK requires positions in meters rather than in pixels. Set to_meters to True.')
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
# marker_Z_positions
|
|
1640
|
+
# if 'none': No IK possible.
|
|
1641
|
+
# visible_side=='auto'
|
|
1642
|
+
|
|
1643
|
+
# convert_to_c3d(trc_path)
|