├── .gitignore
├── README.md
├── ampere_law.py
├── code
├── 00_image.py
├── 01_video.py
├── 02_gesture.py
├── 03_game_rps.py
├── 04_hand_rom.py
├── 05_wrist_rom.py
├── 06_face_mask.py
├── 07_triangulate.py
├── 08_skeleton_3D.py
├── 09_objectron.py
├── utils_3d_reconstruct.py
├── utils_display.py
├── utils_joint_angle.py
└── utils_mediapipe.py
├── data
├── canonical_face_model.obj
├── canonical_face_model.ply
├── gesture_train.csv
├── gesture_train_fy.csv
├── handrom_train.csv
└── sample
│ ├── hand.png
│ ├── lower_limb1.png
│ ├── lower_limb2.png
│ ├── lower_limb3.png
│ ├── lower_limb4.png
│ ├── lower_limb5.png
│ ├── lower_limb6.png
│ ├── mona.png
│ ├── object.png
│ ├── upper_limb1.png
│ ├── upper_limb2.png
│ ├── upper_limb3.png
│ ├── upper_limb4.png
│ ├── upper_limb5.png
│ ├── upper_limb6.png
│ ├── wrist_extension.png
│ ├── wrist_flexion.png
│ ├── wrist_radial.png
│ └── wrist_ulnar.png
├── doc
├── 00_image.gif
├── 02_gesture.gif
├── 03_game_rps.gif
├── 04_hand_rom.gif
├── 05_wrist_rom.gif
├── 06_face_mask.gif
├── 07_triangulate.gif
├── 08_skeleton_3D.gif
├── 09_objectron.gif
└── rris_database.gif
├── dual.py
├── environment.yaml
├── fan.py
├── fy_filter.py
├── gather_dataset.py
├── java
├── README.md
├── elbow
│ ├── codepen.css
│ ├── codepen.html
│ ├── codepen.js
│ └── head.js
├── hand-fruit-ninja
│ ├── codepen.css
│ ├── codepen.html
│ └── codepen.js
├── hand
│ ├── codepen.css
│ ├── codepen.html
│ ├── codepen.js
│ └── head.js
└── pose-fruit-ninja
│ ├── codepen.css
│ ├── codepen.html
│ └── codepen.js
├── result.gif
├── result_fan.gif
├── result_fy_filter.png
└── single.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | __pycache__
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rock Paper Scissors Machine
2 |
3 | Simplify [RRIS](https://github.com/ntu-rris)'s [code](https://github.com/ntu-rris/google-mediapipe) A.I. rock paper scissors machine using MediaPipe and KNN.
4 |
5 | 
6 |
7 | ### Run
8 |
9 | ```
10 | python dual.py
11 | ```
12 |
13 | ## Fxck You Filter
14 |
15 | Mosaic the middle finger in video.
16 |
17 | 
18 |
19 | ```
20 | python fy_filter.py
21 | ```
22 |
23 | ## AI Fan
24 |
25 | Controlling the fan speed by AI gesture recognition
26 | BLDC Motor using [DynamiKontrol](https://dk.m47rix.com) Speed
27 |
28 | 
29 |
30 | ```
31 | python fan.py
32 | ```
33 |
34 | ---
35 |
36 | # [Google MediaPipe](https://github.com/google/mediapipe) for Pose Estimation
37 |
38 | [](https://www.nature.com/articles/s41597-020-00627-7?sf237508323=1)
39 |
40 | [MediaPipe](https://opensource.google/projects/mediapipe) is a cross-platform framework for building multimodal applied machine learning pipelines including inference models and media processing functions.
41 |
42 | The main purpose of this repo is to:
43 | * Customize output of MediaPipe solutions
44 | * Customize visualization of 2D & 3D outputs
45 | * Demo some simple applications on Python (refer to [Demo Overview](#demo-overview))
46 | * Demo some simple applications on JavaScript refer to [java](java/) folder
47 |
48 |
49 | ## Pose Estimation with Input Color Image
50 | Attractiveness of Google MediaPipe as compared to other SOTA (e.g. [FrankMocap](https://github.com/facebookresearch/frankmocap), [CMU OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose), [DeepPoseKit](https://github.com/jgraving/DeepPoseKit), [DeepLabCut](https://github.com/DeepLabCut/DeepLabCut), [MinimalHand](https://github.com/CalciferZh/minimal-hand)):
51 | * **Fast**: Runs at almost realtime rate on CPU and even mobile devices
52 | * **Open-source**: Codes are freely available at [github](https://github.com/google/mediapipe) (except that [details of network models are not released](https://github.com/google/mediapipe/issues/155))
53 | * **User-friendly**: For python API just `pip install mediapipe` will work (but C++ API is much more troublesome to [build and use](https://google.github.io/mediapipe/getting_started/cpp))
54 | * **Cross-platform**: Works across Android, iOS, desktop, [JavaScript](https://google.github.io/mediapipe/getting_started/javascript.html) and [web](https://developers.googleblog.com/2020/01/mediapipe-on-web.html) (Note: this repo only focuses on using Python API for desktop usage)
55 | * **ML Solutions**: Apart from face, hand, body and object pose estimations, MediaPipe offers an array of machine learning applications refer to their [github](https://github.com/google/mediapipe) for more details
56 |
57 | ## Features
58 | Latest [MediaPipe Python API version 0.8.4.2](https://pypi.org/project/mediapipe/) (Released 11 May 2021) features:
59 |
60 | **Face Mesh** (468 **3D** face landmarks)
61 |
62 | * [**Blog**](https://ai.googleblog.com/2019/03/real-time-ar-self-expression-with.html) | [**Code**](https://google.github.io/mediapipe/solutions/face_mesh) | [**Paper**](https://arxiv.org/abs/1907.06724) | [**Video**](https://www.youtube.com/watch?v=JNSXC3E0-s4) | [**Model Card**](https://drive.google.com/file/d/1QvwWNfFoweGVjsXF3DXzcrCnz-mx-Lha/view)
63 |
64 | **Hands** (21 **3D** landmarks and able to support multiple hands)
65 |
66 | * [**Blog**](https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html) | [**Code**](https://google.github.io/mediapipe/solutions/hands) | [**Paper**](https://arxiv.org/abs/2006.10214) | [**Video**](https://www.youtube.com/watch?v=I-UOrvxxXEk) | [**Model Card**](https://drive.google.com/file/d/1yiPfkhb4hSbXJZaSq9vDmhz24XVZmxpL/view)
67 |
68 | **Body Pose** (33 **3D** landmarks for whole body, **3 levels of model complexity (NEW)**)
69 |
70 | * [**Blog**](https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html) | [**Code**](https://google.github.io/mediapipe/solutions/pose) | [**Paper**](https://arxiv.org/abs/2006.10204) | [**Video**](https://www.youtube.com/watch?v=YPpUOTRn5tA&feature=emb_logo) | [**Model Card**](https://drive.google.com/file/d/1zhYyUXhQrb_Gp0lKUFv1ADT3OCxGEQHS/view)
71 |
72 | **Holistic (Face + Hands + Body)** (A total of 543/535 landmarks: 468 face + 2 x 21 hands + 33/25 pose)
73 |
74 | * [**Blog**](https://ai.googleblog.com/2020/12/mediapipe-holistic-simultaneous-face.html) | [**Code**](https://google.github.io/mediapipe/solutions/holistic#smooth_landmarks)
75 |
76 | **Objectron (3D object detection and tracking)** (4 possible objects: Shoe / Chair / Camera / Cup)
77 |
78 | * [**Blog**](https://ai.googleblog.com/2020/03/real-time-3d-object-detection-on-mobile.html) | [**Code**](https://google.github.io/mediapipe/solutions/objectron) | [**Paper**](https://arxiv.org/abs/2003.03522) | [**Paper**](https://drive.google.com/file/d/1O_zHmlgXIzAdKljp20U_JUkEHOGG52R8/view) | [**Model Card**](https://drive.google.com/file/d/1CMhN7Npdq0Dt2j0_z69mai2-m7oUTRKF/view)
79 |
80 | Note: The above videos are presented at [CVPR 2020 Fourth Workshop on Computer Vision for AR/VR](https://xr.cornell.edu/workshop/2020/papers), interested reader can refer to the link for other related works.
81 |
82 |
83 | ## Installation
84 | The simplest way to run our implementation is to use [anaconda](https://www.anaconda.com/).
85 |
86 | You can create an anaconda environment called `mp` with
87 | ```
88 | conda env create -f environment.yaml
89 | conda activate mp
90 | ```
91 |
92 | ## Demo Overview
93 |
94 |
95 | | Single Image | Video Input | Gesture Recognition | Rock Paper Scissor Game |
96 | | ------------ | ----------- | ------------------- | ----------------------- |
97 | |  | |  | 
98 |
99 | | Measure Hand ROM | Measure Wrist and Forearm ROM | Face Mask | Triangulate Points for 3D Pose |
100 | | ---------------- | ----------------------------- | --------- | ------------------------------ |
101 | |  | |  |  |
102 |
103 | | 3D Skeleton | 3D Object Detection |
104 | | ----------- | ------------------- |
105 | |  | |
106 |
107 |
108 |
109 | ## Usage
110 | ### [0. Single Image](code/00_image.py)
111 |
112 | 4 different modes are available and sample images are located in [data/sample/](data/sample/) folder
113 | ```
114 | python 00_image.py --mode face
115 | python 00_image.py --mode hand
116 | python 00_image.py --mode body
117 | python 00_image.py --mode holistic
118 | ```
119 | Note: The sample images for subject with body marker are adapted from [An Asian-centric human movement database capturing activities of daily living](https://www.nature.com/articles/s41597-020-00627-7?sf237508323=1) and the image of Mona Lisa is adapted from [Wiki](https://upload.wikimedia.org/wikipedia/commons/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg)
120 |
121 |
122 | ### [1. Video Input](code/01_video.py)
123 |
124 | 4 different modes are available and video capture can be done online through [webcam](https://github.com/ntu-rris/google-mediapipe/blob/5e155130ba3477b84e873c57251c59f4206da3ee/code/01_video.py#L45) or offline from your own [.mp4 file](https://github.com/ntu-rris/google-mediapipe/blob/5e155130ba3477b84e873c57251c59f4206da3ee/code/01_video.py#L46)
125 | ```
126 | python 01_video.py --mode face
127 | python 01_video.py --mode hand
128 | python 01_video.py --mode body
129 | python 01_video.py --mode holistic
130 | ```
131 |
132 | Note: It takes around 10 to 30 FPS on CPU, depending on the mode selected. The [video](https://www.youtube.com/watch?v=rqFp-ZH5tpo) demonstrating supported mini-squats is adapted from [National Stroke Association](https://www.youtube.com/watch?v=WLjOoQUgWs4)
133 |
134 |
135 | ### [2. Gesture Recognition](code/02_gesture.py)
136 |
137 | 2 modes are available: Use evaluation mode to perform recognition of 11 gestures and use train mode to log your own training data
138 | ```
139 | python 02_gesture.py --mode eval
140 | python 02_gesture.py --mode train
141 | ```
142 |
143 | Note: A simple but effective K-nearest neighbor (KNN) algorithm is used as the classifier. For the hand gesture recognition demo, since 3D hand joints are available, we can compute flexion joint angles (feature vector) and use it to classify different hand poses. On the other hand, if 3D body joints are not yet reliable, the normalized pairwise distances between predifined lists of joints as described in [MediaPipe Pose Classification](https://google.github.io/mediapipe/solutions/pose_classification.html) could also be used as the feature vector for KNN.
144 |
145 |
146 | ### [3. Rock Paper Scissor Game](code/03_game_rps.py)
147 |
148 | Simple game of rock paper scissor requires a pair of hands facing the camera
149 | ```
150 | python 03_game_rps.py
151 | ```
152 | For another game of flappy bird refer to this [github](https://github.com/limgm/flappy-mediapipe)
153 |
154 |
155 | ### [4. Measure Hand Range of Motion](code/04_hand_rom.py)
156 |
157 | 2 modes are available: Use evaluation mode to perform hand ROM recognition and use train mode to log your own training data
158 | ```
159 | python 04_hand_rom.py --mode eval
160 | python 04_hand_rom.py --mode train
161 | ```
162 |
163 | ### [5. Measure Wrist and Forearm Range of Motion](code/05_wrist_rom.py)
164 |
165 | 3 modes are available and user has to input the side of the hand to be measured
166 | * 0: Wrist flexion/extension
167 | * 1: Wrist radial/ulnar deviation
168 | * 2: Forearm pronation/supination
169 |
170 | ```
171 | python 05_wrist_rom.py --mode 0 --side right
172 | python 05_wrist_rom.py --mode 1 --side right
173 | python 05_wrist_rom.py --mode 2 --side right
174 | python 05_wrist_rom.py --mode 0 --side left
175 | python 05_wrist_rom.py --mode 1 --side left
176 | python 05_wrist_rom.py --mode 2 --side left
177 | ```
178 |
179 | Note: For measuring forearm pronation/supination, the camera has to be placed at the same level as the hand such that palmar side of the hand is directly facing camera. For measuring wrist ROM, the camera has to be placed such that upper body of the subject is visible, refer to examples of wrist_XXX.png images in [data/sample/](data/sample/) folder. The wrist images are adapted from [Goni Wrist Flexion, Extension, Radial & Ulnar Deviation](https://www.youtube.com/watch?v=nIPaGkDh3dI)
180 |
181 | ### [6. Face Mask](code/06_face_mask.py)
182 |
183 | Overlay a 3D face mask on the detected face in image plane
184 | ```
185 | python 06_face_mask.py
186 | ```
187 | Note: The face image is adapted from [MediaPipe 3D Face Transform](https://developers.googleblog.com/2020/09/mediapipe-3d-face-transform.html)
188 |
189 |
190 | ### [7. Triangulate Points](code/07_triangulate.py)
191 |
192 | Estimating 3D body pose from a single 2D image is an ill-posed problem and extremely challenging.
193 | One way to reconstruct 3D body pose is to make use of multiview setup and perform triangulation.
194 | For offline testing, use [CMU Panoptic Dataset](http://domedb.perception.cs.cmu.edu/171204_pose1.html), follow the instructions on [PanopticStudio Toolbox](https://github.com/CMU-Perceptual-Computing-Lab/panoptic-toolbox) to download a sample dataset 171204_pose1_sample into data/ folder
195 | ```
196 | python 07_triangulate.py --mode body --use_panoptic_dataset
197 | ```
198 |
199 |
205 |
206 | ### [8. 3D Skeleton](code/08_skeleton_3D.py)
207 |
208 | 3D pose estimation is available in full-body mode and this demo displays the estimated 3D skeleton of the hand and/or body. 3 different modes are available and video capture can be done online through [webcam](https://github.com/ntu-rris/google-mediapipe/blob/990a4a73c969450f6414940af5a832c7820f5c01/code/08_skeleton_3D.py#L24) or offline from your own [.mp4 file](https://github.com/ntu-rris/google-mediapipe/blob/990a4a73c969450f6414940af5a832c7820f5c01/code/08_skeleton_3D.py#L25)
209 |
210 | ```
211 | python 08_skeleton_3D.py --mode hand
212 | python 08_skeleton_3D.py --mode body
213 | python 08_skeleton_3D.py --mode holistic
214 | ```
215 |
216 |
217 | ### [9. 3D Object Detection](code/09_objectron.py)
218 |
219 | 4 different modes are available and a sample image is located in [data/sample/](data/sample/) folder. Currently supports 4 classes: Shoe / Chair / Cup / Camera.
220 |
221 | ```
222 | python 09_objectron.py --mode shoe
223 | python 09_objectron.py --mode chair
224 | python 09_objectron.py --mode cup
225 | python 09_objectron.py --mode camera
226 | ```
227 |
228 | ## Limitations:
229 | Estimating 3D pose from a single 2D image is an ill-posed problem and extremely challenging, thus the measurement of ROM may not be accurate!
230 | Please refer to the respective model cards for more details on other types of limitations such as lighting, motion blur, occlusions, image resolution, etc.
231 |
--------------------------------------------------------------------------------
/ampere_law.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import mediapipe as mp
3 | import numpy as np
4 |
5 | THRESHOLD = 0.2 # 20%, 값이 클수록 손이 카메라와 가까워야 인식함
6 |
7 | gesture = {
8 | 0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
9 | 6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok',
10 | }
11 |
12 | # MediaPipe hands model
13 | mp_hands = mp.solutions.hands
14 | mp_drawing = mp.solutions.drawing_utils
15 | hands = mp_hands.Hands(
16 | max_num_hands=1,
17 | min_detection_confidence=0.5,
18 | min_tracking_confidence=0.5)
19 |
20 | # Gesture recognition model
21 | file = np.genfromtxt('data/gesture_train.csv', delimiter=',')
22 | angle = file[:,:-1].astype(np.float32)
23 | label = file[:, -1].astype(np.float32)
24 | knn = cv2.ml.KNearest_create()
25 | knn.train(angle, cv2.ml.ROW_SAMPLE, label)
26 |
27 | cap = cv2.VideoCapture(1)
28 |
29 | while cap.isOpened():
30 | ret, img = cap.read()
31 | if not ret:
32 | continue
33 |
34 | img = cv2.flip(img, 1) # 이미지 좌우 반전
35 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
36 |
37 | result = hands.process(img)
38 |
39 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
40 |
41 | if result.multi_hand_landmarks is not None:
42 | for res in result.multi_hand_landmarks:
43 | joint = np.zeros((21, 3))
44 | for j, lm in enumerate(res.landmark):
45 | joint[j] = [lm.x, lm.y, lm.z]
46 |
47 | # Compute angles between joints
48 | v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:] # Parent joint
49 | v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:] # Child joint
50 | v = v2 - v1 # [20,3]
51 | # Normalize v
52 | v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
53 |
54 | # Get angle using arcos of dot product
55 | angle = np.arccos(np.einsum('nt,nt->n',
56 | v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
57 | v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
58 |
59 | angle = np.degrees(angle) # Convert radian to degree
60 |
61 | # Inference gesture
62 | data = np.array([angle], dtype=np.float32)
63 | ret, results, neighbours, dist = knn.findNearest(data, 3)
64 | idx = int(results[0][0])
65 |
66 | if idx == 0 or idx == 6: # fist or six
67 | thumb_end = res.landmark[4]
68 | fist_end = res.landmark[17]
69 |
70 | text = None
71 |
72 | if thumb_end.x - fist_end.x > THRESHOLD:
73 | text = 'RIGHT'
74 | elif fist_end.x - thumb_end.x > THRESHOLD:
75 | text = 'LEFT'
76 | elif thumb_end.y - fist_end.y > THRESHOLD:
77 | text = 'DOWN'
78 | elif fist_end.y - thumb_end.y > THRESHOLD:
79 | text = 'UP'
80 |
81 | if text is not None:
82 | cv2.putText(img, text=text, org=(int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0] + 20)), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)
83 | elif idx in [1, 2, 3, 4, 5, 9]: # 숫자 1,2,3,4,5 인식
84 | if idx == 9:
85 | idx = 2
86 |
87 | cv2.putText(img, text=str(idx), org=(int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0] + 20)), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)
88 |
89 | mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
90 |
91 | cv2.imshow('Ampere\'s Law', img)
92 | if cv2.waitKey(1) == ord('q'):
93 | break
94 |
--------------------------------------------------------------------------------
/code/00_image.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo with a single color image
3 | ### Input : Color image of face / hand / body
4 | ### Output: 2D/2.5D/3D display of face, hand, body keypoint/joint
5 | ### Usage : python 00_image.py -m face
6 | ### python 00_image.py -m hand
7 | ### python 00_image.py -m body
8 | ### python 00_image.py -m holistic
9 | ###############################################################################
10 |
11 | import cv2
12 | import sys
13 | import argparse
14 |
15 | from utils_display import DisplayFace, DisplayHand, DisplayBody, DisplayHolistic
16 | from utils_mediapipe import MediaPipeFace, MediaPipeHand, MediaPipeBody, MediaPipeHolistic
17 |
18 |
19 | # User select mode
20 | parser = argparse.ArgumentParser()
21 | parser.add_argument('-m', '--mode', default='hand',
22 | help='Select mode: face / hand / body / holistic')
23 | args = parser.parse_args()
24 | mode = args.mode
25 |
26 | # Load mediapipe and display class
27 | if mode=='face':
28 | pipe = MediaPipeFace(static_image_mode=True, max_num_faces=1)
29 | disp = DisplayFace(draw3d=True)
30 | file = '../data/sample/mona.png'
31 | elif mode=='hand':
32 | pipe = MediaPipeHand(static_image_mode=True, max_num_hands=1)
33 | disp = DisplayHand(draw3d=True, max_num_hands=1)
34 | file = '../data/sample/hand.png'
35 | elif mode=='body':
36 | pipe = MediaPipeBody(static_image_mode=True, model_complexity=1)
37 | disp = DisplayBody(draw3d=True)
38 | file = '../data/sample/upper_limb4.png'
39 | elif mode=='holistic':
40 | pipe = MediaPipeHolistic(static_image_mode=True, model_complexity=1)
41 | disp = DisplayHolistic(draw3d=True)
42 | file = '../data/sample/lower_limb4.png'
43 | else:
44 | print('Undefined mode only the following modes are available: \nface / hand / body / holistic')
45 | sys.exit()
46 |
47 | # Read in image (Note: You can change the file path to your own test image)
48 | img = cv2.imread(file)
49 |
50 | # # Preprocess image if necessary
51 | # img = cv2.resize(img, None, fx=0.5, fy=0.5)
52 | img = cv2.flip(img, 1)
53 | # # Select ROI
54 | # r = cv2.selectROI(img)
55 | # # Crop image
56 | # img = img[r[1]:r[1]+r[3], r[0]:r[0]+r[2]]
57 |
58 | # Feedforward to extract pose param
59 | param = pipe.forward(img)
60 |
61 | # Display 2D keypoint
62 | cv2.imshow('img 2D', disp.draw2d(img.copy(), param))
63 | # Display 2.5D keypoint
64 | cv2.imshow('img 2.5D', disp.draw2d_(img.copy(), param))
65 | cv2.waitKey(0) # Press escape to dispay 3D view
66 |
67 | # Display 3D joint
68 | disp.draw3d(param)
69 | disp.vis.update_geometry(None)
70 | disp.vis.poll_events()
71 | disp.vis.update_renderer()
72 | disp.vis.run()
73 |
74 | pipe.pipe.close()
75 |
--------------------------------------------------------------------------------
/code/01_video.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo with video input
3 | ### Input : Live video of face / hand / body
4 | ### Output: 2D/2.5D/3D display of face, hand, body keypoint/joint
5 | ### Usage : python 01_video.py -m face
6 | ### python 01_video.py -m hand
7 | ### python 01_video.py -m body
8 | ### python 01_video.py -m holistic
9 | ###############################################################################
10 |
11 | import cv2
12 | import sys
13 | import time
14 | import argparse
15 |
16 | from utils_display import DisplayFace, DisplayHand, DisplayBody, DisplayHolistic
17 | from utils_mediapipe import MediaPipeFace, MediaPipeHand, MediaPipeBody, MediaPipeHolistic
18 |
19 |
20 | # User select mode
21 | parser = argparse.ArgumentParser()
22 | parser.add_argument('-m', '--mode', default='hand',
23 | help='Select mode: face / hand / body / holistic')
24 | args = parser.parse_args()
25 | mode = args.mode
26 |
27 | # Load mediapipe and display class
28 | if mode=='face':
29 | pipe = MediaPipeFace(static_image_mode=False, max_num_faces=1)
30 | disp = DisplayFace(draw3d=True)
31 | elif mode=='hand':
32 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=2)
33 | disp = DisplayHand(draw3d=True, max_num_hands=2)
34 | elif mode=='body':
35 | pipe = MediaPipeBody(static_image_mode=False, model_complexity=1)
36 | disp = DisplayBody(draw3d=True)
37 | elif mode=='holistic':
38 | pipe = MediaPipeHolistic(static_image_mode=False, model_complexity=1)
39 | disp = DisplayHolistic(draw3d=True)
40 | else:
41 | print('Undefined mode only the following modes are available: \nface / hand / body / holistic')
42 | sys.exit()
43 |
44 | # Start video capture
45 | cap = cv2.VideoCapture(0) # By default webcam is index 0
46 | # cap = cv2.VideoCapture('../data/video.mp4') # Read from .mp4 file
47 | # cap.set(cv2.CAP_PROP_POS_FRAMES, 1) # Set starting position of frame
48 |
49 | # # Log video
50 | # fps = 30
51 | # ret, img = cap.read()
52 | # width, height = int(cap.get(3)), int(cap.get(4))
53 | # fourcc = cv2.VideoWriter_fourcc(*'mp4v') # Be sure to use lower case
54 | # video = cv2.VideoWriter('../data/video_.mp4', fourcc, fps, (width, height))
55 |
56 | prev_time = time.time()
57 | while cap.isOpened():
58 | ret, img = cap.read()
59 | if not ret:
60 | break
61 |
62 | # Preprocess image if necessary
63 | img = cv2.flip(img, 1) # Flip image for 3rd person view
64 | # img = cv2.resize(img, None, fx=0.5, fy=0.5)
65 |
66 | # To improve performance, optionally mark image as not writeable to pass by reference
67 | img.flags.writeable = False
68 |
69 | # Feedforward to extract keypoint
70 | param = pipe.forward(img)
71 |
72 | # Compute FPS
73 | curr_time = time.time()
74 | fps = 1/(curr_time-prev_time)
75 | if mode=='body':
76 | param['fps'] = fps
77 | elif mode=='face' or mode=='hand':
78 | param[0]['fps'] = fps
79 | elif mode=='holistic':
80 | for p in param:
81 | p['fps'] = fps
82 | prev_time = curr_time
83 |
84 | img.flags.writeable = True
85 |
86 | # Display 2D keypoint
87 | cv2.imshow('img 2D', disp.draw2d(img.copy(), param))
88 | # Display 2.5D keypoint
89 | cv2.imshow('img 2.5D', disp.draw2d_(img.copy(), param))
90 | # Display 3D
91 | disp.draw3d(param)
92 | disp.vis.update_geometry(None)
93 | disp.vis.poll_events()
94 | disp.vis.update_renderer()
95 |
96 | # # Write to video
97 | # img = disp.draw2d(img.copy(), param)
98 | # cv2.imshow('img 2D', img)
99 | # video.write(img)
100 |
101 | key = cv2.waitKey(1)
102 | if key==27:
103 | break
104 |
105 | pipe.pipe.close()
106 | # video.release()
107 | cap.release()
108 |
--------------------------------------------------------------------------------
/code/02_gesture.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo on gesture recognition
3 | ### Input : Live video of hand
4 | ### Output: 2D display of hand keypoint
5 | ### with gesture classification
6 | ### Usage : python 02_gesture.py -m train (to log data)
7 | ### : python 02_gesture.py -m eval (to perform gesture recognition)
8 | ###############################################################################
9 |
10 | import cv2
11 | import argparse
12 |
13 | from utils_display import DisplayHand
14 | from utils_mediapipe import MediaPipeHand
15 | from utils_joint_angle import GestureRecognition
16 |
17 |
18 | parser = argparse.ArgumentParser()
19 | parser.add_argument('-m', '--mode', default='eval', help='train / eval')
20 | args = parser.parse_args()
21 | mode = args.mode
22 |
23 | # Load mediapipe hand class
24 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=1)
25 |
26 | # Load display class
27 | disp = DisplayHand(max_num_hands=1)
28 |
29 | # Start video capture
30 | cap = cv2.VideoCapture(0) # By default webcam is index 0
31 |
32 | # Load gesture recognition class
33 | gest = GestureRecognition(mode)
34 |
35 | counter = 0
36 | while cap.isOpened():
37 | ret, img = cap.read()
38 | if not ret:
39 | break
40 |
41 | # Flip image for 3rd person view
42 | img = cv2.flip(img, 1)
43 |
44 | # To improve performance, optionally mark image as not writeable to pass by reference
45 | img.flags.writeable = False
46 |
47 | # Feedforward to extract keypoint
48 | param = pipe.forward(img)
49 | if (param[0]['class'] is not None) and (mode=='eval'):
50 | param[0]['gesture'] = gest.eval(param[0]['angle'])
51 |
52 | img.flags.writeable = True
53 |
54 | # Display keypoint
55 | cv2.imshow('img 2D', disp.draw2d(img.copy(), param))
56 |
57 | key = cv2.waitKey(1)
58 | if key==27:
59 | break
60 | if key==32 and (param[0]['class'] is not None) and (mode=='train'):
61 | # Press spacebar to log training data
62 | # Note: Need to manually change class label
63 | # 'fist','one','two','three','four','five','six',
64 | # 'rock','spiderman',
65 | # 'yeah','ok',
66 | gest.train(param[0]['angle'], gest.gesture['fist'])
67 | print('Saved', counter) # Log around 10 for each class
68 | counter += 1
69 | if key==32 and (param[0]['class'] is not None) and (mode=='eval'):
70 | cv2.waitKey(0) # Pause display until user press any key
71 |
72 | pipe.pipe.close()
73 | cap.release()
74 |
--------------------------------------------------------------------------------
/code/03_game_rps.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo on playing rock paper scissor
3 | ### Input : Live video of 2 hands playing rock paper scissor
4 | ### Output: 2D display of hand keypoint
5 | ### with gesture classification (rock=fist, paper=five, scissor=three/yeah)
6 | ###############################################################################
7 |
8 | import cv2
9 |
10 | from utils_display import DisplayHand
11 | from utils_mediapipe import MediaPipeHand
12 | from utils_joint_angle import GestureRecognition
13 |
14 |
15 | # Load mediapipe hand class
16 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=2)
17 |
18 | # Load display class
19 | disp = DisplayHand(max_num_hands=2)
20 |
21 | # Start video capture
22 | cap = cv2.VideoCapture(0) # By default webcam is index 0
23 |
24 | # Load gesture recognition class
25 | gest = GestureRecognition(mode='eval')
26 |
27 | counter = 0
28 | while cap.isOpened():
29 | ret, img = cap.read()
30 | if not ret:
31 | break
32 |
33 | # Flip image for 3rd person view
34 | img = cv2.flip(img, 1)
35 |
36 | # To improve performance, optionally mark image as not writeable to pass by reference
37 | img.flags.writeable = False
38 |
39 | # Feedforward to extract keypoint
40 | param = pipe.forward(img)
41 | # Evaluate gesture for all hands
42 | for p in param:
43 | if p['class'] is not None:
44 | p['gesture'] = gest.eval(p['angle'])
45 |
46 | img.flags.writeable = True
47 |
48 | # Display keypoint and result of rock paper scissor game
49 | cv2.imshow('Game: Rock Paper Scissor', disp.draw_game_rps(img.copy(), param))
50 |
51 | key = cv2.waitKey(1)
52 | if key==27:
53 | break
54 |
55 | pipe.pipe.close()
56 | cap.release()
57 |
--------------------------------------------------------------------------------
/code/04_hand_rom.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo on measuring hand rom
3 | ### Input : Live video of hand
4 | ### Output: 2D display of hand keypoint
5 | ### with hand rom classification and corresponding joint angle
6 | ### Usage : python 04_hand_rom.py -m train (to log data)
7 | ### : python 04_hand_rom.py -m eval (to perform hand rom recognition)
8 | ###############################################################################
9 |
10 | import cv2
11 | import argparse
12 |
13 | from utils_display import DisplayHand
14 | from utils_mediapipe import MediaPipeHand
15 | from utils_joint_angle import HandRomRecognition
16 |
17 |
18 | parser = argparse.ArgumentParser()
19 | parser.add_argument('-m', '--mode', default='eval', help='train / eval')
20 | args = parser.parse_args()
21 | mode = args.mode
22 |
23 | # Load mediapipe hand class
24 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=1)
25 |
26 | # Load display class
27 | disp = DisplayHand(max_num_hands=1)
28 |
29 | # Start video capture
30 | cap = cv2.VideoCapture(0) # By default webcam is index 0
31 |
32 | # Load hand rom recognition class
33 | gest = HandRomRecognition(mode)
34 |
35 | counter = 0
36 | while cap.isOpened():
37 | ret, img = cap.read()
38 | if not ret:
39 | break
40 |
41 | # Flip image for 3rd person view
42 | img = cv2.flip(img, 1)
43 |
44 | # To improve performance, optionally mark image as not writeable to pass by reference
45 | img.flags.writeable = False
46 |
47 | # Feedforward to extract keypoint
48 | param = pipe.forward(img)
49 | if (param[0]['class'] is not None) and (mode=='eval'):
50 | param[0]['gesture'] = gest.eval(param[0]['angle'])
51 |
52 | img.flags.writeable = True
53 |
54 | # Display keypoint
55 | cv2.imshow('img 2D', disp.draw2d(img.copy(), param))
56 |
57 | key = cv2.waitKey(1)
58 | if key==27:
59 | break
60 | if key==32 and (param[0]['class'] is not None) and (mode=='train'):
61 | # Press spacebar to log training data
62 | # Note: Need to manually change class label
63 | # 'Finger MCP Flexion' :0,
64 | # 'Finger PIP DIP Flexion':1,
65 | # 'Thumb MCP Flexion' :2,
66 | # 'Thumb IP Flexion' :3,
67 | # 'Thumb Radial Abduction':4,
68 | # 'Thumb Palmar Abduction':5, # From this class onwards hard to differentiate
69 | # 'Thumb Opposition' :6,
70 | # 'Forearm Pronation' :7, # Not done yet
71 | # 'Forearm Supination' :8, # Not done yet
72 | # 'Wrist Flexion' :9, # Not done yet
73 | # 'Wrist Extension' :10,# Not done yet
74 | # 'Wrist Radial Deviation':11,# Not done yet
75 | # 'Wrist Ulnar Deviation' :12,# Not done yet
76 | gest.train(param[0]['angle'], gest.gesture['Finger MCP Flexion'])
77 | print('Saved', counter) # Log around 10 for each class
78 | counter += 1
79 | if key==32 and (param[0]['class'] is not None) and (mode=='eval'):
80 | cv2.waitKey(0) # Pause display until user press any key
81 |
82 | pipe.pipe.close()
83 | cap.release()
84 |
--------------------------------------------------------------------------------
/code/05_wrist_rom.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo on measuring wrist and forearm rom
3 | ### Input : Live video of hand and upper body
4 | ### Output: 2D display of hand and upper body keypoint
5 | ### with corresponding joint angle
6 | ### Usage : python 05_wrist_rom.py -m 0 -s right
7 | ### python 05_wrist_rom.py -m 1 -s right
8 | ### python 05_wrist_rom.py -m 2 -s right
9 | ### python 05_wrist_rom.py -m 0 -s left
10 | ### python 05_wrist_rom.py -m 1 -s left
11 | ### python 05_wrist_rom.py -m 2 -s left
12 | ###############################################################################
13 |
14 | import cv2
15 | import sys
16 | import argparse
17 |
18 | from utils_display import DisplayHolistic, DisplayHand
19 | from utils_mediapipe import MediaPipeHolistic, MediaPipeHand
20 | from utils_joint_angle import WristArmRom
21 |
22 |
23 | # User select mode of measurement
24 | # 0: Wrist flexion/extension
25 | # 1: Wrist radial/ulnar deviation
26 | # 2: Forearm pronation/supination
27 | parser = argparse.ArgumentParser()
28 | parser.add_argument('-s', '--side', default='right')
29 | parser.add_argument('-m', '--mode', default=2,
30 | help='Select mode: \
31 | 0:Wrist flex/ext, \
32 | 1:Wrist radial/ulnar dev, \
33 | 2:Forearm pronation/supination')
34 | args = parser.parse_args()
35 | mode = int(args.mode)
36 | side = args.side
37 |
38 | # Load mediapipe and display class
39 | if mode==0 or mode==1: # Note; To determine wrist rom need upper body elbow joint
40 | pipe = MediaPipeHolistic(static_image_mode=False, model_complexity=1)
41 | disp = DisplayHolistic(draw3d=True)
42 | elif mode==2:
43 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=1)
44 | disp = DisplayHand(draw3d=True, max_num_hands=1)
45 | else:
46 | print('Undefined mode only 3 modes are available: \n \
47 | 0:Wrist flex/ext, \n \
48 | 1:Wrist radial/ulnar dev, \n \
49 | 2:Forearm pronation/supination')
50 | sys.exit()
51 |
52 | # Start video capture
53 | cap = cv2.VideoCapture(0) # By default webcam is index 0
54 |
55 | # Load wrist and forearm rom class
56 | rom = WristArmRom(mode, side)
57 |
58 | while cap.isOpened():
59 | ret, img = cap.read()
60 | # img = cv2.imread('../data/sample/wrist_extension.png') # python 05_wrist_rom.py -m 0 -s right
61 | # img = cv2.imread('../data/sample/wrist_flexion.png') # python 05_wrist_rom.py -m 0 -s right
62 | # img = cv2.imread('../data/sample/wrist_radial.png') # python 05_wrist_rom.py -m 1 -s right
63 | # img = cv2.imread('../data/sample/wrist_ulnar.png') # python 05_wrist_rom.py -m 1 -s right
64 | if not ret:
65 | break
66 |
67 | # Flip image for 3rd person view
68 | img = cv2.flip(img, 1)
69 |
70 | # To improve performance, optionally mark image as not writeable to pass by reference
71 | img.flags.writeable = False
72 |
73 | # Feedforward to extract keypoint
74 | param = pipe.forward(img)
75 |
76 | img.flags.writeable = True
77 |
78 | # Compute wrist/forearm range of motion
79 | param = rom.eval(param)
80 |
81 | # Display keypoint
82 | cv2.imshow('img 2D', disp.draw2d(img.copy(), param))
83 | # Display 3D
84 | disp.draw3d(param)
85 | disp.vis.update_geometry(None)
86 | disp.vis.poll_events()
87 | disp.vis.update_renderer()
88 |
89 | key = cv2.waitKey(1)
90 | if key==27:
91 | break
92 | if key==32:
93 | cv2.imwrite('img.png', disp.draw2d(img.copy(), param))
94 | cv2.waitKey(0) # Pause display until user press any key
95 |
96 | pipe.pipe.close()
97 | cap.release()
98 |
--------------------------------------------------------------------------------
/code/06_face_mask.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo with video input of face
3 | ### Input : Live video of face
4 | ### Output: Overlay 3D face mesh on image
5 | ###############################################################################
6 |
7 | import cv2
8 | import time
9 |
10 | from utils_display import DisplayFaceMask
11 | from utils_mediapipe import MediaPipeFace
12 |
13 |
14 | # Load mediapipe class
15 | pipe = MediaPipeFace(static_image_mode=False, max_num_faces=1)
16 |
17 | # Start video capture
18 | cap = cv2.VideoCapture(0) # By default webcam is index 0
19 |
20 | # Load display class
21 | ret, img = cap.read(0) # Read in a sample image
22 | disp = DisplayFaceMask(img=img, draw3d=True, max_num_faces=1)
23 |
24 | prev_time = time.time()
25 | while cap.isOpened():
26 | ret, img = cap.read()
27 | if not ret:
28 | break
29 |
30 | # Preprocess image if necessary
31 | # img = cv2.flip(img, 1) # Flip image for 3rd person view
32 | # img = cv2.resize(img, None, fx=0.5, fy=0.5)
33 |
34 | # To improve performance, optionally mark image as not writeable to pass by reference
35 | img.flags.writeable = False
36 |
37 | # Feedforward to extract keypoint
38 | param = pipe.forward(img)
39 |
40 | # Compute FPS
41 | curr_time = time.time()
42 | fps = 1/(curr_time-prev_time)
43 | param[0]['fps'] = fps
44 | prev_time = curr_time
45 |
46 | img.flags.writeable = True
47 | cv2.imshow('img', img)
48 |
49 | # Display 3D
50 | img = disp.draw3d(param, img)
51 |
52 | key = cv2.waitKey(1)
53 | if key==27:
54 | break
55 |
56 | pipe.pipe.close()
57 | cap.release()
58 |
--------------------------------------------------------------------------------
/code/07_triangulate.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo with at least 2 cameras for triangulation
3 | ### Input : Live videos of face / hand / body
4 | ### : Calibrated camera intrinsics and extrinsics
5 | ### Output: 2D/3D (triangulated) display of hand, body keypoint/joint
6 | ### Usage : python 07_triangulate.py -m body --use_panoptic_dataset
7 | ###############################################################################
8 |
9 | import cv2
10 | import sys
11 | import time
12 | import argparse
13 | import numpy as np
14 | import open3d as o3d
15 |
16 | from utils_display import DisplayHand, DisplayBody, DisplayHolistic
17 | from utils_mediapipe import MediaPipeHand, MediaPipeBody, MediaPipeHolistic
18 | from utils_3d_reconstruct import Triangulation
19 |
20 |
21 | # User select mode
22 | parser = argparse.ArgumentParser()
23 | parser.add_argument('--use_panoptic_dataset', action='store_true')
24 | parser.add_argument('-m', '--mode', default='body',
25 | help='Select mode: hand / body / holistic')
26 | args = parser.parse_args()
27 | mode = args.mode
28 |
29 | # Define list of camera index
30 | # cam_idx = [4,10] # Note: Hardcoded for my setup
31 | # Read from .mp4 file
32 | if args.use_panoptic_dataset:
33 | # Test with 2 views
34 | cam_idx = ['../data/171204_pose1_sample/hdVideos/hd_00_00.mp4',
35 | '../data/171204_pose1_sample/hdVideos/hd_00_11.mp4']
36 |
37 | # # Test with n views
38 | # num_views = 5 # Note: Maximum 31 hd cameras but processing time will be extremely slow
39 | # cam_idx = []
40 | # for i in range(num_views):
41 | # cam_idx.append(
42 | # '../data/171204_pose1_sample/hdVideos/hd_00_'+str(i).zfill(2)+'.mp4')
43 |
44 | # Start video capture
45 | cap = [cv2.VideoCapture(cam_idx[i]) for i in range(len(cam_idx))]
46 |
47 | # Define list of other variable
48 | img = [None for i in range(len(cam_idx))] # Store image
49 | pipe = [None for i in range(len(cam_idx))] # MediaPipe class
50 | disp = [None for i in range(len(cam_idx))] # Display class
51 | param = [None for i in range(len(cam_idx))] # Store pose parameter
52 | prev_time = [time.time() for i in range(len(cam_idx))]
53 |
54 | # Open3D visualization
55 | vis = o3d.visualization.Visualizer()
56 | vis.create_window(width=640, height=480)
57 | vis.get_render_option().point_size = 5.0
58 |
59 | # Load triangulation class
60 | tri = Triangulation(cam_idx=cam_idx, vis=vis,
61 | use_panoptic_dataset=args.use_panoptic_dataset)
62 |
63 | # Load mediapipe and display class
64 | if mode=='hand':
65 | for i in range(len(cam_idx)):
66 | pipe[i] = MediaPipeHand(static_image_mode=False, max_num_hands=1)
67 | disp[i] = DisplayHand(draw3d=True, max_num_hands=1, vis=vis)
68 | elif mode=='body':
69 | for i in range(len(cam_idx)):
70 | pipe[i] = MediaPipeBody(static_image_mode=False, model_complexity=1)
71 | disp[i] = DisplayBody(draw3d=True, vis=vis)
72 | elif mode=='holistic':
73 | for i in range(len(cam_idx)):
74 | pipe[i] = MediaPipeHolistic(static_image_mode=False, model_complexity=1)
75 | disp[i] = DisplayHolistic(draw3d=True, vis=vis)
76 | else:
77 | print('Undefined mode only the following modes are available: \n hand / body / holistic')
78 | sys.exit()
79 |
80 | while True:
81 | # Loop through video capture
82 | for i, c in enumerate(cap):
83 | if not c.isOpened():
84 | break
85 | ret, img[i] = c.read()
86 | if not ret:
87 | break
88 |
89 | # Preprocess image if necessary
90 | # img[i] = cv2.flip(img[i], 1) # Flip image for 3rd person view
91 |
92 | # To improve performance, optionally mark image as not writeable to pass by reference
93 | img[i].flags.writeable = False
94 |
95 | # Feedforward to extract keypoint
96 | param[i] = pipe[i].forward(img[i])
97 |
98 | img[i].flags.writeable = True
99 |
100 | # Compute FPS
101 | curr_time = time.time()
102 | fps = 1/(curr_time-prev_time[i])
103 | if mode=='body':
104 | param[i]['fps'] = fps
105 | elif mode=='hand':
106 | param[i][0]['fps'] = fps
107 | elif mode=='holistic':
108 | for p in param[i]:
109 | p['fps'] = fps
110 | prev_time[i] = curr_time
111 |
112 | # Perform triangulation
113 | if args.use_panoptic_dataset:
114 | if len(cam_idx)==2:
115 | param = tri.triangulate_2views(param, mode)
116 | else:
117 | param = tri.triangulate_nviews(param, mode)
118 |
119 | for i in range(len(cam_idx)):
120 | # Display 2D keypoint
121 | img[i] = disp[i].draw2d(img[i].copy(), param[i])
122 | img[i] = cv2.resize(img[i], None, fx=0.5, fy=0.5)
123 | cv2.imshow('img'+str(i), img[i])
124 | # Display 3D
125 | disp[i].draw3d(param[i])
126 |
127 | vis.update_geometry(None)
128 | vis.poll_events()
129 | vis.update_renderer()
130 |
131 | key = cv2.waitKey(1)
132 | if key==27:
133 | break
134 |
135 | # vis.run() # Keep 3D display for visualization
136 |
137 | for p, c in zip(pipe, cap):
138 | p.pipe.close()
139 | c.release()
140 |
--------------------------------------------------------------------------------
/code/08_skeleton_3D.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo on displaying 3D hand/body skeleton
3 | ### Input : Live video of hand/body
4 | ### Output: 3D display of hand/body skeleton
5 | ### Usage : python 08_skeleton_3D.py -m hand
6 | ### : python 08_skeleton_3D.py -m body
7 | ### : python 08_skeleton_3D.py -m holistic
8 | ###############################################################################
9 |
10 | import cv2
11 | import time
12 | import argparse
13 |
14 | from utils_display import DisplayHand, DisplayBody, DisplayHolistic
15 | from utils_mediapipe import MediaPipeHand, MediaPipeBody, MediaPipeHolistic
16 |
17 |
18 | parser = argparse.ArgumentParser()
19 | parser.add_argument('-m', '--mode', default='hand', help=' Select mode: hand / body / holistic')
20 | args = parser.parse_args()
21 | mode = args.mode
22 |
23 | # Start video capture
24 | cap = cv2.VideoCapture(0) # By default webcam is index 0
25 | # cap = cv2.VideoCapture('../data/video.mp4') # Read from .mp4 file
26 |
27 | # Read in sample image to estimate camera intrinsic
28 | ret, img = cap.read(0)
29 | # img = cv2.resize(img, None, fx=0.5, fy=0.5)
30 | img_width = img.shape[1]
31 | img_height = img.shape[0]
32 | intrin = {
33 | 'fx': img_width*0.9, # Approx 0.7w < f < w https://www.learnopencv.com/approximate-focal-length-for-webcams-and-cell-phone-cameras/
34 | 'fy': img_width*0.9,
35 | 'cx': img_width*0.5, # Approx center of image
36 | 'cy': img_height*0.5,
37 | 'width': img_width,
38 | 'height': img_height,
39 | }
40 |
41 | # Load mediapipe and display class
42 | if mode=='hand':
43 | pipe = MediaPipeHand(static_image_mode=False, max_num_hands=2, intrin=intrin)
44 | disp = DisplayHand(draw3d=True, draw_camera=True, max_num_hands=2, intrin=intrin)
45 | elif mode=='body':
46 | # Note: As of version 0.8.3 3D joint estimation is only available in full body mode
47 | pipe = MediaPipeBody(static_image_mode=False, model_complexity=1, intrin=intrin)
48 | disp = DisplayBody(draw3d=True, draw_camera=True, intrin=intrin)
49 | elif mode=='holistic':
50 | # Note: As of version 0.8.3 3D joint estimation is only available in full body mode
51 | pipe = MediaPipeHolistic(static_image_mode=False, model_complexity=1, intrin=intrin)
52 | disp = DisplayHolistic(draw3d=True, draw_camera=True, intrin=intrin)
53 |
54 | prev_time = time.time()
55 | while cap.isOpened():
56 | ret, img = cap.read()
57 | if not ret:
58 | cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # Loop back
59 | ret, img = cap.read()
60 | # break
61 |
62 | # Flip image for 3rd person view
63 | img = cv2.flip(img, 1)
64 | # img = cv2.resize(img, None, fx=0.5, fy=0.5)
65 |
66 | # To improve performance, optionally mark image as not writeable to pass by reference
67 | img.flags.writeable = False
68 |
69 | # Feedforward to extract keypoint
70 | param = pipe.forward(img)
71 |
72 | # Compute FPS
73 | curr_time = time.time()
74 | fps = 1/(curr_time-prev_time)
75 | if mode=='body':
76 | param['fps'] = fps
77 | elif mode=='face' or mode=='hand':
78 | param[0]['fps'] = fps
79 | elif mode=='holistic':
80 | for p in param:
81 | p['fps'] = fps
82 | prev_time = curr_time
83 |
84 | img.flags.writeable = True
85 |
86 | # Display keypoint
87 | cv2.imshow('img 2D', disp.draw2d(img, param))
88 | # Display 3D
89 | disp.draw3d(param,)
90 | disp.draw3d_(param, img)
91 | disp.vis.update_geometry(None)
92 | disp.vis.poll_events()
93 | disp.vis.update_renderer()
94 |
95 | key = cv2.waitKey(1)
96 | if key==27:
97 | break
98 | if key==ord('r'): # Press 'r' to reset camera view
99 | disp.camera.reset_view()
100 |
101 | pipe.pipe.close()
102 | cap.release()
103 |
--------------------------------------------------------------------------------
/code/09_objectron.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Simple demo with a single color image
3 | ### Input : Color image of object
4 | ### Output: 2D/3D display of object's bounding box
5 | ### Usage : python 09_objectron.py -m shoe
6 | ### python 09_objectron.py -m chair
7 | ### python 09_objectron.py -m cup
8 | ### python 09_objectron.py -m camera
9 | ###############################################################################
10 |
11 | import cv2
12 | import argparse
13 |
14 | from utils_display import DisplayObjectron
15 | from utils_mediapipe import MediaPipeObjectron
16 |
17 |
18 | # User select mode
19 | parser = argparse.ArgumentParser()
20 | parser.add_argument('-m', '--mode', default='shoe',
21 | help='Select mode: shoe / chair / cup / camera')
22 | args = parser.parse_args()
23 |
24 | # Read in image (Note: You can change the file path to your own test image)
25 | file = '../data/sample/object.png'
26 | img = cv2.imread(file)
27 |
28 | # Estimate camera intrinsic
29 | img_width = img.shape[1]
30 | img_height = img.shape[0]
31 | intrin = {
32 | 'fx': img_width*0.9, # Approx 0.7w < f < w https://www.learnopencv.com/approximate-focal-length-for-webcams-and-cell-phone-cameras/
33 | 'fy': img_width*0.9,
34 | 'cx': img_width*0.5, # Approx center of image
35 | 'cy': img_height*0.5,
36 | 'width': img_width,
37 | 'height': img_height,
38 | }
39 |
40 | # Load mediapipe
41 | pipe = MediaPipeObjectron(static_image_mode=True, max_num_objects=5,
42 | model_name=args.mode, intrin=intrin)
43 |
44 | # Load display class
45 | disp = DisplayObjectron(draw3d=True, draw_camera=True, intrin=intrin, max_num_objects=5)
46 |
47 | # Feedforward to extract pose param
48 | param = pipe.forward(img)
49 |
50 | # # Display 2D keypoint
51 | cv2.imshow('img', disp.draw2d(img, param))
52 | cv2.waitKey(0) # Press escape to dispay 3D view
53 |
54 | # Display 3D joint
55 | disp.draw3d(param, img)
56 | disp.vis.update_geometry(None)
57 | disp.vis.poll_events()
58 | disp.vis.update_renderer()
59 | disp.vis.run()
60 |
61 | pipe.pipe.close()
--------------------------------------------------------------------------------
/code/utils_3d_reconstruct.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Useful function for 3D reconstruction from multiple images
3 | ###
4 | ### First requirement is to obtain camera intrinsics and extrinsics parameters
5 | ### Refer to Calibration class
6 | ###
7 | ### Next use triangulation/direct linear transformation (DLT)
8 | ### To reconstruct 3D points from 2D image points
9 | ###############################################################################
10 |
11 | import cv2
12 | import glob
13 | import json
14 | import yaml
15 | import numpy as np
16 | import open3d as o3d
17 |
18 |
19 | class Calibration:
20 | def __init__(self, chessboard_size=(6,5), chessboard_sq_size=0.015):
21 | super(Calibration, self).__init__()
22 |
23 | self.chessboard_size = chessboard_size
24 | self.chessboard_sq_size = chessboard_sq_size
25 |
26 | # Prepare 3D object points in real world space
27 | self.obj_pts = np.zeros((chessboard_size[0]*chessboard_size[1],3), np.float32)
28 | # [[0,0,0], [1,0,0], [2,0,0] ....,[9,6,0]]
29 | self.obj_pts[:,:2] = np.mgrid[0:chessboard_size[0],0:chessboard_size[1]].T.reshape(-1,2)
30 | self.obj_pts *= chessboard_sq_size # Convert length of each black square to units in meter
31 |
32 | # Termination criteria for cornerSubPix
33 | self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-3)
34 |
35 | # Flag for findChessboardCorners
36 | self.flags_findChessboard = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_NORMALIZE_IMAGE + cv2.CALIB_CB_FAST_CHECK
37 |
38 | # Termination criteria for stereoCalibrate
39 | self.criteria_stereoCalibrate = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 100, 1e-5)
40 |
41 | # Flag for stereoCalibrate
42 | self.flags_stereoCalibrate = cv2.CALIB_FIX_INTRINSIC + cv2.CALIB_RATIONAL_MODEL + cv2.CALIB_CB_FAST_CHECK
43 |
44 |
45 | def get_intrin(self, folder):
46 | # Use chessboard pattern to calib intrinsic of color camera
47 |
48 | # Read in filename of .png from folder
49 | file = glob.glob(folder+'*.png')
50 | file.sort()
51 |
52 | # List to store object point and image point from all images
53 | objpt = [] # 3d point in real world space
54 | imgpt = [] # 2d point in image plane
55 | file_ = [] # Filename of images with success findChessboardCorners
56 |
57 | for f in file:
58 | # Read in image
59 | img = cv2.imread(f)
60 |
61 | # Convert to grayscale
62 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
63 |
64 | # Find chessboard corners
65 | ret, corners = cv2.findChessboardCorners(gray, self.chessboard_size, self.flags_findChessboard)
66 |
67 | # If found, add object points, image points (after refining them)
68 | if ret==True:
69 | corners2 = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), self.criteria) # (chessboard_size[0]*chessboard_size[1], 1, 2}
70 | imgpt.append(corners2)
71 | objpt.append(self.obj_pts)
72 | file_.append(f)
73 | print('Found ChessboardCorners', f)
74 | else:
75 | print('Cannot find ChessboardCorners', f)
76 |
77 | # Calibration
78 | if len(objpt)>0 and len(imgpt)>0:
79 | print('Calibrating ...')
80 | ret, mat, dist, rvec, tvec = cv2.calibrateCamera(objpt, imgpt, gray.shape[::-1], None, None)
81 | print('Calibrating done')
82 |
83 | # Draw projected xyz axis on the image
84 | for i, f in enumerate(file_):
85 | # Read in image
86 | img = cv2.imread(f)
87 |
88 | # Draw corners
89 | img = cv2.drawChessboardCorners(img, self.chessboard_size, imgpt[i], True)
90 |
91 | # solvePnp will return the transformation matrix to transform 3D model coordinate to 2D camera coordinate
92 | ret, rvec, tvec = cv2.solvePnP(objpt[i], imgpt[i], mat, dist)
93 | self.project_3Daxis_to_2Dimage(img, mat, dist, rvec, tvec)
94 |
95 | # Save image with new name and extension
96 | f_ = f[:-4] + '_.jpg'
97 | cv2.imwrite(f_, img)
98 |
99 | # Get reprojection error
100 | error = self.get_reprojection_error(
101 | np.asarray(objpt[i]).reshape(-1, 3), # To convert from m list of (n, 3) to (m*n, 3)
102 | np.asarray(imgpt[i]).reshape(-1, 2), # To convert from m list of (n, 1, 2) to (m*n, 2)
103 | mat, dist, rvec, tvec)
104 | print('Img', i, f, 'reprojection error', error)
105 |
106 | # Save camera intrinsic
107 | img = cv2.imread(file[0])
108 | data = dict(intrin_mat=mat.tolist(),
109 | dist_coeff=dist.tolist(),
110 | img_height=img.shape[0],
111 | img_width=img.shape[1])
112 | filepath = folder + 'intrin.yaml'
113 | with open(filepath, 'w') as f:
114 | yaml.dump(data, f, default_flow_style=False)
115 | print('Saved camera intrinsic to', filepath)
116 |
117 |
118 | def get_extrin(self, folder):
119 | # Use chessboard pattern to get extrinsic of color cameras
120 | # Note: Simplified calibration with only one image per camera view
121 |
122 | # Read in filename of .png from folder
123 | file = glob.glob(folder+'*.png')
124 | file.sort()
125 |
126 | for i, f in enumerate(file):
127 | # Extract camera index from last few characters of filename
128 | cam_idx = f.split('/')[-1]
129 | cam_idx = cam_idx.split('.')[0] # Note: f = '../data/calib_extrin/cam_X.png', where X is camera index
130 |
131 | # Read in camera intrinsic
132 | filepath = '../data/calib_intrin/'+cam_idx+'/intrin.yaml'
133 | param = yaml.load(open(filepath), Loader=yaml.FullLoader)
134 | mat = np.asarray(param['intrin_mat'])
135 | dist = np.asarray(param['dist_coeff'])
136 |
137 | # Read in image
138 | img = cv2.imread(f)
139 |
140 | # Convert to grayscale first
141 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
142 |
143 | # Find chessboard corners
144 | ret, corners = cv2.findChessboardCorners(gray, self.chessboard_size, self.flags_findChessboard)
145 |
146 | # If found, add object points, image points (after refining them)
147 | if ret == True:
148 | corners2 = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), self.criteria) # (chessboard_size[0]*chessboard_size[1], 1, 2}
149 |
150 | # Draw corners
151 | img = cv2.drawChessboardCorners(img, self.chessboard_size, corners2, ret)
152 |
153 | # solvePnp will return the transformation matrix to transform 3D model coordinate to 2D camera coordinate
154 | ret, rvec, tvec = cv2.solvePnP(self.obj_pts, corners2, mat, dist)
155 | self.project_3Daxis_to_2Dimage(img, mat, dist, rvec, tvec)
156 |
157 | # Save image with new name and extension
158 | f_ = f[:-4] + '_.jpg'
159 | cv2.imwrite(f_, img)
160 | # Display image
161 | cv2.imshow('img'+str(i), img)
162 | cv2.waitKey(1)
163 |
164 | # Get reprojection error
165 | error = self.get_reprojection_error(
166 | np.asarray(self.obj_pts).reshape(-1, 3), # To convert from m list of (n, 3) to (m*n, 3)
167 | np.asarray(corners2).reshape(-1, 2), # To convert from m list of (n, 1, 2) to (m*n, 2)
168 | mat, dist, rvec, tvec)
169 | print('Img', f, 'reprojection error', error)
170 |
171 | # Create 4 by 4 homo matrix [R|T] to transform 3D model coordinate to 3D camera coordinate
172 | homo_matrix = np.hstack((cv2.Rodrigues(rvec)[0], tvec)) # 3 by 4 matrix
173 | homo_matrix = np.vstack((homo_matrix, np.array([0,0,0,1]))) # 4 by 4 matrix
174 |
175 | # Save camera extrinsic
176 | data = dict(extrin_mat=homo_matrix.tolist())
177 | filepath = f[:-4] + '_extrin.yaml'
178 | with open(filepath, 'w') as f:
179 | yaml.dump(data, f, default_flow_style=False)
180 | print('Saved camera extrinsic to', filepath)
181 |
182 |
183 | def get_extrin_mirror(self, folder, idx=0):
184 | # Use chessboard to calib extrin of color cameras
185 | # Note: Simplified calibration with only one image per camera view
186 | # Note: Assume setup contains one camera with two plane mirrors -> total of 3 camera views
187 |
188 | print('Assume setup contains one camera with two plane mirrors')
189 | print('Select region of interest (ROI) containing chessboard pattern in the following order:')
190 | print(' 1) Select ROI of actual camera view')
191 | print(' 2) Select ROI of 1st virtual camera view')
192 | print(' 3) Select ROI of 2nd virtual camera view')
193 |
194 | # Read in camera intrinsic
195 | cam_idx = 'cam_'+str(idx).zfill(2)
196 | filepath = '../data/calib_intrin/'+cam_idx+'/intrin.yaml'
197 | param = yaml.load(open(filepath), Loader=yaml.FullLoader)
198 | mat = np.asarray(param['intrin_mat'])
199 | dist = np.asarray(param['dist_coeff'])
200 |
201 | # Read in image
202 | img = cv2.imread(folder+'image.png')
203 | # Keep original image for drawing
204 | ori = cv2.imread(folder+'image.png')
205 |
206 | for i in range(3): # Note: Hardcode 3 views
207 | # Manually select ROI
208 | print('')
209 | roi = cv2.selectROI(img) # Return top left x, y, width, height
210 | # Mask out non ROI
211 | tmp = self.mask_non_roi(img, roi)
212 |
213 | # Convert to grayscale
214 | gray = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
215 |
216 | # Find chessboard corners
217 | ret, corners = cv2.findChessboardCorners(gray, self.chessboard_size, self.flags_findChessboard)
218 |
219 | if ret==False:
220 | print('Cannot find ChessboardCorners')
221 | else:
222 | # If found, add object points, image points (after refining them)
223 | corners2 = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), self.criteria) # (chessboard_size[0]*chessboard_size[1], 1, 2}
224 |
225 | # For next two views need to flip corners as they are from virtual camera
226 | if i>0:
227 | corners2 = self.flip_corners(corners2)
228 | # Need to mirror reflect about the x axis also
229 | corners2[:,:,0] = img.shape[1] - corners2[:,:,0]
230 | img = cv2.flip(img, flipCode=1)
231 | ori = cv2.flip(ori, flipCode=1)
232 |
233 | # Draw corners
234 | ori = cv2.drawChessboardCorners(ori, self.chessboard_size, corners2, ret)
235 |
236 | # solvePnp will return the transformation matrix to transform 3D model coordinate to 2D camera coordinate
237 | ret, rvec, tvec = cv2.solvePnP(self.obj_pts, corners2, mat, dist)
238 | self.project_3Daxis_to_2Dimage(ori, mat, dist, rvec, tvec)
239 |
240 | # Mask out chessboard
241 | img = self.mask_chessboard(img, corners2)
242 |
243 | # Flip back image
244 | if i>0:
245 | img = cv2.flip(img, flipCode=1)
246 | ori = cv2.flip(ori, flipCode=1)
247 |
248 | # Display image
249 | cv2.imshow('ori', ori)
250 | cv2.waitKey(1)
251 |
252 | # Get reprojection error
253 | error = self.get_reprojection_error(
254 | np.asarray(self.obj_pts).reshape(-1, 3), # To convert from m list of (n, 3) to (m*n, 3)
255 | np.asarray(corners2).reshape(-1, 2), # To convert from m list of (n, 1, 2) to (m*n, 2)
256 | mat, dist, rvec, tvec)
257 | print('Img', i, 'reprojection error', error)
258 |
259 | # Create 4 by 4 homo matrix [R|T] to transform 3D model coordinate to 3D camera coordinate
260 | homo_matrix = np.hstack((cv2.Rodrigues(rvec)[0], tvec)) # 3 by 4 matrix
261 | homo_matrix = np.vstack((homo_matrix, np.array([0,0,0,1]))) # 4 by 4 matrix
262 | # Note: Inverse matrix to get transformation from image to camera frame
263 | homo_matrix = np.linalg.inv(homo_matrix)
264 |
265 | # Save camera extrinsic
266 | data = dict(extrin_mat=homo_matrix.tolist())
267 | filepath = folder + 'cam_' + str(i).zfill(2) + '_extrin.yaml'
268 | with open(filepath, 'w') as f:
269 | yaml.dump(data, f, default_flow_style=False)
270 | print('Saved camera extrinsic to', filepath)
271 |
272 | # Save image with new name and extension
273 | cv2.imwrite(folder+'image_.jpg', ori)
274 |
275 |
276 | def project_3Daxis_to_2Dimage(self, img, mat, dist, rvec, tvec):
277 | axis_3D = np.float32([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]) * 2 * self.chessboard_sq_size # Create a 3D axis that is twice the length of chessboard_sq_size
278 | axis_2D = cv2.projectPoints(axis_3D, rvec, tvec, mat, dist)[0].reshape(-1, 2)
279 | if axis_2D.shape[0]==4:
280 | colours = [(0,0,255),(0,255,0),(255,0,0)] # BGR
281 | for i in range(1,4):
282 | (x0, y0), (x1, y1) = axis_2D[0], axis_2D[i]
283 | cv2.line(img, (int(x0), int(y0)), (int(x1), int(y1)), colours[i-1], 3)
284 |
285 |
286 | def get_reprojection_error(self, p3D, p2D, mat, dist, rvec, tvec):
287 | p2D_reproj = cv2.projectPoints(p3D, rvec, tvec, mat, dist)[0].reshape(-1, 2)
288 | error = cv2.norm(p2D, p2D_reproj, cv2.NORM_L2) / len(p2D)
289 |
290 | return error # https://docs.opencv.org/3.4.3/dc/dbb/tutorial_py_calibration.html
291 |
292 |
293 | def mask_non_roi(self, img, roi):
294 | x, y, width, height = roi
295 |
296 | # Create binary mask
297 | msk = np.zeros((img.shape[0],img.shape[1]), dtype=np.uint8)
298 | msk[y:y+height,x:x+width] = 255
299 |
300 | # Mask non ROI
301 | tmp = cv2.bitwise_and(img, img, mask=msk)
302 |
303 | return tmp
304 |
305 |
306 | def mask_chessboard(self, img, corners):
307 | # Get four corners
308 | c0 = corners[0,:,:].astype(np.int32)
309 | c1 = corners[self.chessboard_size[0]-1,:,:].astype(np.int32)
310 | c2 = corners[-self.chessboard_size[0],:,:].astype(np.int32)
311 | c3 = corners[-1,:,:].astype(np.int32)
312 | c_ = np.array([c0,c1,c3,c2])
313 |
314 | tmp = img.copy()
315 | cv2.fillPoly(tmp, [c_], color=(255,255,255)) # Mask out the corners with white polygon
316 |
317 | return tmp
318 |
319 |
320 | def flip_corners(self, corners):
321 | col, row = self.chessboard_size[0], self.chessboard_size[1]
322 | temp = corners.copy()
323 |
324 | # Note: For (col=odd,row=even) checkerboard
325 | if row%2==0:
326 | for r in range(row):
327 | temp[r*col:r*col+col, :, :] = corners[(row-r-1)*col:(row-r-1)*col+col:, :, :]
328 |
329 | # Note: For (col=even,row=odd) checkerboard
330 | elif col%2==0:
331 | for r in range(row):
332 | for c in range(col):
333 | temp[r*col+c, :, :] = corners[r*col+(col-c-1), :, :]
334 |
335 | return temp
336 |
337 |
338 | def visualize_cam_pose(self, folder):
339 | file = glob.glob(folder+'*.yaml')
340 | file.sort()
341 | color = [[1,0,0],[0,1,0],[0,0,1]] # Note: maximum 3 cameras
342 | cam_frame = []
343 | for i, f in enumerate(file):
344 | # Read in camera extrin
345 | param = yaml.load(open(f), Loader=yaml.FullLoader)
346 | extrin = np.asarray(param['extrin_mat'])
347 | # Create camera frame
348 | frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.1)
349 | frame.transform(np.linalg.inv(extrin))
350 | frame.paint_uniform_color(color[i])
351 | cam_frame.append(frame)
352 |
353 | # Create world reference frame
354 | ref_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=self.chessboard_sq_size*4)
355 |
356 | # Create chessboard pattern
357 | mesh = self.create_chessboard_pattern_open3d()
358 |
359 | o3d.visualization.draw_geometries([ref_frame, mesh] + cam_frame)
360 |
361 |
362 | def create_chessboard_pattern_open3d(self):
363 | # Create chessboard pattern for Open3D visualization
364 |
365 | # Origin at top left of the checkerboard
366 | # Axis is +ve X points to right, +ve Y points downwards
367 | # o-->X
368 | # |
369 | # Y
370 | # Vertex order is anti clockwise
371 | # 0 ---- 3
372 | # | |
373 | # 1 ---- 2
374 | vertices = []
375 | triangles = [] # Must be anti clockwise order when view from outside of the mesh
376 | black = True # Use to alternate between black and white square when loop across row and col
377 | index = 0 # To keep track of the number of vertices
378 | size = self.chessboard_sq_size
379 | for i in range(self.chessboard_size[1]+1): # In +ve Y axis direction
380 | for j in range(self.chessboard_size[0]+1): # In +ve X axis direction
381 | if black:
382 | x0, y0 = j*size, i*size # In anti clockwise order from top left
383 | x1, y1 = j*size, i*size+size # bottom left
384 | x2, y2 = j*size+size, i*size+size # bottom right
385 | x3, y3 = j*size+size, i*size # top right
386 | vertices.append([x0, y0, 0])
387 | vertices.append([x1, y1, 0])
388 | vertices.append([x2, y2, 0])
389 | vertices.append([x3, y3, 0])
390 | triangles.append([index, index+1, index+2])
391 | triangles.append([index, index+2, index+3])
392 | index += 4
393 |
394 | black = not black # Toggle the flag for next square
395 |
396 | if (self.chessboard_size[0]+1)%2 == 0: # Important: Need to check if col is even else will get parallel black strips as for even col the sq in the next row follw the same color
397 | black = not black
398 |
399 | # To shift the origin to the bottom right of first top left black square
400 | vertices = np.asarray(vertices) - np.array([size, size, 0])
401 |
402 | mesh = o3d.geometry.TriangleMesh()
403 | mesh.vertices = o3d.utility.Vector3dVector(vertices)
404 | mesh.triangles = o3d.utility.Vector3iVector(triangles)
405 | mesh.paint_uniform_color([0,0,0]) # Black color
406 |
407 | return mesh
408 |
409 |
410 | class Triangulation:
411 | def __init__(self, cam_idx, vis=None, use_panoptic_dataset=False):
412 | super(Triangulation, self).__init__()
413 |
414 | #############################
415 | ### Load camera parameter ###
416 | #############################
417 | if use_panoptic_dataset:
418 | data_path = '../data/'
419 | seq_name = '171204_pose1_sample'
420 | # Load camera calibration param
421 | with open(data_path+seq_name+'/calibration_{0}.json'.format(seq_name)) as f:
422 | calib = json.load(f)
423 |
424 | # Cameras are identified by a tuple of (panel#,node#)
425 | # Note: 31 HD cameras (0,0) - (0,30), where the zero in the first index means HD camera
426 | cameras = {(cam['panel'],cam['node']):cam for cam in calib['cameras']}
427 |
428 | # Convert data into numpy arrays for convenience
429 | for k, cam in cameras.items():
430 | cam['K'] = np.matrix(cam['K'])
431 | cam['distCoef'] = np.array(cam['distCoef'])
432 | cam['R'] = np.matrix(cam['R'])
433 | cam['t'] = np.array(cam['t'])*0.01 # Convert cm to m
434 |
435 | # Extract camera index integer from video file name
436 | cam_idx_ = []
437 | for c in cam_idx:
438 | # Example of file name
439 | # ../data/171204_pose1_sample/hdVideos/hd_00_00.mp4
440 | value = c.split('_')[-1] # Select the last split (XX.mp4)
441 | value = value.split('.')[0] # Select the first split (XX)
442 | cam_idx_.append(int(value))
443 |
444 | # Compute projection matrix
445 | self.pmat = []
446 | for i in range(len(cam_idx_)):
447 | cam = cameras[(0,cam_idx_[i])]
448 | extrin_mat = np.zeros((3,4))
449 | extrin_mat[:3,:3] = cam['R']
450 | extrin_mat[:3,3:] = cam['t']
451 | self.pmat.append(cam['K'] @ extrin_mat)
452 |
453 | #############################
454 | ### Visualize camera pose ###
455 | #############################
456 | # Draw world frame
457 | frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.5)
458 | vis.add_geometry(frame)
459 |
460 | # Draw camera axis
461 | hd_cam_idx = zip([0] * 30,range(0,30)) # Choose only HD cameras
462 | hd_cameras = [cameras[cam].copy() for cam in hd_cam_idx]
463 | for i, cam in enumerate(hd_cameras):
464 | if i in cam_idx_: # Show only those selected camera
465 | extrin_mat = np.eye(4)
466 | extrin_mat[:3,:3] = cam['R']
467 | extrin_mat[:3,3:] = cam['t']
468 | axis = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.2)
469 | axis.transform(np.linalg.inv(extrin_mat))
470 | vis.add_geometry(axis)
471 |
472 |
473 | def triangulate_2views(self, param, mode):
474 |
475 | if mode=='body':
476 | p0 = param[0]['keypt'] # [nPt,2]
477 | p1 = param[1]['keypt'] # [nPt,2]
478 |
479 | elif mode=='holistic':
480 | _, param_lh, param_rh, param_bd = param[0]
481 | p0 = np.vstack((param_lh['keypt'],
482 | param_rh['keypt'],
483 | param_bd['keypt'])) # [21+21+33/25,2]
484 |
485 | _, param_lh, param_rh, param_bd = param[1]
486 | p1 = np.vstack((param_lh['keypt'],
487 | param_rh['keypt'],
488 | param_bd['keypt'])) # [21+21+33/25,2]
489 |
490 |
491 | # Note: OpenCV can only triangulate from 2 views
492 | p3d = cv2.triangulatePoints(
493 | self.pmat[0], self.pmat[1],
494 | p0.T, p1.T) # Note: triangulatePoints requires 2xN arrays, so transpose
495 |
496 | # However, homgeneous point is returned so need to divide by last term
497 | p3d /= p3d[3] # [4,nPt]
498 | p3d = p3d[:3,:].T # [nPt,3]
499 |
500 | # Update param 3D joint
501 | if mode=='body':
502 | param[0]['joint'] = p3d # [nPt,3]
503 | param[1]['joint'] = p3d # [nPt,3]
504 |
505 | elif mode=='holistic':
506 | _, param_lh, param_rh, param_bd = param[0]
507 | param_lh['joint'] = p3d[ :21]
508 | param_rh['joint'] = p3d[21:42]
509 | param_bd['joint'] = p3d[42:]
510 |
511 | _, param_lh, param_rh, param_bd = param[1]
512 | param_lh['joint'] = p3d[ :21]
513 | param_rh['joint'] = p3d[21:42]
514 | param_bd['joint'] = p3d[42:]
515 |
516 | return param
517 |
518 |
519 | def triangulate_nviews(self, param, mode):
520 |
521 | p2d = [] # List of len nCam to store [nPt,2] for each view
522 | if mode=='body':
523 | for p in param:
524 | p2d.append(p['keypt']) # [nPt,2]
525 |
526 | elif mode=='holistic':
527 | for p in param:
528 | _, param_lh, param_rh, param_bd = p
529 | p2d.append(np.vstack((
530 | param_lh['keypt'],
531 | param_rh['keypt'],
532 | param_bd['keypt'])) # [21+21+33/25,2]
533 | )
534 |
535 | # Convert list into a single array
536 | p2d = np.concatenate(p2d, axis=1) # [nPt,2*nCam]
537 | nPt = p2d.shape[0]
538 |
539 | p3d = np.zeros((nPt,3))
540 | for i in range(nPt):
541 | p3d[i,:] = self.triangulate_point(p2d[i,:].reshape(-1,2))
542 |
543 | # Update param 3D joint
544 | if mode=='body':
545 | for p in param:
546 | p['joint'] = p3d # [nPt,3]
547 |
548 | elif mode=='holistic':
549 | for p in param:
550 | _, param_lh, param_rh, param_bd = p
551 | param_lh['joint'] = p3d[ :21]
552 | param_rh['joint'] = p3d[21:42]
553 | param_bd['joint'] = p3d[42:]
554 |
555 | return param
556 |
557 |
558 | def triangulate_point(self, point):
559 | # Modified from
560 | # https://gist.github.com/davegreenwood/e1d2227d08e24cc4e353d95d0c18c914
561 |
562 | # Other possible python implementation
563 | # https://www.mail-archive.com/floatcanvas@mithis.com/msg00513.html
564 |
565 | # Also its worthwhile to read through the below link
566 | # http://kwon3d.com/theory/dlt/dlt.html
567 | # For indepth explanation on DLT and many other useful theories
568 | # required by multicam mocap by Prof Young-Hoo Kwon
569 |
570 | # Use DLT to triangulate a 3D point from N image points in N camera views
571 | N = len(self.pmat) # Number of camera views
572 | M = np.zeros((3*N, 4+N))
573 | for i in range(N):
574 | M[3*i:3*i+3, :4] = self.pmat[i] # [3,4]
575 | M[3*i:3*i+2,4+i] = -point[i] # [2,1]
576 | M[3*i+2 ,4+i] = -1 # Homogeneous coordinate
577 | V = np.linalg.svd(M)[-1] # [4+N,4+N]
578 | X = V[-1,:4] # [4]
579 | X = X / X[3] # [4]
580 |
581 | return X[:3] # [3]
582 |
583 |
584 | class PanopticDataset:
585 | def __init__(self, data_path='../data/', seq_name='171204_pose1_sample'):
586 | super(PanopticDataset, self).__init__()
587 |
588 | # Load camera calibration parameters
589 | with open(data_path+seq_name+'/calibration_{0}.json'.format(seq_name)) as f:
590 | calib = json.load(f)
591 |
592 | # Cameras are identified by a tuple of (panel#,node#)
593 | cameras = {(cam['panel'],cam['node']):cam for cam in calib['cameras']}
594 |
595 | # Convert data into numpy arrays for convenience
596 | for k, cam in cameras.items():
597 | cam['K'] = np.matrix(cam['K'])
598 | cam['distCoef'] = np.array(cam['distCoef'])
599 | cam['R'] = np.matrix(cam['R'])
600 | cam['t'] = np.array(cam['t']).reshape((3,1)) * 0.01 # Convert cm to m
601 |
602 | # Choose only HD cameras for visualization
603 | hd_cam_idx = zip([0] * 30,range(0,30))
604 | hd_cameras = [cameras[cam].copy() for cam in hd_cam_idx]
605 |
606 | # Select an HD camera (0,0) - (0,30), where the zero in the first index means HD camera
607 | # cam = cameras[(0,5)]
608 |
609 | # Visualize 3D camera pose
610 | self.vis = o3d.visualization.Visualizer()
611 | self.vis.create_window(width=640, height=480)
612 |
613 | # Draw world frame
614 | frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.5)
615 |
616 | # Draw camera axis
617 | axes = []
618 | for cam in hd_cameras:
619 | axis = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.1)
620 |
621 | tmat = np.eye(4)
622 | tmat[:3,:3] = cam['R']
623 | tmat[:3,3:] = cam['t']
624 | axis.transform(np.linalg.inv(tmat))
625 | axes.append(axis)
626 |
627 | # Draw body pose
628 | hd_idx = 0
629 | hd_skel_json_path = data_path+seq_name+'/hdPose3d_stage1_coco19/'
630 | # Edges between joints in the body skeleton
631 | body_edges = np.array([[1,2],[1,4],[4,5],[5,6],[1,3],[3,7],[7,8],[8,9],[3,13],[13,14],[14,15],[1,10],[10,11],[11,12]])-1
632 |
633 | # Load the json file with this frame's skeletons
634 | skel_json_fname = hd_skel_json_path+'body3DScene_{0:08d}.json'.format(hd_idx)
635 | with open(skel_json_fname) as f:
636 | bframe = json.load(f)
637 |
638 | # Bodies
639 | for ids in range(len(bframe['bodies'])):
640 | body = bframe['bodies'][ids]
641 | skel = np.array(body['joints19']).reshape((-1,4))[:,:3] * 0.01
642 | pcd = o3d.geometry.PointCloud()
643 | pcd.points = o3d.utility.Vector3dVector(skel)
644 |
645 | bone = o3d.geometry.LineSet()
646 | bone.points = o3d.utility.Vector3dVector(skel)
647 | bone.lines = o3d.utility.Vector2iVector(body_edges)
648 |
649 | self.vis.add_geometry(pcd)
650 | self.vis.add_geometry(bone)
651 |
652 | self.vis.add_geometry(frame)
653 | for a in axes:
654 | self.vis.add_geometry(a)
655 |
656 | self.vis.run()
657 |
658 |
659 | def projectPoints(X, K, R, t, Kd):
660 | """ Projects points X (3xN) using camera intrinsics K (3x3),
661 | extrinsics (R,t) and distortion parameters Kd=[k1,k2,p1,p2,k3].
662 |
663 | Roughly, x = K*(R*X + t) + distortion
664 |
665 | See http://docs.opencv.org/2.4/doc/tutorials/calib3d/camera_calibration/camera_calibration.html
666 | or cv2.projectPoints
667 | """
668 |
669 | x = np.asarray(R*X + t)
670 |
671 | x[0:2,:] = x[0:2,:]/x[2,:]
672 |
673 | r = x[0,:]*x[0,:] + x[1,:]*x[1,:]
674 |
675 | x[0,:] = x[0,:]*(1 + Kd[0]*r + Kd[1]*r*r + Kd[4]*r*r*r) + 2*Kd[2]*x[0,:]*x[1,:] + Kd[3]*(r + 2*x[0,:]*x[0,:])
676 | x[1,:] = x[1,:]*(1 + Kd[0]*r + Kd[1]*r*r + Kd[4]*r*r*r) + 2*Kd[3]*x[0,:]*x[1,:] + Kd[2]*(r + 2*x[1,:]*x[1,:])
677 |
678 | x[0,:] = K[0,0]*x[0,:] + K[0,1]*x[1,:] + K[0,2]
679 | x[1,:] = K[1,0]*x[0,:] + K[1,1]*x[1,:] + K[1,2]
680 |
681 | return x
682 |
683 |
684 | ###############################################################################
685 | ### Simple example to test program ###
686 | ###############################################################################
687 | if __name__ == '__main__':
688 |
689 | pano = PanopticDataset()
690 |
--------------------------------------------------------------------------------
/code/utils_joint_angle.py:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | ### Useful function for converting 3D joint to joint angles
3 | ### Joint angle is then used for:
4 | ### 1) Gesture recognition
5 | ### 2) Hand ROM recognition
6 | ###############################################################################
7 |
8 | import cv2
9 | import numpy as np
10 |
11 |
12 | def convert_relative_to_actual_3d_joint_(param, intrin):
13 | # Adapted from Iqbal et al.
14 | # (ECCV 2018) Hand Pose Estimation via Latent 2.5D Heatmap Regression
15 | # But there are some errors in equation:
16 | # (4) missing camera intrinsic
17 | # (6) missing a constant multiplication of 2
18 |
19 | # Select wrist joint (n) and index finger MCP (m)
20 | xn, yn = param['keypt'][0] # Wrist
21 | xm, ym = param['keypt'][9] # Index finger MCP
22 | xn = (xn-intrin['cx'])/intrin['fx']
23 | xm = (xm-intrin['cx'])/intrin['fx']
24 | yn = (yn-intrin['cy'])/intrin['fy']
25 | ym = (ym-intrin['cy'])/intrin['fy']
26 |
27 | Zn = param['joint'][0,2] # Relative Z coor of wrist
28 | Zm = param['joint'][9,2] # Relative Z coor of index finger MCP
29 |
30 | # Precalculate value for computing Zroot
31 | xx = xn-xm
32 | yy = yn-ym
33 | xZ = xn*Zn - xm*Zm
34 | yZ = yn*Zn - ym*Zm
35 | ZZ = Zn-Zm
36 |
37 | # Compute Zroot relative
38 | C = 1
39 | a = xx*xx + yy*yy
40 | b = 2*(xx*xZ + yy*yZ)
41 | c = xZ*xZ + yZ*yZ + ZZ*ZZ - C*C
42 | Zroot = (-b + np.sqrt(b*b - 4*a*c))/(2*a)
43 |
44 | # Convert to actual scale
45 | s = 0.08 # Note: Hardcode distance from wrist to index finger MCP as 8cm
46 | Zroot *= s / C
47 |
48 | # Compute actual depth
49 | param['joint_3d'][:,2] = param['joint'][:,2] + Zroot
50 |
51 | # Compute X and Y
52 | param['joint_3d'][:,0] = (param['keypt'][:,0]-intrin['cx'])/intrin['fx']
53 | param['joint_3d'][:,1] = (param['keypt'][:,1]-intrin['cy'])/intrin['fy']
54 | param['joint_3d'][:,:2] *= param['joint_3d'][:,2:3]
55 |
56 | return param['joint_3d']
57 |
58 |
59 | #############################################################
60 | ### Simple gesture recognition from joint angle using KNN ###
61 | #############################################################
62 | class GestureRecognition:
63 | def __init__(self, mode='train'):
64 | super(GestureRecognition, self).__init__()
65 |
66 | # 11 types of gesture 'name':class label
67 | self.gesture = {
68 | 'fist':0,'one':1,'two':2,'three':3,'four':4,'five':5,'six':6,
69 | 'rock':7,'spiderman':8,'yeah':9,'ok':10,
70 | }
71 |
72 | if mode=='train':
73 | # Create .csv file to log training data
74 | self.file = open('../data/gesture_train.csv', 'a+')
75 | elif mode=='eval':
76 | # Load training data
77 | file = np.genfromtxt('../data/gesture_train.csv', delimiter=',')
78 | # Extract input joint angles
79 | angle = file[:,:-1].astype(np.float32)
80 | # Extract output class label
81 | label = file[:, -1].astype(np.float32)
82 | # Use OpenCV KNN
83 | self.knn = cv2.ml.KNearest_create()
84 | self.knn.train(angle, cv2.ml.ROW_SAMPLE, label)
85 |
86 |
87 | def train(self, angle, label):
88 | # Log training data
89 | data = np.append(angle, label) # Combine into one array
90 | np.savetxt(self.file, [data], delimiter=',', fmt='%f')
91 |
92 |
93 | def eval(self, angle):
94 | # Use KNN for gesture recognition
95 | data = np.asarray([angle], dtype=np.float32)
96 | ret, results, neighbours ,dist = self.knn.findNearest(data, 3)
97 | idx = int(results[0][0]) # Index of class label
98 |
99 | return list(self.gesture)[idx] # Return name of class label
100 |
101 |
102 | ##############################################################
103 | ### Simple hand ROM recognition from joint angle using KNN ###
104 | ##############################################################
105 | class HandRomRecognition:
106 | def __init__(self, mode='train'):
107 | super(HandRomRecognition, self).__init__()
108 |
109 | # 13 types of hand ROM 'name':class label
110 | self.gesture = {
111 | 'Finger MCP Flexion' :0,
112 | 'Finger PIP DIP Flexion':1,
113 | 'Thumb MCP Flexion' :2,
114 | 'Thumb IP Flexion' :3,
115 | 'Thumb Radial Abduction':4,
116 | 'Thumb Palmar Abduction':5, # From this class onwards hard to differentiate
117 | 'Thumb Opposition' :6,
118 | 'Wrist Flexion' :7, # Refer to WristArmRom
119 | 'Wrist Extension' :8, # Refer to WristArmRom
120 | 'Wrist Radial Deviation':9, # Refer to WristArmRom
121 | 'Wrist Ulnar Deviation' :10,# Refer to WristArmRom
122 | 'Forearm Neutral' :11,# Refer to WristArmRom
123 | 'Forearm Pronation' :12,# Refer to WristArmRom
124 | 'Forearm Supination' :13,# Refer to WristArmRom
125 | }
126 |
127 | if mode=='train':
128 | # Create .csv file to log training data
129 | self.file = open('../data/handrom_train.csv', 'a+')
130 | elif mode=='eval':
131 | # Load training data
132 | file = np.genfromtxt('../data/handrom_train.csv', delimiter=',')
133 | # Extract input joint angles
134 | angle = file[:,:-1].astype(np.float32)
135 | # Extract output class label
136 | label = file[:, -1].astype(np.float32)
137 | # Use OpenCV KNN
138 | self.knn = cv2.ml.KNearest_create()
139 | self.knn.train(angle, cv2.ml.ROW_SAMPLE, label)
140 |
141 |
142 | def train(self, angle, label):
143 | # Log training data
144 | data = np.append(angle, label) # Combine into one array
145 | np.savetxt(self.file, [data], delimiter=',', fmt='%f')
146 |
147 |
148 | def eval(self, angle):
149 | # Use KNN for gesture recognition
150 | data = np.asarray([angle], dtype=np.float32)
151 | ret, results, neighbours ,dist = self.knn.findNearest(data, 3)
152 | idx = int(results[0][0]) # Index of class label
153 |
154 | return list(self.gesture)[idx] # Return name of class label
155 |
156 |
157 | ###########################################################
158 | ### Simple measurement of wrist and forearm joint angle ###
159 | ###########################################################
160 | class WristArmRom:
161 | def __init__(self, mode=2, side='right'):
162 | super(WristArmRom, self).__init__()
163 |
164 | # 3 modes of measurement
165 | # 0: Wrist flexion/extension
166 | # 1: Wrist radial/ulnar deviation
167 | # 2: Forearm pronation/supination
168 | self.mode = mode
169 | self.side = side # left / right
170 |
171 | # 13 types of hand ROM 'name':class label
172 | self.gesture = {
173 | 'Finger MCP Flexion' :0, # Refer to HandRomRecognition
174 | 'Finger PIP DIP Flexion':1, # Refer to HandRomRecognition
175 | 'Thumb MCP Flexion' :2, # Refer to HandRomRecognition
176 | 'Thumb IP Flexion' :3, # Refer to HandRomRecognition
177 | 'Thumb Radial Abduction':4, # Refer to HandRomRecognition
178 | 'Thumb Palmar Abduction':5, # Refer to HandRomRecognition
179 | 'Thumb Opposition' :6, # Refer to HandRomRecognition
180 | 'Wrist Flex/Extension' :7, # Use mode 0 # Note: hard to differentiate
181 | # 'Wrist Extension' :8, # Note: hard to differentiate
182 | 'Wrist Radial/Ulnar Dev':9, # Use mode 1 # Note: hard to differentiate
183 | # 'Wrist Ulnar Deviation' :10, # Note: hard to differentiate
184 | 'Forearm Neutral' :11,# Use mode 2
185 | 'Forearm Pronation' :12,# Use mode 2
186 | 'Forearm Supination' :13,# Use mode 2
187 | }
188 |
189 |
190 | def eval(self, param):
191 | # Wrist flexion/extension, wrist radial/ulnar deviation
192 | if self.mode==0 or self.mode==1:
193 | # Assume camera is placed such that upper body of the subject
194 | # is visible especially the elbow joints
195 |
196 | # Wrist joint angle can be simply calculated from angle between
197 | # Vector 1: joining body elbow joint[13/14] to wrist[15/16]
198 | # Vector 2: joining hand wrist[0] to middle finger MCP joint[9]
199 | _, param_lh, param_rh, param_bd = param
200 |
201 | if self.side=='left':
202 | v1 = param_bd['joint'][15]- param_bd['joint'][13]
203 | v2 = param_lh['joint'][9] - param_lh['joint'][0]
204 | elif self.side=='right':
205 | v1 = param_bd['joint'][16]- param_bd['joint'][14]
206 | v2 = param_rh['joint'][9] - param_rh['joint'][0]
207 |
208 | # Normalize vector
209 | v1 = v1/(np.linalg.norm(v1)+1e-6)
210 | v2 = v2/(np.linalg.norm(v2)+1e-6)
211 | # Get angle using arcos of dot product between the two vectors
212 | angle = np.arccos(np.dot(v1, v2))
213 | angle = np.degrees(angle) # Convert radian to degree
214 |
215 | if self.side=='left':
216 | param_lh['angle'][0] = angle # Note: Temporary make use hand joint angles
217 | if self.mode==0 and angle>10:
218 | param_lh['gesture'] = 'Wrist Flex/Extension'
219 | elif self.mode==1 and angle>10:
220 | param_lh['gesture'] = 'Wrist Radial/Ulnar Dev'
221 | elif self.side=='right':
222 | param_rh['angle'][0] = angle # Note: Temporary make use hand joint angles
223 | if self.mode==0 and angle>10:
224 | param_rh['gesture'] = 'Wrist Flex/Extension'
225 | elif self.mode==1 and angle>10:
226 | param_rh['gesture'] = 'Wrist Radial/Ulnar Dev'
227 |
228 |
229 | # Forearm pronation/supination
230 | elif self.mode==2:
231 | # Assume camera is placed at the same level as the hand
232 | # such that palmar side of the hand is directly facing camera
233 | # Forearm pronation/supination can be simply calculated from palm direction normal
234 |
235 | # Get normal of plane joining wrist[0], index finger MCP[5] and little finger MCP[17]
236 | # Note: Palm normal points from palmar side to dorsal side
237 | v1 = param[0]['joint'][5] - param[0]['joint'][0] # Vector pointing from wrist to index finger MCP
238 | v2 = param[0]['joint'][17]- param[0]['joint'][0] # Vector pointing from wrist to little finger MCP
239 | n = np.cross(v1,v2) # Palm normal vector
240 | # Normalize normal vector
241 | n = n/(np.linalg.norm(n)+1e-6)
242 |
243 | # When forearm is in neutral position, palm is directly facing camera
244 | # Palm normal vector shld be parallel to camera z-axis
245 | # Get angle using arcos of dot product between palm normal and z-axis
246 | angle = np.arccos(np.dot(n,np.array([0,0,1])))
247 | angle = np.degrees(angle) # Convert radian to degree
248 | # Get direction by checking on y component of normal vector
249 | direc = n[1]
250 |
251 | if self.side=='left':
252 | # Reverse angle and direction for left hand
253 | angle = 180-angle
254 | direc = -direc
255 |
256 | if angle<30:
257 | param[0]['gesture'] = 'Forearm Neutral'
258 | elif angle>30 and direc<0:
259 | param[0]['gesture'] = 'Forearm Pronation'
260 | elif angle>30 and direc>0:
261 | param[0]['gesture'] = 'Forearm Supination'
262 |
263 | param[0]['angle'][0] = angle # Note: Temporary make use hand joint angles
264 |
265 | return param
266 |
--------------------------------------------------------------------------------
/data/gesture_train.csv:
--------------------------------------------------------------------------------
1 | 32.647995,27.334458,18.777239,32.558145,149.876268,29.578624,37.800344,133.458050,45.085468,32.342115,142.727088,37.366495,26.056262,139.415512,40.519754,0.000000
2 | 45.166793,26.117019,18.645196,31.567098,138.965945,20.022370,33.176819,132.806630,21.381263,28.201580,138.854737,14.808465,26.329570,128.394781,23.400235,0.000000
3 | 20.826588,35.926397,39.943409,20.257126,88.651968,29.781060,17.812546,102.683664,16.751548,11.247996,103.264677,18.000063,15.584646,82.515342,23.045704,0.000000
4 | 31.327256,24.482902,27.073693,22.894414,64.537885,47.775269,16.256466,89.803293,40.146627,9.443338,91.327471,35.448822,14.446064,71.702363,35.047523,0.000000
5 | 28.164625,31.028619,23.568194,21.468457,141.750809,21.386416,29.323112,129.772949,38.852031,24.264641,138.309162,33.321451,23.231644,129.452243,30.819853,0.000000
6 | 39.493933,30.825017,37.798715,48.789407,101.569152,29.543310,62.408973,102.175635,65.566150,59.301602,112.161872,55.427656,60.565796,102.860590,49.489659,0.000000
7 | 46.636410,27.871526,25.808261,22.547515,69.819060,42.556670,16.971363,85.544017,36.496952,11.288500,75.542530,39.251253,16.539423,48.637377,29.019384,0.000000
8 | 45.042568,19.961871,11.893033,13.603903,32.278803,27.388893,7.747610,17.797417,5.794515,2.940872,16.492116,1.481793,9.127502,8.934992,6.483260,0.000000
9 | 47.299112,32.659637,25.455925,33.674011,52.766652,43.792789,29.175726,97.063906,24.384931,27.473441,104.583461,24.572468,32.064351,94.460182,22.230902,0.000000
10 | 46.483112,30.871050,36.277547,40.493482,96.787611,39.591184,38.159227,110.260392,30.181509,30.411843,119.519070,30.006762,26.418003,122.606342,33.310417,0.000000
11 | 27.792747,27.346865,18.358880,13.459867,12.675573,14.827358,21.851454,94.371765,35.972556,19.283318,104.185629,31.780360,26.073177,81.116557,33.292727,1.000000
12 | 35.320273,26.381613,20.191984,14.668020,7.185043,4.248832,25.126893,121.084548,26.620646,24.078772,117.775156,30.698631,30.541078,89.432634,37.903796,1.000000
13 | 26.048195,19.402587,26.436642,6.365824,9.542588,3.241575,28.086456,113.535227,26.468682,20.288474,116.216192,24.670197,26.922405,95.790580,20.527646,1.000000
14 | 27.221395,15.414500,25.939750,6.623381,7.608257,2.696879,28.052599,108.763997,27.351137,21.551970,108.780979,26.114410,25.929242,91.324413,21.103923,1.000000
15 | 32.014172,10.991831,28.415461,5.728636,6.481493,0.889479,23.010316,90.874638,34.321755,12.829574,92.857042,35.219209,11.395936,68.408045,41.629204,1.000000
16 | 35.391177,18.592058,33.007410,4.140826,6.493375,1.912261,25.824411,100.550619,29.616766,19.956852,99.451776,31.702370,24.273458,82.648994,25.282382,1.000000
17 | 33.667543,32.918064,19.468711,7.488233,2.724066,7.948471,28.506697,49.497494,13.477383,55.206680,70.580958,23.938099,54.585635,72.790658,27.310205,1.000000
18 | 34.297057,31.368970,12.617624,12.278204,1.644287,8.980053,30.469056,26.478147,7.106024,52.598374,70.892878,25.786772,42.428934,65.808129,34.571704,1.000000
19 | 37.283942,26.503899,38.901662,12.471588,1.771188,4.433059,38.654754,109.571409,23.595078,35.738030,105.880785,31.167126,25.614191,107.024708,29.663542,1.000000
20 | 37.309748,20.163841,29.692652,7.735964,3.484735,2.936218,33.402019,102.752624,25.379006,27.139984,100.369156,26.013738,25.387794,95.077939,19.624609,1.000000
21 | 27.064267,4.116995,11.001622,16.486113,10.552898,11.181537,14.660537,7.230117,6.805212,5.992467,1.279764,12.949280,2.583446,5.751485,12.190489,2.000000
22 | 31.191134,4.290191,11.017077,14.137684,3.406932,3.979965,14.172432,62.540096,5.982379,4.602033,33.828657,3.623022,7.488203,11.515055,14.876723,2.000000
23 | 32.675400,6.051246,9.601219,16.191259,6.049887,3.021566,23.574714,34.476614,52.095947,20.944558,28.680245,10.530155,21.732707,9.850545,3.993437,2.000000
24 | 36.983640,10.596499,14.782542,8.568285,5.152676,2.903961,20.725178,85.560392,20.407994,15.760408,73.182706,27.857098,16.365531,47.081374,18.958339,2.000000
25 | 28.553979,4.386473,14.044008,14.800291,8.822664,4.434532,12.653980,85.107713,5.685620,4.419841,57.803916,8.104906,9.925122,21.634206,28.194048,2.000000
26 | 43.905587,4.436173,6.406616,7.984581,3.269949,4.945221,21.120639,23.389945,11.310605,16.343349,24.295424,15.957736,9.628379,15.356038,23.239797,2.000000
27 | 47.227182,2.093504,13.185004,10.198872,9.016928,4.762649,30.488841,87.460182,19.246491,31.417656,83.437891,35.377278,26.301992,71.631192,41.428162,2.000000
28 | 38.963707,3.312926,14.764106,13.346643,2.701027,3.122325,15.783439,70.890089,24.271086,16.162595,55.105191,27.125569,14.065205,31.765137,30.163877,2.000000
29 | 38.235214,9.765067,12.204059,13.054985,3.186906,6.008764,30.496132,105.006685,19.614097,37.621662,101.843967,33.236817,36.027421,96.934316,33.416261,2.000000
30 | 44.894136,5.703140,18.672275,16.862815,7.250208,1.171276,27.349864,91.238617,15.085455,31.288754,83.884860,30.505573,32.303599,72.093530,33.306199,2.000000
31 | 33.233165,5.251370,12.361472,7.406677,2.403716,1.809087,14.060716,1.544563,4.514234,31.659243,100.935772,25.510835,35.251030,89.933031,19.148129,3.000000
32 | 52.796238,11.266403,14.081873,4.255632,1.900488,2.120873,8.555830,4.423145,2.657591,32.922917,102.226827,22.889829,35.501908,85.247861,24.733276,3.000000
33 | 45.530282,6.599183,2.705272,11.851240,11.457306,8.534295,10.935138,3.143677,7.689091,40.015122,108.650040,20.131360,44.474448,95.466861,23.438341,3.000000
34 | 40.178526,5.883763,11.293230,9.412355,5.010292,3.067079,17.325150,5.933416,5.999398,39.241775,112.753544,23.859172,37.437907,104.919452,24.762976,3.000000
35 | 41.220657,6.328683,11.057584,8.445178,5.794255,1.084988,11.758416,8.098485,5.679773,36.361657,116.513918,16.134616,37.406455,101.434675,18.718483,3.000000
36 | 58.029470,7.936083,18.771053,7.850386,3.486047,9.073247,6.786294,4.949442,3.565229,34.004830,84.326025,32.755850,41.777266,74.807435,32.737475,3.000000
37 | 47.655365,6.263117,10.347858,4.807681,4.465829,8.093180,5.107651,1.721795,4.607089,33.309515,102.655187,23.337544,38.665966,107.032195,18.660136,3.000000
38 | 37.110564,9.127239,10.668107,9.388768,7.970887,9.143346,12.781739,13.336964,6.699117,2.049043,44.085116,10.394698,5.635468,36.843559,23.718730,3.000000
39 | 38.684006,11.977840,10.109062,4.396552,1.626858,0.703276,12.755652,4.171569,4.843316,33.498154,83.193397,31.964912,32.269514,74.880485,33.148861,3.000000
40 | 37.850077,11.219031,11.298609,4.051946,5.171304,1.238764,9.662268,7.351379,3.868443,29.601931,85.170091,34.670269,24.091245,67.618521,43.369632,3.000000
41 | 33.375924,45.897643,44.959032,4.206236,2.510320,3.222605,6.785700,5.948434,5.084050,4.488179,10.262063,2.284405,10.728425,9.843519,2.926731,4.000000
42 | 42.745151,37.643024,50.160862,5.435130,4.830251,6.826986,5.556171,3.002356,0.205582,4.943730,4.228434,1.315191,14.777243,1.532173,2.396397,4.000000
43 | 36.649501,39.702291,44.822909,3.642930,8.181224,8.751886,3.456824,9.219551,4.474447,5.561067,16.236727,1.138055,18.158290,4.719707,7.470625,4.000000
44 | 33.732343,45.422586,55.822462,7.638146,2.152629,4.033472,1.464054,4.268573,1.663308,2.984584,7.626433,3.389159,6.941995,4.292772,5.763352,4.000000
45 | 38.116837,42.441980,54.622501,5.664158,2.892497,5.785950,4.736157,4.173156,2.245456,4.465982,7.503835,2.509713,12.375703,4.960036,2.874021,4.000000
46 | 24.033897,40.088233,38.787525,9.386012,9.464247,10.413310,5.538884,8.901695,6.428157,5.607933,8.132257,8.182374,12.767122,4.234681,9.934021,4.000000
47 | 22.047348,48.635157,48.085973,4.204146,8.782940,8.546689,8.453367,8.064216,4.190607,8.495972,9.646204,6.860789,15.861648,9.336959,7.072326,4.000000
48 | 33.365355,38.958717,46.813890,9.899286,7.299461,2.715544,9.758196,11.715413,5.594981,17.783596,9.339260,5.354658,18.192992,8.105073,12.362090,4.000000
49 | 38.594702,38.014552,39.186073,10.419738,4.358816,1.884457,3.774121,6.272337,6.311814,4.514871,3.737852,3.404841,16.175068,7.367417,6.986737,4.000000
50 | 34.595418,45.275342,38.231041,16.250354,8.341614,3.207451,15.948974,17.222175,0.766148,21.815026,15.449771,4.494622,21.761233,9.026763,6.234337,4.000000
51 | 35.255824,12.681438,12.162714,1.949913,4.178936,2.694380,5.003759,5.714465,4.993314,2.866081,7.437629,5.021466,6.839853,3.950859,7.966758,5.000000
52 | 35.381951,6.525500,19.199896,3.555485,5.347654,2.759508,1.414179,4.648061,6.142657,1.858605,7.096097,7.531731,6.483093,6.014813,12.057738,5.000000
53 | 41.645858,15.220567,8.432321,3.891638,2.995404,3.117573,2.547617,4.607339,4.529059,4.610331,8.582590,3.758644,12.137491,3.805865,7.507453,5.000000
54 | 31.975783,6.425621,27.088570,1.623824,0.324600,3.288059,4.708754,6.496409,2.555214,3.167269,10.568400,3.977490,5.989966,12.268311,8.626645,5.000000
55 | 52.354737,15.120519,8.159867,12.150277,3.072984,2.300643,6.897060,7.296752,5.010795,13.108232,14.797280,6.259755,33.110258,40.388509,18.435610,5.000000
56 | 42.999924,9.240689,12.167061,6.017784,5.659045,3.776685,8.656432,8.790815,2.774698,9.226938,7.683244,2.427294,5.585492,5.957392,7.547471,5.000000
57 | 43.477665,14.105631,13.982119,10.789480,3.810610,5.423428,6.094256,6.136796,5.340599,8.386305,7.252753,1.856217,5.491802,10.913479,5.328268,5.000000
58 | 20.702271,10.081805,4.982003,23.877205,7.948928,1.551362,19.273723,11.337202,3.647189,20.742387,9.034537,1.285506,19.839281,4.202442,2.836296,5.000000
59 | 17.150237,8.940540,9.556369,14.012305,6.129931,4.739453,8.193098,13.911509,5.344650,6.554458,10.177747,5.336161,3.375271,4.698845,3.420790,5.000000
60 | 39.389713,1.117582,18.518180,2.084555,9.091669,5.572484,4.861925,9.557979,1.797316,8.672537,6.123965,4.182991,5.379867,3.624339,9.622453,5.000000
61 | 32.768108,9.503798,21.593121,70.120588,79.257446,19.285731,62.786166,89.545887,25.282514,35.479257,113.421803,22.441275,2.555735,2.533934,11.763745,6.000000
62 | 46.482827,7.663155,23.567131,63.902830,93.346809,22.482054,56.086617,100.846042,23.564547,33.851183,117.178513,25.286268,6.500882,2.959386,19.501039,6.000000
63 | 38.634087,11.621667,12.321092,65.498904,85.213781,26.907686,64.852347,83.909753,32.034636,62.020283,83.323703,34.953183,29.379008,6.594299,10.074840,6.000000
64 | 36.741209,5.558563,16.938405,62.598507,83.631724,28.465062,62.411796,78.189144,32.007691,54.511854,86.981567,39.002972,30.600811,3.231690,22.656990,6.000000
65 | 32.129682,4.522638,20.253860,56.993430,92.831512,27.698204,64.360650,95.358514,25.051703,46.830764,114.904887,27.940107,11.182135,6.640856,21.113219,6.000000
66 | 44.091949,7.621931,10.947011,55.501159,89.272723,24.395429,55.948755,82.842522,30.297209,38.470464,104.984128,27.955863,14.350666,8.625918,9.942533,6.000000
67 | 27.479936,4.097992,12.034439,54.818352,83.537471,23.730609,48.670413,87.563799,21.889500,49.545667,79.728856,24.308692,24.089367,8.214144,6.436579,6.000000
68 | 36.959546,8.284973,14.947872,68.824398,94.120399,21.986314,66.216127,86.226797,19.891681,68.870759,84.502687,33.513477,39.479300,6.941457,13.541512,6.000000
69 | 31.751109,4.022216,18.241775,50.880285,84.077726,26.394814,47.129386,86.697861,22.057838,45.542544,80.530217,21.078212,23.212825,9.845619,9.567523,6.000000
70 | 32.046934,2.906928,16.507149,65.617373,85.127539,28.785882,59.764329,87.419937,28.859106,55.605317,84.626147,30.414226,24.107689,8.410958,11.024586,6.000000
71 | 44.811824,2.247344,16.590034,3.907380,1.037199,5.722256,24.259901,79.244223,50.637637,27.387484,89.464871,38.621035,18.931667,12.572601,2.455508,7.000000
72 | 37.624495,9.248881,15.595617,13.943688,6.967265,3.055396,23.551784,87.673090,25.656546,26.030782,73.236715,36.542141,17.261648,14.773418,0.548913,7.000000
73 | 46.128234,4.915813,20.525325,18.394542,5.316863,7.119637,36.039645,100.389825,28.537262,32.345505,93.461557,34.594623,16.737188,8.445271,13.111714,7.000000
74 | 44.808399,4.501318,15.921841,5.676711,2.912810,3.308981,47.062820,107.593445,23.310176,44.997130,102.337257,28.679087,22.346223,1.190159,15.552526,7.000000
75 | 26.530469,6.008313,17.595035,18.261555,3.655714,3.716061,38.781065,99.707067,33.522438,39.233322,94.661400,30.749988,17.065474,7.716530,4.521263,7.000000
76 | 25.322479,18.037882,12.067315,32.241247,4.182720,10.241490,64.298837,85.322581,35.394987,66.325426,85.916786,50.186207,33.529981,4.814649,21.073403,7.000000
77 | 26.159149,11.137073,9.392574,32.497866,5.530923,21.209995,58.690552,88.162466,44.455593,66.220628,82.889458,53.075216,26.491866,8.719384,20.402972,7.000000
78 | 27.655743,8.826697,10.426987,24.228312,4.138157,1.587366,43.504149,89.435706,34.546310,49.337679,85.551153,39.430488,23.411189,3.340633,15.438018,7.000000
79 | 36.260453,2.525784,12.413987,6.889512,1.646308,7.154511,41.815285,94.048841,25.811301,32.851651,98.766322,29.309239,13.612372,3.147535,24.566801,7.000000
80 | 24.798043,8.936647,9.570077,23.075096,20.969004,7.107377,43.418623,85.495565,23.589483,45.492214,78.922022,23.059844,20.685546,13.108988,7.381683,7.000000
81 | 24.596646,29.736908,25.081947,8.884024,6.559019,0.456588,29.827859,102.927281,18.240074,12.725527,97.401429,17.777858,15.098036,16.796076,6.547921,8.000000
82 | 50.252621,28.077263,30.040298,22.374885,2.026989,4.306113,48.841783,103.821814,30.767418,54.614023,96.278374,33.056198,26.596671,9.641538,4.516223,8.000000
83 | 46.720347,25.202566,24.210076,17.260718,9.712490,3.675929,28.947328,75.844410,28.755709,30.145959,65.809682,40.230338,16.848285,4.840667,8.473947,8.000000
84 | 28.024332,40.119232,25.496783,13.016671,7.596354,14.071081,32.813221,113.327516,27.055776,24.094514,106.255872,24.239304,2.695157,12.629440,4.089145,8.000000
85 | 34.887251,22.980568,30.863599,3.881194,2.064988,2.772694,38.137402,103.151437,25.618216,29.328686,97.400949,24.351962,5.864652,4.536465,12.272375,8.000000
86 | 44.262576,27.125702,15.256883,20.713364,3.406373,9.884314,48.267375,88.476030,29.769608,53.448143,86.022664,36.539874,34.193978,5.617906,18.224099,8.000000
87 | 34.424282,20.819976,23.608225,11.864899,2.669081,5.032476,42.855215,93.195115,35.297608,43.077306,90.292816,43.513382,13.542847,2.543531,21.420034,8.000000
88 | 23.015253,23.751870,6.535196,3.763148,11.911140,3.379227,11.032025,87.060834,28.273261,6.145853,86.565476,29.938395,12.812652,48.514888,26.460021,8.000000
89 | 25.593816,27.246954,20.144459,20.815079,5.138932,12.616756,49.827635,88.011111,33.921899,49.312024,90.373766,37.904222,21.777395,2.446046,13.132388,8.000000
90 | 25.844453,24.825664,26.760306,17.165493,1.734105,12.963703,47.661407,79.048283,33.757782,44.984442,81.283531,35.549849,6.113230,3.008991,13.152020,8.000000
91 | 37.490134,32.495884,23.745591,10.263570,5.490865,1.261483,15.089296,8.650429,9.230090,48.912789,79.448029,45.186757,67.013845,71.875879,37.987664,9.000000
92 | 29.998130,43.524030,25.121150,15.417454,5.852091,7.589172,16.529002,0.725466,7.439875,24.380792,120.390560,19.199350,15.767035,114.411769,15.472736,9.000000
93 | 34.622599,37.017378,27.669637,2.741039,8.577513,2.243323,14.292178,6.378143,11.999477,45.919511,94.120950,44.025584,55.995302,88.266768,37.774801,9.000000
94 | 46.051695,54.653762,13.225411,14.781907,10.640506,4.251865,26.399216,6.573900,13.331524,71.594673,72.859317,49.275027,84.113429,61.311140,49.315297,9.000000
95 | 42.190255,30.782101,19.135231,8.149782,0.703450,2.855704,12.872799,7.649640,10.776583,41.484759,94.828726,26.366485,53.216605,91.162535,30.145703,9.000000
96 | 38.171631,28.749776,23.954708,7.154842,3.578104,5.526701,9.363086,5.571996,12.836742,43.175990,94.345481,30.334696,56.697473,91.082536,34.231611,9.000000
97 | 33.777163,44.571014,9.712207,2.335842,12.885402,6.266991,6.820075,3.736563,1.888396,65.052365,84.113728,15.254376,46.649516,92.143424,12.844981,9.000000
98 | 33.079898,42.409666,15.798544,5.416477,5.498882,2.514304,11.149445,1.194875,2.339769,48.673194,98.541595,16.054597,34.371564,104.661793,14.501611,9.000000
99 | 40.680805,40.801298,8.909207,5.691228,5.981328,2.100842,4.616145,2.721628,1.298055,40.710221,108.934659,12.880654,35.708418,112.317369,14.130233,9.000000
100 | 36.514748,45.446515,14.760226,4.549033,8.010085,5.285616,6.942849,0.038435,0.997313,46.468994,97.612162,17.154017,36.863595,107.383317,16.551778,9.000000
101 | 35.166499,10.133535,24.003586,20.414161,65.397661,53.280209,1.070313,10.611034,5.501693,5.086899,6.810580,2.684862,12.257347,2.932739,6.857893,10.000000
102 | 27.177540,7.165712,7.382681,27.979051,59.310011,43.088365,5.833023,13.201885,5.084093,5.462635,7.353229,7.037985,20.449650,11.142431,6.311775,10.000000
103 | 34.202671,12.923296,4.436527,7.954484,69.748850,41.098671,3.179318,9.590110,1.405584,8.344494,5.966596,0.871905,10.470855,2.615677,4.257028,10.000000
104 | 25.280928,15.253511,41.613189,19.461021,79.660231,45.791824,1.437713,7.270879,2.006402,2.456731,6.494313,1.439689,7.423358,3.559971,7.069693,10.000000
105 | 20.440146,14.117206,39.528597,16.725306,73.608294,36.204705,12.769179,54.409591,18.337170,9.325091,8.840259,3.291002,10.046129,0.961772,5.568736,10.000000
106 | 42.600813,1.628398,5.641295,29.320372,59.391940,37.741437,2.664271,1.934314,5.444876,8.683744,1.608252,4.105315,14.743984,4.496544,5.945350,10.000000
107 | 29.871450,9.303312,17.007823,31.072375,81.973636,33.115093,3.079640,6.669931,2.969039,11.754539,10.396972,5.003912,17.389056,5.201686,6.154532,10.000000
108 | 38.840654,10.783952,14.997242,40.915437,51.729814,31.135298,3.212290,3.550812,4.857939,13.059346,1.997148,4.008071,22.157437,2.847960,3.501141,10.000000
109 | 36.957795,16.899360,21.456777,49.829542,76.993500,35.017241,8.948312,8.467755,1.604304,6.864062,5.030107,3.714164,20.978079,4.631371,3.630589,10.000000
110 | 36.359711,12.904628,12.775588,55.680258,67.934220,28.683606,6.375559,0.552467,4.157252,14.441847,1.584075,2.392591,33.491625,6.145185,1.497830,10.000000
111 |
--------------------------------------------------------------------------------
/data/handrom_train.csv:
--------------------------------------------------------------------------------
1 | 24.980672,7.836687,12.198086,54.799112,149.431577,42.285964,60.854295,120.394795,85.025315,51.545303,130.710423,74.330390,39.330515,100.898659,79.656672,0.000000
2 | 27.778368,20.923018,15.878485,56.965238,152.964000,41.730110,60.632759,128.091563,86.006800,50.670370,135.271401,78.064358,35.431228,113.289719,72.738860,0.000000
3 | 28.253230,15.691632,22.501012,40.331309,167.285507,37.158387,47.521096,134.794214,74.192608,39.976452,148.073053,57.332697,27.969948,142.009022,48.671489,0.000000
4 | 28.675995,15.062306,20.129900,50.119760,163.817291,29.327212,56.444076,138.334334,63.220250,45.865355,146.061906,51.596180,32.044012,122.905108,28.062548,0.000000
5 | 23.972362,11.765355,18.980875,47.134513,165.459569,36.317885,51.935593,138.614626,78.865742,39.864323,149.500953,60.747533,19.683549,135.718627,33.740904,0.000000
6 | 27.783580,12.287740,17.511700,42.800175,164.590807,41.175678,55.003108,131.291313,78.773776,49.814852,138.453872,67.858153,42.445997,111.326860,83.269350,0.000000
7 | 27.582997,10.612048,16.048186,50.377670,157.103003,36.724371,54.894586,133.302963,71.439204,44.961021,138.011720,59.743336,23.581839,121.843166,20.394588,0.000000
8 | 29.247137,11.857716,16.057565,41.996109,159.189465,34.551147,52.283832,125.505391,74.721660,45.024971,128.715937,60.650899,28.101788,113.983640,15.198230,0.000000
9 | 31.642552,27.283449,22.736593,45.013501,162.378557,28.980256,51.939889,131.959796,62.784720,39.849946,139.720919,47.052839,26.589354,121.929776,16.913772,0.000000
10 | 31.956472,40.550577,59.410367,34.638097,163.310178,33.625908,45.540286,132.444856,65.782252,42.419350,140.348636,49.648872,30.346833,132.799026,32.247155,0.000000
11 | 21.477633,7.113319,5.165919,40.999855,94.061988,34.287945,29.487936,116.494854,26.713977,18.134278,126.572083,32.232763,19.350768,100.322862,41.714466,1.000000
12 | 31.554162,4.472374,8.395717,44.339242,58.615681,37.961702,33.672262,76.098864,42.454786,27.039329,89.155907,47.072395,34.521344,66.247448,63.819846,1.000000
13 | 37.063814,19.403312,7.814462,39.681587,72.236413,44.754032,32.880977,88.963974,41.390164,26.677168,95.667087,40.281745,37.337466,66.785574,54.908393,1.000000
14 | 29.528545,16.023841,0.995855,40.222523,60.191399,35.383227,33.561779,80.162904,33.384381,23.305174,90.314569,41.510099,29.058720,70.232726,53.075547,1.000000
15 | 32.660108,26.309939,16.660749,39.273520,69.309475,38.725787,34.089429,93.917276,35.435773,25.223282,97.841032,41.113381,32.729285,67.994795,53.291261,1.000000
16 | 37.763216,30.288631,24.040320,37.284819,65.682980,43.385032,33.206334,79.437373,44.157359,27.997968,83.415959,43.585994,37.392654,58.047341,50.581197,1.000000
17 | 33.084958,32.079755,29.040894,32.980005,60.613441,37.870700,30.946266,69.310785,39.405582,25.810325,78.428306,35.994690,25.375326,65.185660,38.139771,1.000000
18 | 34.405388,26.981957,13.330024,44.915726,69.493511,40.295784,38.321214,96.100643,37.940608,29.624373,107.189254,38.758777,38.693976,83.036053,46.146845,1.000000
19 | 39.828902,30.199452,18.734031,38.082253,66.499474,43.133046,35.324314,76.628554,43.577052,29.059295,83.098060,39.563511,39.051934,60.390956,40.331934,1.000000
20 | 38.662251,26.773778,9.975648,46.991586,56.555213,40.384134,40.332510,75.878567,39.579019,30.659696,89.343048,42.106874,41.475154,67.038009,54.776279,1.000000
21 | 35.832764,79.534354,22.190897,28.324440,9.907121,6.437652,9.447296,6.193965,9.334546,5.740372,12.243239,10.900193,22.920817,2.348960,12.542933,2.000000
22 | 38.510422,87.482982,17.397301,29.995844,7.935827,7.942670,10.752697,6.745110,9.255018,5.385962,12.874881,12.211182,24.112409,4.490294,17.004607,2.000000
23 | 34.375996,73.974527,24.754854,31.120657,8.546488,7.282532,11.306074,4.444392,8.370283,3.900641,8.846753,6.964540,23.286091,1.864999,8.402216,2.000000
24 | 32.503833,76.240740,26.433529,31.235630,7.507752,6.658946,10.825302,10.530689,6.249863,6.071969,12.658310,8.682494,25.756767,1.421538,10.108059,2.000000
25 | 23.744018,73.480195,23.798883,30.729704,14.363662,8.949000,12.757638,10.016690,4.634576,8.555262,2.689541,3.616634,21.963359,5.639529,9.525494,2.000000
26 | 33.486107,67.324179,26.276275,28.414760,9.230945,7.135567,9.991510,5.028491,7.717026,6.832246,7.932713,5.233004,25.583683,2.080481,7.227445,2.000000
27 | 30.168309,70.969542,32.205064,27.526369,9.944240,8.075054,9.389364,7.984605,9.331336,6.970410,8.225229,7.859249,29.443447,2.937119,9.249800,2.000000
28 | 31.194238,57.566591,39.703982,28.054722,8.811356,5.090669,9.681432,8.450576,8.181026,7.748618,7.735988,3.237065,25.603529,2.326355,4.642785,2.000000
29 | 35.587754,80.020470,20.744294,30.995421,9.068732,7.811603,13.010017,7.375602,7.932416,7.540829,3.582032,8.393649,33.172826,4.158346,14.899550,2.000000
30 | 32.470085,76.429840,22.620470,31.476825,7.977530,8.590058,13.292598,6.334827,8.417723,4.855955,9.802514,10.716954,24.611552,0.940413,13.988034,2.000000
31 | 22.479300,40.233208,67.036035,15.974295,9.905494,4.552424,1.924334,16.692569,6.737317,10.543362,6.333858,1.048378,21.835228,6.948224,7.330450,3.000000
32 | 23.290824,36.919276,59.243925,19.299222,7.550911,2.177158,2.060850,16.636044,5.726581,8.917429,10.669330,5.174115,21.070265,8.027950,9.957394,3.000000
33 | 21.024151,40.163456,59.066326,21.083884,11.146967,3.652880,4.211324,15.074664,5.587046,10.332758,8.650549,5.869982,23.683990,9.880418,11.977771,3.000000
34 | 22.235946,39.738490,60.400840,20.834795,9.333227,1.935512,3.502491,14.338161,6.693658,11.046677,6.713871,3.963731,23.501564,7.278813,7.790933,3.000000
35 | 26.304876,45.566057,64.590795,12.927208,5.189612,11.482090,6.059140,16.706563,9.143405,3.201219,11.589890,2.906998,7.145993,6.988630,7.120544,3.000000
36 | 25.312071,41.987075,71.382830,13.092798,5.035776,8.259406,6.639226,17.571916,7.818047,5.780957,11.624989,5.869027,11.049822,8.839376,10.136944,3.000000
37 | 24.971463,43.983381,62.047571,15.079330,9.676598,6.583385,5.263508,18.179005,8.468395,7.408785,12.885185,1.371180,16.253385,10.286129,8.179925,3.000000
38 | 23.019779,41.016332,58.484368,17.372242,8.946337,3.320532,5.714648,16.730802,9.692179,7.156845,8.262615,2.266728,17.625941,6.552479,7.268329,3.000000
39 | 26.189264,44.191889,62.408085,14.997343,5.004746,8.487306,4.738317,15.467748,8.345673,7.461954,8.332968,3.563215,17.037250,7.342545,7.555611,3.000000
40 | 25.666389,40.612491,71.231588,20.961764,10.391506,6.821114,8.772635,18.101853,8.962714,4.927817,10.321938,4.671901,16.085222,3.900565,1.891323,3.000000
41 | 21.202102,18.523284,9.219103,26.491315,11.670058,7.836982,11.743995,6.895257,10.854348,14.306590,5.679977,6.095020,25.925134,8.514456,9.912693,5.000000
42 | 18.214808,18.439986,6.005788,27.812686,12.163658,6.502393,15.212880,9.091534,10.530123,9.889143,8.441060,5.577102,21.553281,8.562910,11.469539,5.000000
43 | 14.757515,12.995421,7.156902,27.358220,11.535121,7.551199,13.150245,7.965087,8.490933,11.868143,6.384315,5.399020,27.711058,3.908572,10.224543,5.000000
44 | 13.634542,12.725070,10.301115,28.268059,11.028661,8.252470,14.682126,8.200386,10.500822,13.690616,7.096391,7.066143,22.785399,4.222525,10.535989,5.000000
45 | 18.450614,14.773956,10.409221,25.206606,12.311439,9.879131,11.004110,7.455125,11.947796,16.256508,4.608633,9.252899,26.217065,8.501416,12.234386,5.000000
46 | 18.297211,13.101727,10.571010,32.225272,14.600588,7.362581,17.299273,9.708418,4.335881,15.885483,10.265136,1.603028,30.632075,8.853812,4.971644,5.000000
47 | 25.331805,12.556043,15.517905,37.205811,14.114626,6.201578,21.112416,6.889460,6.273254,17.286860,2.230909,4.543966,29.082611,1.540538,0.849100,5.000000
48 | 29.076582,10.853922,10.231537,39.625616,10.033734,5.200170,23.683916,10.031595,4.855586,16.453337,7.973402,6.468697,21.057331,4.607568,7.536145,5.000000
49 | 31.605327,9.889499,14.981022,43.239472,11.730687,4.233398,25.597082,6.905698,7.466872,15.845175,4.905297,8.585843,29.361060,0.651012,5.815040,5.000000
50 | 17.378155,13.522293,10.688753,31.245072,12.905821,9.104013,16.534070,7.934471,5.576988,15.307555,5.137994,1.495535,28.773005,5.531837,5.357963,5.000000
51 | 22.540392,7.975358,14.583452,27.260001,5.960455,2.698397,7.462900,13.875745,3.934212,10.392291,8.554501,3.418969,26.460748,5.386650,11.504141,4.000000
52 | 21.252498,9.572638,13.047589,29.531380,8.157350,4.459736,9.510163,14.133387,5.132636,7.696712,7.673124,4.081244,22.410690,5.151429,9.993897,4.000000
53 | 21.615291,8.817530,14.012632,27.122992,5.883064,3.018872,7.225376,14.412378,3.772858,10.254862,8.104627,5.454421,24.762186,8.459349,13.759472,4.000000
54 | 19.919600,7.029719,12.619832,30.395712,2.861364,1.999099,10.278523,11.439746,4.179174,13.117478,3.002854,5.860521,27.097180,4.479328,16.042752,4.000000
55 | 20.592580,8.337752,13.976443,28.499198,4.194280,1.849648,10.499883,14.194021,4.233971,10.522489,9.514510,5.388564,23.240210,7.205099,14.672210,4.000000
56 | 20.847649,8.795391,13.664240,27.772561,5.613650,3.186849,9.178398,14.919599,4.319260,8.091681,8.479897,6.012374,21.909198,7.984232,13.595576,4.000000
57 | 19.792088,8.266345,13.355282,28.236172,4.849156,2.887348,8.813806,11.838671,4.304404,11.109292,3.344327,4.085917,26.464145,6.359762,13.385742,4.000000
58 | 20.485052,8.309982,14.368009,28.524444,4.820090,1.493171,9.705231,14.380765,3.099102,9.393969,7.142402,4.988604,23.038336,5.169868,14.085092,4.000000
59 | 21.046851,6.921125,15.484157,28.733976,3.023861,2.161805,9.222091,10.822185,3.907296,12.783563,5.168014,6.202989,26.686237,6.429382,16.008585,4.000000
60 | 18.594501,7.436277,12.662291,28.107907,4.367439,1.558868,8.557479,13.043491,4.057493,11.410553,4.465351,4.340432,24.763384,2.046462,13.534055,4.000000
61 | 37.149256,38.590075,13.708292,4.128614,6.374718,7.263470,18.485715,18.974774,13.231541,7.569011,26.188266,8.170061,26.491809,37.370945,7.028859,6.000000
62 | 41.320672,50.762978,14.067883,3.234356,6.283897,6.350027,19.149663,17.147302,11.743074,3.449296,36.226201,16.266229,29.561021,27.669231,17.855836,6.000000
63 | 39.752560,60.657628,12.488850,4.189097,3.015528,1.975925,12.325735,7.826493,5.383029,13.406100,20.186253,13.919919,37.262153,52.091827,39.365681,6.000000
64 | 38.819805,57.024414,9.823122,6.203312,4.396216,7.868157,8.298203,7.706825,4.986412,14.611068,25.235321,10.806337,33.182677,48.883124,35.114938,6.000000
65 | 42.655558,57.375734,10.034795,5.207012,7.849752,1.780630,9.162498,5.009651,11.324765,6.821544,40.979628,29.952271,29.868513,42.680140,33.906748,6.000000
66 | 41.139131,63.109532,12.160901,3.405289,2.356179,2.481547,17.405802,11.246595,4.185575,10.965256,27.065727,6.737836,36.633181,33.465734,20.964098,6.000000
67 | 37.433725,46.351477,13.715816,7.453569,6.443460,5.532805,21.713660,16.773146,8.149731,10.832615,30.341696,7.571042,28.177957,39.168665,16.891807,6.000000
68 | 38.512712,53.147500,13.518451,3.094810,1.955753,4.383393,17.625983,15.458993,6.095049,6.493764,31.560551,5.802108,32.641259,29.635781,16.828846,6.000000
69 | 37.671748,52.413328,16.137471,4.008517,5.835990,4.788203,17.176833,19.624005,6.472517,7.741927,36.450148,6.159748,31.891899,38.157599,21.962429,6.000000
70 | 37.064584,50.160315,18.791848,3.173202,0.566580,2.538843,16.905359,15.340525,4.229249,6.654419,30.008033,5.482276,32.792877,30.761226,16.285375,6.000000
71 |
--------------------------------------------------------------------------------
/data/sample/hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/hand.png
--------------------------------------------------------------------------------
/data/sample/lower_limb1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb1.png
--------------------------------------------------------------------------------
/data/sample/lower_limb2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb2.png
--------------------------------------------------------------------------------
/data/sample/lower_limb3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb3.png
--------------------------------------------------------------------------------
/data/sample/lower_limb4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb4.png
--------------------------------------------------------------------------------
/data/sample/lower_limb5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb5.png
--------------------------------------------------------------------------------
/data/sample/lower_limb6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/lower_limb6.png
--------------------------------------------------------------------------------
/data/sample/mona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/mona.png
--------------------------------------------------------------------------------
/data/sample/object.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/object.png
--------------------------------------------------------------------------------
/data/sample/upper_limb1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb1.png
--------------------------------------------------------------------------------
/data/sample/upper_limb2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb2.png
--------------------------------------------------------------------------------
/data/sample/upper_limb3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb3.png
--------------------------------------------------------------------------------
/data/sample/upper_limb4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb4.png
--------------------------------------------------------------------------------
/data/sample/upper_limb5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb5.png
--------------------------------------------------------------------------------
/data/sample/upper_limb6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/upper_limb6.png
--------------------------------------------------------------------------------
/data/sample/wrist_extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/wrist_extension.png
--------------------------------------------------------------------------------
/data/sample/wrist_flexion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/wrist_flexion.png
--------------------------------------------------------------------------------
/data/sample/wrist_radial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/wrist_radial.png
--------------------------------------------------------------------------------
/data/sample/wrist_ulnar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/data/sample/wrist_ulnar.png
--------------------------------------------------------------------------------
/doc/00_image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/00_image.gif
--------------------------------------------------------------------------------
/doc/02_gesture.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/02_gesture.gif
--------------------------------------------------------------------------------
/doc/03_game_rps.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/03_game_rps.gif
--------------------------------------------------------------------------------
/doc/04_hand_rom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/04_hand_rom.gif
--------------------------------------------------------------------------------
/doc/05_wrist_rom.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/05_wrist_rom.gif
--------------------------------------------------------------------------------
/doc/06_face_mask.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/06_face_mask.gif
--------------------------------------------------------------------------------
/doc/07_triangulate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/07_triangulate.gif
--------------------------------------------------------------------------------
/doc/08_skeleton_3D.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/08_skeleton_3D.gif
--------------------------------------------------------------------------------
/doc/09_objectron.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/09_objectron.gif
--------------------------------------------------------------------------------
/doc/rris_database.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kairess/Rock-Paper-Scissors-Machine/fd5f4f2f6541206675d0833e096b66db90a9a61c/doc/rris_database.gif
--------------------------------------------------------------------------------
/dual.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import mediapipe as mp
3 | import numpy as np
4 |
5 | max_num_hands = 2
6 | gesture = {
7 | 0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
8 | 6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok',
9 | }
10 | rps_gesture = {0:'rock', 5:'paper', 9:'scissors'}
11 |
12 | # MediaPipe hands model
13 | mp_hands = mp.solutions.hands
14 | mp_drawing = mp.solutions.drawing_utils
15 | hands = mp_hands.Hands(
16 | max_num_hands=max_num_hands,
17 | min_detection_confidence=0.5,
18 | min_tracking_confidence=0.5)
19 |
20 | # Gesture recognition model
21 | file = np.genfromtxt('data/gesture_train.csv', delimiter=',')
22 | angle = file[:,:-1].astype(np.float32)
23 | label = file[:, -1].astype(np.float32)
24 | knn = cv2.ml.KNearest_create()
25 | knn.train(angle, cv2.ml.ROW_SAMPLE, label)
26 |
27 | cap = cv2.VideoCapture(0)
28 |
29 | while cap.isOpened():
30 | ret, img = cap.read()
31 | if not ret:
32 | continue
33 |
34 | img = cv2.flip(img, 1)
35 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
36 |
37 | result = hands.process(img)
38 |
39 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
40 |
41 | if result.multi_hand_landmarks is not None:
42 | rps_result = []
43 |
44 | for res in result.multi_hand_landmarks:
45 | joint = np.zeros((21, 3))
46 | for j, lm in enumerate(res.landmark):
47 | joint[j] = [lm.x, lm.y, lm.z]
48 |
49 | # Compute angles between joints
50 | v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:] # Parent joint
51 | v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:] # Child joint
52 | v = v2 - v1 # [20,3]
53 | # Normalize v
54 | v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
55 |
56 | # Get angle using arcos of dot product
57 | angle = np.arccos(np.einsum('nt,nt->n',
58 | v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
59 | v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
60 |
61 | angle = np.degrees(angle) # Convert radian to degree
62 |
63 | # Inference gesture
64 | data = np.array([angle], dtype=np.float32)
65 | ret, results, neighbours, dist = knn.findNearest(data, 3)
66 | idx = int(results[0][0])
67 |
68 | # Draw gesture result
69 | if idx in rps_gesture.keys():
70 | org = (int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0]))
71 | cv2.putText(img, text=rps_gesture[idx].upper(), org=(org[0], org[1] + 20), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)
72 |
73 | rps_result.append({
74 | 'rps': rps_gesture[idx],
75 | 'org': org
76 | })
77 |
78 | mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
79 |
80 | # Who wins?
81 | if len(rps_result) >= 2:
82 | winner = None
83 | text = ''
84 |
85 | if rps_result[0]['rps']=='rock':
86 | if rps_result[1]['rps']=='rock' : text = 'Tie'
87 | elif rps_result[1]['rps']=='paper' : text = 'Paper wins' ; winner = 1
88 | elif rps_result[1]['rps']=='scissors': text = 'Rock wins' ; winner = 0
89 | elif rps_result[0]['rps']=='paper':
90 | if rps_result[1]['rps']=='rock' : text = 'Paper wins' ; winner = 0
91 | elif rps_result[1]['rps']=='paper' : text = 'Tie'
92 | elif rps_result[1]['rps']=='scissors': text = 'Scissors wins'; winner = 1
93 | elif rps_result[0]['rps']=='scissors':
94 | if rps_result[1]['rps']=='rock' : text = 'Rock wins' ; winner = 1
95 | elif rps_result[1]['rps']=='paper' : text = 'Scissors wins'; winner = 0
96 | elif rps_result[1]['rps']=='scissors': text = 'Tie'
97 |
98 | if winner is not None:
99 | cv2.putText(img, text='Winner', org=(rps_result[winner]['org'][0], rps_result[winner]['org'][1] + 70), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=2, color=(0, 255, 0), thickness=3)
100 | cv2.putText(img, text=text, org=(int(img.shape[1] / 2), 100), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=2, color=(0, 0, 255), thickness=3)
101 |
102 | cv2.imshow('Game', img)
103 | if cv2.waitKey(1) == ord('q'):
104 | break
105 |
--------------------------------------------------------------------------------
/environment.yaml:
--------------------------------------------------------------------------------
1 | name: mp
2 | channels:
3 | - open3d-admin
4 | - conda-forge
5 | - defaults
6 | dependencies:
7 | - pip=20.2.2
8 | - python=3.7.7
9 | - numpy=1.19.1
10 | - pandas=1.1.4
11 | - plyfile=0.7.2
12 | - pyyaml=5.3.1
13 | - tqdm=4.54.1
14 | - matplotlib=3.3.3
15 | - open3d=0.11.2.0
16 | - pip:
17 | - opencv-python==4.2.0.34
18 | - mediapipe==0.8.4.2
19 | - addict==2.4.0
20 | - sklearn==0.0
--------------------------------------------------------------------------------
/fan.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import mediapipe as mp
3 | import numpy as np
4 | from dynamikontrol import Module
5 |
6 | module = Module()
7 |
8 | max_num_hands = 1
9 | gesture = {
10 | 0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
11 | 6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok',
12 | }
13 | rps_gesture = {0:'OFF', 1:'LOW', 2:'MID', 3: 'HIGH'}
14 |
15 | # MediaPipe hands model
16 | mp_hands = mp.solutions.hands
17 | mp_drawing = mp.solutions.drawing_utils
18 | hands = mp_hands.Hands(
19 | max_num_hands=max_num_hands,
20 | min_detection_confidence=0.5,
21 | min_tracking_confidence=0.5)
22 |
23 | # Gesture recognition model
24 | file = np.genfromtxt('data/gesture_train.csv', delimiter=',')
25 | angle = file[:,:-1].astype(np.float32)
26 | label = file[:, -1].astype(np.float32)
27 | knn = cv2.ml.KNearest_create()
28 | knn.train(angle, cv2.ml.ROW_SAMPLE, label)
29 |
30 | cap = cv2.VideoCapture(0)
31 |
32 | while cap.isOpened():
33 | ret, img = cap.read()
34 | if not ret:
35 | break
36 |
37 | img = cv2.flip(img, 1)
38 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
39 |
40 | result = hands.process(img)
41 |
42 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
43 |
44 | if result.multi_hand_landmarks is not None:
45 | for res in result.multi_hand_landmarks:
46 | joint = np.zeros((21, 3))
47 | for j, lm in enumerate(res.landmark):
48 | joint[j] = [lm.x, lm.y, lm.z]
49 |
50 | # Compute angles between joints
51 | v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:] # Parent joint
52 | v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:] # Child joint
53 | v = v2 - v1 # [20,3]
54 | # Normalize v
55 | v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
56 |
57 | # Get angle using arcos of dot product
58 | angle = np.arccos(np.einsum('nt,nt->n',
59 | v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
60 | v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
61 |
62 | angle = np.degrees(angle) # Convert radian to degree
63 |
64 | # Inference gesture
65 | data = np.array([angle], dtype=np.float32)
66 | ret, results, neighbours, dist = knn.findNearest(data, 5)
67 | idx = int(results[0][0])
68 |
69 | # Draw gesture result
70 | if idx in rps_gesture.keys():
71 | cv2.putText(img, text=rps_gesture[idx].upper(), org=(int(res.landmark[0].x * img.shape[1]), int(res.landmark[0].y * img.shape[0] + 20)), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)
72 |
73 | if idx > 0:
74 | module.motor.speed(idx * 1000)
75 | else:
76 | module.motor.stop()
77 |
78 | mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
79 |
80 | cv2.imshow('AI Fan', img)
81 | if cv2.waitKey(1) == ord('q'):
82 | break
83 |
84 | module.motor.stop()
85 | module.disconnect()
86 |
--------------------------------------------------------------------------------
/fy_filter.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import mediapipe as mp
3 | import numpy as np
4 |
5 | max_num_hands = 1
6 | gesture = {
7 | 0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
8 | 6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok', 11:'fy'
9 | }
10 |
11 | # MediaPipe hands model
12 | mp_hands = mp.solutions.hands
13 | mp_drawing = mp.solutions.drawing_utils
14 | hands = mp_hands.Hands(
15 | max_num_hands=max_num_hands,
16 | min_detection_confidence=0.5,
17 | min_tracking_confidence=0.5)
18 |
19 | # Gesture recognition model
20 | file = np.genfromtxt('data/gesture_train_fy.csv', delimiter=',')
21 | angle = file[:,:-1].astype(np.float32)
22 | label = file[:, -1].astype(np.float32)
23 | knn = cv2.ml.KNearest_create()
24 | knn.train(angle, cv2.ml.ROW_SAMPLE, label)
25 |
26 | cap = cv2.VideoCapture(0)
27 |
28 | while cap.isOpened():
29 | ret, img = cap.read()
30 | if not ret:
31 | continue
32 |
33 | img = cv2.flip(img, 1)
34 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
35 |
36 | result = hands.process(img)
37 |
38 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
39 |
40 | if result.multi_hand_landmarks is not None:
41 | for res in result.multi_hand_landmarks:
42 | joint = np.zeros((21, 3))
43 | for j, lm in enumerate(res.landmark):
44 | joint[j] = [lm.x, lm.y, lm.z]
45 |
46 | # Compute angles between joints
47 | v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:] # Parent joint
48 | v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:] # Child joint
49 | v = v2 - v1 # [20,3]
50 | # Normalize v
51 | v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
52 |
53 | # Get angle using arcos of dot product
54 | angle = np.arccos(np.einsum('nt,nt->n',
55 | v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
56 | v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
57 |
58 | angle = np.degrees(angle) # Convert radian to degree
59 |
60 | # Inference gesture
61 | data = np.array([angle], dtype=np.float32)
62 | ret, results, neighbours, dist = knn.findNearest(data, 3)
63 | idx = int(results[0][0])
64 |
65 | if idx == 11:
66 | x1, y1 = tuple((joint.min(axis=0)[:2] * [img.shape[1], img.shape[0]] * 0.95).astype(int))
67 | x2, y2 = tuple((joint.max(axis=0)[:2] * [img.shape[1], img.shape[0]] * 1.05).astype(int))
68 |
69 | fy_img = img[y1:y2, x1:x2].copy()
70 | fy_img = cv2.resize(fy_img, dsize=None, fx=0.05, fy=0.05, interpolation=cv2.INTER_NEAREST)
71 | fy_img = cv2.resize(fy_img, dsize=(x2 - x1, y2 - y1), interpolation=cv2.INTER_NEAREST)
72 |
73 | img[y1:y2, x1:x2] = fy_img
74 |
75 | # mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
76 |
77 | cv2.imshow('Filter', img)
78 | if cv2.waitKey(1) == ord('q'):
79 | break
80 |
--------------------------------------------------------------------------------
/gather_dataset.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import mediapipe as mp
3 | import numpy as np
4 |
5 | max_num_hands = 1
6 | gesture = {
7 | 0:'fist', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five',
8 | 6:'six', 7:'rock', 8:'spiderman', 9:'yeah', 10:'ok', 11:'fy'
9 | }
10 |
11 | # MediaPipe hands model
12 | mp_hands = mp.solutions.hands
13 | mp_drawing = mp.solutions.drawing_utils
14 | hands = mp_hands.Hands(
15 | max_num_hands=max_num_hands,
16 | min_detection_confidence=0.5,
17 | min_tracking_confidence=0.5)
18 |
19 | # Gesture recognition data
20 | file = np.genfromtxt('data/gesture_train.csv', delimiter=',')
21 | print(file.shape)
22 |
23 | cap = cv2.VideoCapture(0)
24 |
25 | def click(event, x, y, flags, param):
26 | global data, file
27 | if event == cv2.EVENT_LBUTTONDOWN:
28 | file = np.vstack((file, data))
29 | print(file.shape)
30 |
31 | cv2.namedWindow('Dataset')
32 | cv2.setMouseCallback('Dataset', click)
33 |
34 | while cap.isOpened():
35 | ret, img = cap.read()
36 | if not ret:
37 | continue
38 |
39 | img = cv2.flip(img, 1)
40 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
41 |
42 | result = hands.process(img)
43 |
44 | img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
45 |
46 | if result.multi_hand_landmarks is not None:
47 | for res in result.multi_hand_landmarks:
48 | joint = np.zeros((21, 3))
49 | for j, lm in enumerate(res.landmark):
50 | joint[j] = [lm.x, lm.y, lm.z]
51 |
52 | # Compute angles between joints
53 | v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19],:] # Parent joint
54 | v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],:] # Child joint
55 | v = v2 - v1 # [20,3]
56 | # Normalize v
57 | v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
58 |
59 | # Get angle using arcos of dot product
60 | angle = np.arccos(np.einsum('nt,nt->n',
61 | v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
62 | v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
63 |
64 | angle = np.degrees(angle) # Convert radian to degree
65 |
66 | data = np.array([angle], dtype=np.float32)
67 | data = np.append(data, 11)
68 |
69 | mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
70 |
71 | cv2.imshow('Dataset', img)
72 | if cv2.waitKey(1) == ord('q'):
73 | break
74 |
75 | np.savetxt('data/gesture_train_fy.csv', file, delimiter=',')
76 |
--------------------------------------------------------------------------------
/java/README.md:
--------------------------------------------------------------------------------
1 | ## Sample application of MediaPipe using JavaScript
2 |
3 | Click on the links below to try out realtime, online, markerless pose estimation from color camera. Note: You will need to allow the website to access the camera connected to your computer, and the recommended camera resolution is HD 720p (1280 x 720).
4 |
5 | * [Extract left and right elbow flexion angles](https://codepen.io/rris/full/YzpLrVg)
6 | * [Extract hand flexion angles](https://codepen.io/rris/full/vYyVyoa)
7 | * [Fruit ninja game with wrist joint](https://codepen.io/rris/full/ExNOLNp)
8 | * [Fruit ninja game with index fingertip](https://codepen.io/rris/full/jOVQjbj)
9 |
10 | Refer to [MediaPipe project on CodePen](https://codepen.io/mediapipe) for more examples.
11 |
--------------------------------------------------------------------------------
/java/elbow/codepen.css:
--------------------------------------------------------------------------------
1 | @keyframes spin {
2 | 0% { transform: rotate(0deg); }
3 | 100% { transform: rotate(360deg); }
4 | }
5 |
6 | .abs {
7 | position: absolute;
8 | }
9 |
10 | a {
11 | color: white;
12 | text-decoration: none;
13 | &:hover {
14 | color: lightblue;
15 | }
16 | }
17 |
18 | body {
19 | bottom: 0;
20 | font-family: 'Titillium Web', sans-serif;
21 | color: white;
22 | left: 0;
23 | margin: 0;
24 | position: absolute;
25 | right: 0;
26 | top: 0;
27 | transform-origin: 0px 0px;
28 | overflow: hidden;
29 | }
30 |
31 | .container {
32 | position: absolute;
33 | background-color: #596e73;
34 | height: 720px;
35 | width: 1280px;
36 | }
37 |
38 | .input_video {
39 | position:relative;
40 | top: 0;
41 | left: 0;
42 | right: 0;
43 | bottom: 0;
44 | &.selfie {
45 | transform: scale(-1, 1);
46 | }
47 | }
48 |
49 | .output_canvas {
50 | position:absolute;
51 | height: 720px;
52 | width: 1280px;
53 | left: 0;
54 | top: 0;
55 | }
56 |
57 | .logo {
58 | bottom: 10px;
59 | right: 20px;
60 |
61 | .title {
62 | color: white;
63 | font-size: 28px;
64 | }
65 |
66 | .subtitle {
67 | position: relative;
68 | color: white;
69 | font-size: 10px;
70 | left: -30px;
71 | top: 20px;
72 | }
73 | }
74 |
75 | .control-panel {
76 | position: absolute;
77 | left: 10px;
78 | top: 10px;
79 | }
80 |
81 | .loading {
82 | display: flex;
83 | position: absolute;
84 | top: 0;
85 | right: 0;
86 | bottom: 0;
87 | left: 0;
88 | align-items: center;
89 | backface-visibility: hidden;
90 | justify-content: center;
91 | opacity: 1;
92 | transition: opacity 1s;
93 |
94 | .message {
95 | font-size: x-large;
96 | }
97 |
98 | .spinner {
99 | position: absolute;
100 | width: 120px;
101 | height: 120px;
102 | animation: spin 1s linear infinite;
103 | border: 32px solid #bebebe;
104 | border-top: 32px solid #3498db;
105 | border-radius: 50%;
106 | }
107 | }
108 |
109 | .loaded .loading {
110 | opacity: 0;
111 | }
112 |
113 | .shoutout {
114 | left: 0;
115 | right: 0;
116 | bottom: 40px;
117 | text-align: center;
118 | font-size: 24px;
119 | position: absolute;
120 | background-color: black;
121 | color: rgb(255,255,255);
122 | }
123 |
--------------------------------------------------------------------------------
/java/elbow/codepen.html:
--------------------------------------------------------------------------------
1 |