{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"objc[67754]: Class CaptureDelegate is implemented in both /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/mediapipe/.dylibs/libopencv_videoio.3.4.16.dylib (0x10a8c8860) and /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/cv2/cv2.abi3.so (0x161476480). One of the two will be used. Which one is undefined.\n",
"objc[67754]: Class CVWindow is implemented in both /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/mediapipe/.dylibs/libopencv_highgui.3.4.16.dylib (0x10567ca68) and /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/cv2/cv2.abi3.so (0x1614764d0). One of the two will be used. Which one is undefined.\n",
"objc[67754]: Class CVView is implemented in both /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/mediapipe/.dylibs/libopencv_highgui.3.4.16.dylib (0x10567ca90) and /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/cv2/cv2.abi3.so (0x1614764f8). One of the two will be used. Which one is undefined.\n",
"objc[67754]: Class CVSlider is implemented in both /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/mediapipe/.dylibs/libopencv_highgui.3.4.16.dylib (0x10567cab8) and /Users/fuixlabsdev1/Programming/PP/graduation-thesis/env/lib/python3.8/site-packages/cv2/cv2.abi3.so (0x161476520). One of the two will be used. Which one is undefined.\n"
]
}
],
"source": [
"import mediapipe as mp\n",
"import cv2\n",
"import numpy as np\n",
"import pandas as pd\n",
"import os, csv\n",
"import seaborn as sns\n",
"\n",
"import warnings\n",
"warnings.filterwarnings('ignore')\n",
"\n",
"# Drawing helpers\n",
"mp_drawing = mp.solutions.drawing_utils\n",
"mp_pose = mp.solutions.pose"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. Describe the data gathering process and build dataset from Video\n",
"\n",
"The purpose is to gather data to determine the correct standing posture for Bicep Curl exercise\n",
"There are 2 stages:\n",
"- Correct: \"C\"\n",
"- Lean-back-error: \"L\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Determine important landmarks for plank\n",
"IMPORTANT_LMS = [\n",
" \"NOSE\",\n",
" \"LEFT_SHOULDER\",\n",
" \"RIGHT_SHOULDER\",\n",
" \"RIGHT_ELBOW\",\n",
" \"LEFT_ELBOW\",\n",
" \"RIGHT_WRIST\",\n",
" \"LEFT_WRIST\",\n",
" \"LEFT_HIP\",\n",
" \"RIGHT_HIP\",\n",
"]\n",
"\n",
"# Generate all columns of the data frame\n",
"\n",
"HEADERS = [\"label\"] # Label column\n",
"\n",
"for lm in IMPORTANT_LMS:\n",
" HEADERS += [f\"{lm.lower()}_x\", f\"{lm.lower()}_y\", f\"{lm.lower()}_z\", f\"{lm.lower()}_v\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1.2. Set up important functions"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def rescale_frame(frame, percent=50):\n",
" '''\n",
" Rescale a frame to a certain percentage compare to its original frame\n",
" '''\n",
" width = int(frame.shape[1] * percent/ 100)\n",
" height = int(frame.shape[0] * percent/ 100)\n",
" dim = (width, height)\n",
" return cv2.resize(frame, dim, interpolation = cv2.INTER_AREA)\n",
" \n",
"\n",
"def init_csv(dataset_path: str):\n",
" '''\n",
" Create a blank csv file with just columns\n",
" '''\n",
"\n",
" # Ignore if file is already exist\n",
" if os.path.exists(dataset_path):\n",
" return\n",
"\n",
" # Write all the columns to a empty file\n",
" with open(dataset_path, mode=\"w\", newline=\"\") as f:\n",
" csv_writer = csv.writer(f, delimiter=\",\", quotechar='\"', quoting=csv.QUOTE_MINIMAL)\n",
" csv_writer.writerow(HEADERS)\n",
"\n",
"\n",
"def export_landmark_to_csv(dataset_path: str, results, action: str) -> None:\n",
" '''\n",
" Export Labeled Data from detected landmark to csv\n",
" '''\n",
" landmarks = results.pose_landmarks.landmark\n",
" keypoints = []\n",
"\n",
" try:\n",
" # Extract coordinate of important landmarks\n",
" for lm in IMPORTANT_LMS:\n",
" keypoint = landmarks[mp_pose.PoseLandmark[lm].value]\n",
" keypoints.append([keypoint.x, keypoint.y, keypoint.z, keypoint.visibility])\n",
" \n",
" keypoints = list(np.array(keypoints).flatten())\n",
"\n",
" # Insert action as the label (first column)\n",
" keypoints.insert(0, action)\n",
"\n",
" # Append new row to .csv file\n",
" with open(dataset_path, mode=\"a\", newline=\"\") as f:\n",
" csv_writer = csv.writer(f, delimiter=\",\", quotechar='\"', quoting=csv.QUOTE_MINIMAL)\n",
" csv_writer.writerow(keypoints)\n",
" \n",
"\n",
" except Exception as e:\n",
" print(e)\n",
" pass\n",
"\n",
"\n",
"def describe_dataset(dataset_path: str):\n",
" '''\n",
" Describe dataset\n",
" '''\n",
"\n",
" data = pd.read_csv(dataset_path)\n",
" print(f\"Headers: {list(data.columns.values)}\")\n",
" print(f'Number of rows: {data.shape[0]} \\nNumber of columns: {data.shape[1]}\\n')\n",
" print(f\"Labels: \\n{data['label'].value_counts()}\\n\")\n",
" print(f\"Missing values: {data.isnull().values.any()}\\n\")\n",
" \n",
" duplicate = data[data.duplicated()]\n",
" print(f\"Duplicate Rows : {len(duplicate.sum(axis=1))}\")\n",
"\n",
" return data\n",
"\n",
"\n",
"def remove_duplicate_rows(dataset_path: str):\n",
" '''\n",
" Remove duplicated data from the dataset then save it to another files\n",
" '''\n",
" \n",
" df = pd.read_csv(dataset_path)\n",
" df.drop_duplicates(keep=\"first\", inplace=True)\n",
" df.to_csv(f\"cleaned_train.csv\", sep=',', encoding='utf-8', index=False)\n",
" \n",
"\n",
"def concat_csv_files_with_same_headers(file_paths: list, saved_path: str):\n",
" '''\n",
" Concat different csv files\n",
" '''\n",
" all_df = []\n",
" for path in file_paths:\n",
" df = pd.read_csv(path, index_col=None, header=0)\n",
" all_df.append(df)\n",
" \n",
" results = pd.concat(all_df, axis=0, ignore_index=True)\n",
" results.to_csv(saved_path, sep=',', encoding='utf-8', index=False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. Extract data from video"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[1;31mThe Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click here for more info. View Jupyter log for further details."
]
}
],
"source": [
"DATASET_PATH = \"train.csv\"\n",
"\n",
"cap = cv2.VideoCapture(\"../data/db_curl/stand_posture_11.mp4\")\n",
"save_counts = 0\n",
"\n",
"# init_csv(DATASET_PATH)\n",
"\n",
"with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:\n",
" while cap.isOpened():\n",
" ret, image = cap.read()\n",
"\n",
" if not ret:\n",
" break\n",
"\n",
" # Reduce size of a frame\n",
" image = rescale_frame(image, 60)\n",
" image = cv2.flip(image, 1)\n",
"\n",
" # Recolor image from BGR to RGB for mediapipe\n",
" image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n",
" image.flags.writeable = False\n",
"\n",
" results = pose.process(image)\n",
"\n",
" if not results.pose_landmarks:\n",
" print(\"Cannot detect pose - No human found\")\n",
" continue\n",
"\n",
" # Recolor image from BGR to RGB for mediapipe\n",
" image.flags.writeable = True\n",
" image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)\n",
"\n",
" # Draw landmarks and connections\n",
" mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, mp_drawing.DrawingSpec(color=(244, 117, 66), thickness=2, circle_radius=4), mp_drawing.DrawingSpec(color=(245, 66, 230), thickness=2, circle_radius=2))\n",
"\n",
" # Display the saved count\n",
" cv2.putText(image, f\"Saved: {save_counts}\", (50, 50), cv2.FONT_HERSHEY_COMPLEX, 2, (0, 0, 0), 2, cv2.LINE_AA)\n",
"\n",
" cv2.imshow(\"CV2\", image)\n",
"\n",
" # Pressed key for action\n",
" k = cv2.waitKey(1) & 0xFF\n",
"\n",
" # Press C to save as correct form\n",
" if k == ord('c'): \n",
" export_landmark_to_csv(DATASET_PATH, results, \"C\")\n",
" save_counts += 1\n",
" # Press L to save as low back\n",
" elif k == ord(\"l\"):\n",
" export_landmark_to_csv(DATASET_PATH, results, \"L\")\n",
" save_counts += 1\n",
"\n",
" # Press q to stop\n",
" elif k == ord(\"q\"):\n",
" break\n",
" else: continue\n",
"\n",
" cap.release()\n",
" cv2.destroyAllWindows()\n",
"\n",
" # (Optional)Fix bugs cannot close windows in MacOS (https://stackoverflow.com/questions/6116564/destroywindow-does-not-close-window-on-mac-using-python-and-opencv)\n",
" for i in range (1, 5):\n",
" cv2.waitKey(1)\n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# csv_files = [os.path.join(\"./\", f) for f in os.listdir(\"./\") if \"csv\" in f]\n",
"\n",
"# concat_csv_files_with_same_headers(csv_files, \"train.csv\")\n",
"\n",
"df = describe_dataset(\"./train.csv\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3. Clean Data and Visualize data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"remove_duplicate_rows(\"./train.csv\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Headers: ['label', 'nose_x', 'nose_y', 'nose_z', 'nose_v', 'left_shoulder_x', 'left_shoulder_y', 'left_shoulder_z', 'left_shoulder_v', 'right_shoulder_x', 'right_shoulder_y', 'right_shoulder_z', 'right_shoulder_v', 'right_elbow_x', 'right_elbow_y', 'right_elbow_z', 'right_elbow_v', 'left_elbow_x', 'left_elbow_y', 'left_elbow_z', 'left_elbow_v', 'right_wrist_x', 'right_wrist_y', 'right_wrist_z', 'right_wrist_v', 'left_wrist_x', 'left_wrist_y', 'left_wrist_z', 'left_wrist_v', 'left_hip_x', 'left_hip_y', 'left_hip_z', 'left_hip_v', 'right_hip_x', 'right_hip_y', 'right_hip_z', 'right_hip_v']\n",
"Number of rows: 15372 \n",
"Number of columns: 37\n",
"\n",
"Labels: \n",
"C 8238\n",
"L 7134\n",
"Name: label, dtype: int64\n",
"\n",
"Missing values: False\n",
"\n",
"Duplicate Rows : 0\n"
]
},
{
"data": {
"text/plain": [
""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"