diff --git a/.gitignore b/.gitignore index 8f8a4ba..d9863f1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ data/ .DS_Store cache/ +cache_processed/ cache_wlasl/ __pycache__/ diff --git a/analyze_model.ipynb b/analyze_model.ipynb deleted file mode 100644 index f02de7a..0000000 --- a/analyze_model.ipynb +++ /dev/null @@ -1,120 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from src.keypoint_extractor import KeypointExtractor\n", - "from src.datasets.finger_spelling_dataset import FingerSpellingDataset\n", - "from src.identifiers import LANDMARKS\n", - "import torch\n", - "from src.model import SPOTER\n", - "from sklearn.metrics import confusion_matrix\n", - "import numpy as np\n", - "import pandas as pd\n", - "import seaborn as sn\n", - "import matplotlib.pyplot as plt\n", - "from src.augmentations import MirrorKeypoints\n", - "\n", - "keypoints_extractor = KeypointExtractor(\"data/fingerspelling/data/\")\n", - "test_set = FingerSpellingDataset(\"data/fingerspelling/data/\", keypoints_extractor, keypoints_identifier=LANDMARKS, subset=\"val\", transform=MirrorKeypoints())\n", - "\n", - "spoter = SPOTER(num_classes=5, hidden_dim=len(LANDMARKS) *2)\n", - "spoter.load_state_dict(torch.load('models/spoter_40.pth'))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA28AAAJMCAYAAABtgJ7QAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/av/WaAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAydUlEQVR4nO3de5xVZb0/8O+GgS0ijFyGi9fsaBAqlEhi6EnICwR4yWN5fppA5Uklb2Qlngov2Zial5IoFUFPmWRejpfEo3CU/CmKkOBdsbwitxRGvGwZ9v790U9yAmX2sGfWrMX73Wv9sdfstfZ3fD3t8etnPc+TK5VKpQAAAKBVa5N0AQAAAGya5g0AACAFNG8AAAApoHkDAABIAc0bAABACmjeAAAAUkDzBgAAkAKaNwAAgBTQvAEAAKSA5g0AACAFNG8AAADN7BOf+ETkcrkNjvHjxzf6HlXNWB8AAAARMW/evFi3bt3610888UQcdNBBcdRRRzX6HrlSqVRqjuIAAADYuNNOOy3uuOOOeP755yOXyzXqGskbAABAExQKhSgUCg3O5fP5yOfzH3vd+++/H7/5zW9iwoQJjW7cIlpR87bm+19OugQyZttLH066BACA1Kp//7WkS2iStSv/0mKfVXvFdXHOOec0ODdp0qQ4++yzP/a6W2+9NVatWhVjx44t6/NazWOTmjcqTfMGANB0mrdNK3bavknJ2yGHHBLt27eP22+/vazPazXJGwAAwGYrrtv0eyqkMY3aP3vppZfi3nvvjZtvvrnsz7NVAAAAQAuZNm1a9OjRI0aOHFn2tZI3AAAgO0rFpCv4SMViMaZNmxZjxoyJqqryWzHJGwAAQAu499574+WXX46vf/3rTbpe8gYAANACDj744Nic9SI1bwAAQHYUW+9jk5vLY5MAAAApIHkDAAAyo9SKFyzZXJI3AACAFJC8AQAA2WHOGwAAAEmSvAEAANlhzhsAAABJkrwBAADZUVyXdAXNRvIGAACQApI3AAAgO8x5AwAAIEmSNwAAIDvs8wYAAECSJG8AAEBmlMx5AwAAIEmSNwAAIDvMeQMAACBJmjcAAIAU8NgkAACQHRYsAQAAIEmSNwAAIDuK65KuoNlI3gAAAFJA8gYAAGSHOW8AAAAkSfIGAABkh026AQAASJLkDQAAyA5z3gAAAEiS5A0AAMgOc94AAABIkuQNAADIjFJpXdIlNBvJGwAAQApI3gAAgOyw2iQAAABJkrwBAADZYbVJAAAAkiR5AwAAssOcNwAAAJKkeQMAAEgBj00CAADZUbRJNwlqf+BXY5uf3tzg2Po7P0+6LFLuxBPGxOLn5saauhfiwQduj0F7fybpkkg5Y4pKM6aoNGOKtNO8pcS6pS/H2+d9ff3xzpT/TLokUuyoow6Niy+aFOf9+JIYtM/wWLjoqfjjnb+NmppuSZdGShlTVJoxRaUZU1uQUrHljhZW0ebtiSeeqOTt+LDiuiitWbX+iHfeSroiUuz0U4+Pq6deH9de9/t4+unn46TxZ8Y777wb48YenXRppJQxRaUZU1SaMUUWbHbz9tZbb8WVV14Zn/vc52LAgAGVqImNaNO9d2z9n1fH1t/7ZeSPPi1y23ZPuiRSql27drHXXv1j1uw/rT9XKpVi1uwHYvDggQlWRloZU1SaMUWlGVNbmGKx5Y4W1uTmbc6cOTFmzJjo3bt3XHzxxTFs2LCYO3duJWvj/1v3ynPx3u9/Ee9NPS8Kt14Zbbr2iA4nnB/RfqukSyOFunfvGlVVVbF82coG55cvXxG9etYkVBVpZkxRacYUlWZMkRVlrTa5dOnSmD59ekydOjXq6uriK1/5ShQKhbj11lujX79+jb5PoVCIQqHQ4Nza+nWRr2pbTjlbjHXP/vkfL5a+FO++/Fx0nPjrqBowJOrnzUquMAAAaG1s0h0xevTo6NOnTyxatCguu+yyWLJkSfziF79o0ofW1tZGdXV1g+Nnc59r0r22SO+9E8UVr0ebbr2SroQUWrnyjaivr48ePRs+etujR00sXbYioapIM2OKSjOmqDRjiqxodPN21113xTe+8Y0455xzYuTIkdG2bdNTsokTJ8bq1asbHN8Z/Kkm32+L036raNOtZ5Tq3ky6ElJo7dq1sWDBohg2dL/153K5XAwbul/MnTs/wcpIK2OKSjOmqDRjaguT4TlvjX5s8oEHHoipU6fGwIED49Of/nR87Wtfi6OPbtrqPPl8PvL5fINzazwy+ZHajxwT9U/Ni9KqFZHr3DXaH3R0RLEYaxc+kHRppNSll18V06ZeGvMXLIp58/4cp5x8fHTs2CGmXzsj6dJIKWOKSjOmqDRjiixodPM2ePDgGDx4cFx22WUxY8aMuOaaa2LChAlRLBbjnnvuiR133DE6derUnLVusXLV3WKr/zMhclt3itLbdbHuxafjnclnRrxdl3RppNSNN94WNd27xtk/OiN69aqJhQufjJGjjo3ly1du+mLYCGOKSjOmqDRjaguSQCLWUnKlUqnU1IufffbZmDp1avzXf/1XrFq1Kg466KC47bbbmnSvNd//clPLgI3a9tKHky4BACC16t9/LekSmuS9P/1Xi33WVvt/rcU+K2Iz93nr06dPXHjhhfHqq6/G7373u0rVBAAA0CSl0roWO1raZm/SHRHRtm3bOPzww5ucugEAAPDxytrnDQAAoFXL8Jy3iiRvAAAANC/JGwAAkB0lyRsAAAAJ0rwBAACkgMcmAQCA7LBgCQAAAEmSvAEAANlhwRIAAACSJHkDAACyw5w3AAAAkiR5AwAAssOcNwAAADbHa6+9Fscee2x069YtOnToEHvuuWc8+uijjb5e8gYAAGRHK53z9uabb8aQIUNi6NChcdddd0VNTU08//zz0aVLl0bfQ/MGAADQzH7605/GjjvuGNOmTVt/bpdddinrHh6bBAAAsqNYbLGjUChEXV1dg6NQKGy0rNtuuy323nvvOOqoo6JHjx7x2c9+Nq666qqyfjXNGwAAQBPU1tZGdXV1g6O2tnaj7/3LX/4SU6ZMid122y3uvvvuOPHEE+OUU06Ja6+9ttGflyuVSqVKFb851nz/y0mXQMZse+nDSZcAAJBa9e+/lnQJTfLuHZe02Ge1OWj8BklbPp+PfD6/wXvbt28fe++9dzz44IPrz51yyikxb968eOihhxr1eea8AQAANMFHNWob07t37+jXr1+Dc5/+9KfjpptuavTnad4AAIDsaKWrTQ4ZMiSeffbZBueee+652HnnnRt9D3PeAAAAmtnpp58ec+fOjZ/85CexePHiuP766+PKK6+M8ePHN/oekjcAACA7Sq0zeRs0aFDccsstMXHixDj33HNjl112icsuuyyOOeaYRt9D8wYAANACRo0aFaNGjWry9Zo3AAAgO1rpnLdKMOcNAAAgBTRvAAAAKeCxSQAAIDta6YIllSB5AwAASAHJGwAAkB0WLAEAACBJkjcAACA7JG8AAAAkSfIGAABkR6mUdAXNRvIGAACQApI3AAAgO8x5AwAAIEmSNwAAIDskbwAAACRJ8gYAAGRHSfIGAABAgiRvAABAdpjzBgAAQJIkbwAAQHaUSklX0GwkbwAAACmgeQMAAEgBj00CAADZYcESAAAAktRqkrdtL3046RLImLdu+W7SJZAhnY64KOkSAIDGkLwBAACQpFaTvAEAAGy2kuQNAACABEneAACAzCgVbdINAABAgiRvAABAdlhtEgAAgCRJ3gAAgOyw2iQAAABJkrwBAADZYbVJAAAAkiR5AwAAssNqkwAAACRJ8gYAAGSH5A0AAIAkad4AAABSwGOTAABAdpRsFQAAAECCJG8AAEB2WLAEAACAJEneAACA7Cia8wYAAECCJG8AAEB2lMx5AwAAIEGSNwAAIDvMeQMAACBJkjcAACAzSvZ5AwAAIEmSNwAAIDvMeQMAACBJkjcAACA77PMGAABAkiRvAABAdpjzBgAAQJIkbwAAQHbY5w0AAIAkad4AAABSwGOTAABAdliwBAAAgCRJ3gAAgOywSTcAAABJ0rwBAADZUSy13FGGs88+O3K5XIOjb9++Zd3DY5MAAAAtYPfdd4977713/euqqvLaMc0bAACQGaVWvEl3VVVV9OrVq8nXe2wSAACgCQqFQtTV1TU4CoXCR77/+eefj+222y4++clPxjHHHBMvv/xyWZ+neQMAALKjBee81dbWRnV1dYOjtrZ2o2Xts88+MX369Jg5c2ZMmTIl/vrXv8b+++8fb731VqN/tVypVGoVu9hVtd8+6RLImLdu+W7SJZAhnY64KOkSAKBF1b//WtIlNMma73+5xT6r3bm/2yBpy+fzkc/nN3ntqlWrYuedd45LLrkkvvGNbzTq8yRvKXLiCWNi8XNzY03dC/HgA7fHoL0/k3RJpNSyVWvirN/cG1/4wTWxz/eujH+7cEY8+crypMsi5XxHUWnGFJVmTG0hWjB5y+fz0blz5wZHYxq3iIhtt902PvWpT8XixYsb/atp3lLiqKMOjYsvmhTn/fiSGLTP8Fi46Kn4452/jZqabkmXRsrUvVOIsb+4Naratokrjh8ZN3//6Jhw2Oejc4fGfdHAxviOotKMKSrNmKK1WbNmTbzwwgvRu3fvRl/jscmUePCB22Peowvj1NN+EBERuVwuXvzLvJj8y2lx4UWTE66udfLY5MZdfsfceOyvr8e0k49IupRU8djkx/MdRaUZU1SaMVW+1D42ecZhLfZZ21z8341+7xlnnBGjR4+OnXfeOZYsWRKTJk2Kxx57LJ566qmoqalp1D0kbynQrl272Guv/jFr9p/WnyuVSjFr9gMxePDABCsjje5/8sXot2OPOOPau2Poj6bFV392Y9z00FNJl0WK+Y6i0owpKs2YojV49dVX49///d+jT58+8ZWvfCW6desWc+fObXTjFtHEfd7+9re/Rbduf4+YX3nllbjqqqvi3XffjUMPPTT233//ptySj9G9e9eoqqqK5ctWNji/fPmK6NvnXxKqirR69W91ceODT8axX+gf3/ziXvHEKyviwlseiHZVbeLQQX2TLo8U8h1FpRlTVJoxtYUptooHCzdwww03bPY9ymreHn/88Rg9enS88sorsdtuu8UNN9wQw4cPj7fffjvatGkTl156afzhD3+Iww8//GPvUygUNliVpVQqRS6XK/sXAMpTLJWi3441ccrIwRER0XeHmnjh9TfiDw8+pXkDAGjFynps8nvf+17sueeeMWfOnDjggANi1KhRMXLkyFi9enW8+eab8a1vfSsuuOCCTd5nY/shlIqN399gS7Ny5RtRX18fPXp2b3C+R4+aWLpsRUJVkVY1nbeOf+nZpcG5XXpuG6+/uSahikg731FUmjFFpRlTW5ZSsdRiR0srq3mbN29enH/++TFkyJC4+OKLY8mSJXHSSSdFmzZtok2bNnHyySfHM888s8n7TJw4MVavXt3gyLXp1ORfIuvWrl0bCxYsimFD91t/LpfLxbCh+8XcufMTrIw0GvCJXvHi8lUNzr20YnX07rpNMgWRer6jqDRjikozpsiKsh6bfOONN6JXr14REbHNNttEx44do0uXf/wX/C5dujRqh/CNbVznkcmPd+nlV8W0qZfG/AWLYt68P8cpJx8fHTt2iOnXzki6NFLm2C8MiLE/vyWuvnd+HDxg13ji5WVx09yn4odHfSHp0kgx31FUmjFFpRlTZEHZC5b8c5Ol6WoZN954W9R07xpn/+iM6NWrJhYufDJGjjo2li9fuemL4UP22KlHXDLukPj5nQ/Hlf8zP7bv2im+e9iQGDnwU0mXRor5jqLSjCkqzZjagrTSBUsqoax93tq0aRMjRoxYn5rdfvvtMWzYsOjYsWNE/H0hkpkzZ8a6devKLsQ+b1Safd6oJPu8AbClSes+b2+dMqrFPqvTz+9osc+KKDN5GzNmTIPXxx577AbvOe644zavIgAAgKYqFpOuoNmU1bxNmzatueoAAADgYzRpk24A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YUKMGTMm9t577/jc5z4Xl112Wbz99tsxbty4Rl2veUuRfD4fkyZNMsGWijGmqDRjikozpqgk44mkffWrX40VK1bEj370o1i6dGl85jOfiZkzZ26wiMlHsWAJAABACpjzBgAAkAKaNwAAgBTQvAEAAKSA5g0AACAFNG8p8dBDD0Xbtm1j5MiRSZdCyo0dOzZyudz6o1u3bjF8+PBYtGhR0qWRckuXLo2TTz45PvnJT0Y+n48dd9wxRo8e3WA/G2iMD39PtWvXLnr27BkHHXRQXHPNNVEsFpMujxT65799HxzDhw9PujQoi+YtJaZOnRonn3xyzJkzJ5YsWZJ0OaTc8OHD4/XXX4/XX389Zs2aFVVVVTFq1KikyyLFXnzxxRg4cGDMnj07Lrroonj88cdj5syZMXTo0Bg/fnzS5ZFCH3xPvfjii3HXXXfF0KFD49RTT41Ro0ZFfX190uWRQh/+2/fB8bvf/S7psqAs9nlLgTVr1sSMGTPi0UcfjaVLl8b06dPjrLPOSrosUiyfz0evXr0iIqJXr15x5plnxv777x8rVqyImpqahKsjjU466aTI5XLxyCOPRMeOHdef33333ePrX/96gpWRVh/+ntp+++1jr732isGDB8cXv/jFmD59enzzm99MuELS5sNjCtJK8pYCv//976Nv377Rp0+fOPbYY+Oaa64J2/NRKWvWrInf/OY3seuuu0a3bt2SLocUeuONN2LmzJkxfvz4Bo3bB7bddtuWL4pMGjZsWAwYMCBuvvnmpEsBSITmLQWmTp0axx57bET8PfJfvXp13H///QlXRZrdcccdsc0228Q222wTnTp1ittuuy1mzJgRbdr4SqB8ixcvjlKpFH379k26FLYAffv2jRdffDHpMkihD//t++D4yU9+knRZUBaPTbZyzz77bDzyyCNxyy23REREVVVVfPWrX42pU6fGAQcckGxxpNbQoUNjypQpERHx5ptvxi9/+csYMWJEPPLII7HzzjsnXB1p40kAWlKpVIpcLpd0GaTQh//2faBr164JVQNNo3lr5aZOnRr19fWx3XbbrT9XKpUin8/HFVdcEdXV1QlWR1p17Ngxdt111/Wvr7766qiuro6rrroqfvzjHydYGWm02267RS6Xi2eeeSbpUtgCPP3007HLLrskXQYp9M9/+yCNPCPVitXX18d1110XP/vZz+Kxxx5bfyxcuDC22247KyRRMblcLtq0aRPvvvtu0qWQQl27do1DDjkkJk+eHG+//fYGP1+1alXLF0UmzZ49Ox5//PE48sgjky4FIBGSt1bsjjvuiDfffDO+8Y1vbJCwHXnkkTF16tQ44YQTEqqONCsUCrF06dKI+Ptjk1dccUWsWbMmRo8enXBlpNXkyZNjyJAh8bnPfS7OPffc6N+/f9TX18c999wTU6ZMiaeffjrpEkmZD76n1q1bF8uWLYuZM2dGbW1tjBo1Ko477rikyyOFPvy37wNVVVXRvXv3hCqC8mneWrGpU6fGgQceuNFHI4888si48MILY9GiRdG/f/8EqiPNZs6cGb17946IiE6dOkXfvn3jxhtvNI+SJvvkJz8ZCxYsiPPPPz++853vxOuvvx41NTUxcODADeaYQGN88D1VVVUVXbp0iQEDBsTPf/7zGDNmjMWVaJIP/+37QJ8+fTzyTarkSmaaAwAAtHr+0xUAAEAKaN4AAABSQPMGAACQApo3AACAFNC8AQAApIDmDQAAIAU0bwAAACmgeQMAAEgBzRsAAEAKaN4AAABSQPMGAACQApo3AACAFPh/JjvFdS+oC5gAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "y_pred = []\n", - "y_true = []\n", - "\n", - "for inputs, label in test_set:\n", - " output = spoter(inputs) # Feed Network\n", - "\n", - " output = torch.argmax(output, dim=2) # Get Prediction\n", - " output = output.detach().numpy().tolist()[0] # Convert to list\n", - " \n", - " y_true.extend(output) # Save Truth\n", - "\n", - " y_pred.append(label) # Save Prediction\n", - "\n", - "# constant for classes\n", - "classes = ('A', 'B', 'C', 'D', 'E')\n", - "\n", - "# Build confusion matrix\n", - "cf_matrix = confusion_matrix(y_true, y_pred)\n", - "df_cm = pd.DataFrame(cf_matrix, index = [i for i in classes],\n", - " columns = [i for i in classes])\n", - "plt.figure(figsize = (12,7))\n", - "sn.heatmap(df_cm, annot=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/export.py b/export.py deleted file mode 100644 index 5f57003..0000000 --- a/export.py +++ /dev/null @@ -1,31 +0,0 @@ -import torch -import torchvision -import onnx -import numpy as np - -from src.model import SPOTER -from src.identifiers import LANDMARKS - -model_name = 'Fingerspelling_AE' - -# load PyTorch model from .pth file -model = SPOTER(num_classes=5, hidden_dim=len(LANDMARKS) *2) -state_dict = torch.load('models/' + model_name + '.pth') -model.load_state_dict(state_dict) - -# set model to evaluation mode -model.eval() - -# create dummy input tensor -batch_size = 1 -num_of_frames = 1 -input_shape = (108, num_of_frames) -dummy_input = torch.randn(batch_size, *input_shape) - -# export model to ONNX format -output_file = 'models/' + model_name + '.onnx' -torch.onnx.export(model, dummy_input, output_file, input_names=['input'], output_names=['output']) - -# load exported ONNX model for verification -onnx_model = onnx.load(output_file) -onnx.checker.check_model(onnx_model) \ No newline at end of file diff --git a/export_json.py b/export_json.py new file mode 100644 index 0000000..cef85af --- /dev/null +++ b/export_json.py @@ -0,0 +1,17 @@ +import json + +from src.identifiers import HAND_LANDMARKS, POSE_LANDMARKS + + +def export_json(pose_landmarks, hand_landmarks, filename): + + l = { + "pose_landmarks": list(pose_landmarks.values()), + "hand_landmarks": list(hand_landmarks.values()) + } + + # write l to filename + with open(filename, 'w') as f: + json.dump(l, f) + +export_json(POSE_LANDMARKS, HAND_LANDMARKS, "landmarks.json") \ No newline at end of file diff --git a/landmarks.json b/landmarks.json new file mode 100644 index 0000000..ecc5b22 --- /dev/null +++ b/landmarks.json @@ -0,0 +1 @@ +{"pose_landmarks": [0, 2, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16], "hand_landmarks": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]} \ No newline at end of file diff --git a/models/model_A-L.onnx b/models/model_A-L.onnx new file mode 100644 index 0000000..f10f15c Binary files /dev/null and b/models/model_A-L.onnx differ diff --git a/models/model_A-Z.onnx b/models/model_A-Z.onnx new file mode 100644 index 0000000..19893c4 Binary files /dev/null and b/models/model_A-Z.onnx differ diff --git a/models/model_A-Z.pth b/models/model_A-Z.pth new file mode 100644 index 0000000..b55a3f4 Binary files /dev/null and b/models/model_A-Z.pth differ diff --git a/requirements.txt b/requirements.txt index ec3947f..e0c64df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ torchvision==0.14.1 pandas==1.5.3 mediapipe==0.9.1.0 tensorboard==2.12.0 -mediapy==1.1.6 \ No newline at end of file +mediapy==1.1.6 +scikit-learn==0.24.2 \ No newline at end of file diff --git a/src/augmentations.py b/src/augmentations.py index 2c1adbe..4710630 100644 --- a/src/augmentations.py +++ b/src/augmentations.py @@ -1,5 +1,39 @@ +import math import random +import numpy as np + +import math +import torch + +def circle_intersection(x0, y0, r0, x1, y1, r1): + # circle 1: (x0, y0), radius r0 + # circle 2: (x1, y1), radius r1 + + d=math.sqrt((x1-x0)**2 + (y1-y0)**2) + + # non intersecting + if d > r0 + r1 : + return None + # One circle within other + if d < abs(r0-r1): + return None + # coincident circles + if d == 0 and r0 == r1: + return None + else: + a=(r0**2-r1**2+d**2)/(2*d) + h=math.sqrt(r0**2-a**2) + x2=x0+a*(x1-x0)/d + y2=y0+a*(y1-y0)/d + x3=x2+h*(y1-y0)/d + y3=y2-h*(x1-x0)/d + + x4=x2-h*(y1-y0)/d + y4=y2+h*(x1-x0)/d + + return (np.array([x3, y3]), np.array([x4, y4])) + class MirrorKeypoints: def __call__(self, sample): @@ -8,4 +42,86 @@ class MirrorKeypoints: # flip the keypoints tensor sample = 1 - sample + return sample + +class Z_augmentation: + + def __init__(self, hand_side="left"): + self.hand_side = hand_side + + def new_wrist(self, sample, hand_side="left", new_wrist=None): + if hand_side == "left": + wrist = sample[30:32] + shoulder = sample[22:24] + elbow = sample[26:28] + else: + wrist = sample[32:34] + shoulder = sample[24:26] + elbow = sample[28:30] + + # calculate the length of the shoulder to elbow using math package + shoulder_elbow_length = math.sqrt((shoulder[0] - elbow[0])**2 + (shoulder[1] - elbow[1])**2) + # calculate the length of the wrist to elbow using math package + wrist_elbow_length = math.sqrt((wrist[0] - elbow[0])**2 + (wrist[1] - elbow[1])**2) + + if shoulder_elbow_length == 0 or wrist_elbow_length == 0: + return sample, None + + first_time = True + new_loc = False + while not new_loc: + + if new_wrist is None or not first_time: + # get random new wrist point that is not too far from the elbow + new_wrist = [random.uniform(elbow[0] - 0.3, elbow[0] + 0.3), random.uniform(elbow[1] - 0.3, elbow[1] + 0.3)] + + # get intersection points of the circles + c = circle_intersection(shoulder[0], shoulder[1], shoulder_elbow_length, new_wrist[0], new_wrist[1], wrist_elbow_length) + if c is not None: + (i1, i2) = c + new_loc = True + first_time = False + + # get the point that is below the hand + if i1[1] > i2[1]: + new_elbow = i1 + else: + new_elbow = i2 + # new_elbow to shape (2,1) + new_elbow = np.array(new_elbow) + new_wrist = np.array(new_wrist) + + # replace the keypoints in the sample + if hand_side == "left": + sample[26:28] = new_elbow + sample[30:32] = new_wrist + else: + sample[28:30] = new_elbow + sample[32:34] = new_wrist + return sample, new_wrist + + def __call__(self, samples): + # transform each sample in the batch + t_new = [] + + t = samples.numpy() + new_wrist = None + for t_i in t: + # if new_wrist is None: + # new_t, w = self.new_wrist(t_i.reshape(-1), self.hand_side) + # new_wrist = w + # else: + new_t, _ = self.new_wrist(t_i.reshape(-1), self.hand_side) + # reshape back to 2 dimensions + t_new.append(new_t.reshape(-1, 2)) + return torch.tensor(np.array(t_new)) + +# augmentation to add little randow noise to the keypoints +class NoiseAugmentation: + def __init__(self, noise=0.05): + self.noise = noise + + def __call__(self, sample): + # add noise to the keypoints + sample = sample + torch.randn(sample.shape) * self.noise return sample \ No newline at end of file diff --git a/src/datasets/finger_spelling_dataset.py b/src/datasets/finger_spelling_dataset.py index 6ee25e8..23d466f 100644 --- a/src/datasets/finger_spelling_dataset.py +++ b/src/datasets/finger_spelling_dataset.py @@ -9,43 +9,43 @@ from src.keypoint_extractor import KeypointExtractor class FingerSpellingDataset(torch.utils.data.Dataset): - def __init__(self, data_folder: str, keypoint_extractor: KeypointExtractor, subset:str="train", keypoints_identifier: dict = None, transform=None): + def __init__(self, data_folder: str, bad_data_folder: str = "", subset:str="train", keypoints_identifier: dict = None, transform=None): - # list data from data folder - self.data_folder = data_folder - # list files in the datafolder ending with .mp4 - files = [f for f in os.listdir(self.data_folder) if f.endswith(".mp4")] + # list files with path in the datafolder ending with .mp4 + files = [data_folder + f for f in os.listdir(data_folder) if f.endswith(".mp4")] - labels = [f.split("!")[0] for f in files] + # append files from bad data folder + if bad_data_folder != "": + files += [bad_data_folder + f for f in os.listdir(bad_data_folder) if f.endswith(".mp4")] + + labels = [f.split("/")[-1].split("!")[0] for f in files] + train_test = [f.split("/")[-1].split("!")[1] for f in files] # count the number of each label self.label_mapping, counts = np.unique(labels, return_counts=True) - # save the label mapping to a file - with open(os.path.join(self.data_folder, "label_mapping.txt"), "w") as f: - for i, label in enumerate(self.label_mapping): - f.write(f"{label} {i}") + # map the labels to their integer labels = [np.where(self.label_mapping == label)[0][0] for label in labels] - # TODO: make split for train and val and test when enough data is available - # split the data into train and val and test and make them balanced - x_train, x_test, y_train, y_test = train_test_split(files, labels, test_size=0.3, random_state=1, stratify=labels) - + # TODO: make split for train and val and test when enough data is available if subset == "train": - self.data = x_train - self.labels = y_train - elif subset == "val": - self.data = x_test - self.labels = y_test + # mask for train data + mask = np.array(train_test) == "train" + elif subset == "test": + mask = np.array(train_test) == "test" + + # filter data and labels + self.data = np.array(files)[mask] + self.labels = np.array(labels)[mask] # filter wlasl data by subset self.transform = transform self.subset = subset - self.keypoint_extractor = keypoint_extractor + self.keypoint_extractor = KeypointExtractor() if keypoints_identifier: self.keypoints_to_keep = [f"{i}_{j}" for i in keypoints_identifier.values() for j in ["x", "y"]] @@ -56,24 +56,40 @@ class FingerSpellingDataset(torch.utils.data.Dataset): # get i th element from ordered dict video_name = self.data[index] - # get the keypoints for the video - keypoints_df = self.keypoint_extractor.extract_keypoints_from_video(video_name, normalize="minxmax") + cache_name = video_name.split("/")[-1].split(".")[0] + ".npy" - # filter the keypoints by the identified subset - if self.keypoints_to_keep: - keypoints_df = keypoints_df[self.keypoints_to_keep] - - current_row = np.empty(shape=(keypoints_df.shape[0], keypoints_df.shape[1] // 2, 2)) - for i in range(0, keypoints_df.shape[1], 2): - current_row[:, i//2, 0] = keypoints_df.iloc[:,i] - current_row[:, i//2, 1] = keypoints_df.iloc[:,i+1] + # check if cache_name file exists + if not os.path.isfile(os.path.join("cache_processed", cache_name)): + + # get the keypoints for the video (normalizations: minxmax, bohacek) + keypoints_df = self.keypoint_extractor.extract_keypoints_from_video(video_name, normalize="bohacek") + + # filter the keypoints by the identified subset + if self.keypoints_to_keep: + keypoints_df = keypoints_df[self.keypoints_to_keep] + + current_row = np.empty(shape=(keypoints_df.shape[0], keypoints_df.shape[1] // 2, 2)) + for i in range(0, keypoints_df.shape[1], 2): + current_row[:, i // 2, 0] = keypoints_df.iloc[:, i] + current_row[:, i // 2, 1] = keypoints_df.iloc[:, i + 1] + + # check if cache_processed folder exists + if not os.path.isdir("cache_processed"): + os.mkdir("cache_processed") + + # save the processed data to a file + np.save(os.path.join("cache_processed", cache_name), current_row) + + else: + current_row = np.load(os.path.join("cache_processed", cache_name)) + + # get the label label = self.labels[index] - # data to tensor data = torch.from_numpy(current_row) if self.transform: data = self.transform(data) - return data, label \ No newline at end of file + return data, label diff --git a/src/export.py b/src/export.py new file mode 100644 index 0000000..217513b --- /dev/null +++ b/src/export.py @@ -0,0 +1,44 @@ +import torch +import torchvision +import onnx +import numpy as np + +from src.model import SPOTER +from src.identifiers import LANDMARKS + +# set parameters of the model +model_name = 'model_A-Z' +num_classes = 26 + +# load PyTorch model from .pth file +model = SPOTER(num_classes=num_classes, hidden_dim=len(LANDMARKS) *2) +if torch.cuda.is_available(): + state_dict = torch.load('models/' + model_name + '.pth') +else: + state_dict = torch.load('models/' + model_name + '.pth', map_location=torch.device('cpu')) +model.load_state_dict(state_dict) + +# set model to evaluation mode +model.eval() + +# create dummy input tensor +dummy_input = torch.randn(10, 108) + +# export model to ONNX format +output_file = 'models/' + model_name + '.onnx' +torch.onnx.export(model, dummy_input, output_file, input_names=['input'], output_names=['output']) + +torch.onnx.export(model, # model being run + dummy_input, # model input (or a tuple for multiple inputs) + 'models/' + model_name + '.onnx', # where to save the model (can be a file or file-like object) + export_params=True, # store the trained parameter weights inside the model file + opset_version=9, # the ONNX version to export the model to + do_constant_folding=True, # whether to execute constant folding for optimization + input_names = ['X'], # the model's input names + output_names = ['Y'] # the model's output names + ) + + +# load exported ONNX model for verification +onnx_model = onnx.load(output_file) +onnx.checker.check_model(onnx_model) \ No newline at end of file diff --git a/src/identifiers.py b/src/identifiers.py index 9d9e88e..462c197 100644 --- a/src/identifiers.py +++ b/src/identifiers.py @@ -80,3 +80,65 @@ LANDMARKS = { "right_pinky_dip": 73, "right_pinky_tip": 74, } + +POSE_LANDMARKS = { + # Pose Landmarks + "nose": 0, + # "left_eye_inner": 1, + "left_eye": 2, + # "left_eye_outer": 3, + # "right_eye_inner": 4, + "right_eye": 5, + # "right_eye_outer": 6, + "left_ear": 7, + "right_ear": 8, + "mouth_left": 9, + # "mouth_right": 10, + "left_shoulder": 11, + "right_shoulder": 12, + "left_elbow": 13, + "right_elbow": 14, + "left_wrist": 15, + "right_wrist": 16, + # "left_pinky": 17, + # "right_pinky": 18, + # "left_index": 19, + # "right_index": 20, + # "left_thumb": 21, + # "right_thumb": 22, + # "left_hip": 23, + # "right_hip": 24, + # "left_knee": 25, + # "right_knee": 26, + # "left_ankle": 27, + # "right_ankle": 28, + # "left_heel": 29, + # "right_heel": 30, + # "left_foot_index": 31, + # "right_foot_index": 32, +} + +HAND_LANDMARKS = { + # Left Hand Landmarks + "wrist": 0, + "thumb_cmc": 1, + "thumb_mcp": 2, + "thumb_ip": 3, + "thumb_tip": 4, + "index_finger_mcp": 5, + "index_finger_pip": 6, + "index_finger_dip": 7, + "index_finger_tip": 8, + "middle_finger_mcp": 9, + "middle_finger_pip": 10, + "middle_finger_dip": 11, + "middle_finger_tip": 12, + "ring_finger_mcp": 13, + "ring_finger_pip": 14, + "ring_finger_dip": 15, + "ring_finger_tip": 16, + "pinky_mcp": 17, + "pinky_pip": 18, + "pinky_dip": 19, + "pinky_tip": 20, +} \ No newline at end of file diff --git a/src/keypoint_extractor.py b/src/keypoint_extractor.py index ac24154..83859d0 100644 --- a/src/keypoint_extractor.py +++ b/src/keypoint_extractor.py @@ -10,10 +10,10 @@ import pandas as pd class KeypointExtractor: - def __init__(self, video_folder: str, cache_folder: str = "cache"): + def __init__(self, cache_folder: str = "cache"): self.mp_drawing = mp.solutions.drawing_utils self.mp_holistic = mp.solutions.holistic - self.video_folder = video_folder + # self.video_folder = video_folder self.cache_folder = cache_folder # we will store the keypoints of each frame as a row in the dataframe. The columns are the keypoints: Pose (33), Left Hand (21), Right Hand (21). Each keypoint has 3 values: x, y @@ -40,10 +40,12 @@ class KeypointExtractor: :rtype: pd.DataFrame """ + video_name = video.split("/")[-1].split(".")[0] + if not draw: # check if video exists - if not os.path.exists(self.video_folder + video): - logging.error("Video does not exist at path: " + self.video_folder + video) + if not os.path.exists(video): + logging.error("Video does not exist at path: " + video) return None # check if cache exists @@ -51,22 +53,22 @@ class KeypointExtractor: os.makedirs(self.cache_folder) # check if cache file exists and return - if os.path.exists(self.cache_folder + "/" + video + ".npy"): + if os.path.exists(self.cache_folder + "/" + video_name + ".npy"): # create dataframe from cache - df = pd.DataFrame(np.load(self.cache_folder + "/" + video + ".npy", allow_pickle=True), columns=self.columns) + df = pd.DataFrame(np.load(self.cache_folder + "/" + video_name + ".npy", allow_pickle=True), columns=self.columns) if normalize: df = self.normalize_hands(df, norm_algorithm=normalize) - df = self.normalize_pose_bohacek(df) + df, _ = self.normalize_pose_bohacek(df) return df # open video - cap = cv2.VideoCapture(self.video_folder + video) + cap = cv2.VideoCapture(video) keypoints_df = pd.DataFrame(columns=self.columns) # extract frames from video so we extract 5 frames per second frame_rate = int(cap.get(cv2.CAP_PROP_FPS)) - frame_skip = frame_rate // 10 + frame_skip = (frame_rate // 10) -1 output_frames = [] @@ -113,12 +115,12 @@ class KeypointExtractor: cap.release() # save keypoints to cache - np.save(self.cache_folder + "/" + video + ".npy", keypoints_df.to_numpy()) + np.save(self.cache_folder + "/" + video_name + ".npy", keypoints_df.to_numpy()) # normalize hands and pose keypoints if normalize: keypoints_df = self.normalize_hands(keypoints_df, norm_algorithm=normalize) - keypoints_df = self.normalize_pose_bohacek(keypoints_df) + keypoints_df, _ = self.normalize_pose_bohacek(keypoints_df) if draw: return keypoints_df, output_frames @@ -179,28 +181,28 @@ class KeypointExtractor: if norm_algorithm == "minmax": # normalize left hand - dataframe = self.normalize_hand_minmax(dataframe, "left_hand") + dataframe, _= self.normalize_hand_minmax(dataframe, "left_hand") # normalize right hand - dataframe = self.normalize_hand_minmax(dataframe, "right_hand") + dataframe, _= self.normalize_hand_minmax(dataframe, "right_hand") elif norm_algorithm == "bohacek": # normalize left hand - dataframe = self.normalize_hand_bohacek(dataframe, "left_hand") + dataframe, _= self.normalize_hand_bohacek(dataframe, "left_hand") # normalize right hand - dataframe = self.normalize_hand_bohacek(dataframe, "right_hand") + dataframe, _= self.normalize_hand_bohacek(dataframe, "right_hand") else: return dataframe return dataframe - def normalize_hand_minmax(self, dataframe: pd.DataFrame, hand: str) -> pd.DataFrame: - """normalize_hand_minmax this function normalizes the hand keypoints of a dataframe with respect to the minimum and maximum coordinates + def normalize_hand_minmax(self, dataframe: pd.DataFrame, hand: str) -> Tuple[pd.DataFrame, pd.DataFrame]: + """normalize_hand_helper this function normalizes the hand keypoints of a dataframe with respect to the minimum and maximum coordinates :param dataframe: the dataframe to normalize :type dataframe: pd.DataFrame :param hand: the hand to normalize :type hand: str - :return: the normalized dataframe - :rtype: pd.DataFrame + :return: the normalized dataframe and the bounding boxes dataframe + :rtype: Tuple[pd.DataFrame, pd.DataFrame] """ # get all columns that belong to the hand (left hand column 66 - 107, right hand column 108 - 149) hand_columns = np.array([i for i in range(66 + (42 if hand == "right_hand" else 0), 108 + (42 if hand == "right_hand" else 0))]) @@ -226,24 +228,28 @@ class KeypointExtractor: bbox_dims = np.concatenate((np.tile(bbox_width, (1, 21, 1)), np.tile(bbox_height, (1, 21, 1))), axis=2) if np.any(bbox_dims == 0): - return dataframe + return dataframe, None # normalize the hand keypoints based on the bounding box around the hand norm_hand_coords = (hand_coords - center_coords) / bbox_dims # flatten the normalized hand keypoints array and replace the original hand keypoints with the normalized hand keypoints in the dataframe dataframe.iloc[:, hand_columns] = norm_hand_coords.reshape(-1, 42) + + # merge starting and ending points of the bounding boxes in a dataframe + bbox_array = np.hstack((min_x.reshape(-1, 1), min_y.reshape(-1, 1), max_x.reshape(-1, 1), max_y.reshape(-1, 1))) + bbox = pd.DataFrame(bbox_array, columns=['starting_x', 'starting_y', 'ending_x', 'ending_y']) - return dataframe + return dataframe, bbox - def normalize_hand_bohacek(self, dataframe: pd.DataFrame, hand: str) -> pd.DataFrame: - """normalize_hand_bohacek this function normalizes the hand keypoints of a dataframe using the Bohacek-normalization algorithm + def normalize_hand_bohacek(self, dataframe: pd.DataFrame, hand: str) -> Tuple[pd.DataFrame, pd.DataFrame]: + """normalize_hand_helper this function normalizes the hand keypoints of a dataframe using the bohacek normalization algorithm :param dataframe: the dataframe to normalize :type dataframe: pd.DataFrame :param hand: the hand to normalize :type hand: str - :return: the normalized dataframe - :rtype: pd.DataFrame + :return: the normalized dataframe and the bounding boxes dataframe + :rtype: Tuple[pd.DataFrame, pd.DataFrame] """ # get all columns that belong to the hand (left hand column 66 - 107, right hand column 108 - 149) hand_columns = np.array([i for i in range(66 + (42 if hand == "right_hand" else 0), 108 + (42 if hand == "right_hand" else 0))]) @@ -287,22 +293,28 @@ class KeypointExtractor: bbox_dims = np.concatenate((np.tile(bbox_width, (1, 21, 1)), np.tile(bbox_height, (1, 21, 1))), axis=2) if np.any(bbox_dims == 0): - return dataframe + return dataframe, None # normalize the hand keypoints based on the bounding box around the hand norm_hand_coords = (hand_coords - center_coords) / bbox_dims # flatten the normalized hand keypoints array and replace the original hand keypoints with the normalized hand keypoints in the dataframe dataframe.iloc[:, hand_columns] = norm_hand_coords.reshape(-1, 42) - - return dataframe - def normalize_pose_bohacek(self, dataframe: pd.DataFrame) -> pd.DataFrame: + # merge starting and ending points of the bounding boxes in a dataframe + bbox_array = np.hstack((starting_x.reshape(-1, 1), starting_y.reshape(-1, 1), ending_x.reshape(-1, 1), ending_y.reshape(-1, 1))) + bbox = pd.DataFrame(bbox_array, columns=['starting_x', 'starting_y', 'ending_x', 'ending_y']) + + return dataframe, bbox + + def normalize_pose_bohacek(self, dataframe: pd.DataFrame, bbox_size: float = 4) -> Tuple[pd.DataFrame, pd.DataFrame]: """normalize_pose_bohacek this function normalizes the pose keypoints of a dataframe using the Bohacek-normalization algorithm :param dataframe: the dataframe to normalize :type dataframe: pd.DataFrame - :return: the normalized dataframe - :rtype: pd.DataFrame + :param bbox_size: the width and height of the normalization bounding box expressed in head metrics, defaults to 4 + :type bbox_size: float, optional + :return: the normalized dataframe and the bounding boxes dataframe + :rtype: Tuple[pd.DataFrame, pd.DataFrame] """ # get the columns that belong to the pose pose_columns = np.array([i for i in range(66)]) @@ -311,28 +323,22 @@ class KeypointExtractor: pose_coords = dataframe.iloc[:, pose_columns].values.reshape(-1, 33, 2) # check in what frames shoulders are visible - left_shoulder_present_mask = pose_coords[:, 11, 0]!=0 - right_shoulder_present_mask = pose_coords[:, 12, 0]!=0 - shoulders_present_mask = np.logical_and(left_shoulder_present_mask,right_shoulder_present_mask) + left_shoulder_present_mask = pose_coords[:, 11, 0] != 0 + right_shoulder_present_mask = pose_coords[:, 12, 0] != 0 + shoulders_present_mask = np.logical_and(left_shoulder_present_mask, right_shoulder_present_mask) # calculate shoulder distance - left_shoulder, right_shoulder = pose_coords[shoulders_present_mask, 11,], pose_coords[shoulders_present_mask, 12,] + left_shoulder, right_shoulder = pose_coords[shoulders_present_mask, 11], pose_coords[shoulders_present_mask, 12] shoulder_distance = ((left_shoulder[:, 0] - right_shoulder[:, 0])**2 + (left_shoulder[:, 1] - right_shoulder[:, 1])**2)**0.5 head_metric = shoulder_distance # center of shoulders and left eye are necessary to construct bounding box - center_shoulders = right_shoulder + (left_shoulder - right_shoulder)/2 + center_shoulders = right_shoulder + (left_shoulder - right_shoulder) / 2 left_eye = pose_coords[shoulders_present_mask, 2] # set the starting and ending point of the normalization bounding box - starting_x, starting_y = center_shoulders[:, 0] - 2*head_metric, left_eye[:, 1] - 0.5*head_metric - ending_x, ending_y = center_shoulders[:, 0] + 2*head_metric, starting_y + 4*head_metric - - # ensure that the starting and ending point of the bounding box are not out of the frame - #starting_x = np.clip(starting_x, 0, None) - #starting_y = np.clip(starting_y, 0 ,None) - #ending_x = np.clip(ending_x, 0, None) - #ending_y = np.clip(ending_y, 0 ,None) + starting_x, starting_y = center_shoulders[:, 0] - (bbox_size / 2) * head_metric, left_eye[:, 1] - 0.5 * head_metric + ending_x, ending_y = center_shoulders[:, 0] + (bbox_size / 2) * head_metric, starting_y + (bbox_size - 0.5) * head_metric # calculate the center of the bounding box and the bounding box dimensions bbox_center_x, bbox_center_y = (starting_x + ending_x) / 2, (starting_y + ending_y) / 2 @@ -342,15 +348,19 @@ class KeypointExtractor: bbox_center_x, bbox_center_y = bbox_center_x.reshape(-1, 1, 1), bbox_center_y.reshape(-1, 1, 1) center_coords = np.concatenate((np.tile(bbox_center_x, (1, 33, 1)), np.tile(bbox_center_y, (1, 33, 1))), axis=2) - bbox_width, bbox_height = bbox_width.reshape(-1, 1, 1), bbox_height.reshape(-1, 1 ,1) + bbox_width, bbox_height = bbox_width.reshape(-1, 1, 1), bbox_height.reshape(-1, 1, 1) bbox_dims = np.concatenate((np.tile(bbox_width, (1, 33, 1)), np.tile(bbox_height, (1, 33, 1))), axis=2) if np.any(bbox_dims == 0): - return dataframe + return dataframe, None # normalize the pose keypoints based on the bounding box - norm_pose_coords= (pose_coords - center_coords) / bbox_dims + norm_pose_coords = (pose_coords - center_coords) / bbox_dims # flatten the normalized pose keypoints array and replace the original pose keypoints with the normalized pose keypoints in the dataframe dataframe.iloc[shoulders_present_mask, pose_columns] = norm_pose_coords.reshape(-1, 66) - return dataframe \ No newline at end of file + # merge starting and ending points of the bounding boxes in a dataframe + bbox_array = np.hstack((starting_x.reshape(-1, 1), starting_y.reshape(-1, 1), ending_x.reshape(-1, 1), ending_y.reshape(-1, 1))) + bbox = pd.DataFrame(bbox_array, columns=['starting_x', 'starting_y', 'ending_x', 'ending_y']) + + return dataframe, bbox diff --git a/src/loss_function.py b/src/loss_function.py new file mode 100644 index 0000000..266fb68 --- /dev/null +++ b/src/loss_function.py @@ -0,0 +1,21 @@ +# create custom loss function +import torch +import torch.nn as nn +from src.datasets.finger_spelling_dataset import FingerSpellingDataset + +from src.keypoint_extractor import KeypointExtractor +from torch.utils.data import DataLoader +from src.identifiers import LANDMARKS + +class CustomLoss(nn.Module): + # combine cross entropy loss and L1 loss + def __init__(self): + super(CustomLoss, self).__init__() + + def forward(self, pred, target): + # the prediciton for Z cannot be higher than 0.6 else give a high loss, backward must be able to learn this (return tensor) + + if torch.nn.functional.softmax(pred, dim=2)[0][0][25] > 0.4: + return torch.tensor(100.0, requires_grad=True) + + return torch.tensor(0.0, requires_grad=True) \ No newline at end of file diff --git a/src/model.py b/src/model.py index 6ee9cce..758843c 100644 --- a/src/model.py +++ b/src/model.py @@ -1,6 +1,7 @@ ### SPOTER model implementation from the paper "SPOTER: Sign Pose-based Transformer for Sign Language Recognition from Sequence of Skeletal Data" import copy +import math from typing import Optional import torch @@ -38,7 +39,20 @@ class SPOTERTransformerDecoderLayer(nn.TransformerDecoderLayer): return tgt +class PositionalEmbedding(nn.Module): + def __init__(self, d_model, max_len=60): + super().__init__() + pe = torch.zeros(max_len, d_model) + position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) + div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) + pe[:, 0::2] = torch.sin(position * div_term) + pe[:, 1::2] = torch.cos(position * div_term) + pe = pe.unsqueeze(0).transpose(0, 1) + self.register_buffer('pe', pe) + def forward(self, x): + return x + self.pe[:x.size(0), :] + class SPOTER(nn.Module): """ Implementation of the SPOTER (Sign POse-based TransformER) architecture for sign language recognition from sequence @@ -48,8 +62,9 @@ class SPOTER(nn.Module): def __init__(self, num_classes, hidden_dim=55): super().__init__() - self.row_embed = nn.Parameter(torch.rand(50, hidden_dim)) - self.pos = nn.Parameter(torch.cat([self.row_embed[0].unsqueeze(0).repeat(1, 1, 1)], dim=-1).flatten(0, 1).unsqueeze(0)) + + self.pos = PositionalEmbedding(hidden_dim) + self.class_query = nn.Parameter(torch.rand(1, hidden_dim)) self.transformer = nn.Transformer(hidden_dim, 9, 6, 6) self.linear_class = nn.Linear(hidden_dim, num_classes) @@ -61,7 +76,13 @@ class SPOTER(nn.Module): def forward(self, inputs): h = torch.unsqueeze(inputs.flatten(start_dim=1), 1).float() - h = self.transformer(self.pos + h, self.class_query.unsqueeze(0)).transpose(0, 1) + # add positional encoding + h = self.pos(h) + + # add class query + h = self.transformer(h, self.class_query.unsqueeze(0)).transpose(0, 1) + + # get class prediction res = self.linear_class(h) return res \ No newline at end of file diff --git a/src/normalizations.py b/src/normalizations.py new file mode 100644 index 0000000..b04d880 --- /dev/null +++ b/src/normalizations.py @@ -0,0 +1,64 @@ +import numpy as np + + +def normalize_hand_bohaecek(keypoints): + min_x, min_y = np.min(keypoints[::2]), np.min(keypoints[1::2]) + max_x, max_y = np.max(keypoints[::2]), np.max(keypoints[1::2]) + + width, height = max_x - min_x, max_y - min_y + + delta_x = 0.0 + delta_y = 0.0 + + if width > height: + delta_x = 0.1 * width + delta_y = delta_x + ((width - height) / 2) + else: + delta_y = 0.1 * height + delta_x = delta_y + ((height - width) / 2) + + starting_x, starting_y = min_x - delta_x, min_y - delta_y + ending_x, ending_y = max_x + delta_x, max_y + delta_y + + bbox_center_x, bbox_center_y = (starting_x + ending_x) / 2, (starting_y + ending_y) / 2 + bbox_width, bbox_height = ending_x - starting_x, ending_y - starting_y + + if bbox_width == 0 or bbox_height == 0: + return keypoints, None + + # every odd index minus center_x and divide by width, every even index minus center_y and divide by height + normalized_keypoints = np.zeros(keypoints.shape) + normalized_keypoints[::2] = (keypoints[::2] - bbox_center_x) / bbox_width + normalized_keypoints[1::2] = (keypoints[1::2] - bbox_center_y) / bbox_height + + return normalized_keypoints, (int(starting_x), int(starting_y), int(bbox_width), int(bbox_height)) + + +def normalize_pose(keypoints, bbox_size: float = 4.0): + shoulder_left = keypoints[22:24] + shoulder_right = keypoints[24:26] + + # distance between shoulders + shoulder_distance = np.linalg.norm(shoulder_left - shoulder_right) + + # center of shoulders + shoulder_center = (shoulder_left + shoulder_right) / 2 + + # left eye + eye_left = keypoints[4:6] + + starting_x, starting_y = shoulder_center[0] - (bbox_size / 2) * shoulder_distance, eye_left[1] - 0.5 * shoulder_distance + ending_x, ending_y = shoulder_center[0] + (bbox_size / 2) * shoulder_distance, starting_y + (bbox_size - 0.5) * shoulder_distance + + bbox_center_x, bbox_center_y = (starting_x + ending_x) / 2, (starting_y + ending_y) / 2 + bbox_width, bbox_height = ending_x - starting_x, ending_y - starting_y + + if bbox_width == 0 or bbox_height == 0: + return keypoints, None + + # every odd index minus center_x and divide by width, every even index minus center_y and divide by height + normalized_keypoints = np.zeros(keypoints.shape) + normalized_keypoints[::2] = (keypoints[::2] - bbox_center_x) / bbox_width + normalized_keypoints[1::2] = (keypoints[1::2] - bbox_center_y) / bbox_height + + return normalized_keypoints, (int(starting_x), int(starting_y), int(bbox_width), int(bbox_height)) diff --git a/src/train.py b/src/train.py index 7c8de98..1933b9a 100644 --- a/src/train.py +++ b/src/train.py @@ -1,11 +1,6 @@ -import argparse -import logging import os import random -from pathlib import Path -import matplotlib.pyplot as plt -import matplotlib.ticker as ticker import numpy as np import torch import torch.nn as nn @@ -13,15 +8,17 @@ import torch.optim as optim from torch.utils.data import DataLoader from torchvision import transforms -from src.augmentations import MirrorKeypoints +from src.augmentations import MirrorKeypoints, Z_augmentation, NoiseAugmentation from src.datasets.finger_spelling_dataset import FingerSpellingDataset -from src.datasets.wlasl_dataset import WLASLDataset from src.identifiers import LANDMARKS -from src.keypoint_extractor import KeypointExtractor from src.model import SPOTER +from src.loss_function import CustomLoss +import torch +from torch.utils.tensorboard import SummaryWriter def train(): + writer = SummaryWriter() random.seed(379) np.random.seed(379) os.environ['PYTHONHASHSEED'] = str(379) @@ -32,48 +29,57 @@ def train(): g = torch.Generator() g.manual_seed(379) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + device = torch.device("cuda:0") - spoter_model = SPOTER(num_classes=12, hidden_dim=len(LANDMARKS) *2) + spoter_model = SPOTER(num_classes=26, hidden_dim=len(LANDMARKS) *2) spoter_model.train(True) spoter_model.to(device) + criterion = nn.CrossEntropyLoss() - optimizer = optim.SGD(spoter_model.parameters(), lr=0.0001, momentum=0.9) - scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.1, patience=5) + criterion_bad = CustomLoss() + optimizer = optim.Adam(spoter_model.parameters(), lr=0.00001) + scheduler = None + + # check if checkpoints folder exists + if not os.path.exists("checkpoints"): + os.makedirs("checkpoints") - # TODO: create paths for checkpoints + transform = transforms.Compose([MirrorKeypoints(), NoiseAugmentation(noise=0.1)]) - # TODO: transformations + augmentations - - k = KeypointExtractor("data/fingerspelling/data/") - - transform = transforms.Compose([MirrorKeypoints()]) - - train_set = FingerSpellingDataset("data/fingerspelling/data/", k, keypoints_identifier=LANDMARKS, subset="train", transform=transform) + train_set = FingerSpellingDataset("data/fingerspelling/data/", bad_data_folder="", keypoints_identifier=LANDMARKS, subset="train", transform=transform) train_loader = DataLoader(train_set, shuffle=True, generator=g) - val_set = FingerSpellingDataset("data/fingerspelling/data/", k, keypoints_identifier=LANDMARKS, subset="val") + val_set = FingerSpellingDataset("data/fingerspelling/data/", bad_data_folder="", keypoints_identifier=LANDMARKS, subset="test") val_loader = DataLoader(val_set, shuffle=True, generator=g) - + + train_acc, val_acc = 0, 0 lr_progress = [] top_train_acc, top_val_acc = 0, 0 checkpoint_index = 0 - for epoch in range(100): + epochs_without_improvement = 0 + best_val_acc = 0 + + for epoch in range(300): running_loss = 0.0 pred_correct, pred_all = 0, 0 # train for i, (inputs, labels) in enumerate(train_loader): + # skip videos that are too short + if inputs.shape[1] < 20: + continue + inputs = inputs.squeeze(0).to(device) labels = labels.to(device, dtype=torch.long) optimizer.zero_grad() outputs = spoter_model(inputs).expand(1, -1, -1) loss = criterion(outputs[0], labels) + loss.backward() optimizer.step() running_loss += loss @@ -81,12 +87,17 @@ def train(): if int(torch.argmax(torch.nn.functional.softmax(outputs, dim=2))) == int(labels[0]): pred_correct += 1 pred_all += 1 + if scheduler: - scheduler.step(running_loss.item() / len(train_loader)) + scheduler.step(running_loss.item() / (len(train_loader)) ) + + writer.add_scalar("Loss/train", loss, epoch) + writer.add_scalar("Accuracy/train", (pred_correct / pred_all), epoch) # validate and print val acc val_pred_correct, val_pred_all = 0, 0 + val_loss = 0.0 with torch.no_grad(): for i, (inputs, labels) in enumerate(val_loader): inputs = inputs.squeeze(0).to(device) @@ -94,26 +105,45 @@ def train(): outputs = spoter_model(inputs).expand(1, -1, -1) + # calculate loss + val_loss += criterion(outputs[0], labels) + if int(torch.argmax(torch.nn.functional.softmax(outputs, dim=2))) == int(labels[0]): val_pred_correct += 1 val_pred_all += 1 val_acc = (val_pred_correct / val_pred_all) + writer.add_scalar("Loss/val", val_loss, epoch) + writer.add_scalar("Accuracy/val", val_acc, epoch) + + print(f"Epoch: {epoch} | Train Acc: {(pred_correct / pred_all)} | Val Acc: {val_acc}") + # save checkpoint and update epochs_without_improvement + if val_acc > best_val_acc: + best_val_acc = val_acc + epochs_without_improvement = 0 + if epoch > 55: + top_val_acc = val_acc + top_train_acc = train_acc + checkpoint_index = epoch + torch.save(spoter_model.state_dict(), f"checkpoints/spoter_{epoch}.pth") + else: + epochs_without_improvement += 1 - # save checkpoint - if val_acc > top_val_acc and epoch > 55: - top_val_acc = val_acc - top_train_acc = train_acc - checkpoint_index = epoch - torch.save(spoter_model.state_dict(), f"checkpoints/spoter_{epoch}.pth") + # early stopping + if epochs_without_improvement >= 40: + print("Early stopping due to no improvement in validation accuracy for 40 epochs.") + break lr_progress.append(optimizer.param_groups[0]['lr']) print(f"Best val acc: {top_val_acc} | Best train acc: {top_train_acc} | Epoch: {checkpoint_index}") + writer.flush() + writer.close() + # Path: src/train.py if __name__ == "__main__": - train() \ No newline at end of file + train() diff --git a/visualizations/__init__.py b/visualizations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/visualizations/analyze_model.ipynb b/visualizations/analyze_model.ipynb new file mode 100644 index 0000000..a805896 --- /dev/null +++ b/visualizations/analyze_model.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Created TensorFlow Lite XNNPACK delegate for CPU.\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import sys\n", + "sys.path.insert(0,'..')\n", + "\n", + "from src.keypoint_extractor import KeypointExtractor\n", + "from src.datasets.finger_spelling_dataset import FingerSpellingDataset\n", + "from src.identifiers import LANDMARKS\n", + "import torch\n", + "from src.model import SPOTER\n", + "from sklearn.metrics import confusion_matrix\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sn\n", + "import matplotlib.pyplot as plt\n", + "from src.augmentations import MirrorKeypoints\n", + "\n", + "keypoints_extractor = KeypointExtractor()\n", + "test_set = FingerSpellingDataset(\"../data/fingerspelling/data/\", bad_data_folder=\"\", keypoints_identifier=LANDMARKS, subset=\"test\")\n", + "\n", + "spoter = SPOTER(num_classes=26, hidden_dim=len(LANDMARKS) *2)\n", + "spoter.load_state_dict(torch.load('../checkpoints/spoter_111.pth'))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 4 6 8 10 8 2 3 10 6 4 3 4 1 5 3 4 3 0 7 3 3 5 3 1\n", + " 3]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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RERERPQ/V5uoXUlISAiqVauG7Oxsl/3Z2dkIDw936xh8zxsREREREZHO/Pz80LZtWyQmJpbvKy0tRWJiosuVuGvhlTciIiIiIiIDjB8/HkOHDkW7du1wxx13YO7cuTh79iyGDx/u1uNNPXmz2+2YMmWK25cqzZjJjsw0S54smezITLPkiciUoaOITBk6ishkRxLhoYceQm5uLiZPnoysrCy0bt0an3/+OcLCwtx6vKlvWEJERERERCQLvueNiIiIiIjIBDh5IyIiIiIiMgFO3oiIiIiIiEyAkzciIiIiIiITMO3k7dtvv0W1atXQu3dv3bOGDRsGRVHKt7p166JXr17IyMjQNTcrKwtjxozBjTfeCLvdjoiICPTt29dlbQhPubSjr68vwsLC0L17d6xYsQKlpaUez/t75qVbr169dMm7VuahQ4d0y8zKysLYsWPRuHFjVK9eHWFhYbjrrruwePFi/Pnnnx7PGzZsGO67777L9m/duhWKoqCgoMDjmRVl68mozCvlfPTRR6hevTrefPNNwzL1UvbfxsiRIy/73OjRo6EoCoYNG6ZL5syZM132f/LJJ1AUxaNZl/rtt98wYsQIXHfddfDz88P111+PsWPH4o8//tAl79KfO35+fmjcuDGmT5+OkpISXfJE+Pu/IY0aNcKLL76I8+fP65qbm5uLUaNGoWHDhrDb7QgPD0fPnj3xzTffeDTnSv9uXLpNnTrVo3llunTpgnHjxl22PyEhAbVr1/ZoVt++fa/67++OHTugKIrHfu955513UKtWLZf/Bs6cOQNfX1906dLF5WvL/u06fPiwR7IvXryIjh07YsCAAS77CwsLERERgZdfftkjOZdSVRXdunVDz549L/vcokWLULt2bfz+++8eyys7Z1fboqKiPJZFxjPt5G358uUYM2YMtm/fjhMnTuie16tXL5w8eRInT55EYmIibDYb+vTpo1vesWPH0LZtWyQlJWH27Nn44Ycf8PnnnyMqKgqjR4/WJbOs47Fjx/DZZ58hKioKY8eORZ8+fXT7JePS81q2ffDBB7pkXSuzUaNGumQdOXIEt99+O7788ku8/vrr2Lt3L7799lu8+OKL2LRpE77++mtdckl/y5Ytw+DBg7F48WI8//zzoofjEREREfjwww9x7ty58n3nz5/H+++/j4YNG+qSWb16dbzxxhs4deqULsf/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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "y_pred = []\n", + "y_true = []\n", + "\n", + "for inputs, label in test_set:\n", + " output = spoter(inputs) # Feed Network\n", + "\n", + " output = torch.argmax(output, dim=2) # Get Prediction\n", + " output = output.detach().numpy().tolist()[0] # Convert to list\n", + " \n", + " y_true.extend(output) # Save Truth\n", + "\n", + " y_pred.append(label) # Save Prediction\n", + "\n", + "\n", + "# how many items for each class\n", + "print(np.bincount(y_true))\n", + "\n", + "# Build confusion matrix\n", + "cf_matrix = confusion_matrix(y_true, y_pred)\n", + "df_cm = pd.DataFrame(cf_matrix, index = [i for i in [chr(i) for i in range(65, 91)]],\n", + " columns = [i for i in [chr(i) for i in range(65, 91)]])\n", + "plt.figure(figsize = (12,7))\n", + "sn.heatmap(df_cm, annot=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/visualizations/visualize_data.ipynb b/visualizations/visualize_data.ipynb new file mode 100644 index 0000000..52c3ee1 --- /dev/null +++ b/visualizations/visualize_data.ipynb @@ -0,0 +1,1781 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0,'..')\n", + "from src.keypoint_extractor import KeypointExtractor\n", + "\n", + "# reload modules\n", + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "video_name = '../data/fingerspelling/data/Z!train!26_20230323144550450800_2RIPN.mp4'\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# extract keypoints\n", + "keypoint_extractor = KeypointExtractor()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "from IPython.display import HTML\n", + "from base64 import b64encode\n", + "import mediapy as media\n", + "%matplotlib inline\n", + "\n", + "# Define the frames per second (fps) and duration of the video\n", + "fps = 25\n", + "duration = 10\n", + "\n", + "# Create a dummy video of random noise\n", + "_, video_frames = keypoint_extractor.extract_keypoints_from_video(video_name, normalize=\"minmax\", draw=True)\n", + "\n", + "# Convert the video to a numpy array\n", + "video = np.array(video_frames)\n", + "media.show_video(video, height=400, codec='gif', fps=4)\n", + "\n", + "# write the video to a file\n", + "media.write_video('test.mp4',video, fps=10)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: 'models/spoter_40.pth'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[5], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mtorch\u001b[39;00m\n\u001b[1;32m 5\u001b[0m spoter_model \u001b[39m=\u001b[39m SPOTER(num_classes\u001b[39m=\u001b[39m\u001b[39m5\u001b[39m, hidden_dim\u001b[39m=\u001b[39m\u001b[39mlen\u001b[39m(LANDMARKS) \u001b[39m*\u001b[39m\u001b[39m2\u001b[39m)\n\u001b[0;32m----> 6\u001b[0m spoter_model\u001b[39m.\u001b[39mload_state_dict(torch\u001b[39m.\u001b[39;49mload(\u001b[39m'\u001b[39;49m\u001b[39mmodels/spoter_40.pth\u001b[39;49m\u001b[39m'\u001b[39;49m))\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/torch/serialization.py:771\u001b[0m, in \u001b[0;36mload\u001b[0;34m(f, map_location, pickle_module, weights_only, **pickle_load_args)\u001b[0m\n\u001b[1;32m 768\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m'\u001b[39m\u001b[39mencoding\u001b[39m\u001b[39m'\u001b[39m \u001b[39mnot\u001b[39;00m \u001b[39min\u001b[39;00m pickle_load_args\u001b[39m.\u001b[39mkeys():\n\u001b[1;32m 769\u001b[0m pickle_load_args[\u001b[39m'\u001b[39m\u001b[39mencoding\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m \u001b[39m'\u001b[39m\u001b[39mutf-8\u001b[39m\u001b[39m'\u001b[39m\n\u001b[0;32m--> 771\u001b[0m \u001b[39mwith\u001b[39;00m _open_file_like(f, \u001b[39m'\u001b[39;49m\u001b[39mrb\u001b[39;49m\u001b[39m'\u001b[39;49m) \u001b[39mas\u001b[39;00m opened_file:\n\u001b[1;32m 772\u001b[0m \u001b[39mif\u001b[39;00m _is_zipfile(opened_file):\n\u001b[1;32m 773\u001b[0m \u001b[39m# The zipfile reader is going to advance the current file position.\u001b[39;00m\n\u001b[1;32m 774\u001b[0m \u001b[39m# If we want to actually tail call to torch.jit.load, we need to\u001b[39;00m\n\u001b[1;32m 775\u001b[0m \u001b[39m# reset back to the original position.\u001b[39;00m\n\u001b[1;32m 776\u001b[0m orig_position \u001b[39m=\u001b[39m opened_file\u001b[39m.\u001b[39mtell()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/torch/serialization.py:270\u001b[0m, in \u001b[0;36m_open_file_like\u001b[0;34m(name_or_buffer, mode)\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m_open_file_like\u001b[39m(name_or_buffer, mode):\n\u001b[1;32m 269\u001b[0m \u001b[39mif\u001b[39;00m _is_path(name_or_buffer):\n\u001b[0;32m--> 270\u001b[0m \u001b[39mreturn\u001b[39;00m _open_file(name_or_buffer, mode)\n\u001b[1;32m 271\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 272\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39m'\u001b[39m\u001b[39mw\u001b[39m\u001b[39m'\u001b[39m \u001b[39min\u001b[39;00m mode:\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/torch/serialization.py:251\u001b[0m, in \u001b[0;36m_open_file.__init__\u001b[0;34m(self, name, mode)\u001b[0m\n\u001b[1;32m 250\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__init__\u001b[39m(\u001b[39mself\u001b[39m, name, mode):\n\u001b[0;32m--> 251\u001b[0m \u001b[39msuper\u001b[39m(_open_file, \u001b[39mself\u001b[39m)\u001b[39m.\u001b[39m\u001b[39m__init__\u001b[39m(\u001b[39mopen\u001b[39;49m(name, mode))\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'models/spoter_40.pth'" + ] + } + ], + "source": [ + "from src.model import SPOTER\n", + "from src.identifiers import LANDMARKS\n", + "import torch\n", + "\n", + "spoter_model = SPOTER(num_classes=5, hidden_dim=len(LANDMARKS) *2)\n", + "spoter_model.load_state_dict(torch.load('models/spoter_40.pth'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get average number of frames in test set\n", + "from src.keypoint_extractor import KeypointExtractor\n", + "from src.datasets.finger_spelling_dataset import FingerSpellingDataset\n", + "from src.identifiers import LANDMARKS\n", + "import numpy as np\n", + "\n", + "keypoints_extractor = KeypointExtractor(\"../data/fingerspelling/data/\")\n", + "test_set = FingerSpellingDataset(\"../data/fingerspelling/data/\", keypoints_extractor, keypoints_identifier=LANDMARKS, subset=\"val\")\n", + "\n", + "frames = []\n", + "labels = []\n", + "for sample, label in test_set:\n", + " frames.append(sample.shape[0])\n", + " labels.append(label)\n", + "\n", + "print(np.mean(frames))\n", + "# get label frequency in the labels list\n", + "from collections import Counter\n", + "\n", + "counter = Counter(labels)\n", + "print(counter)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hand keypoint visualization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "def plot_hand_keypoints(dataframe, hand, frame, ret=False):\n", + " plt.clf()\n", + " hand_columns = np.array([i for i in range(66 + (42 if hand == \"right\" else 0), 108 + (42 if hand == \"right\" else 0))])\n", + " \n", + " # get the x, y coordinates of the hand keypoints\n", + " frame_df = dataframe.iloc[frame:frame+1, hand_columns]\n", + " hand_coords = frame_df.values.reshape(21, 2)\n", + " \n", + " x_coords = hand_coords[:, ::2] #Even indices\n", + " y_coords = -hand_coords[:, 1::2] #Uneven indices (negative because pixels start from the top left)\n", + " if ret:\n", + " return plt.scatter(x_coords, y_coords, c='b')\n", + " #Plot the keypoints\n", + " plt.scatter(x_coords, y_coords)\n", + " return frame_df.style" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Set video, hand and frame to display\n", + "video_name = 'A!1_20230301111632818161_I3EC3.mp4'\n", + "hand = \"right\"\n", + "frame = 5\n", + "%reload_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Created TensorFlow Lite XNNPACK delegate for CPU.\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 54_x54_y55_x55_y56_x56_y57_x57_y58_x58_y59_x59_y60_x60_y61_x61_y62_x62_y63_x63_y64_x64_y65_x65_y66_x66_y67_x67_y68_x68_y69_x69_y70_x70_y71_x71_y72_x72_y73_x73_y74_x74_y
5206.460323241.187210230.258865224.058051240.719643194.888663243.763103176.291513245.644760160.364571222.654018164.100680244.763489159.433722241.298084180.507073232.752953187.535305211.368618164.291081237.624207160.842934232.236347185.703449221.870766190.408745200.001240168.123393226.508846165.884171221.417446190.005541210.547810194.531407188.253899175.198359212.090034172.334862210.177135188.840246200.824184193.307934
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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sbZv3676+npJ0m233aZz587plVde8R5n8uTJGj9+vDZs2ODX9/LWbAAAzMff63dI+8w4nU6lpaV5P1dVVemGG27wBhlJys/P18GDB3X69GlvmenTp/scJz8/X1VVVb1+T3t7u1wul88CAACiU8jCTENDg5544gndfffd3nUOh0MZGRk+5TyfHQ7HRct4tvdkzZo1stls3iUzM7O/TgMAAESYgMNMcXGxLBbLRRfPIyKPxsZGFRQUaO7cuVqwYEG/Vb43K1askNPp9C7Hjh0L+ncCAIDwGBjoDsuXL1dhYeFFy4wcOdL7301NTcrLy1Nubq5Px15Jstvtamlp8Vnn+Wy32y9axrO9J1arVVar9ZLnAgAAzC/gMJOenq709HS/yjY2NiovL0+TJk3S5s2bFRfneyMoJydHq1atUmdnp+Lj4yVJlZWVuu666zR48GBvmddff11Llizx7ldZWamcnJxAqw4AAKJQ0PrMNDY2atq0aRo+fLjWr1+vEydOyOFw+PR1mT9/vhISElRUVKTa2lpt3bpVjz/+uJYtW+Yts3jxYlVUVOiRRx5RfX29HnjgAe3du1c/+tGPglV1AABgIgHfmfFXZWWlGhoa1NDQoGHDhvls84wGt9ls+sMf/qBFixZp0qRJuuqqq3T//ffrrrvu8pbNzc3Vc889p/vuu08rV67U6NGjtX37do0dOzZYVQcAACYS0nlmwoV5ZgAAMJ+InGcGAACgvxFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqRFmAACAqQUtzBw5ckRFRUXKyspSUlKSRo0apZKSEnV0dHjLtLW1qbCwUOPGjdPAgQM1Z86cHo+1e/duTZw4UVarVddee63KysqCVW0AAGAyQQsz9fX1crvdeuqpp1RbW6tHH31UGzZs0MqVK71lurq6lJSUpJ/85CeaPn16j8c5fPiwZs2apby8PFVXV2vJkiX64Q9/qNdeey1YVQcAACZiMQzDCNWXrVu3TqWlpTp06FC3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GmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYGmEGAACYWtDCzJEjR1RUVKSsrCwlJSVp1KhRKikpUUdHR4/lGxoalJycrNTU1G7btm3bpjFjxigxMVHjxo3Tjh07glVtAABgMkELM/X19XK73XrqqadUW1urRx99VBs2bNDKlSu7le3s7NS8efM0ZcqUbtv27NmjefPmqaioSPv379ecOXM0Z84c1dTUBKvqAADARCyGYRih+rJ169aptLRUhw4d8ll/7733qqmpSTfeeKOWLFmiM2fOeLfddtttOnfunF555RXvusmTJ2v8+PHasGGDX9/rcrlks9nkdDqVkpLSL+cCAACCy9/rd0j7zDidTqWlpfms27lzp7Zt26Ynn3yyx32qqqo0ffp0n3X5+fmqqqoKWj0BAIB5DAzVFzU0NOiJJ57Q+vXrvetOnjypwsJCbdmypdfE5XA4lJGR4bMuIyNDDoej1+9qb29Xe3u797PL5brM2gP9p8tt6N3Dp3S8tU1DkhN1fVaaBsRZwl0tADCtgMNMcXGx1q5de9EyH330kcaMGeP93NjYqIKCAs2dO1cLFizwrl+wYIHmz5+vG264IdBqXNSaNWu0evXqfj0m0B8qapq1urxOzc4277qhtkSVzM5WwdihYawZAJhXwH1mTpw4oZMnT160zMiRI5WQkCBJampq0rRp0zR58mSVlZUpLu6zJ1upqak6e/as97NhGHK73RowYIA2btyoH/zgBxo+fLiWLVumJUuWeMuVlJRo+/bt+uCDD3r8/p7uzGRmZtJnBmFVUdOshVv26Yv/4Dz3ZErvmEigAYDP8bfPTMB3ZtLT05Wenu5X2cbGRuXl5WnSpEnavHmzT5CRLvSH6erq8n5++eWXtXbtWu3Zs0fXXHONJCknJ0evv/66T5iprKxUTk5Or99rtVpltVoDOCsguLrchlaX13ULMpJk6EKgWV1epxnZdh45AUCAgtZnprGxUdOmTdOIESO0fv16nThxwrvNbrdLkr7yla/47LN3717FxcVp7Nix3nWLFy/W1KlT9cgjj2jWrFl64YUXtHfvXm3cuDFYVQf63buHT/k8WvoiQ1Kz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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from src.keypoint_extractor import KeypointExtractor\n", + "import numpy as np\n", + "\n", + "#Extract keypoints from requested video\n", + "keypoints_extractor = KeypointExtractor(\"../data/fingerspelling/data/\")\n", + "\n", + "#Plot the hand keypoints\n", + "df = keypoints_extractor.extract_keypoints_from_video(video_name)\n", + "df.head()\n", + "plot_hand_keypoints(df, hand, frame)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 54_x54_y55_x55_y56_x56_y57_x57_y58_x58_y59_x59_y60_x60_y61_x61_y62_x62_y63_x63_y64_x64_y65_x65_y66_x66_y67_x67_y68_x68_y69_x69_y70_x70_y71_x71_y72_x72_y73_x73_y74_x74_y
5-0.1827640.5000000.2319100.2904780.414183-0.0663190.467213-0.2937970.500000-0.4886140.099401-0.4429140.484644-0.5000000.424262-0.2422330.275368-0.156264-0.097240-0.4405850.360247-0.4827630.266367-0.1786710.085753-0.121117-0.295310-0.3937090.166569-0.4210990.077854-0.126049-0.111542-0.070689-0.500000-0.307169-0.084670-0.342195-0.118001-0.140303-0.280971-0.085654
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Plot the NORMALIZED hand keypoints (using minxmax)\n", + "df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"minmax\")\n", + "plt.xlim(-0.5,0.5)\n", + "plt.ylim(-0.5,0.5)\n", + "plot_hand_keypoints(df, hand, frame)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 54_x54_y55_x55_y56_x56_y57_x57_y58_x58_y59_x59_y60_x60_y61_x61_y62_x62_y63_x63_y64_x64_y65_x65_y66_x66_y67_x67_y68_x68_y69_x69_y70_x70_y71_x71_y72_x72_y73_x73_y74_x74_y
5-0.1069170.4166670.1356670.2420650.242297-0.0552660.273319-0.2448310.292500-0.4071780.058149-0.3690950.283517-0.4166670.248193-0.2018610.161090-0.130220-0.056886-0.3671540.210744-0.4023020.155824-0.1488930.050165-0.100931-0.172756-0.3280910.097442-0.3509160.045545-0.105041-0.065252-0.058907-0.292500-0.255974-0.049532-0.285162-0.069031-0.116919-0.164368-0.071379
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#Plot the NORMALIZED hand keypoints (using bohacek)\n", + "df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"bohacek\")\n", + "plt.xlim(-0.5,0.5)\n", + "plt.ylim(-0.5,0.5)\n", + "plot_hand_keypoints(df, hand, frame)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pose keypoint visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "import sys\n", + "sys.path.insert(0,'..')\n", + "from src.augmentations import Z_augmentation\n", + "from src.keypoint_extractor import KeypointExtractor\n", + "\n", + "\n", + "def plot_pose_keypoints(dataframe, frame, ret=False, aug=False):\n", + " # clear plt\n", + " plt.clf()\n", + " pose_columns = np.array([i for i in range(34)])\n", + "\n", + " # get the x, y coordinates of the relevant pose keypoints\n", + " frame_df = dataframe.iloc[frame:frame+1, pose_columns]\n", + " pose_coords = frame_df.values.reshape(34, 1)\n", + "\n", + " if aug:\n", + " pose_coords = Z_augmentation()(pose_coords, hand_side=\"right\")\n", + " pose_coords = pose_coords.reshape(17, 2)\n", + "\n", + " x_coords = pose_coords[:, ::2] #Even indices\n", + " y_coords = -pose_coords[:, 1::2] #Uneven indices (negative because pixels start from the top left)\n", + "\n", + " # return a scatter plot of the pose keypoints\n", + " if ret:\n", + " plt.xlim(-0.5,0.5)\n", + " plt.ylim(-0.5,0.5)\n", + " return plt.scatter(x_coords, y_coords, c='r')\n", + "\n", + " plt.xlim(-0.5,0.5)\n", + " plt.ylim(-0.5,0.5)\n", + " plt.scatter(x_coords, y_coords)\n", + " return frame_df.style\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#Set video, hand and frame to display\n", + "video_name = 'Z!26_20230313174809489724_CGG2F.mp4'\n", + "frame = 2\n", + "%reload_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Created TensorFlow Lite XNNPACK delegate for CPU.\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 0_x0_y1_x1_y2_x2_y3_x3_y4_x4_y5_x5_y6_x6_y7_x7_y8_x8_y9_x9_y10_x10_y11_x11_y12_x12_y13_x13_y14_x14_y15_x15_y16_x16_y
2314.597130203.601093333.606339177.124128343.284836178.611474353.475113180.199170299.696846173.968377287.641029173.626041276.889381173.728795369.040298195.947542261.266518190.308652332.395744235.577416290.494938233.121829437.649574348.909016172.417698350.768766524.350166508.146744121.787348523.435078461.661911373.101339122.718906719.282742
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi8AAAGdCAYAAADaPpOnAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAaQklEQVR4nO3df2xV9f348VdLpXWRthB+dGAZwzmRibCAVMiIyWwEZQYSlyEyQcJkfwxcRBNhMeD0j+KCG07Jx8hc3BIN6BKMYY6N8SNG7UBBE1AhGZOIYIuM2Fth4Ud7vn/4tVsVaovctu/yeCTnjx7OufdV3sB95vTcS0GWZVkAACSisKsHAADoCPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUoq6eoDzrbm5OQ4dOhR9+vSJgoKCrh4HAGiHLMuisbExBg8eHIWFbV9b6XHxcujQoaisrOzqMQCAc3DgwIG49NJL2zymx8VLnz59IuLTb760tLSLpwEA2iOXy0VlZWXL63hbely8fPajotLSUvECAIlpzy0fbtgFAJIiXgCApIgXACAp4gUASIp4AQCSIl4AgKSIFwAgKeIFAEiKeAEAkiJeAICkiBcAICniBQBIingBAJIiXgCApIgXACAp4gUASIp4AQCSIl4AgKSIFwAgKeIFAEiKeAEAkiJeAICkiBcAICniBQBIingBAJIiXgCApIgXACAp4gUASIp4AQCS0inxsmrVqhg2bFiUlJREVVVVbN++vV3nrVmzJgoKCmL69On5HRAASEbe42Xt2rWxaNGiWLZsWezcuTNGjx4dkydPjsOHD7d53v79++Pee++NSZMm5XtEACAheY+XX//613HnnXfG3LlzY+TIkfHEE0/E1772tfj9739/1nOamppi1qxZ8ctf/jKGDx+e7xEBgITkNV5OnjwZO3bsiOrq6v8+YWFhVFdXR21t7VnPe/DBB2PgwIExb968fI4HACSoKJ8PfuTIkWhqaopBgwa12j9o0KDYs2fPGc955ZVX4qmnnoq33nqrXc9x4sSJOHHiRMvXuVzunOcFALq/bvVuo8bGxrj99ttj9erV0b9//3adU1NTE2VlZS1bZWVlnqcEALpSXq+89O/fP3r16hX19fWt9tfX10dFRcUXjt+3b1/s378/br755pZ9zc3Nnw5aVBR79+6Nyy67rNU5S5YsiUWLFrV8ncvlBAwA9GB5jZfevXvH2LFjY9OmTS1vd25ubo5NmzbFggULvnD8iBEjYteuXa323X///dHY2BiPPvroGaOkuLg4iouL8zI/AND95DVeIiIWLVoUc+bMiXHjxsX48eNj5cqVcezYsZg7d25ERMyePTuGDBkSNTU1UVJSEldddVWr88vLyyMivrAfALgw5T1eZsyYER999FEsXbo06urqYsyYMbFhw4aWm3jff//9KCzsVrfeAADdWEGWZVlXD3E+5XK5KCsri4aGhigtLe3qcQCAdujI67dLHgBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJKVT4mXVqlUxbNiwKCkpiaqqqti+fftZj129enVMmjQp+vbtG3379o3q6uo2jwcALix5j5e1a9fGokWLYtmyZbFz584YPXp0TJ48OQ4fPnzG47du3RozZ86MLVu2RG1tbVRWVsYNN9wQBw8ezPeoAEACCrIsy/L5BFVVVXHNNdfE448/HhERzc3NUVlZGQsXLozFixd/6flNTU3Rt2/fePzxx2P27Nlfenwul4uysrJoaGiI0tLSrzw/AJB/HXn9zuuVl5MnT8aOHTuiurr6v09YWBjV1dVRW1vbrsc4fvx4nDp1Kvr165evMQGAhBTl88GPHDkSTU1NMWjQoFb7Bw0aFHv27GnXY9x3330xePDgVgH0v06cOBEnTpxo+TqXy537wABAt9et3220fPnyWLNmTaxbty5KSkrOeExNTU2UlZW1bJWVlZ08JQDQmfIaL/37949evXpFfX19q/319fVRUVHR5rkrVqyI5cuXx9/+9re4+uqrz3rckiVLoqGhoWU7cODAeZkdAOie8hovvXv3jrFjx8amTZta9jU3N8emTZtiwoQJZz3vV7/6VTz00EOxYcOGGDduXJvPUVxcHKWlpa02AKDnyus9LxERixYtijlz5sS4ceNi/PjxsXLlyjh27FjMnTs3IiJmz54dQ4YMiZqamoiIePjhh2Pp0qXx7LPPxrBhw6Kuri4iIi655JK45JJL8j0uANDN5T1eZsyYER999FEsXbo06urqYsyYMbFhw4aWm3jff//9KCz87wWg//u//4uTJ0/GD3/4w1aPs2zZsnjggQfyPS4A0M3l/XNeOpvPeQGA9HSbz3kBADjfxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkpVPiZdWqVTFs2LAoKSmJqqqq2L59e5vHP//88zFixIgoKSmJUaNGxUsvvdQZYwIACch7vKxduzYWLVoUy5Yti507d8bo0aNj8uTJcfjw4TMe/9prr8XMmTNj3rx58eabb8b06dNj+vTpsXv37nyPCgAkoCDLsiyfT1BVVRXXXHNNPP744xER0dzcHJWVlbFw4cJYvHjxF46fMWNGHDt2LNavX9+y79prr40xY8bEE0888aXPl8vloqysLBoaGqK0tPT8fSMAQN505PU7r1deTp48GTt27Ijq6ur/PmFhYVRXV0dtbe0Zz6mtrW11fETE5MmTz3r8iRMnIpfLtdoAgJ4rr/Fy5MiRaGpqikGDBrXaP2jQoKirqzvjOXV1dR06vqamJsrKylq2ysrK8zM8ANAtJf9uoyVLlkRDQ0PLduDAga4eCQDIo6J8Pnj//v2jV69eUV9f32p/fX19VFRUnPGcioqKDh1fXFwcxcXF52dgAKDby+uVl969e8fYsWNj06ZNLfuam5tj06ZNMWHChDOeM2HChFbHR0Rs3LjxrMcDABeWvF55iYhYtGhRzJkzJ8aNGxfjx4+PlStXxrFjx2Lu3LkRETF79uwYMmRI1NTURETEz3/+87juuuvikUceialTp8aaNWvijTfeiCeffDLfowIACch7vMyYMSM++uijWLp0adTV1cWYMWNiw4YNLTflvv/++1FY+N8LQBMnToxnn3027r///vjFL34Rl19+ebzwwgtx1VVX5XtUACABef+cl87mc14AID3d5nNeAADON/ECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASREvAEBSxAsAkBTxAgAkRbwAAEkRLwBAUsQLAJAU8QIAJEW8AABJES8AQFLECwCQFPECACRFvAAASclbvBw9ejRmzZoVpaWlUV5eHvPmzYtPPvmkzeMXLlwYV1xxRVx88cUxdOjQuOuuu6KhoSFfIwIACcpbvMyaNSvefvvt2LhxY6xfvz5efvnlmD9//lmPP3ToUBw6dChWrFgRu3fvjqeffjo2bNgQ8+bNy9eIAECCCrIsy873g7777rsxcuTIeP3112PcuHEREbFhw4a46aab4oMPPojBgwe363Gef/75+PGPfxzHjh2LoqKidp2Ty+WirKwsGhoaorS09Jy/BwCg83Tk9TsvV15qa2ujvLy8JVwiIqqrq6OwsDC2bdvW7sf57BtoK1xOnDgRuVyu1QYA9Fx5iZe6uroYOHBgq31FRUXRr1+/qKura9djHDlyJB566KE2f9QUEVFTUxNlZWUtW2Vl5TnPDQB0fx2Kl8WLF0dBQUGb2549e77yULlcLqZOnRojR46MBx54oM1jlyxZEg0NDS3bgQMHvvLzAwDdV/tuJPn/7rnn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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from src.keypoint_extractor import KeypointExtractor\n", + "import numpy as np\n", + "\n", + "#Extract keypoints from requested video\n", + "keypoints_extractor = KeypointExtractor(\"../data/fingerspelling/data/\")\n", + "\n", + "#Plot the hand keypoints\n", + "df = keypoints_extractor.extract_keypoints_from_video(video_name)\n", + "df.head()\n", + "plot_pose_keypoints(df, frame)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 0_x0_y1_x1_y2_x2_y3_x3_y4_x4_y5_x5_y6_x6_y7_x7_y8_x8_y9_x9_y10_x10_y11_x11_y12_x12_y13_x13_y14_x14_y15_x15_y16_x16_y
20.009014-0.3302240.026931-0.3587450.036054-0.3571430.045658-0.355433-0.005030-0.362144-0.016393-0.362513-0.026527-0.3624020.060329-0.338468-0.041253-0.3445430.025790-0.295779-0.013703-0.2984240.124997-0.173699-0.124997-0.1716950.206716-0.002168-0.1727180.0143000.147630-0.147639-0.1718400.225267
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "norm_df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"bohacek\")\n", + "plt.xlim(-0.5,0.5)\n", + "plt.ylim(-0.5,0.5)\n", + "plot_pose_keypoints(norm_df, frame)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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hWVZls/ns+644w7rzjvvtHp6euJRasopLS21Vq5c2f88EAhYhYWFww7Y/dnPfhbWNmvWLAbsxkC0x8KyLOvZZ5+1XC6X1dLSEo8SR5Vojse333474LOhsrLS+ulPf2odOXLE6u3tjWfptiC8JJG5c+daP/7xj60DBw5Y7777rnXTTTdZixYt6n/99OnT1s0332wdOHBgyH2I2UYxEe2x8Pl8VllZmTVt2jTr888/tzo6OvofFy9eTNSvYZytW7damZmZ1iuvvGJ9+umn1ooVK6ycnBzL6/ValmVZDz30kFVXV9ff/7333rPS09OtDRs2WJ999plVX19vXXfdddaRI0cS9SukjGiPxfr1662MjAxr+/btYX/+u7u7E/UrpJRoj8eVUm22EeEliXz11VfWokWLrBtuuMFyuVzW8uXLw/7inzx50pJk7d27d8h9EF5iI9pjsXfvXkvSoI+TJ08m5pcw1PPPP29NmjTJysjIsEpLS63//Oc//a/deeed1tKlS8P6v/7669YPf/hDKyMjw/rRj35k/f3vf49zxakrmmPxve99b9A///X19fEvPEVF+3fjcqkWXhyWZVnxvlQFAAAwUsw2AgAARiG8AAAAoxBeAACAUQgvAADAKIQXAABgFMILAAAwCuEFAAAYhfACAACMQngBAABGIbwAAACjEF4AAIBRCC8AAMAo/wdVq89nuOSkywAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "norm_df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"bohacek\")\n", + "\n", + "# create animation of the keypoints over time\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.animation as animation\n", + "from IPython.display import HTML\n", + "\n", + "fig = plt.figure()\n", + "plt.xlim(-0.5, 0.5)\n", + "plt.ylim(-0.5, 0.5)\n", + "\n", + "def init():\n", + " return plot_pose_keypoints(norm_df, 0, ret=True)\n", + "\n", + "def animate(i):\n", + " return plot_pose_keypoints(norm_df, i, ret=True, aug=True)\n", + "# show animation of the keypoints over time, show one frame every 1000ms and remove the other frames\n", + "anim = animation.FuncAnimation(fig, animate, init_func=init, frames=norm_df.shape[0], interval=1000, repeat=False, )\n", + "HTML(anim.to_html5_video())\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'plot_hand_keypoints' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 16\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[39m# show animation of the keypoints over time, show one frame every 1000ms and remove the other frames\u001b[39;00m\n\u001b[1;32m 15\u001b[0m anim \u001b[39m=\u001b[39m animation\u001b[39m.\u001b[39mFuncAnimation(fig, animate_hand, init_func\u001b[39m=\u001b[39minit_hand, frames\u001b[39m=\u001b[39mnorm_df\u001b[39m.\u001b[39mshape[\u001b[39m0\u001b[39m], interval\u001b[39m=\u001b[39m\u001b[39m1000\u001b[39m, repeat\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m, )\n\u001b[0;32m---> 16\u001b[0m HTML(anim\u001b[39m.\u001b[39;49mto_html5_video())\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/animation.py:1288\u001b[0m, in \u001b[0;36mAnimation.to_html5_video\u001b[0;34m(self, embed_limit)\u001b[0m\n\u001b[1;32m 1284\u001b[0m Writer \u001b[39m=\u001b[39m writers[mpl\u001b[39m.\u001b[39mrcParams[\u001b[39m'\u001b[39m\u001b[39manimation.writer\u001b[39m\u001b[39m'\u001b[39m]]\n\u001b[1;32m 1285\u001b[0m writer \u001b[39m=\u001b[39m Writer(codec\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mh264\u001b[39m\u001b[39m'\u001b[39m,\n\u001b[1;32m 1286\u001b[0m bitrate\u001b[39m=\u001b[39mmpl\u001b[39m.\u001b[39mrcParams[\u001b[39m'\u001b[39m\u001b[39manimation.bitrate\u001b[39m\u001b[39m'\u001b[39m],\n\u001b[1;32m 1287\u001b[0m fps\u001b[39m=\u001b[39m\u001b[39m1000.\u001b[39m \u001b[39m/\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_interval)\n\u001b[0;32m-> 1288\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49msave(\u001b[39mstr\u001b[39;49m(path), writer\u001b[39m=\u001b[39;49mwriter)\n\u001b[1;32m 1289\u001b[0m \u001b[39m# Now open and base64 encode.\u001b[39;00m\n\u001b[1;32m 1290\u001b[0m vid64 \u001b[39m=\u001b[39m base64\u001b[39m.\u001b[39mencodebytes(path\u001b[39m.\u001b[39mread_bytes())\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/animation.py:1090\u001b[0m, in \u001b[0;36mAnimation.save\u001b[0;34m(self, filename, writer, fps, dpi, codec, bitrate, extra_args, metadata, extra_anim, savefig_kwargs, progress_callback)\u001b[0m\n\u001b[1;32m 1085\u001b[0m \u001b[39mwith\u001b[39;00m mpl\u001b[39m.\u001b[39mrc_context({\u001b[39m'\u001b[39m\u001b[39msavefig.bbox\u001b[39m\u001b[39m'\u001b[39m: \u001b[39mNone\u001b[39;00m}), \\\n\u001b[1;32m 1086\u001b[0m writer\u001b[39m.\u001b[39msaving(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_fig, filename, dpi), \\\n\u001b[1;32m 1087\u001b[0m cbook\u001b[39m.\u001b[39m_setattr_cm(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_fig\u001b[39m.\u001b[39mcanvas,\n\u001b[1;32m 1088\u001b[0m _is_saving\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m, manager\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m):\n\u001b[1;32m 1089\u001b[0m \u001b[39mfor\u001b[39;00m anim \u001b[39min\u001b[39;00m all_anim:\n\u001b[0;32m-> 1090\u001b[0m anim\u001b[39m.\u001b[39;49m_init_draw() \u001b[39m# Clear the initial frame\u001b[39;00m\n\u001b[1;32m 1091\u001b[0m frame_number \u001b[39m=\u001b[39m \u001b[39m0\u001b[39m\n\u001b[1;32m 1092\u001b[0m \u001b[39m# TODO: Currently only FuncAnimation has a save_count\u001b[39;00m\n\u001b[1;32m 1093\u001b[0m \u001b[39m# attribute. Can we generalize this to all Animations?\u001b[39;00m\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/animation.py:1750\u001b[0m, in \u001b[0;36mFuncAnimation._init_draw\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1748\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_draw_frame(frame_data)\n\u001b[1;32m 1749\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 1750\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_drawn_artists \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_init_func()\n\u001b[1;32m 1751\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_blit:\n\u001b[1;32m 1752\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_drawn_artists \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "Cell \u001b[0;32mIn[6], line 9\u001b[0m, in \u001b[0;36minit_hand\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39minit_hand\u001b[39m():\n\u001b[0;32m----> 9\u001b[0m \u001b[39mreturn\u001b[39;00m plot_hand_keypoints(norm_df, \u001b[39m\"\u001b[39m\u001b[39mright\u001b[39m\u001b[39m\"\u001b[39m, \u001b[39m0\u001b[39m, ret\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'plot_hand_keypoints' is not defined" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error in callback (for post_execute):\n" + ] + }, + { + "ename": "NameError", + "evalue": "name 'plot_hand_keypoints' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/pyplot.py:120\u001b[0m, in \u001b[0;36m_draw_all_if_interactive\u001b[0;34m()\u001b[0m\n\u001b[1;32m 118\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m_draw_all_if_interactive\u001b[39m():\n\u001b[1;32m 119\u001b[0m \u001b[39mif\u001b[39;00m matplotlib\u001b[39m.\u001b[39mis_interactive():\n\u001b[0;32m--> 120\u001b[0m draw_all()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/_pylab_helpers.py:132\u001b[0m, in \u001b[0;36mGcf.draw_all\u001b[0;34m(cls, force)\u001b[0m\n\u001b[1;32m 130\u001b[0m \u001b[39mfor\u001b[39;00m manager \u001b[39min\u001b[39;00m \u001b[39mcls\u001b[39m\u001b[39m.\u001b[39mget_all_fig_managers():\n\u001b[1;32m 131\u001b[0m \u001b[39mif\u001b[39;00m force \u001b[39mor\u001b[39;00m manager\u001b[39m.\u001b[39mcanvas\u001b[39m.\u001b[39mfigure\u001b[39m.\u001b[39mstale:\n\u001b[0;32m--> 132\u001b[0m manager\u001b[39m.\u001b[39;49mcanvas\u001b[39m.\u001b[39;49mdraw_idle()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/backend_bases.py:2082\u001b[0m, in \u001b[0;36mFigureCanvasBase.draw_idle\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2080\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_is_idle_drawing:\n\u001b[1;32m 2081\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_idle_draw_cntx():\n\u001b[0;32m-> 2082\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mdraw(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/backends/backend_agg.py:400\u001b[0m, in \u001b[0;36mFigureCanvasAgg.draw\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 396\u001b[0m \u001b[39m# Acquire a lock on the shared font cache.\u001b[39;00m\n\u001b[1;32m 397\u001b[0m \u001b[39mwith\u001b[39;00m RendererAgg\u001b[39m.\u001b[39mlock, \\\n\u001b[1;32m 398\u001b[0m (\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mtoolbar\u001b[39m.\u001b[39m_wait_cursor_for_draw_cm() \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mtoolbar\n\u001b[1;32m 399\u001b[0m \u001b[39melse\u001b[39;00m nullcontext()):\n\u001b[0;32m--> 400\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mfigure\u001b[39m.\u001b[39;49mdraw(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mrenderer)\n\u001b[1;32m 401\u001b[0m \u001b[39m# A GUI class may be need to update a window using this draw, so\u001b[39;00m\n\u001b[1;32m 402\u001b[0m \u001b[39m# don't forget to call the superclass.\u001b[39;00m\n\u001b[1;32m 403\u001b[0m \u001b[39msuper\u001b[39m()\u001b[39m.\u001b[39mdraw()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/artist.py:95\u001b[0m, in \u001b[0;36m_finalize_rasterization..draw_wrapper\u001b[0;34m(artist, renderer, *args, **kwargs)\u001b[0m\n\u001b[1;32m 93\u001b[0m \u001b[39m@wraps\u001b[39m(draw)\n\u001b[1;32m 94\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mdraw_wrapper\u001b[39m(artist, renderer, \u001b[39m*\u001b[39margs, \u001b[39m*\u001b[39m\u001b[39m*\u001b[39mkwargs):\n\u001b[0;32m---> 95\u001b[0m result \u001b[39m=\u001b[39m draw(artist, renderer, \u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 96\u001b[0m \u001b[39mif\u001b[39;00m renderer\u001b[39m.\u001b[39m_rasterizing:\n\u001b[1;32m 97\u001b[0m renderer\u001b[39m.\u001b[39mstop_rasterizing()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/artist.py:72\u001b[0m, in \u001b[0;36mallow_rasterization..draw_wrapper\u001b[0;34m(artist, renderer)\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[39mif\u001b[39;00m artist\u001b[39m.\u001b[39mget_agg_filter() \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[1;32m 70\u001b[0m renderer\u001b[39m.\u001b[39mstart_filter()\n\u001b[0;32m---> 72\u001b[0m \u001b[39mreturn\u001b[39;00m draw(artist, renderer)\n\u001b[1;32m 73\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 74\u001b[0m \u001b[39mif\u001b[39;00m artist\u001b[39m.\u001b[39mget_agg_filter() \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/figure.py:3150\u001b[0m, in \u001b[0;36mFigure.draw\u001b[0;34m(self, renderer)\u001b[0m\n\u001b[1;32m 3147\u001b[0m \u001b[39mfinally\u001b[39;00m:\n\u001b[1;32m 3148\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mstale \u001b[39m=\u001b[39m \u001b[39mFalse\u001b[39;00m\n\u001b[0;32m-> 3150\u001b[0m DrawEvent(\u001b[39m\"\u001b[39;49m\u001b[39mdraw_event\u001b[39;49m\u001b[39m\"\u001b[39;49m, \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcanvas, renderer)\u001b[39m.\u001b[39;49m_process()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/backend_bases.py:1263\u001b[0m, in \u001b[0;36mEvent._process\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1261\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m_process\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[1;32m 1262\u001b[0m \u001b[39m \u001b[39m\u001b[39m\"\"\"Generate an event with name ``self.name`` on ``self.canvas``.\"\"\"\u001b[39;00m\n\u001b[0;32m-> 1263\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mcanvas\u001b[39m.\u001b[39;49mcallbacks\u001b[39m.\u001b[39;49mprocess(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mname, \u001b[39mself\u001b[39;49m)\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/cbook/__init__.py:309\u001b[0m, in \u001b[0;36mCallbackRegistry.process\u001b[0;34m(self, s, *args, **kwargs)\u001b[0m\n\u001b[1;32m 307\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m \u001b[39mas\u001b[39;00m exc:\n\u001b[1;32m 308\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mexception_handler \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m--> 309\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mexception_handler(exc)\n\u001b[1;32m 310\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 311\u001b[0m \u001b[39mraise\u001b[39;00m\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/cbook/__init__.py:96\u001b[0m, in \u001b[0;36m_exception_printer\u001b[0;34m(exc)\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m_exception_printer\u001b[39m(exc):\n\u001b[1;32m 95\u001b[0m \u001b[39mif\u001b[39;00m _get_running_interactive_framework() \u001b[39min\u001b[39;00m [\u001b[39m\"\u001b[39m\u001b[39mheadless\u001b[39m\u001b[39m\"\u001b[39m, \u001b[39mNone\u001b[39;00m]:\n\u001b[0;32m---> 96\u001b[0m \u001b[39mraise\u001b[39;00m exc\n\u001b[1;32m 97\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 98\u001b[0m traceback\u001b[39m.\u001b[39mprint_exc()\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/cbook/__init__.py:304\u001b[0m, in \u001b[0;36mCallbackRegistry.process\u001b[0;34m(self, s, *args, **kwargs)\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[39mif\u001b[39;00m func \u001b[39mis\u001b[39;00m \u001b[39mnot\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n\u001b[1;32m 303\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m--> 304\u001b[0m func(\u001b[39m*\u001b[39;49margs, \u001b[39m*\u001b[39;49m\u001b[39m*\u001b[39;49mkwargs)\n\u001b[1;32m 305\u001b[0m \u001b[39m# this does not capture KeyboardInterrupt, SystemExit,\u001b[39;00m\n\u001b[1;32m 306\u001b[0m \u001b[39m# and GeneratorExit\u001b[39;00m\n\u001b[1;32m 307\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m \u001b[39mas\u001b[39;00m exc:\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/animation.py:904\u001b[0m, in \u001b[0;36mAnimation._start\u001b[0;34m(self, *args)\u001b[0m\n\u001b[1;32m 901\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_fig\u001b[39m.\u001b[39mcanvas\u001b[39m.\u001b[39mmpl_disconnect(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_first_draw_id)\n\u001b[1;32m 903\u001b[0m \u001b[39m# Now do any initial draw\u001b[39;00m\n\u001b[0;32m--> 904\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_init_draw()\n\u001b[1;32m 906\u001b[0m \u001b[39m# Add our callback for stepping the animation and\u001b[39;00m\n\u001b[1;32m 907\u001b[0m \u001b[39m# actually start the event_source.\u001b[39;00m\n\u001b[1;32m 908\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mevent_source\u001b[39m.\u001b[39madd_callback(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_step)\n", + "File \u001b[0;32m/usr/local/lib/python3.8/dist-packages/matplotlib/animation.py:1750\u001b[0m, in \u001b[0;36mFuncAnimation._init_draw\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1748\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_draw_frame(frame_data)\n\u001b[1;32m 1749\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m-> 1750\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_drawn_artists \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_init_func()\n\u001b[1;32m 1751\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_blit:\n\u001b[1;32m 1752\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_drawn_artists \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "Cell \u001b[0;32mIn[6], line 9\u001b[0m, in \u001b[0;36minit_hand\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39minit_hand\u001b[39m():\n\u001b[0;32m----> 9\u001b[0m \u001b[39mreturn\u001b[39;00m plot_hand_keypoints(norm_df, \u001b[39m\"\u001b[39m\u001b[39mright\u001b[39m\u001b[39m\"\u001b[39m, \u001b[39m0\u001b[39m, ret\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'plot_hand_keypoints' is not defined" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcQAAAHHCAYAAAAhyyixAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAiL0lEQVR4nO3de3DV9Z34/2cu5sQKSWAhRGIgBS2IonSIibBlUMwYwCqMdKVAuS0Fu6vWAu0KO13w0tmgg5q2ZmWkCrXViZUZGOqyWOSyFI3clC7e2EpBIyEBlnoSQUNIPr8/+uN8jYSQBJKTA8/HzPkj77w/57zOZyBPP8knGBcEQYAkSRe5+GgPIElSR2AQJUnCIEqSBBhESZIAgyhJEmAQJUkCDKIkSYBBlCQJMIiSJAEGUYqqm266iZtuuqnVx1577bXnd6AObvny5cTFxbF///5oj6ILkEGUzqNTX7BPPRITE8nMzGTatGkcOHAgKjOVl5fz4IMPsmvXrmbtP/UeduzY0ejnL8YQ6+KQGO0BpAvRww8/zNe//nW++OIL3nzzTZYvX86WLVt45513SE5Ojuz7wx/+0OazlJeX89BDD5Gdnc2gQYPa/PWkWGUQpTYwatQocnJyAPj+979Pt27dePTRR1m9ejV33XVXZF9SUlK0RpT0FX7LVGoHw4YNA2Dv3r0N1hv7GeJHH33EHXfcwWWXXUZ6ejqzZ8/m1VdfJS4ujk2bNp323O+99x4333wzX/va18jMzOSxxx6LfG7Tpk3ccMMNAEyfPj3yrdzly5ef1/e3bNkyRowYQXp6OqFQiAEDBvD000+fti87O5tvf/vbbNmyhdzcXJKTk+nTpw/PP//8aXvfffddRowYwaWXXsoVV1zBz372M+rr68/r3NKXeYUotYNTN4F06dKlyX3Hjh1jxIgRHDx4kPvvv5+MjAxefPFFNm7c2Oj+v/71r4wcOZI777yTu+66ixUrVvDAAw8wcOBARo0axdVXX83DDz/MggULmDVrViTMQ4cOPevM4XCYI0eOnLZeW1t72trTTz/NNddcwx133EFiYiK///3v+ed//mfq6+u55557Guz98MMP+c53vsOMGTOYOnUqzz33HNOmTWPw4MFcc801AFRUVHDzzTdz8uRJ5s2bx2WXXcYzzzzDpZdeeta5pVYLJJ03y5YtC4DgtddeCw4fPhyUlZUFK1asCLp37x6EQqGgrKyswf7hw4cHw4cPj3z8+OOPB0CwatWqyNrnn38e9O/fPwCCjRs3NjgWCJ5//vnIWk1NTZCRkRGMGzcusrZ9+/YACJYtW9ai99DU45prrmlwzPHjx097noKCgqBPnz4N1nr37h0AwebNmyNrhw4dCkKhUDB37tzI2o9+9KMACLZu3dpgX2pqagAE+/bta9Z7kVrCK0SpDeTn5zf4ODs7m9/+9rdcccUVTR63du1aMjMzueOOOyJrycnJzJw5k7lz5562v1OnTnzve9+LfJyUlERubi5/+ctfzvEdQHFxMd/4xjdOW587dy51dXUN1r585RYOh6mtrWX48OG8+uqrhMNhUlNTI58fMGBA5EoVoHv37vTr16/BzGvWrOHGG28kNze3wb5JkybxH//xH+f83qTGGESpDZyKSTgc5rnnnmPz5s2EQqGzHvfRRx/Rt29f4uLiGqxfeeWVje6/4oorTtvbpUsX/ud//qf1w///cnNzIzcGffX5v/qt1Ndff52FCxdSWlrK8ePHG3zuq0Hs1atXo8/517/+NfLxRx99RF5e3mn7+vXr1+L3ITWXQZTawJdjMnbsWL71rW8xceJE9uzZQ6dOnc7b6yQkJDS6HgTBeXuNs9m7dy+33HIL/fv354knniArK4ukpCTWrFnDk08+edqNMB1hZqkx3mUqtbGEhAQKCwspLy/nqaeeanJv79692bt372lx+PDDD1v9+l+9gjzffv/731NTU8Pq1au5++67GT16NPn5+ed0A0zv3r3585//fNr6nj17zmVUqUkGUWoHN910E7m5uRQVFfHFF1+ccV9BQQEHDhxg9erVkbUvvviCpUuXtvq1L7vsMgA+/fTTVj9HU05d8X054uFwmGXLlrX6OUePHs2bb77Jtm3bImuHDx/mhRdeaP2g0lkYRKmd/OQnP6GysrLJ3wG8++67yc7OZsKECcyfP59f/OIXDB8+PPKv27Tmaq9v376kpaWxZMkSnn32WUpKSti3b19r38Zpbr31VpKSkrj99tspLi7m0UcfZfDgwaSnp7f6Of/lX/6Fv/u7v2PkyJE89NBDLF68mL//+7+nd+/e521u6asMotRO7rzzTvr27cvixYtPu0vzlE6dOrFhwwZGjBjBz3/+c372s58xbNgw/u3f/g2gwT/71lyXXHIJv/71r0lI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rroqGDduXDtO3fZKSkqCpKSk4LnnngvefffdYObMmUFaWlpQWVnZ6P7XX389SEhICB577LHgvffeC376058Gl1xySbB79+52nrz9tPQcTZw4MSguLg7efvvt4P333w+mTZsWpKamBp988kk7T96+WnqeTtm3b1+QmZkZDBs2LBgzZkz7DNsKBrGDe++99wIg2L59e2Ttv/7rv4K4uLjgwIEDTR779ttvB5mZmcHBgwcv+CCey3n6st/97ndBUlJSUFtb2xZjRkVubm5wzz33RD6uq6sLevbsGRQWFja6/6677gpuu+22Bmt5eXnB3Xff3aZzRlNLz9FXnTx5MujcuXPw61//uq1G7BBac55OnjwZDB06NPjVr34VTJ06tUMH0W+ZdnClpaWkpaWRk5MTWcvPzyc+Pp6tW7ee8bjjx48zceJEiouLycjIaI9Ro6q15+mrwuEwKSkpJCZeGP/M74kTJ9i5cyf5+fmRtfj4ePLz8yktLW30mNLS0gb7AQoKCs64P9a15hx91fHjx6mtraVr165tNWbUtfY8Pfzww6SnpzNjxoz2GPOcXBh/6y9gFRUVpKenN1hLTEyka9euVFRUnPG42bNnM3ToUMaMGdPWI3YIrT1PX3bkyBEeeeQRZs2a1RYjRsWRI0eoq6ujR48eDdZ79OjBBx980OgxFRUVje5v7nmMNa05R1/1wAMP0LNnz9P+Q+JC0prztGXLFp599ll27drVDhOeO68Qo2TevHnExcU1+WjuX8avWr16NRs2bKCoqOj8Dh0FbXmevqyqqorbbruNAQMG8OCDD5774LpoLFq0iJKSElauXElycnK0x+kwqqurmTx5MkuXLqVbt27RHqdZvEKMkrlz5zJt2rQm9/Tp04eMjAwOHTrUYP3kyZMcPXr0jN8K3bBhA3v37iUtLa3B+rhx4xg2bBibNm06h8nbV1uep1Oqq6sZOXIknTt3ZuXKlVxyySXnOnaH0a1bNxISEqisrGywXllZecbzkpGR0aL9sa415+iUxYsXs2jRIl577TWuu+66thwz6lp6nvbu3cv+/fu5/fbbI2v19fXA3757s2fPHvr27du2Q7dUtH+Iqaadullkx44dkbVXX321yZtFDh48GOzevbvBAwh+/vOfB3/5y1/aa/R21ZrzFARBEA6HgxtvvDEYPnx4cOzYsfYYtd3l5uYG9957b+Tjurq6IDMzs8mbar797W83WBsyZMgFf1NNS85REATBo48+GqSkpASlpaXtMWKH0JLz9Pnnn5/2dWjMmDHBiBEjgt27dwc1NTXtOXqzGMQYMHLkyOCb3/xmsHXr1mDLli3BVVdd1eDXCT755JOgX79+wdatW8/4HFzgd5kGQcvPUzgcDvLy8oKBAwcGH374YXDw4MHI4+TJk9F6G+ddSUlJEAqFguXLlwfvvfdeMGvWrCAtLS2oqKgIgiAIJk+eHMybNy+y//XXXw8SExODxYsXB++//36wcOHCi+LXLlpyjhYtWhQkJSUFK1asaPDnprq6OlpvoV209Dx9VUe/y9QgxoD/+7//CyZMmBB06tQpSElJCaZPn97gL96+ffsCINi4ceMZn+NiCGJLz9PGjRsDoNHHvn37ovMm2sgvf/nLoFevXkFSUlKQm5sbvPnmm5HPDR8+PJg6dWqD/b/73e+Cb3zjG0FSUlJwzTXXBP/5n//ZzhO3v5aco969ezf652bhwoXtP3g7a+mfpS/r6EH0f/8kSRLeZSpJEmAQJUkCDKIkSYBBlCQJMIiSJAEGUZIkwCBKkgQYREmSAIMoSRJgECVJAgyidFE6fPgwGRkZ/Pu//3tk7Y033iApKYn169dHcTIpevy3TKWL1Jo1axg7dixvvPEG/fr1Y9CgQYwZM4Ynnngi2qNJUWEQpYvYPffcw2uvvUZOTg67d+9m+/bthEKhaI8lRYVBlC5in3/+Oddeey1lZWXs3LmTgQMHRnskKWr8GaJ0Edu7dy/l5eXU19ezf//+aI8jRZVXiNJF6sSJE+Tm5jJo0CD69etHUVERu3fvJj09PdqjSVFhEKWL1E9+8hNWrFjBn/70Jzp16sTw4cNJTU3llVdeifZoUlT4LVPpIrRp0yaKior4zW9+Q0pKCvHx8fzmN7/hj3/8I08//XS0x5OiwitESZLwClGSJMAgSpIEGERJkgCDKEkSYBAlSQIMoiRJgEGUJAkwiJIkAQZRkiTAIEqSBBhESZIAgyhJEgD/H2yXQG88yW6rAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure()\n", + "ax = plt.axes(xlim=(-0.5, 0.5), ylim=(-0.5, 0.5))\n", + "ax.set_aspect('equal')\n", + "ax.set_title(\"Right Hand\")\n", + "ax.set_xlabel(\"x\")\n", + "ax.set_ylabel(\"y\")\n", + "\n", + "def init_hand():\n", + " return plot_hand_keypoints(norm_df, \"right\", 0, ret=True)\n", + "\n", + "def animate_hand(i):\n", + " return plot_hand_keypoints(norm_df, \"right\", i, ret=True)\n", + "\n", + "# show animation of the keypoints over time, show one frame every 1000ms and remove the other frames\n", + "anim = animation.FuncAnimation(fig, animate_hand, init_func=init_hand, frames=norm_df.shape[0], interval=1000, repeat=False, )\n", + "HTML(anim.to_html5_video())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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AACbEF48yu93boeWBgaY3q8bGtjvBQBgH8KLhwWD0rZtUklJbHlpqVPOPi8AABuxSZ3HBYNSTQ077AIAvIPwkgH8fmnBglS3AgCA5GDaCAAAWIXwAgAArEJ4AQAAViG8AAAAqxBeAACAVQgvAADAKoQXAABgFcILAACwCuEFAABYhfACAACsQngBAABWIbwAAACrcDAjAABDiEal5mapo0MqLpYqK50Db5E6hBcAAAYRCkmrVkltbRfLSkuljRulYDB17cp0TBsBABBHKCTV1sYGF0lqb3fKQ6HUtAuEFwAABohGnREXYwa+1lu2erVTD2OP8AIAQD/NzQNHXC5ljNTa6tTD2GPNS4JYsAUAmaOjI7n1kFyElwSwYAsAMktxcXLrIbmYNroMFmwBQOaprHR+SfX54r/u80mBgFMPY4/wMgQWbAFAZvL7ndF1aWCA6f26oYHlA6kyJuFl06ZNKisrU25ursrLy7V79+5B627evFmVlZWaNGmSJk2apKqqqiHru4kFWwCQuYJBads2qaQktry01Cln2UDquB5eXnzxRa1Zs0Z1dXV6++23NXv2bC1atEjHjh2LW7+pqUlLlizRzp071dLSokAgoNtuu03t7e1uN3UAFmwBQGYLBqWPP5Z27pReeMF5PnyY4JJqPmPiTYokT3l5ub785S/r6aefliT19PQoEAjowQcf1Nq1ay97fTQa1aRJk/T0009r2bJll60fiUSUn5+vrq4u5eXljartTU3Srbdevt7OndKCBaP6Vq7hLikAgA2G8/nt6sjLuXPntHfvXlVVVV38hllZqqqqUktLS0LvcfbsWZ0/f16TJ0+O+3p3d7cikUjMI1lsX7AVCkllZU4Au+ce57msjEXGAAC7uRpejh8/rmg0qsLCwpjywsJChcPhhN7jX//1XzV9+vSYAHSp+vp65efn9z0CgcCo293L5gVb3CUFAPCqtL7baMOGDdq6datefvll5ebmxq2zbt06dXV19T1aW1uT2gYbF2xxlxQAwMtc3aRuypQp8vv96uzsjCnv7OxUUVHRkNc++eST2rBhg/7whz/ohhtuGLReTk6OcnJyktLewQSDUk2NPWtHhnOXVLqu1QEAYDCujrxkZ2dr7ty5amxs7Cvr6elRY2OjKioqBr3uRz/6kR5//HFt375d8+bNc7OJCfP7nQ/6JUuc53QNLhJ3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zdX111+v3/72tzGvG2O0fv16FRcX6zOf+Yyqqqr04YcfutmFEUt23++9994B/8bV1dVudmHEhtP39957T3fffXffz66GhoZRv2cqJbvvjz766IB/91mzZrnYgzRgkLaqq6vN7NmzzZ/+9CfT3NxsPv/5z5slS5YMec1TTz1l6uvrTX19vZFk/u///m9Anauuuso89thjpqOjo+9x+vRpl3oxMm71fSTvO9ZG0sZvfetbJhAImMbGRrNnzx7z13/91+bmm2+OqSPJ/PznP4/5d//zn//sZlcua+vWrSY7O9s899xz5r333jP33XefKSgoMJ2dnXHrv/HGG8bv95sf/ehH5v333zff+973zPjx480777zTV2fDhg0mPz/fvPLKK+Z//ud/zFe/+lVz9dVXp7yv/bnR9+XLl5vq6uqYf+MTJ06MVZcSNty+79692zzyyCNmy5YtpqioyDz11FOjfs9UcaPvdXV15otf/GLMv/snn3zick9Si/CSpt5//30jybz11lt9Zb/73e+Mz+cz7e3tl71+586dQ4aXeP8DpAu3+j7a9x0LI2njyZMnzfjx481LL73UV3bgwAEjybS0tPSVSTIvv/yya20fifnz55sHHnig7+toNGqmT59u6uvr49b/+7//e3PnnXfGlJWXl5tvfvObxhhjenp6TFFRkXniiSf6Xj958qTJyckxW7ZscaEHI5fsvhvjhJeamhpX2ptMw+37pQb7+TWa9xxLbvS9rq7OzJ49O4mtTH9MG6WplpYWFRQUaN68eX1lVVVVysrK0q5du0b9/hs2bNBnP/tZ3XjjjXriiSd04cKFUb9nsrjVd7f/TpNhJG3cu3evzp8/r6qqqr6yWbNm6corr1RLS0tM3QceeEBTpkzR/Pnz9dxzzyV09Lxbzp07p71798a0OysrS1VVVQPa3aulpSWmviQtWrSor/7hw4cVDodj6uTn56u8vHzQ90wFN/req6mpSdOmTdPMmTP1T//0T/r000+T34FRGEnfU/GebnCznR9++KGmT5+uGTNmaOnSpTpy5Mhom5vWPHcwo1eEw2FNmzYtpmzcuHGaPHmywuHwqN77oYce0k033aTJkyfrzTff1Lp169TR0aGf/OQno3rfZHGr727+nSbLSNoYDoeVnZ2tgoKCmPLCwsKYax577DH93d/9na644gr9/ve/17e//W2dPn1aDz30UNL7kYjjx48rGo2qsLAwprywsFAffPBB3GvC4XDc+r397H0eqk46cKPvklRdXa1gMKirr75ahw4d0ne/+13dfvvtamlpkd/vT35HRmAkfU/Fe7rBrXaWl5fr+eef18yZM9XR0aEf/OAHqqys1LvvvquJEyeOttlpifAyxtauXasf/vCHQ9Y5cOCAq21Ys2ZN359vuOEGZWdn65vf/Kbq6+td3X46HfqeKunQ9+9///t9f77xxht15swZPfHEEykLL0i+r3/9631/vv7663XDDTfoc5/7nJqamrRw4cIUtgxuuv322/v+fMMNN6i8vFxXXXWVfv3rX2vlypUpbJl7CC9j7OGHH9a99947ZJ0ZM2aoqKhIx44diym/cOGCTpw4oaKioqS2qby8XBcuXNDHH3+smTNnJvW9L5Xqvo/l32l/bva9qKhI586d08mTJ2NGXzo7O4fsV3l5uR5//HF1d3en5MyUKVOmyO/3D7graqh2FxUVDVm/97mzs1PFxcUxdebMmZPE1o+OG32PZ8aMGZoyZYo++uijtAkvI+l7Kt7TDWPVzoKCAn3hC1/QRx99lLT3TDeseRljU6dO1axZs4Z8ZGdnq6KiQidPntTevXv7rt2xY4d6enpUXl6e1Dbt379fWVlZA6Yrki3VfR/Lv9P+3Oz73LlzNX78eDU2NvaVHTx4UEeOHFFFRcWgbdq/f78mTZqUssPesrOzNXfu3Jh29/T0qLGxcdB2V1RUxNSXpNdee62v/tVXX62ioqKYOpFIRLt27Rry72KsudH3eNra2vTpp5/GBLlUG0nfU/Gebhirdp4+fVqHDh1Kq3/3pEv1imEMrrq62tx4441m165d5o9//KO55pprYm6ZbWtrMzNnzjS7du3qK+vo6DD79u0zmzdvNpLM66+/bvbt22c+/fRTY4wxb775pnnqqafM/v37zaFDh8wvf/lLM3XqVLNs2bIx799Q3Oh7Iu+bDkbS929961vmyiuvNDt27DB79uwxFRUVpqKiou/1V1991WzevNm888475sMPPzT/8R//Ya644gqzfv36Me1bf1u3bjU5OTnm+eefN++//765//77TUFBgQmHw8YYY77xjW+YtWvX9tV/4403zLhx48yTTz5pDhw4YOrq6uLeKl1QUGD+67/+y/zv//6vqampSdtbpZPZ91OnTplHHnnEtLS0mMOHD5s//OEP5qabbjLXXHON+ctf/pKSPg5muH3v7u42+/btM/v27TPFxcXmkUceMfv27TMffvhhwu+ZLtzo+8MPP2yamprM4cOHzRtvvGGqqqrMlClTzLFjx8a8f2OF8JLGPv30U7NkyRIzYcIEk5eXZ1asWGFOnTrV9/rhw4eNJLNz586+srq6OiNpwOPnP/+5McaYvXv3mvLycpOfn29yc3PNtddea/7t3/4t7X64udH3RN43HYyk73/+85/Nt7/9bTNp0iRzxRVXmK997Wumo6Oj7/Xf/e53Zs6cOWbChAnmr/7qr8zs2bPNM888Y6LR6Fh2La6f/vSn5sorrzTZ2dlm/vz55k9/+lPfa7fccotZvnx5TP1f//rX5gtf+ILJzs42X/ziF81///d/x7ze09Njvv/975vCwkKTk5NjFi5caA4ePDgWXRm2ZPb97Nmz5rbbbjNTp04148ePN1dddZW577770u7Du9dw+t7733z/xy233JLwe6aTZPd98eLFpri42GRnZ5uSkhKzePFi89FHH41hj8aez5gU3isJAAAwTKx5AQAAViG8AAAAqxBeAACAVQgvAADAKoQXAABgFcILAACwCuEFAABYhfACAACsQngBAABWIbwAAACrEF4AAIBVCC8AAMAq/w/72+xGPwjDOgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "fig = plt.figure()\n", + "ax = plt.axes(xlim=(-0.5, 0.5), ylim=(-0.5, 0.5))\n", + "ax.set_aspect('equal')\n", + "ax.set_title(\"Left Hand\")\n", + "ax.set_xlabel(\"x\")\n", + "ax.set_ylabel(\"y\")\n", + "\n", + "def init_hand():\n", + " return plot_hand_keypoints(norm_df, \"left\", 0, ret=True)\n", + "\n", + "def animate_hand(i):\n", + " return plot_hand_keypoints(norm_df, \"left\", i, ret=True)\n", + "\n", + "# show animation of the keypoints over time, show one frame every 1000ms and remove the other frames\n", + "anim = animation.FuncAnimation(fig, animate_hand, init_func=init_hand, frames=norm_df.shape[0], interval=1000, repeat=False, )\n", + "HTML(anim.to_html5_video())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + }, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/visualizations/webcam_view.py b/visualizations/webcam_view.py new file mode 100644 index 0000000..99bf2bb --- /dev/null +++ b/visualizations/webcam_view.py @@ -0,0 +1,116 @@ +import cv2 +import mediapipe as mp +import numpy as np +import pandas as pd +import torch + +from src.identifiers import LANDMARKS +from src.keypoint_extractor import KeypointExtractor +from src.model import SPOTER +from src.normalizations import normalize_hand_bohaecek, normalize_pose + +# Initialize MediaPipe Hands model +holistic = mp.solutions.holistic.Holistic( + min_detection_confidence=0.5, + min_tracking_confidence=0.5, + model_complexity=2 + ) +mp_holistic = mp.solutions.holistic +mp_drawing = mp.solutions.drawing_utils + +# Initialize video capture object +cap = cv2.VideoCapture(0) + +frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + +keypoints = [] + +spoter_model = SPOTER(num_classes=26, hidden_dim=len(LANDMARKS) * 2) +spoter_model.load_state_dict(torch.load('models/spoter_76.pth', map_location=torch.device('cpu'))) + +# get values of the landmarks as a list of integers +values = [] +for i in LANDMARKS.values(): + values.append(i * 2) + values.append(i * 2 + 1) +values = np.array(values) + +while True: + # Read frame from camera + success, frame = cap.read() + + # Convert the frame to RGB + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Detect hand landmarks in the frame + results = holistic.process(frame) + + def extract_keypoints(landmarks): + if landmarks: + return np.array([i for landmark in landmarks.landmark for i in [landmark.x, landmark.y]]) + + k1 = extract_keypoints(results.pose_landmarks) + k2 = extract_keypoints(results.left_hand_landmarks) + k3 = extract_keypoints(results.right_hand_landmarks) + + if k1 is not None and (k2 is not None or k3 is not None): + k2 = k2 if k2 is not None else np.zeros(42) + k3 = k3 if k3 is not None else np.zeros(42) + + k1 = k1 * np.array([frame_width, frame_height] * 33) + k2 = k2 * np.array([frame_width, frame_height] * 21) + k3 = k3 * np.array([frame_width, frame_height] * 21) + + k1, bbox_pose = normalize_pose(k1) + k2, bbox_left = normalize_hand_bohaecek(k2) + k3, bbox_right = normalize_hand_bohaecek(k3) + + # Draw normalization bounding boxes + if bbox_pose is not None: + frame = cv2.rectangle(frame, bbox_pose, (0, 255, 0), 2) + if bbox_left is not None: + frame = cv2.rectangle(frame, bbox_left, (0, 255, 0), 2) + if bbox_right is not None: + frame = cv2.rectangle(frame, bbox_right, (0, 255, 0), 2) + + k = np.concatenate((k1, k2, k3)) + filtered = k[values] + + while len(keypoints) >= 8: + keypoints.pop(0) + keypoints.append(filtered) + + if len(keypoints) == 8: + # keypoints to tensor + keypoints_tensor = torch.tensor(keypoints).float() + outputs = spoter_model(keypoints_tensor).expand(1, -1, -1) + outputs = torch.nn.functional.softmax(outputs, dim=2) + topk = torch.topk(outputs, k=3, dim=2) + + # show overlay on frame at top right with confidence scores of topk predictions + for i, (label, score) in enumerate(zip(topk.indices[0][0], topk.values[0][0])): + # get the label (A-Z), index to char + l = label.item() + if l < 26: + l = chr(l + 65) + + cv2.putText(frame, f"{l} {score.item():.2f}", (frame.shape[1] - 200, 50 + i * 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + mp_drawing.draw_landmarks(frame, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS) + mp_drawing.draw_landmarks(frame, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS) + mp_drawing.draw_landmarks(frame, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS) + + # frame to rgb + frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) + + # Show the frame + cv2.imshow('MediaPipe Hands', frame) + + # Wait for key press to exit + if cv2.waitKey(5) & 0xFF == 27: + break + +# Release the video capture object and destroy the windows +cap.release() +cv2.destroyAllWindows() diff --git a/visualize_data.ipynb b/visualize_data.ipynb deleted file mode 100644 index c90b744..0000000 --- a/visualize_data.ipynb +++ /dev/null @@ -1,301 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from src.keypoint_extractor import KeypointExtractor\n", - "\n", - "# reload modules\n", - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "video_name = '69547.mp4' " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# extract keypoints\n", - "keypoint_extractor = KeypointExtractor('data/videos/')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from IPython.display import HTML\n", - "from base64 import b64encode\n", - "import mediapy as media\n", - "%matplotlib inline\n", - "\n", - "# Define the frames per second (fps) and duration of the video\n", - "fps = 25\n", - "duration = 10\n", - "\n", - "# Create a dummy video of random noise\n", - "_, video_frames = keypoint_extractor.extract_keypoints_from_video(video_name, normalize=\"minmax\", draw=True)\n", - "\n", - "# Convert the video to a numpy array\n", - "video = np.array(video_frames)\n", - "media.show_video(video, height=400, codec='gif', fps=4)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from src.model import SPOTER\n", - "from src.identifiers import LANDMARKS\n", - "import torch\n", - "\n", - "spoter_model = SPOTER(num_classes=5, hidden_dim=len(LANDMARKS) *2)\n", - "spoter_model.load_state_dict(torch.load('models/spoter_40.pth'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get average number of frames in test set\n", - "from src.keypoint_extractor import KeypointExtractor\n", - "from src.datasets.finger_spelling_dataset import FingerSpellingDataset\n", - "from src.identifiers import LANDMARKS\n", - "import numpy as np\n", - "\n", - "keypoints_extractor = KeypointExtractor(\"data/fingerspelling/data/\")\n", - "test_set = FingerSpellingDataset(\"data/fingerspelling/data/\", keypoints_extractor, keypoints_identifier=LANDMARKS, subset=\"val\")\n", - "\n", - "frames = []\n", - "labels = []\n", - "for sample, label in test_set:\n", - " frames.append(sample.shape[0])\n", - " labels.append(label)\n", - "\n", - "print(np.mean(frames))\n", - "# get label frequency in the labels list\n", - "from collections import Counter\n", - "\n", - "counter = Counter(labels)\n", - "print(counter)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Hand keypoint visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "def plot_hand_keypoints(dataframe, hand, frame):\n", - " hand_columns = np.array([i for i in range(66 + (42 if hand == \"right\" else 0), 108 + (42 if hand == \"right\" else 0))])\n", - " \n", - " # get the x, y coordinates of the hand keypoints\n", - " frame_df = dataframe.iloc[frame:frame+1, hand_columns]\n", - " hand_coords = frame_df.values.reshape(21, 2)\n", - " \n", - " x_coords = hand_coords[:, ::2] #Even indices\n", - " y_coords = -hand_coords[:, 1::2] #Uneven indices (negative because pixels start from the top left)\n", - " \n", - " #Plot the keypoints\n", - " plt.scatter(x_coords, y_coords)\n", - " return frame_df.style" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Set video, hand and frame to display\n", - "video_name = '69547.mp4'\n", - "hand = \"right\"\n", - "frame = 3\n", - "%reload_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from src.keypoint_extractor import KeypointExtractor\n", - "import numpy as np\n", - "\n", - "#Extract keypoints from requested video\n", - "keypoints_extractor = KeypointExtractor(\"data/videos/\")\n", - "\n", - "#Plot the hand keypoints\n", - "df = keypoints_extractor.extract_keypoints_from_video(video_name)\n", - "df.head()\n", - "plot_hand_keypoints(df, hand, frame)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Plot the NORMALIZED hand keypoints (using minxmax)\n", - "df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"minmax\")\n", - "plt.xlim(-0.5,0.5)\n", - "plt.ylim(-0.5,0.5)\n", - "plot_hand_keypoints(df, hand, frame)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Plot the NORMALIZED hand keypoints (using bohacek)\n", - "df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"bohacek\")\n", - "plt.xlim(-0.5,0.5)\n", - "plt.ylim(-0.5,0.5)\n", - "plot_hand_keypoints(df, hand, frame)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Pose keypoint visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "def plot_pose_keypoints(dataframe, frame):\n", - " pose_columns = np.array([i for i in range(32)])\n", - " \n", - " # get the x, y coordinates of the relevant pose keypoints\n", - " frame_df = dataframe.iloc[frame:frame+1, pose_columns]\n", - " pose_coords = frame_df.values.reshape(16, 2)\n", - " \n", - " x_coords = pose_coords[:, ::2] #Even indices\n", - " y_coords = -pose_coords[:, 1::2] #Uneven indices (negative because pixels start from the top left)\n", - " \n", - " #Plot the keypoints\n", - " plt.scatter(x_coords, y_coords)\n", - " return frame_df.style" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Set video, hand and frame to display\n", - "video_name = '69547.mp4'\n", - "frame = 2\n", - "%reload_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from src.keypoint_extractor import KeypointExtractor\n", - "import numpy as np\n", - "\n", - "#Extract keypoints from requested video\n", - "keypoints_extractor = KeypointExtractor(\"data/videos/\")\n", - "\n", - "#Plot the hand keypoints\n", - "df = keypoints_extractor.extract_keypoints_from_video(video_name)\n", - "df.head()\n", - "plot_pose_keypoints(df, frame)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "norm_df = keypoints_extractor.extract_keypoints_from_video(video_name, normalize=\"bohacek\")\n", - "plt.xlim(-0.5,0.5)\n", - "plt.ylim(-0.5,0.5)\n", - "plot_pose_keypoints(norm_df, frame)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - }, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/webcam_view.py b/webcam_view.py deleted file mode 100644 index 59ea668..0000000 --- a/webcam_view.py +++ /dev/null @@ -1,167 +0,0 @@ -import cv2 -import mediapipe as mp -import numpy as np -import torch - -from src.identifiers import LANDMARKS -from src.model import SPOTER - -# Initialize MediaPipe Hands model -holistic = mp.solutions.holistic.Holistic( - min_detection_confidence=0.5, - min_tracking_confidence=0.5, - model_complexity=2 - ) -mp_holistic = mp.solutions.holistic -mp_drawing = mp.solutions.drawing_utils -# Initialize video capture object -cap = cv2.VideoCapture(0) - - -keypoints = [] - -spoter_model = SPOTER(num_classes=12, hidden_dim=len(LANDMARKS) *2) -spoter_model.load_state_dict(torch.load('models/spoter_57.pth')) - -m = { - 0: "A", - 1: "B", - 2: "C", - 3: "D", - 4: "E", - 5: "F", - 6: "G", - 7: "H", - 8: "I", - 9: "J", - 10: "K", - 11: "L", -} - -while True: - # Read a frame from the webcam - ret, frame = cap.read() - if not ret: - break - - # Convert the frame to RGB - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - - # Detect hand landmarks in the frame - results = holistic.process(frame) - - def extract_keypoints(landmarks): - if landmarks: - return [i for landmark in landmarks.landmark for i in [landmark.x, landmark.y]] - - k1 = extract_keypoints(results.pose_landmarks) - k2 = extract_keypoints(results.left_hand_landmarks) - k3 = extract_keypoints(results.right_hand_landmarks) - - if k1 and (k2 or k3): - data = np.array([k1 + (k2 or [0] * 42) + (k3 or [0] * 42)]) - - def normalize_hand(frame, data, hand, algorithm="minmax"): - hand_columns = np.array([i for i in range(66 + (42 if hand == "right_hand" else 0), 108 + (42 if hand == "right_hand" else 0))]) - hand_data = np.array(data[0])[hand_columns] - - # convert to absolute pixels - hand_data = hand_data.reshape(21, 2) - hand_data[:, 0] *= frame.shape[1] - hand_data[:, 1] *= frame.shape[0] - - min_x, min_y = np.min(hand_data[:, 0]), np.min(hand_data[:, 1]) - max_x, max_y = np.max(hand_data[:, 0]), np.max(hand_data[:, 1]) - - width, height = max_x - min_x, max_y - min_y - - if algorithm == "minmax": - bbox_height, bbox_width = height, width - center_x, center_y = (min_x + max_x) / 2, (min_y + max_y) / 2 - - starting_x, starting_y = min_x, min_y - ending_x, ending_y = max_x, max_y - - elif algorithm == "bohacek": - if width > height: - delta_x = 0.1 * width - delta_y = delta_x + ((width - height) / 2) - else: - delta_y = 0.1 * height - delta_x = delta_y + ((height - width) / 2) - - starting_x, starting_y = min_x - delta_x, min_y - delta_y - ending_x, ending_y = max_x + delta_x, max_y + delta_y - - center_x, center_y = (starting_x + ending_x) / 2, (starting_y + ending_y) / 2 - bbox_height, bbox_width = ending_y - starting_y, ending_x - starting_x - - else: - print("Not a valid normalization algorithm") - return data, frame - - if bbox_height == 0 or bbox_width == 0: - return data, frame - - center_coords = np.tile(np.array([center_x, center_y]), (21, 1)).reshape(21, 2) - bbox_dims = np.tile(np.array([bbox_width, bbox_height]), (21, 1)).reshape(21, 2) - - hand_data = (hand_data - center_coords) / bbox_dims - - # add bouding box to frame - frame = cv2.rectangle(frame, (int(starting_x), int(starting_y)), (int(ending_x), int(ending_y)), (0, 255, 0), 2) - - data[:, hand_columns] = hand_data.reshape(-1, 42) - return data, frame - - norm_alg = "minmax" - - data, frame = normalize_hand(frame, data, "left_hand", norm_alg) - data, frame = normalize_hand(frame, data, "right_hand", norm_alg) - - # get values of the landmarks as a list of integers - values = [] - for i in LANDMARKS.values(): - values.append(i*2) - values.append(i*2+1) - filtered = np.array(data[0])[np.array(values)] - - while len(keypoints) >= 8: - keypoints.pop(0) - keypoints.append(filtered) - - if len(keypoints) == 8: - # keypoints to tensor - keypoints_tensor = torch.tensor(keypoints).float() - - # predict - outputs = spoter_model(keypoints_tensor).expand(1, -1, -1) - - # softmax - outputs = torch.nn.functional.softmax(outputs, dim=2) - - # get topk predictions - topk = torch.topk(outputs, k=3, dim=2) - - # show overlay on frame at top right with confidence scores of topk predictions - for i, (label, score) in enumerate(zip(topk.indices[0][0], topk.values[0][0])): - cv2.putText(frame, f"{m[label.item()]} {score.item():.2f}", (frame.shape[1] - 200, 50 + i * 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) - - - mp_drawing.draw_landmarks(frame, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS) - mp_drawing.draw_landmarks(frame, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS) - mp_drawing.draw_landmarks(frame, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS) - - # frame to rgb - frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) - - # Show the frame - cv2.imshow('MediaPipe Hands', frame) - - # Wait for key press to exit - if cv2.waitKey(5) & 0xFF == 27: - break - -# Release the video capture object and destroy the windows -cap.release() -cv2.destroyAllWindows()