{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Feature matches and RANSAC\n",
    "\n",
    "EECE 5554 Robotics Sensing and Navigation, Lecture 13. Put `features_ransac.py` in the same folder as this notebook, then run the cells in order."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each dark box below is a code cell. Edit it, then press **Run** or **Shift+Enter**. Cells share variables from top to bottom, like Jupyter."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from features_ransac import make_matches, dlt, ransac_homography, corner_error, tries_needed, residuals\n",
    "\n",
    "src, dst, wrong, H_true = make_matches(n=60, wrong=0.3, noise=1.0)\n",
    "print(wrong.sum(), 'of', len(src), 'matches are wrong')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Least squares with every match\n",
    "The DLT from Lecture 12, on all 60 pairs. How far off are the image corners?"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "H_ls = dlt(src, dst)\n",
    "print(f'least squares: corners off by {corner_error(H_ls, H_true):.0f} px')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. One RANSAC try, by hand\n",
    "Pick 4 pairs at random, fit H to just those, and count how many of the 60 pairs agree within 5 px."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(1)\n",
    "idx = rng.choice(len(src), 4, replace=False)\n",
    "H_try = dlt(src[idx], dst[idx])\n",
    "inliers = residuals(H_try, src, dst) < 5.0\n",
    "print('sample', idx, 'has wrong matches:', wrong[idx].any(), '-> inliers:', inliers.sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Run that cell a few times with other seeds. A sample with a wrong match gets few inliers; a clean one gets about 40."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. The whole loop"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "H_r, inl, tries = ransac_homography(src, dst, thresh=5.0)\n",
    "print(f'RANSAC: {inl.sum()} inliers after {tries} tries, corners off by {corner_error(H_r, H_true):.1f} px')\n",
    "print('wrong matches it kept:', (inl & wrong).sum())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. How many tries?\n",
    "N = log(1 − p) / log(1 − (1 − e)^s). Small samples win."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for s, name in [(2, 'line'), (4, 'homography'), (8, 'fundamental')]:\n",
    "    print(f'{name:12s}', [tries_needed(e, s) for e in (0.1, 0.3, 0.5, 0.7)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Your turn\n",
    "Push the share of wrong matches up. Where does RANSAC start to fail with its 2000-try limit? What does least squares do?"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for e in (0.5, 0.7, 0.8, 0.9):\n",
    "    src, dst, wrong, H_true = make_matches(wrong=e)\n",
    "    H_r, inl, tries = ransac_homography(src, dst, thresh=5.0)\n",
    "    err = corner_error(H_r, H_true) if H_r is not None else float('nan')\n",
    "    print(f'{e:.0%} wrong: {tries} tries, RANSAC off by {err:.1f} px, least squares off by {corner_error(dlt(src, dst), H_true):.0f} px')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}