{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "269dfbae",
   "metadata": {},
   "source": [
    "# Camera Motion Test -- Optical Flow"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5afdc2cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "from pathlib import Path\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d2aebc0",
   "metadata": {},
   "source": [
    "Import video"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "48dd6872",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opened: True, FPS: 30.0, Total Frames: 119.0\n"
     ]
    }
   ],
   "source": [
    "video_number = \"8\"\n",
    "# Define the relative path to the video file\n",
    "notebook_dir = Path().resolve()\n",
    "project_root = notebook_dir.parent.parent\n",
    "video_path = (\n",
    "    project_root\n",
    "    / \"data\"\n",
    "    / f\"recording_{video_number}\"\n",
    "    / f\"Recording_{video_number}.mp4\"\n",
    ")\n",
    "video_path = str(video_path)\n",
    "\n",
    "# Load the video\n",
    "cap = cv2.VideoCapture(video_path)\n",
    "\n",
    "# Check\n",
    "print(\n",
    "    f\"Opened: {cap.isOpened()}, FPS: {cap.get(cv2.CAP_PROP_FPS)}, Total Frames: {cap.get(cv2.CAP_PROP_FRAME_COUNT)}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29a25301",
   "metadata": {},
   "source": [
    "Estimate the camera motion with optical flow"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d4398956",
   "metadata": {},
   "outputs": [],
   "source": [
    "def estimate_background_motion(cap, max_frames=200):\n",
    "    orb = cv2.ORB_create(nfeatures=1000)  # more features helps\n",
    "\n",
    "    ret, prev_frame = cap.read()\n",
    "    if not ret:\n",
    "        return []\n",
    "\n",
    "    prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)\n",
    "    prev_kp, prev_desc = orb.detectAndCompute(prev_gray, None)\n",
    "\n",
    "    motions = []\n",
    "    dxs = []\n",
    "    dys = []\n",
    "\n",
    "    for _ in range(max_frames):\n",
    "        ret, frame = cap.read()\n",
    "        if not ret:\n",
    "            break\n",
    "\n",
    "        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n",
    "        kp, desc = orb.detectAndCompute(gray, None)\n",
    "\n",
    "        if prev_desc is not None and desc is not None:\n",
    "            # Match ORB descriptors\n",
    "            bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n",
    "            matches = bf.match(prev_desc, desc)\n",
    "\n",
    "            # Extract matched keypoints\n",
    "            src_pts = np.float32([prev_kp[m.queryIdx].pt for m in matches]).reshape(\n",
    "                -1, 1, 2\n",
    "            )\n",
    "            dst_pts = np.float32([kp[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)\n",
    "\n",
    "            # Use RANSAC to filter out moving objects\n",
    "            if len(src_pts) >= 10:  # tune\n",
    "                H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)\n",
    "                if H is not None:\n",
    "                    # Extract translation components from homography\n",
    "                    dx, dy = H[0, 2], H[1, 2]\n",
    "                    motion_magnitude = np.sqrt(dx**2 + dy**2)\n",
    "                    motions.append(motion_magnitude)\n",
    "                    dxs.append(dx)\n",
    "                    dys.append(dy)\n",
    "\n",
    "        prev_gray = gray\n",
    "        prev_kp, prev_desc = kp, desc\n",
    "\n",
    "    cap.release()\n",
    "    return motions, dxs, dys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "68b1894a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Camera is still 🎥, motion: 0.962806187591208\n"
     ]
    }
   ],
   "source": [
    "motions, dx, dy = estimate_background_motion(\n",
    "    cap, max_frames=int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    ")\n",
    "avg_motion = np.mean(motions)\n",
    "\n",
    "if avg_motion < 1:\n",
    "    print(\"Camera is still 🎥, motion:\", avg_motion)\n",
    "else:\n",
    "    print(\"Camera is moving 📷, motion:\", avg_motion)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae0a19fe",
   "metadata": {},
   "source": [
    "Save the motions in a csv file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "e6cc35d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Motion data saved to C:\\Users\\miche\\OneDrive\\Documenti\\GitHub\\bowling-analysis\\notebook\\lane_detection\\intermediate_data\\background_motion\\motion_8.csv\n"
     ]
    }
   ],
   "source": [
    "# output path\n",
    "output_motions_path = (\n",
    "    project_root\n",
    "    / \"notebook\"\n",
    "    / \"lane_detection\"\n",
    "    / \"intermediate_data\"\n",
    "    / \"background_motion\"\n",
    "    / f\"motion_{video_number}.csv\"\n",
    ")\n",
    "# save motions in a csv file\n",
    "df = pd.DataFrame({\"dx\": dx, \"dy\": dy})\n",
    "df.to_csv(output_motions_path, index=False)\n",
    "\n",
    "print(f\"Motion data saved to {output_motions_path}\")"
   ]
  }
 ],
 "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.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
