├── src ├── utils │ ├── __init__.py │ ├── visualization.py │ ├── structuring.py │ └── mediapipe.py ├── model │ ├── knnclassifier_file │ ├── KNNclassifier.py │ └── KNNclassifier.ipynb ├── lib │ └── coordinates_mediapipe.so ├── build.py ├── predict.py └── preprocess.py ├── docs ├── images │ ├── palm.png │ └── video_wth_mediapipe.gif └── data_example.csv ├── .gitignore ├── requiriments.txt ├── README.md ├── mediapipe └── demo_run_graph_main_out.cc └── LICENSE /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .mediapipe import MediapipeManager 2 | from . import structuring 3 | -------------------------------------------------------------------------------- /docs/images/palm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samborba/training-mediapipe-model/HEAD/docs/images/palm.png -------------------------------------------------------------------------------- /src/model/knnclassifier_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samborba/training-mediapipe-model/HEAD/src/model/knnclassifier_file -------------------------------------------------------------------------------- /src/lib/coordinates_mediapipe.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samborba/training-mediapipe-model/HEAD/src/lib/coordinates_mediapipe.so -------------------------------------------------------------------------------- /docs/images/video_wth_mediapipe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samborba/training-mediapipe-model/HEAD/docs/images/video_wth_mediapipe.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | env 3 | .pylintrc 4 | __pycache__/ 5 | 6 | # IDE 7 | .vscode 8 | 9 | # Data 10 | data/ 11 | 12 | # Mediapipe 13 | mediapipe/ 14 | !mediapipe/demo_run_graph_main_out.cc 15 | 16 | # Others 17 | *.log 18 | 19 | # Jupyter 20 | *.ipynb_checkpoints 21 | -------------------------------------------------------------------------------- /requiriments.txt: -------------------------------------------------------------------------------- 1 | astroid==2.3.3 2 | cycler==0.10.0 3 | isort==4.3.21 4 | joblib==0.14.1 5 | kiwisolver==1.1.0 6 | lazy-object-proxy==1.4.3 7 | matplotlib==3.1.3 8 | mccabe==0.6.1 9 | numpy==1.18.1 10 | pylint==2.4.4 11 | pyparsing==2.4.6 12 | python-dateutil==2.8.1 13 | scikit-learn==0.22.2 14 | scipy==1.4.1 15 | six==1.14.0 16 | typed-ast==1.4.1 17 | wrapt==1.11.2 18 | -------------------------------------------------------------------------------- /src/model/KNNclassifier.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import pandas as pd 4 | import numpy as np 5 | from sklearn import neighbors 6 | from sklearn.model_selection import train_test_split 7 | 8 | DATA_FOLDER_PATH = os.path.abspath("data") 9 | TRAINING_DATASET = DATA_FOLDER_PATH + "/" + "dataset.csv" 10 | MODEL_BINARY_DESTINATION = os.path.abspath("src/model") 11 | 12 | df = pd.read_csv(TRAINING_DATASET, index_col=False) 13 | 14 | X = np.array(df.drop(['class'], 1)) 15 | y = np.array(df['class']) 16 | 17 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 18 | 19 | clf = neighbors.KNeighborsClassifier() 20 | clf.fit(X_train, y_train) 21 | 22 | knnPickle = open(MODEL_BINARY_DESTINATION + "/" + "knnclassifier_file", "wb") 23 | pickle.dump(clf, knnPickle) 24 | -------------------------------------------------------------------------------- /src/build.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | from utils import structuring 5 | 6 | 7 | def main(datasets): 8 | """Concatenates all the main csv files (which contains the output of the coordinates \ 9 | of all the videos) of each dataset in just one file in which the model will be trained. 10 | 11 | Arguments: 12 | datasets {str} -- all datasets with classes to train the model 13 | """ 14 | logging.info("Starting main csv file concatenation...") 15 | structuring.mount_dataset(datasets) 16 | logging.info(">>> Concatenation done successfully.") 17 | 18 | 19 | if __name__ == "__main__": 20 | parser = argparse.ArgumentParser(description="Build model.") 21 | parser.add_argument("-d", "--datasets_compile", 22 | help="Array of datasets folders which contains main csv.", 23 | required=True, 24 | type=str) 25 | arguments = parser.parse_args() 26 | 27 | dataset_list = arguments.datasets_compile 28 | main(dataset_list) 29 | -------------------------------------------------------------------------------- /src/predict.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import logging 4 | import pickle 5 | from random import randrange 6 | from statistics import mode 7 | import pandas as pd 8 | 9 | from utils.mediapipe import MediapipeManager 10 | from utils import structuring 11 | 12 | MODEL_BINARY_FILE = "knnclassifier_file" 13 | OUTPUT_PATH = os.getcwd() + "/data/" 14 | 15 | def start_predict(video_path, k_frames=10): 16 | classification_list = [] 17 | mediapipe = MediapipeManager(OUTPUT_PATH) 18 | file_name = video_path.split("/")[-1].split(".")[0] + ".csv" 19 | 20 | logging.info("Starting prediction.") 21 | model = pickle.load(open(os.path.abspath(f"src/model/{MODEL_BINARY_FILE}"), "rb")) 22 | 23 | mediapipe.run_mediapipe(video_path) 24 | dataframe = pd.read_csv(OUTPUT_PATH + file_name, index_col=False) 25 | 26 | logging.info("Running model in %i differents frames.", k_frames) 27 | for _ in range(1, k_frames): 28 | frame = structuring.get_row(dataframe, randrange(1, len(dataframe))) 29 | frame = frame.reshape(1, -1) 30 | classification_list.append(model.predict(frame)[0]) 31 | 32 | os.remove(OUTPUT_PATH + file_name) 33 | 34 | logging.info("Classification: %s", mode(classification_list)) 35 | 36 | 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser(description="Run model.") 39 | parser.add_argument("-i", "--input_video_path", 40 | help="Path to the video.", 41 | required=True) 42 | arguments = parser.parse_args() 43 | 44 | input_video_path = arguments.input_video_path 45 | start_predict(input_video_path) 46 | -------------------------------------------------------------------------------- /src/utils/visualization.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import seaborn as sns 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | from pylab import rcParams 6 | 7 | import structuring 8 | 9 | 10 | def plot_frame(data_path, frame_index): 11 | """Show landmarks of a frame into a graph. 12 | 13 | Arguments: 14 | csv {str} -- path to the .csv file 15 | frame_index {int} -- index of the frame 16 | """ 17 | frame_content = structuring.get_row(data_path, frame_index) 18 | x, y = frame_content.T 19 | plt.scatter(x, y) 20 | plt.show() 21 | 22 | 23 | def plot_correlation_matrix(data_path): 24 | """Show correlation matrix between variables. 25 | 26 | Arguments: 27 | data {str} -- path to the .csv file 28 | """ 29 | rcParams['figure.figsize'] = 15, 20 30 | fig = plt.figure() 31 | data = pd.read_csv(data_path) 32 | sns.heatmap(data.corr(), annot=True, fmt=".2f") 33 | fig.savefig('correlation.png') 34 | 35 | 36 | 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser(description="Plot frame into a graph.") 39 | parser.add_argument("-i", "--input_dataset_path", 40 | help="csv file path.", 41 | required=True) 42 | parser.add_argument("-f", "--frame", 43 | help="Frame index (same as row index).", 44 | type=int) 45 | arguments = parser.parse_args() 46 | 47 | input_data_path = arguments.input_dataset_path 48 | frame = arguments.frame 49 | 50 | if frame is not None: 51 | plot_frame(input_data_path, frame) 52 | else: 53 | plot_correlation_matrix(input_data_path) 54 | -------------------------------------------------------------------------------- /src/utils/structuring.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | import numpy as np 3 | import pandas as pd 4 | 5 | 6 | def add_label(csv_path, output_folder, label): 7 | """Add column with classification label in a csv file. 8 | 9 | Arguments: 10 | csv_path {str} -- path of csv file 11 | output_folder {str} -- path where the new csv will be saved 12 | label {srt} -- classification label 13 | """ 14 | csv_name = csv_path.split("/")[-1] 15 | df = pd.read_csv(csv_path, index_col=False) 16 | df["class"] = label 17 | df.to_csv(f"{output_folder}/{csv_name}", index=False) 18 | 19 | 20 | def convert_to_one(dataset_folder): 21 | """Concatenate all files of type csv, which it was generated by the input dataset, \ 22 | to a single one. 23 | 24 | Arguments: 25 | dataset_folder {str} -- path to the folder with all csv files 26 | """ 27 | folder_name = dataset_folder.split("/")[-2] 28 | csv_list = [csv for csv in glob(dataset_folder + "/*.csv")] 29 | combine_csv = pd.concat([pd.read_csv(csv, index_col=False) for csv in csv_list]) 30 | combine_csv.to_csv(f"{dataset_folder}/{folder_name}_dataset.csv", index=False) 31 | 32 | 33 | def mount_dataset(dataset_list): 34 | """Concatenate the main csv files from the datasets to a single one file, \ 35 | where it will be used to train the model. 36 | """ 37 | dataset_list = dataset_list.split(",") 38 | csv_list = [f"data/{dataset}/{dataset}_dataset.csv" for dataset in dataset_list] 39 | concat_csv = pd.concat([pd.read_csv(csv, index_col=False) for csv in csv_list]) 40 | concat_csv.to_csv(f"data/dataset.csv", index=False) 41 | 42 | 43 | def get_row(dataframe, position): 44 | """Select a row in csv file 45 | 46 | Arguments: 47 | path {str} -- path to the csv file 48 | position {int} -- position of the row 49 | 50 | Returns: 51 | numpy.ndarray -- array of landmarks of a frame (row) 52 | """ 53 | selected_row = dataframe.iloc[position].values.tolist() 54 | return np.array([selected_row[i:i+2] for i in range(0, 42, 2)]) 55 | -------------------------------------------------------------------------------- /src/utils/mediapipe.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import logging 3 | import os 4 | 5 | logging.basicConfig(filename="execution.log", filemode='a', 6 | format='[%(asctime)s] # %(message)s', level=logging.INFO) 7 | 8 | class MediapipeManager(): 9 | """Responsible for managing requests for MediaPipe. 10 | 11 | Raises: 12 | FileExistsError: [description] 13 | """ 14 | def __init__(self, output_folder): 15 | """Initializes the class constructor 16 | 17 | Arguments: 18 | output_folder {str} -- path from which the file that will be\ 19 | generated by Mediapipe will be saved 20 | """ 21 | self.output_folder = output_folder.encode("utf-8") 22 | self._graph_path = "mediapipe/graphs/hand_tracking/hand_tracking_desktop_live.pbtxt".encode("utf-8") 23 | self._binary_path = os.path.abspath("src/lib/coordinates_mediapipe.so").encode("utf-8") 24 | 25 | def run_mediapipe(self, video_path, name=None): 26 | """Apply the Media Pipe Framework to the provide video. 27 | 28 | Arguments: 29 | video_path {str} -- video path 30 | 31 | Raises: 32 | FileExistsError: provider video path not found 33 | """ 34 | func = ctypes.cdll.LoadLibrary(self._binary_path) 35 | func.RunMPPGraph.argtypes = [ctypes.c_char_p, 36 | ctypes.c_char_p, 37 | ctypes.c_char_p, 38 | ctypes.c_char_p] 39 | file_name = video_path.split("/")[-1] 40 | 41 | logging.info("Reaching %s file.", file_name) 42 | if not os.path.exists(video_path): 43 | raise FileExistsError 44 | 45 | logging.info("Applying mediapipe hand-tracking.") 46 | try: 47 | func.RunMPPGraph("".encode("utf-8"), 48 | video_path.encode("utf-8"), 49 | self.output_folder, 50 | self._graph_path) 51 | except ProcessLookupError: 52 | print(ProcessLookupError) 53 | -------------------------------------------------------------------------------- /src/model/KNNclassifier.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os\n", 12 | "from glob import glob\n", 13 | "import pandas as pd\n", 14 | "import numpy as np\n", 15 | "from sklearn import neighbors, preprocessing\n", 16 | "from sklearn.model_selection import train_test_split\n", 17 | "\n", 18 | "DATA_FOLDER_PATH = os.path.abspath(\"../../data\")\n", 19 | "TRAINING_DATASET = DATA_FOLDER_PATH + \"/\" + \"dataset.csv\"\n", 20 | "df = pd.read_csv(TRAINING_DATASET, index_col=False)" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "X = np.array(df.drop(['class'], 1))\n", 30 | "y = np.array(df['class'])\n", 31 | "\n", 32 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 3, 38 | "metadata": {}, 39 | "outputs": [ 40 | { 41 | "data": { 42 | "text/plain": [ 43 | "KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',\n", 44 | " metric_params=None, n_jobs=None, n_neighbors=5, p=2,\n", 45 | " weights='uniform')" 46 | ] 47 | }, 48 | "execution_count": 3, 49 | "metadata": {}, 50 | "output_type": "execute_result" 51 | } 52 | ], 53 | "source": [ 54 | "clf = neighbors.KNeighborsClassifier()\n", 55 | "clf.fit(X_train, y_train)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 5, 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "text/plain": [ 66 | "0.9825798423890502" 67 | ] 68 | }, 69 | "execution_count": 5, 70 | "metadata": {}, 71 | "output_type": "execute_result" 72 | } 73 | ], 74 | "source": [ 75 | "accuracy = clf.score(X_test, y_test)\n", 76 | "accuracy" 77 | ] 78 | } 79 | ], 80 | "metadata": { 81 | "kernelspec": { 82 | "display_name": "Python 3", 83 | "language": "python", 84 | "name": "python3" 85 | }, 86 | "language_info": { 87 | "codemirror_mode": { 88 | "name": "ipython", 89 | "version": 3 90 | }, 91 | "file_extension": ".py", 92 | "mimetype": "text/x-python", 93 | "name": "python", 94 | "nbconvert_exporter": "python", 95 | "pygments_lexer": "ipython3", 96 | "version": "3.6.9" 97 | } 98 | }, 99 | "nbformat": 4, 100 | "nbformat_minor": 4 101 | } 102 | -------------------------------------------------------------------------------- /src/preprocess.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import logging 4 | import os 5 | import shutil 6 | 7 | from utils import MediapipeManager 8 | from utils import structuring 9 | 10 | 11 | def main(input_folder, classification_label): 12 | """Valid if the environment contains the files necessary for execution and starts the\ 13 | process of transforming a normal video into a mediapipe type. 14 | 15 | Arguments: 16 | input_folder {str} -- dataset path that contains the .mp4 videos 17 | classification_label {str} -- class type of data provided 18 | 19 | Raises: 20 | Exception: Dependencies required for running Mediapipe were not found, \ 21 | check github.com/google/mediapipe/ 22 | Exception: The folder containing the video files provided is empty 23 | """ 24 | logging.info("Checking input folder.") 25 | file_list = [files for files in glob.glob(os.path.abspath(input_folder) + "**/*.mp4", 26 | recursive=True)] 27 | input_counting = 0 28 | folder_name = input_folder.split("/")[-2] if input_folder.split("/")[-1] == "" \ 29 | else input_folder.split("/")[-1] 30 | output_folder = f"data/{folder_name}/" 31 | 32 | mediapipe_dependencies = ["calculators", "graphs", "models"] 33 | 34 | if not all([os.path.isdir(f"mediapipe/{dep}") for dep in mediapipe_dependencies]): 35 | raise Exception("Mediapipe dependencies not found.") 36 | 37 | if len(file_list) == 0: 38 | raise Exception("The provided folder does not contain any .mp4 files") 39 | 40 | logging.info("%i files were found.", len(file_list)) 41 | 42 | if not os.path.exists(f"data"): 43 | logging.info("Creating data folder...") 44 | os.mkdir(f"data") 45 | 46 | if not os.path.exists(output_folder): 47 | logging.info("Creating %s folder...", folder_name) 48 | else: 49 | logging.info("Output %s folder already exist. Cleaning it up...", folder_name) 50 | shutil.rmtree(output_folder) 51 | 52 | os.mkdir(output_folder) 53 | 54 | mediapipe = MediapipeManager(output_folder) 55 | try: 56 | for file_path in file_list: 57 | mediapipe.run_mediapipe(file_path) 58 | input_counting += 1 59 | logging.info("Done.") 60 | logging.info("Progress so far: %.1f%%", (input_counting/len(file_list)) * 100) 61 | 62 | logging.info(">>> Coordinate extraction done.") 63 | logging.info("Videos analyzed: %i", input_counting) 64 | 65 | csv_list = [files for files in glob.glob(os.path.abspath(f"{output_folder}/*.csv"), 66 | recursive=True)] 67 | if classification_label is not None: 68 | logging.info("Starting data prepation.") 69 | logging.info("Classifying as: %s", classification_label) 70 | logging.info("Adding new column (classification) to all .csv files.") 71 | for csv_path in csv_list: 72 | structuring.add_label(csv_path, output_folder, classification_label) 73 | logging.info(">>> Data prepation done.") 74 | 75 | if len(file_list) > 1: 76 | logging.info("Combining all the .csv file of the dataset folder to a single one.") 77 | structuring.convert_to_one(output_folder) 78 | 79 | logging.info(">>> Pre-processing has been completed.") 80 | except ProcessLookupError: 81 | print(ProcessLookupError) 82 | 83 | 84 | if __name__ == "__main__": 85 | parser = argparse.ArgumentParser(description="Run mediapipe framework.") 86 | parser.add_argument("-i", "--input_dataset_path", 87 | help="Folder containing files with .mp4 \ 88 | extension to be converted by mediapipe", 89 | required=True) 90 | parser.add_argument("-c", "--classification", help="Adds a classification for the dataset \ 91 | that already exists (the parameter input_data_folder must be provided, \ 92 | as this value is used as input).", 93 | type=str, required=False, default=None) 94 | arguments = parser.parse_args() 95 | 96 | input_data_path = arguments.input_dataset_path 97 | classification = arguments.classification 98 | main(input_data_path, classification) 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gesture Recognition with Mediapipe 2 | Training machine learning model for gesture recognition with [Mediapipe Framework](https://github.com/google/mediapipe/) and K-Nearest Neighbors ([K-Neighbors Classifier](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html)) algorithm. 3 | 4 | ## Introduction 5 | The purpose of this project is to explore some Machine Learning algorithms along with the Google Mediapipe framework. 6 | 7 | The "Hand Tracking" feature is used, which consists of recognition of only one hand. The c ++ file located in [here](mediapipe/demo_run_graph_main_out.cc), has been changed to instead return a new mp4 video with Mediapipe on, it will return a .csv file that contains the coordinates of the landmarks. In total there will be 21 landmarks, as they are distributed by hand in full. 8 | Standard Media Pipe output : 9 | ![Normal output from Mediapipe](docs/images/video_wth_mediapipe.gif) 10 | Modified MediaPipe output in a plot: 11 | ![Landmarks - Hands Open](docs/images/palm.png) 12 | 13 | ### KNN Algorithm 14 | The k-nearest neighbors (KNN) algorithm is a simple, easy-to-implement supervised machine learning algorithm that can be used to solve both classification and regression problems. This algorithm assumes that similar things exist in close proximity. In other words, similar things are near to each other. 15 | 16 | ## Preparing environment 17 | To be able to initialize this project, it is necessary to have some settings configured on your machine. 18 | 1. Download and install Python version 3.7+ and the Pip package manager. Follow the instructions (according to your operating system) on the [official website](https://www.python.org/downloads/) of the distributor. 19 | 2. Create a Python [virtual environment](https://virtualenv.pypa.io/en/stable/) for the project using Virtualenv. This will cause project dependencies to be isolated from your Operating System. Once you create the python environment, enable it before proceeding to the next steps. Ex: You should see `(env)your-user-name:$` in the terminal. 20 | 4. Run `$ pip install -r requirements.txt` to install dependencies. 21 | 22 | ### Mediapipe Framework 23 | 1. Clone [Mediapipe](https://github.com/google/mediapipe/) repository; 24 | 2. Install mediapipe as explained [here](https://github.com/google/mediapipe/blob/master/mediapipe/docs/install.md); 25 | 3. Copy **mediapipe** (**~/mediapipe/mediapipe/**) folder to **~/training-mediapipe-model/mediapipe/**; 26 | 27 | ### Datasets 28 | For the models to be able to classify the gesture, it is necessary to have at least two classes, that is, two datasets with different gestures and containing mp4 video files. 29 | 30 | ## Running 31 | ### Pre-process 32 | The input data for the model is the coordinates of the landmarks provided by Mediapipe. It is necessary to start this preprocessing in order to obtain this data. You will need to do this step for all datasets: 33 | 1. At **~/training-mediapipe-model/** run with the first parameter being to indicate the path to your dataset and the second to classify this dataset: 34 | ``` 35 | $ python src/preprocess.py --input_dataset_path /path/to/dataset/ --classification NameOfTheLabel 36 | ``` 37 | ### Build 38 | To train the model, we need all csv's of all classifications in just one file. So this is what the **build** file does. 39 | 1. At **~/training-mediapipe-model/** run with the first parameter (separated by commas) to indicate which datasets will be served to the model: 40 | ``` 41 | $ python src/build.py --datasets_compile "dataset01,dataset02,dataset003" 42 | ``` 43 | 44 | ### Try the model 45 | To use the model (classify a gesture recorded in the video) just perform the following steps: 46 | 1. At **~/training-mediapipe-model/** run with the first parameter (separated by commas) to provide the input: 47 | ``` 48 | $ python src/predict.py --input_video_path "the-input-here" 49 | ``` 50 | 51 | ### Using with the Mediapipe-API 52 | It is now possible to use this model together with the API I made earlier. Make a request to **/recognition/gesture/** endpoint with the **.mp4** file and will return a classification type. Check [Mediapipe-API](https://github.com/samborba/mediapipe-api/) for more details. 53 | 54 | ## Notes 55 | 1. There are only 4 types of classes that the model recognizes: rock, open hand, ok and no hand; 56 | 2. Feel free to change the model (algorithm and/or data training) and integrate into the project; 57 | 58 | ## Reference 59 | 1. [Mediapipe](https://github.com/google/mediapipe/); 60 | 2. [Scikit-Learn Documentation](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html); 61 | 3. [Sentdex](https://www.youtube.com/user/sentdex/featured); -------------------------------------------------------------------------------- /mediapipe/demo_run_graph_main_out.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The MediaPipe Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // An example of sending OpenCV webcam frames into a MediaPipe graph. 16 | 17 | #include "mediapipe/framework/calculator_framework.h" 18 | #include "mediapipe/framework/formats/image_frame.h" 19 | #include "mediapipe/framework/formats/image_frame_opencv.h" 20 | #include "mediapipe/framework/port/file_helpers.h" 21 | #include "mediapipe/framework/port/opencv_highgui_inc.h" 22 | #include "mediapipe/framework/port/opencv_imgproc_inc.h" 23 | #include "mediapipe/framework/port/opencv_video_inc.h" 24 | #include "mediapipe/framework/port/parse_text_proto.h" 25 | #include "mediapipe/framework/port/status.h" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | //Take stream from /mediapipe/graphs/hand_tracking/hand_detection_desktop_live.pbtxt 32 | // RendererSubgraph - LANDMARKS:hand_landmarks 33 | #include "mediapipe/calculators/util/landmarks_to_render_data_calculator.pb.h" 34 | #include "mediapipe/framework/formats/landmark.pb.h" 35 | 36 | // input and output streams to be used/retrieved by calculators 37 | constexpr char kInputStream[] = "input_video"; 38 | constexpr char kOutputStream[] = "output_video"; 39 | constexpr char kLandmarksStream[] = "hand_landmarks"; 40 | constexpr char kWindowName[] = "MediaPipe"; 41 | 42 | extern "C" ::mediapipe::Status RunMPPGraph(char* c_video_path, 43 | char* c_coordinates_path, 44 | char* calculator_graph_config_file) { 45 | 46 | std::string calculator_graph_config_contents; 47 | std::string video_path = c_video_path; // to use rfind() and substr() need to be string 48 | 49 | // Setting input_video name to create txt file later 50 | int beginIdx = video_path.rfind("/"); 51 | std::string video_name = video_path.substr(beginIdx+1); 52 | 53 | // Search for mp4 extension, if found, remove it 54 | if (video_name.find(".mp4")) { 55 | size_t lastindex = video_name.find_last_of("."); 56 | video_name = video_name.substr(0, lastindex); 57 | } 58 | 59 | std::string coordinates_path = c_coordinates_path + video_name + ".csv"; 60 | std::string output_video_path = ""; 61 | 62 | MP_RETURN_IF_ERROR(mediapipe::file::GetContents( 63 | calculator_graph_config_file, &calculator_graph_config_contents)); 64 | LOG(INFO) << "Get calculator graph config contents: " 65 | << calculator_graph_config_contents; 66 | mediapipe::CalculatorGraphConfig config = 67 | mediapipe::ParseTextProtoOrDie( 68 | calculator_graph_config_contents); 69 | 70 | LOG(INFO) << "Initialize the calculator graph."; 71 | mediapipe::CalculatorGraph graph; 72 | MP_RETURN_IF_ERROR(graph.Initialize(config)); 73 | 74 | LOG(INFO) << "Initialize the camera or load the video."; 75 | cv::VideoCapture capture; 76 | const bool load_video = !video_path.empty(); 77 | if (load_video) { 78 | capture.open(video_path); 79 | } else { 80 | capture.open(0); 81 | } 82 | RET_CHECK(capture.isOpened()); 83 | 84 | cv::VideoWriter writer; 85 | const bool save_video = !output_video_path.empty(); 86 | if (save_video) { 87 | LOG(INFO) << "Prepare video writer."; 88 | cv::Mat test_frame; 89 | capture.read(test_frame); // Consume first frame. 90 | capture.set(cv::CAP_PROP_POS_AVI_RATIO, 0); // Rewind to beginning. 91 | writer.open(output_video_path, 92 | mediapipe::fourcc('a', 'v', 'c', '1'), // .mp4 93 | capture.get(cv::CAP_PROP_FPS), test_frame.size()); 94 | RET_CHECK(writer.isOpened()); 95 | } 96 | 97 | // pollers to retrieve streams from graph 98 | // output stream (i.e. rendered landmark frame) 99 | ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller poller, 100 | graph.AddOutputStreamPoller(kOutputStream)); 101 | // hand landmarks stream 102 | ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller poller_landmark, 103 | graph.AddOutputStreamPoller(kLandmarksStream)); 104 | 105 | LOG(INFO) << "Start running the calculator graph."; 106 | MP_RETURN_IF_ERROR(graph.StartRun({})); 107 | 108 | LOG(INFO) << "Start grabbing and processing frames."; 109 | size_t frame_timestamp = 0; 110 | bool grab_frames = true; 111 | 112 | // Save landmark coordinate values into a text file 113 | std::ofstream landmarks_coordinates(coordinates_path, std::ios::out | std::ios_base::app); 114 | 115 | // Initializa columns 116 | for (int i = 1; i < 22; i++) { 117 | if (i == 21) { 118 | landmarks_coordinates << "x" + std::to_string(i) << "," << "y" + std::to_string(i); 119 | } else { 120 | landmarks_coordinates << "x" + std::to_string(i) << "," << "y" + std::to_string(i) << ","; 121 | landmarks_coordinates << "\n"; 122 | } 123 | } 124 | 125 | while (grab_frames) { 126 | // Capture opencv camera or video frame. 127 | cv::Mat camera_frame_raw; 128 | capture >> camera_frame_raw; 129 | if (camera_frame_raw.empty()) break; // End of video. 130 | cv::Mat camera_frame; 131 | cv::cvtColor(camera_frame_raw, camera_frame, cv::COLOR_BGR2RGB); 132 | if (!load_video) { 133 | cv::flip(camera_frame, camera_frame, /*flipcode=HORIZONTAL*/ 1); 134 | } 135 | 136 | // Wrap Mat into an ImageFrame. 137 | auto input_frame = absl::make_unique( 138 | mediapipe::ImageFormat::SRGB, camera_frame.cols, camera_frame.rows, 139 | mediapipe::ImageFrame::kDefaultAlignmentBoundary); 140 | cv::Mat input_frame_mat = mediapipe::formats::MatView(input_frame.get()); 141 | camera_frame.copyTo(input_frame_mat); 142 | 143 | // Send image packet into the graph. 144 | MP_RETURN_IF_ERROR(graph.AddPacketToInputStream( 145 | kInputStream, mediapipe::Adopt(input_frame.release()) 146 | .At(mediapipe::Timestamp(frame_timestamp++)))); 147 | 148 | // Get the graph result packet, or stop if that fails. 149 | mediapipe::Packet packet; 150 | mediapipe::Packet landmark_packet; 151 | 152 | //Polling the poller to get landmark packet 153 | if (!poller.Next(&packet)) break; 154 | if (!poller_landmark.Next(&landmark_packet)) break; 155 | 156 | // Use packet.Get to recover values from packet 157 | auto& output_frame = packet.Get(); 158 | 159 | // Deduce type of "landmark_packet" 160 | auto &output_landmarks = landmark_packet.Get < mediapipe::NormalizedLandmarkList > (); 161 | 162 | // Convert back to opencv for display or saving. 163 | cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame); 164 | cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR); 165 | if (save_video) { 166 | writer.write(output_frame_mat); 167 | } 168 | 169 | // Loop over landmarks list 170 | for (int i = 0; i < output_landmarks.landmark_size(); ++i) { 171 | const mediapipe::NormalizedLandmark& landmark = output_landmarks.landmark(i); 172 | landmarks_coordinates << landmark.x() << "," << landmark.y() << ","; 173 | } 174 | landmarks_coordinates << "\n"; 175 | } 176 | 177 | // filetest.close(); 178 | LOG(INFO) << "Shutting down."; 179 | if (writer.isOpened()) writer.release(); 180 | MP_RETURN_IF_ERROR(graph.CloseInputStream(kInputStream)); 181 | return graph.WaitUntilDone(); 182 | } 183 | 184 | // int main() { 185 | // ::mediapipe::Status run_status = RunMPPGraph("video-input"); 186 | // if (!run_status.ok()) { 187 | // LOG(ERROR) << "Failed to run the graph: " << run_status.message(); 188 | // } else { 189 | // LOG(INFO) << "Success!"; 190 | // } 191 | // return 0; 192 | // } 193 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /docs/data_example.csv: -------------------------------------------------------------------------------- 1 | x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8,x9,y9,x10,y10,x11,y11,x12,y12,x13,y13,x14,y14,x15,y15,x16,y16,x17,y17,x18,y18,x19,y19,x20,y20,x21,y21 2 | 0.551238,0.863165,0.492964,0.854567,0.449728,0.817389,0.433278,0.830624,0.453253,0.858569,0.486402,0.672233,0.46596,0.648441,0.444489,0.690992,0.424388,0.725508,0.527872,0.689724,0.509144,0.681886,0.482726,0.733363,0.462863,0.772622,0.565193,0.727162,0.520575,0.773471,0.494834,0.852727,0.478574,0.892635,0.58766,0.779159,0.552832,0.829354,0.525146,0.877821,0.50355,0.899411 3 | 0.587655,0.782277,0.512704,0.77804,0.483205,0.770027,0.457693,0.786909,0.456044,0.808876,0.511208,0.676295,0.477771,0.69449,0.459185,0.741219,0.443063,0.777901,0.539538,0.695158,0.495597,0.75963,0.473217,0.818147,0.464204,0.849586,0.566528,0.726167,0.507351,0.804051,0.477699,0.870444,0.46612,0.912142,0.587859,0.76702,0.53952,0.814787,0.511537,0.854207,0.497314,0.880091 4 | 0.556927,0.907669,0.475281,0.908436,0.421689,0.839996,0.400405,0.820439,0.414202,0.822505,0.481999,0.67608,0.470023,0.58411,0.447598,0.575819,0.433716,0.598073,0.531518,0.668285,0.480555,0.657174,0.443734,0.740584,0.424318,0.81008,0.569695,0.695417,0.496863,0.738048,0.470138,0.827781,0.463256,0.880144,0.594425,0.746761,0.528425,0.808515,0.497203,0.854471,0.486802,0.872678 5 | 0.539799,0.896237,0.460926,0.905443,0.402831,0.859888,0.378129,0.848653,0.402241,0.852642,0.459216,0.674916,0.433248,0.605615,0.399266,0.607921,0.371434,0.640129,0.51256,0.673844,0.466588,0.641348,0.42945,0.706375,0.403359,0.785667,0.553429,0.706421,0.473184,0.74629,0.457475,0.843662,0.455165,0.907091,0.573224,0.763907,0.515069,0.824367,0.495293,0.872621,0.483923,0.898091 6 | 0.52345,0.906274,0.454713,0.897782,0.401155,0.853649,0.374081,0.845912,0.379867,0.851124,0.448245,0.681609,0.431134,0.631833,0.410629,0.648728,0.38793,0.679153,0.495138,0.678855,0.448219,0.682971,0.410595,0.764704,0.386771,0.840798,0.532433,0.711961,0.472768,0.745053,0.442152,0.836037,0.42785,0.903517,556,0.76562,0.504871,0.816373,0.47424,0.862071,0.456045,0.887532 7 | 0.487824,0.889686,0.498474,0.804793,0.483682,0.766355,0.464508,0.782934,0.445893,0.829424,0.40593,0.733775,0.388978,0.73763,0.397806,0.761929,0.390078,0.7605,0.3752,0.789234,0.361629,0.802014,0.389346,0.834101,0.396394,0.843919,0.359753,0.848716,0.364508,0.879752,0.405403,0.894908,0.420988,0.887691,0.360374,0.900844,0.377867,0.927491,0.411306,0.935688,0.426111,0.926162 8 | 0.484576,0.847815,0.420131,0.854509,0.366876,0.854404,0.37238,0.864416,0.417941,0.85458,0.371167,0.68549,0.351375,0.621198,0.344587,0.584067,0.33697,0.547174,0.426253,0.659513,0.416048,0.599037,0.407482,0.572144,0.403877,0.545619,0.479071,0.652355,0.459814,0.725452,0.438332,0.809657,0.428095,0.843564,0.517078,0.688814,0.49213,0.763013,0.468024,0.824023,0.454878,0.862291 9 | 0.484094,0.852907,0.426003,0.871293,0.364833,0.859902,0.365185,0.863656,0.404792,0.861338,0.371744,0.684968,0.352677,0.620737,0.344352,0.596766,0.338003,0.580132,0.428963,0.666576,0.424584,0.60205,0.4108,0.584534,0.407696,0.576667,0.484342,0.665896,0.459672,0.731207,0.434231,0.823203,0.425258,0.869229,0.523328,0.706225,0.488025,0.771783,0.45863,0.841487,0.44482,0.885304 10 | 0.496641,0.881496,0.425617,0.894946,0.362197,0.871977,0.360842,0.871882,0.405179,0.87165,0.394339,0.691738,0.380314,0.636442,0.361221,0.616914,0.346793,0.601261,0.440003,0.666667,0.432787,0.655304,0.424396,0.698431,0.426407,0.721833,0.486299,0.678668,0.458372,0.775637,0.444321,0.858217,0.442915,0.879087,0.522925,0.728447,0.486007,0.817604,0.46163,0.872006,0.455467,0.889041 11 | 0.507086,0.891868,0.418354,0.891947,0.358072,0.847029,0.351147,0.824433,0.391023,0.824289,0.405191,0.691819,0.391075,0.64034,0.366852,0.642659,0.347779,0.651012,0.450407,0.677112,0.426722,0.682859,0.40588,0.751993,0.396896,0.795864,0.492245,0.697637,0.454064,0.769961,0.429972,0.84362,0.418629,0.865297,0.524404,0.743525,0.482285,0.822302,0.451043,0.866245,0.435307,0.870636 12 | 0.542584,0.855641,0.489835,0.862514,0.446836,0.831512,0.406525,0.822674,0.385998,0.828522,0.476792,0.676446,0.435073,0.676834,0.405465,0.73171,0.383704,0.780052,0.489263,0.690138,0.442434,0.725857,0.413732,0.795396,0.397343,0.839573,0.502652,0.717307,0.447779,0.762252,0.414047,0.839954,0.392985,0.88308,0.511533,0.754548,0.471161,0.802031,0.442599,0.854796,0.418366,0.873821 13 | 0.531415,0.858633,0.475651,0.821568,0.417543,0.775582,0.381639,0.77393,0.376427,801,0.48444,0.651694,0.480339,0.576734,0.471316,0.54224,0.46014,0.521901,0.531539,0.674839,0.447334,0.685309,0.428266,0.740395,0.432478,0.75938,0.556059,0.722482,0.455309,0.778908,0.427788,0.835864,0.429388,0.850638,0.56084,0.779929,0.472774,0.847425,0.453998,0.890035,0.457084,0.896799 14 | 0.507212,0.880457,0.455126,0.879883,0.402902,0.839653,0.384523,0.810025,0.401586,0.804611,0.444854,0.672231,0.440107,0.590677,0.426266,0.542326,0.416301,0.492229,0.495596,0.670505,0.513453,0.603585,0.50169,0.551674,0.488015,0.493949,0.538681,0.689806,0.482763,0.726826,0.44751,0.788426,0.439516,0.810697,0.560868,0.739624,0.48708,0.780266,0.456336,0.82483,0.449878,0.845176 15 | 0.530619,0.864924,0.479815,0.879988,0.428487,0.845162,0.424195,0.82799,0.444723,0.806523,0.452784,0.670976,0.447848,0.586084,0.432353,0.520771,0.422865,0.445266,0.50878,0.666307,0.514576,0.583483,0.514917,0.514981,0.521832,0.429988,0.553621,0.688428,0.518665,0.697012,0.492225,0.777835,0.483832,0.820822,0.586346,0.737983,0.534785,0.768011,0.505814,0.817585,0.491576,0.847152 16 | 0.537738,0.914535,0.485297,0.928066,0.430954,0.882695,0.422135,0.846718,0.451678,0.81247,0.452972,0.670734,0.446561,0.58302,0.429063,0.522521,0.42559,0.463511,0.502838,0.667172,0.50046,0.563191,0.490431,0.478443,0.487528,0.415423,0.54924,0.696977,0.511815,0.703582,0.487149,0.788443,0.480472,0.844159,0.579192,0.753749,0.527631,0.774185,0.501588,0.835467,0.489015,0.877229 17 | 0.548979,0.935675,0.48345,0.923645,0.431146,0.895214,0.39964,0.857495,0.416741,0.821273,0.440611,0.681717,0.423931,0.570442,0.408702,0.507186,0.403063,0.444853,0.494674,0.671466,0.487204,0.553876,0.486286,0.468893,0.493661,0.391352,0.548358,0.696187,0.502967,0.712821,0.478565,0.801135,0.473622,0.846171,0.583123,0.754591,0.529233,0.775305,0.499225,0.833371,0.487317,0.868697 18 | 0.545663,0.935157,0.479939,0.920453,0.427505,0.896808,0.41597,0.876766,0.428227,0.860682,0.423089,0.685664,0.388011,0.569335,0.35808,0.516034,0.337281,0.452668,0.472679,0.665665,0.45303,0.550882,0.441423,0.485538,0.437876,0.406565,0.521412,0.682894,0.486136,0.698263,0.47665,0.786325,0.478141,0.82073,0.554799,0.735335,0.508874,0.788044,0.488298,0.856631,0.487416,0.883985 19 | 0.525496,0.9094,0.4495,0.8926,0.400502,0.866854,0.378228,0.84618,0.396425,0.819778,0.391486,0.660034,0.357195,0.550858,0.330858,0.492199,0.313148,0.426459,0.443106,0.64415,0.42841,0.532582,0.414403,0.453685,0.405561,0.380227,0.498943,0.654211,0.45898,0.696657,0.443058,0.779487,0.442272,0.811882,0.541263,0.705994,0.494195,0.753584,0.467842,0.819795,0.457863,0.852409 20 | 0.513252,0.89918,0.437466,0.893399,0.38252,0.85994,0.361324,0.832625,0.384843,0.806066,0.374914,0.667169,0.3298,0.551011,0.300243,0.487847,0.282321,0.435755,0.432108,0.650854,0.420051,0.508915,0.410428,0.435068,0.41168,0.387736,0.494497,0.656331,0.459939,0.677164,0.430037,0.769417,0.419152,0.814389,0.534748,0.70703,0.495315,0.750712,0.465601,0.825337,0.446999,0.86423 21 | 0.495158,0.886551,0.428442,0.904494,0.367761,0.871726,0.355742,0.826361,0.387316,0.769309,0.356989,0.666902,0.316393,0.575166,0.287369,0.507872,0.265777,0.442889,0.413143,0.635833,0.391151,0.517942,0.382735,0.437112,0.383001,0.375125,0.471351,0.639312,0.439928,0.648431,0.426077,0.731411,0.425474,0.783993,0.510208,0.679823,0.475194,0.703809,0.45074,0.778372,0.435642,0.837324 22 | 0.488701,0.880748,0.42117,0.897966,0.358447,0.865019,0.344702,0.824169,0.371633,774,0.340322,0.664737,0.299717,0.575321,0.273769,0.505931,0.255741,0.438522,0.398587,0.633436,0.379293,0.524302,0.372837,0.439362,0.369283,0.368629,0.457244,0.636108,0.434688,0.643263,0.422253,0.735676,0.419218,0.79325,0.500532,0.673362,0.474468,0.682111,0.446652,0.753778,0.425797,0.814092 23 | 0.482155,0.881922,0.414516,0.895621,0.362572,0.867541,0.351466,0.829754,0.369791,0.783663,0.334408,0.676764,0.291909,0.583902,0.264988,0.510981,0.247318,0.441934,0.389973,0.644114,0.366628,0.532092,0.35903,0.445635,0.354552,0.37289,0.445857,0.646192,0.427593,0.6352,0.418583,0.715659,0.415496,0.77046,0.488259,0.679819,0.465168,0.677509,0.436503,0.749896,0.414618,0.820486 24 | 0.471304,0.893615,0.401859,0.909599,0.349672,0.8815,0.341906,0.847925,0.370831,0.800727,0.327189,0.679838,0.280163,0.590554,0.251802,0.521164,0.231935,0.454616,0.385331,0.648904,0.363613,0.534972,0.356972,0.446198,0.352989,0.373113,0.445702,0.647686,0.424289,0.64531,0.412555,0.72015,0.408564,0.77078,0.487656,0.680685,0.464229,0.67948,0.432224,0.746085,0.407057,0.814444 25 | 0.471531,0.900745,0.407551,0.91556,0.35201,0.886105,0.33592,0.848643,0.357263,0.790305,0.321269,0.679711,0.277676,0.590356,0.24673,0.522319,0.224034,0.457247,0.380807,0.652129,0.358903,0.535218,0.352821,0.450499,0.353285,0.380906,0.437456,0.659991,0.420532,0.619418,0.403253,0.657635,0.397156,0.683779,0.478244,0.694153,0.453725,0.677761,0.428893,0.749891,0.417344,0.821915 26 | 0.468816,0.903407,0.405718,0.916377,0.349897,0.89292,0.334805,0.860741,0.359564,0.813007,0.321965,0.68423,0.278938,0.600863,0.249002,0.532803,0.226922,0.462411,0.379304,0.657315,0.353441,0.542361,0.345459,0.458595,0.34302,0.383583,0.434628,0.667713,0.411948,0.620382,0.395499,0.659865,0.389901,0.684712,0.475355,0.703949,0.445566,0.681955,0.423571,0.758759,0.417151,0.833798 27 | 0.469583,0.90444,0.405713,0.917854,0.348873,0.89687,0.334511,0.864877,0.357324,0.816019,0.318349,0.683347,0.276599,0.604609,0.246797,0.538142,0.224282,0.468367,0.376714,0.655646,0.350783,0.543313,0.341769,0.461473,0.338444,0.387147,0.43297,0.665296,0.409909,0.614885,0.390552,0.642543,0.382101,0.656398,0.474471,0.702842,0.442755,0.685686,0.422206,0.764245,0.418587,0.838506 28 | 0.468202,0.908074,0.405606,0.921291,0.348358,0.899004,0.335241,0.863879,0.357196,0.818166,0.318275,0.688474,0.277954,0.605905,0.248563,0.53703,0.226502,0.463447,0.376011,0.660797,0.349015,0.545467,0.341058,0.462364,0.338519,0.386342,0.432008,0.669706,0.40856,0.619044,0.390764,0.656532,0.383702,0.680128,0.474099,0.70654,0.44413,0.686994,0.422072,0.765505,0.416078,0.840187 29 | 0.471072,0.904582,0.402594,0.91507,0.347854,0.894652,0.337728,0.855823,0.361749,0.81161,0.321874,0.682905,0.281035,0.606454,0.25386,0.538269,0.229153,0.464937,0.377824,0.65148,0.347282,0.545816,0.338055,0.462211,0.334268,0.389089,0.433022,0.657319,0.408506,0.596228,0.387322,0.578729,0.37335,0.559101,0.475711,0.698377,0.442566,0.680367,0.424898,0.753575,0.423475,0.824114 30 | 0.468537,0.898453,0.405771,0.905693,0.352977,0.869319,0.345267,0.807713,0.365044,0.750053,0.321094,0.667534,0.274703,0.581928,0.245488,0.521532,0.218528,0.468827,0.371486,0.633299,0.348767,0.517167,0.339856,0.436849,0.332056,0.382606,0.420383,0.641402,0.403762,0.553272,0.390697,0.498495,0.374575,0.461534,0.461965,0.681391,0.436072,0.645565,0.426413,0.689662,0.423565,0.741487 31 | 0.464193,0.870664,0.409799,0.879121,0.370843,0.830942,0.368297,0.751009,0.392473,0.667444,0.323758,0.648565,0.276342,0.548563,0.253935,0.494251,0.233811,0.445235,0.369226,0.617178,0.351566,0.482262,0.341393,0.416692,0.33358,0.373391,0.414994,0.626553,0.418552,0.525115,0.412901,0.468054,0.401968,0.42323,0.458955,0.672165,0.44846,0.648134,0.441511,0.674966,0.433528,0.696697 32 | 0.454745,0.816534,0.401796,0.825344,0.354207,0.764359,0.350661,0.675378,0.376107,0.59231,0.324162,0.595228,0.280151,0.500618,0.254825,0.440071,0.236214,0.379912,0.370468,0.57124,0.353376,0.455499,0.346056,0.383056,0.339373,0.318424,0.417457,588,0.424069,0.511716,0.423361,0.5171,0.417365,0.515338,0.46375,0.640756,0.451424,0.628798,0.438467,0.675188,0.427748,0.703787 33 | 0.464894,0.771606,0.398785,0.773922,0.344173,0.726106,0.338472,0.659078,0.379097,0.591293,0.328789,0.545701,0.284462,0.455076,0.26166,0.398519,0.249037,0.349392,0.385102,0.515447,0.37943,0.396945,0.373428,0.320293,0.370912,0.259964,0.436261,0.531986,0.423893,0.518452,0.410393,0.605038,0.403896,0.659171,0.480729,0.582173,0.46541,0.587884,0.442564,0.654935,0.426832,0.705226 34 | 0.464883,0.747536,0.401134,0.740083,0.338795,0.69769,0.333658,0.635337,0.376069,0.583295,0.331504,0.529656,0.29009,0.437863,0.268357,0.372232,0.255079,0.310148,387,0.506023,0.382879,0.381503,0.38262,0.300662,0.383275,0.228977,0.435097,0.522491,0.419365,0.471965,0.400733,0.562505,0.395071,0.625952,0.475704,0.556855,0.455796,0.523755,0.435387,0.584628,0.424041,0.638928 35 | 0.466098,0.737857,0.395525,0.724727,0.336643,0.67891,0.339105,0.620821,0.388079,0.572924,0.328433,0.51731,0.289335,0.427787,0.26707,0.365221,0.254515,0.306541,0.383633,0.495776,0.378355,0.372524,0.381525,0.292446,0.388175,0.224382,0.433753,0.51114,0.422922,0.480009,0.404479,0.565682,0.39899,0.620726,0.476297,0.543997,0.461784,0.522196,0.442507,0.583706,0.430554,0.638453 36 | 0.469344,0.732965,0.393014,0.718758,0.336168,0.672544,0.338989,0.612554,0.384089,0.559954,0.333651,0.519753,0.296478,0.42825,0.274221,0.363644,0.259228,0.30019,0.387971,0.496409,0.382094,0.372949,0.384238,0.291367,0.392027,0.223568,0.438398,0.509676,0.423272,0.479913,0.40415,0.560867,0.400019,0.612029,0.481533,0.541946,0.462351,0.528205,0.44289,0.589333,0.433261,0.64118 37 | 0.470665,0.727843,0.394813,0.716276,0.339801,0.66897,0.346686,0.606552,0.398755,0.554691,0.337694,0.515895,0.298635,0.422842,0.277403,0.358262,0.264114,0.29476,0.391755,0.492385,0.38744,0.369084,0.391111,0.290223,0.400925,0.225723,0.442519,0.50658,0.431153,0.473673,0.414561,0.556272,0.411293,0.609584,0.486695,0.541515,0.470776,0.524979,0.45203,0.588178,0.44029,0.641084 38 | 0.47239,0.730315,0.402315,0.725259,0.345927,0.678912,0.350179,0.621407,0.398525,0.564652,0.338435,0.523785,0.299927,0.421644,0.28121,0.35382,0.273045,0.294414,0.394367,0.497457,0.391379,0.374835,0.395457,0.295481,0.405976,0.23214,0.446911,0.511004,0.442053,0.470916,0.423878,0.545788,0.418036,0.594858,0.493287,0.545733,0.478532,0.527635,0.458339,0.591351,0.445942,0.649154 39 | 0.478318,0.743874,0.412536,0.732427,0.365592,0.692045,0.36739,0.637821,0.399986,0.571791,0.345525,0.528702,0.304352,0.428823,0.28784,0.361124,0.285157,0.301006,0.403587,0.503043,0.402724,0.385205,0.409479,0.312234,0.424233,0.245177,0.4574,0.514558,0.46753,0.477309,0.449996,0.540899,0.441329,0.56842,0.505749,0.552494,0.496911,0.546792,0.474962,0.61788,0.463772,0.669693 40 | 0.496986,0.756728,0.413704,0.728784,0.359158,0.66364,0.351262,0.596459,0.377782,0.534738,0.362731,0.532522,0.320202,0.432194,0.303616,365,0.297044,0.309649,0.421878,0.504801,0.418872,0.386507,0.425745,0.308095,0.439524,0.241594,0.473154,0.513982,0.470585,0.479103,0.457084,0.549202,0.452472,0.583525,0.519822,0.547158,0.511414,0.538682,0.493353,0.600705,0.480612,0.644635 41 | 0.512402,0.770832,0.420388,0.734052,0.376481,0.679302,0.366188,0.614526,0.387905,0.551637,0.371274,0.548005,0.335304,0.434027,0.323517,0.364966,0.318731,0.31428,0.431117,0.51366,0.430242,0.395659,0.434177,0.321274,0.445384,0.264568,0.484875,0.520146,0.474436,0.48711,0.45297,0.510802,0.444436,0.510712,0.526913,0.551124,0.501224,0.566361,0.475479,0.62353,0.46359,0.656883 42 | 0.541512,0.778567,0.455505,0.741353,0.409739,0.690274,0.407982,0.637964,0.435939,0.60479,0.400026,0.545888,0.367336,0.43401,0.353523,0.375065,0.341948,0.32892,0.453933,0.523146,0.451045,0.401782,0.452923,0.336621,0.458343,0.285548,0.506889,0.536726,0.498831,0.516945,0.482274,0.564047,0.469566,0.57808,0.548866,0.572234,0.527644,0.596019,0.51425,0.6572,0.503993,0.692141 43 | 0.55028,0.779922,0.478008,0.750221,0.434584,0.690791,0.433234,0.631948,0.466153,0.581114,0.428701,0.545545,0.397705,0.441795,0.381891,0.372639,0.369677,0.317541,0.480625,0.527482,0.48119,0.406298,0.487364,0.323804,0.49569,0.261487,0.530716,0.542522,0.521651,0.5182,0.51343,0.59056,0.508159,0.634753,0.569027,0.573711,0.549769,0.568978,0.53181,0.632992,0.516271,0.687404 44 | 0.556085,0.782622,0.481926,0.752598,0.449068,0.692286,0.476418,0.624744,0.524903,0.595167,0.459131,0.533567,0.434233,0.429571,0.416732,0.360173,0.408336,0.301976,0.510744,0.520712,0.519436,0.399931,0.527343,0.318492,0.540675,0.255088,0.556736,0.540131,0.551755,0.516464,0.537038,0.592733,0.530909,0.640302,0.590435,0.581759,0.579006,0.569599,0.561821,0.628839,0.547009,0.68207 45 | 0.568155,0.787705,0.495462,0.759331,0.461645,0.695113,0.489248,0.634557,0.547724,0.617234,0.481406,0.539158,0.446865,0.431812,0.4276,0.358736,0.416485,0.288233,0.53238,0.527134,0.53824,0.406295,0.545666,0.331314,0.560382,0.266785,0.577342,0.548096,0.564734,0.528524,0.535785,0.609653,0.526416,0.648436,0.610148,0.593748,0.598834,0.58934,0.574398,0.642805,0.55891,0.679058 46 | 0.58163,0.796671,0.521375,0.773657,0.482587,0.72038,0.504222,0.665807,0.559777,0.63958,0.496804,0.533736,0.465577,0.438576,0.443849,0.370755,0.430412,0.304138,0.547207,0.52083,0.557442,0.413353,0.568845,0.335547,0.588866,0.269215,0.592289,0.545607,0.587666,0.5309,0.562789,0.60731,0.553216,0.645911,0.624809,0.599728,0.618017,0.587417,0.595453,0.632558,0.580319,0.663898 47 | 0.595556,0.80214,0.541979,0.783942,0.500246,0.73381,0.515834,0.678825,0.567702,0.650456,0.512212,0.540143,0.480588,0.446457,0.454532,0.381029,0.436975,0.310851,0.560852,0.526355,0.566085,0.422178,0.577467,0.341445,0.59592,0.264021,0.604033,0.552044,0.597272,0.543898,0.582579,0.631148,0.578289,0.676073,0.635349,0.608601,0.631772,0.595355,0.613132,0.637751,0.598691,0.668899 48 | 0.60261,0.803299,0.545715,0.786637,0.500898,0.741262,0.52319,0.684375,0.577495,0.656289,0.51721,0.553867,0.483227,0.453593,0.458123,0.384635,0.441703,0.308706,0.566981,0.535673,0.572786,0.424626,0.583245,0.344021,0.599664,0.267441,0.609212,0.555111,0.604027,0.552891,0.589497,0.643195,0.581867,0.691576,0.638387,0.604963,0.634951,0.601485,0.616824,0.652165,0.601534,0.692336 49 | 0.604443,0.811276,0.544051,0.795776,0.500234,0.745634,0.530323,0.689749,0.587029,0.659232,0.518752,0.565527,0.481141,0.456512,0.45769,0.390043,0.443425,0.322015,0.567365,0.546088,0.576845,0.43324,0.590104,0.352641,0.609034,0.27558,0.608161,0.568405,0.608451,0.55171,0.591562,0.641417,0.582988,0.695038,0.639339,0.617905,0.636639,0.607451,0.617602,0.659776,0.603208,0.703979 50 | 0.609985,0.819741,0.552371,0.80887,0.506647,0.760566,0.527654,0.705458,0.583753,0.671627,0.523391,0.574171,0.48502,0.467893,0.459269,0.399662,0.444429,0.327199,0.571472,0.555525,0.581865,0.445315,0.593795,0.363592,0.613423,0.285418,0.612854,0.576302,0.611354,0.568445,0.594919,0.659994,0.5864,0.712798,0.642896,0.625058,0.642262,0.61979,0.622021,0.670479,0.605868,0.713495 51 | 0.611087,0.825421,0.551036,0.815451,0.502951,0.767844,0.528101,0.723799,0.584021,0.695479,0.518575,0.577933,0.481115,0.476989,0.454828,0.413766,0.439621,0.346294,0.569963,0.561004,0.577451,0.449428,0.58748,0.367623,0.606275,0.290768,0.61452,0.582148,0.615815,0.569437,0.594715,0.666942,0.583277,0.721353,0.647178,0.631285,0.643865,0.625737,0.619735,0.683111,0.602353,0.729083 52 | 0.611048,0.835809,0.547274,0.822411,0.502077,0.776882,0.528677,0.733669,0.585594,0.704948,0.518759,0.5855,0.48238,0.486529,0.456877,0.422454,0.442548,0.353074,0.570388,0.570443,0.579423,0.457719,0.589136,0.373066,0.608685,0.292913,0.613039,0.595227,0.614066,0.57158,0.594287,0.664958,0.585821,0.720473,0.643444,0.644732,0.64035,0.627903,0.617502,0.680722,0.60311,0.72902 53 | 0.612096,0.832356,0.54683,0.818251,0.502637,0.7778,0.52652,0.735387,0.579448,0.70404,0.517784,0.587294,0.479994,0.489032,0.453554,0.426397,0.438514,0.359173,0.570418,0.572448,0.578489,0.46239,0.588465,0.377461,0.608292,0.297999,0.613456,0.594745,0.612995,0.578538,0.593364,0.671944,0.585286,0.727378,0.644023,0.6426,0.640756,0.630141,0.62006,0.682062,0.606448,0.730954 54 | 0.608414,0.832424,0.549543,0.819344,0.504059,0.779296,0.525273,0.740438,0.580583,0.712287,0.517444,0.594562,0.481405,0.494142,0.455224,0.431328,0.441624,0.363484,0.566728,0.578994,0.575411,0.466815,0.584868,0.38329,0.605834,0.302575,0.608529,0.598967,0.612444,0.580556,0.594259,0.674706,0.586696,0.728429,0.639604,0.646604,0.640502,0.633882,0.620445,0.68776,0.607641,0.736436 55 | 0.610983,0.831551,0.549066,0.820835,0.500352,0.780541,0.525443,0.738512,0.582589,0.712532,0.516372,0.588806,0.481118,0.487236,0.455229,0.424629,0.441326,0.35663,0.567304,0.571966,0.577516,0.460188,0.58726,0.381139,0.607108,0.300764,0.610067,0.59337,0.61411,0.578059,0.595948,0.675663,0.587394,0.729452,0.642553,0.644313,0.643526,0.632047,0.62398,0.686719,0.610707,0.732338 56 | --------------------------------------------------------------------------------