Skip to content
Open
166 changes: 166 additions & 0 deletions .ipynb_checkpoints/project_description-checkpoint.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Project 2: Image Stitcher\n",
"## Assigned: 02.01.2019\n",
"## Due Date: TBD (probably 02.20.2019)\n",
"\n",
"Panoramic photography is ubiquitous, with nearly every digital camera having a mode dedicated to doing it. Here's an example from the Italian Alps:\n",
"<img src=\"pano.jpg\">\n",
"Note the extreme aspect ratio: much larger than the 4:3 or 3:2 that is typical of most cameras; suffice to say, the camera that stook this picture did not have a sensor that was this wide. So how are these things made? Stated simply, multiple images are taken, mutually identifiable points are located in each of these images, and the images are warped such that these points are coincident. The matching stage might look like this:\n",
"<img src=\"office.jpeg\">\n",
"\n",
"For this project, you will code your own image stitcher from scratch. Despite the conceptual simplicity of this operation, there are a surprising number of challenges that need to be addressed. A general framework for a stitcher might look like this:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from harris_response import *\n",
"\n",
"\n",
"class Stitcher(object):\n",
" def __init__(self, image_1, image_2):\n",
" \n",
" # Convert both images to gray scale\n",
" image_1 = np.mean(image_1, -1)\n",
" image_2 = np.mean(image_2, -1)\n",
" \n",
" self.images = [image_1, image_2]\n",
"\n",
" def find_keypoints(self, image, n_keypoints):\n",
" \n",
" \"\"\"\n",
" Step 1: This method locates features that are \"good\" for matching. To do this we will implement the Harris \n",
" corner detector\n",
" \"\"\"\n",
" \n",
" filter_size = 5\n",
" \n",
" # Setup gauss filter\n",
" gauss_filter = Filter.make_gauss((filter_size, filter_size), 2)\n",
" \n",
" # Compute smoothed harris response\n",
" out = convolve(compute_harris_response(image, gauss_filter), gauss_filter) # Smooth results\n",
" \n",
" # Find some good features to match\n",
" x, y = adaptive_non_maximal_suppression(out, n_keypoints, filter_size)\n",
" \n",
" # Return the locations\n",
" return x, y\n",
" \n",
" def generate_descriptors(self):\n",
" \"\"\"\n",
" Step 2: After identifying relevant keypoints, we need to come up with a quantitative description of the \n",
" neighborhood of that keypoint, so that we can match it to keypoints in other images.\n",
" \"\"\"\n",
" im1_kpt_x, im1_kpt_y = self.find_keypoints(self.images[0], 100) \n",
" im2_kpt_x, im2_kpt_y = self.find_keypoints(self.images[1], 100) \n",
"\n",
" ofs = l // 2\n",
" im1_desc_out = []\n",
" im1_x_out = []\n",
" im1_y_out = []\n",
" im2_desc_out = []\n",
" im2_x_out = []\n",
" im2_y_out = []\n",
" \n",
" # check for u and v to be same dimensions\n",
" for i in range(len(im1_kpt_x)):\n",
" sub = self.images[0][im1_kpt_x[i]-ofs:im1_kpt_x[i]+ofs+1, im1_kpt_y[i]-ofs:im1_kpt_y[i]+ofs+1] \n",
" if sub.shape[0] == l and sub.shape[1] == l: \n",
" im1_x_out.append(im1_kpt_x[i])\n",
" im1_y_out.append(im1_kpt_y[i])\n",
" im1_desc_out.append(sub)\n",
" \n",
" for i in range(len(im2_kpt_x)):\n",
" sub2 = self.images[1][im2_kpt_x[i]-ofs:im2_kpt_x[i]+ofs+1, im2_kpt_y[i]-ofs:im2_kpt_y[i]+ofs+1]\n",
" if sub2.shape[0] == l and sub2.shape[1] == l:\n",
" im2_x_out.append(im2_kpt_x[i])\n",
" im2_y_out.append(im2_kpt_y[i])\n",
" im2_desc_out.append(sub2)\n",
" \n",
" return np.stack(im1_desc_out), np.asarray(im1_y_out), np.asarray(im1_x_out), np.stack(im2_desc_out), np.asarray(im2_y_out), np.asarray(im2_x_out)\n",
" \n",
" \n",
" def match_keypoints(self,r= 0.7):\n",
" \"\"\"\n",
" Step 3: Compare keypoint descriptions between images, identify potential matches, and filter likely\n",
" mismatches\n",
" \"\"\"\n",
" im1_desc , im1_y ,im1_x ,im2_desc, im2_y ,im2_x = self.generate_descriptors()\n",
" \n",
" match_out = []\n",
" for index1 , D1 in enumerate (im1_desc):\n",
" smallest = np.inf\n",
" temp_index2 = 0\n",
" D1_hat = (D1 - np.mean(D1)) / np.std(D1)\n",
" \n",
" for index2, D2 in enumerate (im2_desc):\n",
" D2_hat = (D2 - np.mean(D2)) / np.std(D2)\n",
" E =np.sum(np.square(D1_hat-D2_hat))\n",
" if E < smallest:\n",
" smallest = E\n",
" temp_index2 = index2\n",
" match_out.append((index1 , temp_index2))\n",
" # delete elemntes from match_out < r \n",
" return np.asarray(match_out)\n",
" \n",
" def find_homography(self):\n",
" \"\"\"\n",
" Step 4: Find a linear transformation (of various complexities) that maps pixels from the second image to \n",
" pixels in the first image\n",
" \"\"\"\n",
" \n",
" def stitch(self):\n",
" \"\"\"\n",
" Step 5: Transform second image into local coordinate system of first image, and (perhaps) perform blending\n",
" to avoid obvious seams between images.\n",
" \"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We will populate these functions over the next several weeks, a process that will involve delving into some of the most elementary operations in digital signal processing. \n",
"\n",
"As a test case, apply your stitcher to at least four overlapping images that you've taken. With a stitcher that works on two images, more images can be added by applying the method recursively."
]
},
{
"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": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.14"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
52 changes: 52 additions & 0 deletions Filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import numpy as np


class Filter:

@staticmethod
def make_average(shape):
"""Creates average filter with given shape"""

# Make sure filter is only 2D and square
assert len(shape) == 2
assert shape[0] == shape[1]

return np.ones(shape) * 1 / np.prod(shape)

@staticmethod
def make_gauss(shape, sigma=1):
"""Creates a Gaußian filter with given shape and standard deviation"""

# Make sure filter is only 2D and square
assert len(shape) == 2
assert shape[0] == shape[1]

range_end = shape[0] // 2

xs = np.arange(-range_end, range_end + 1, 1)
ys = np.arange(-range_end, range_end + 1, 1)

xx, yy = np.meshgrid(xs, ys, indexing='xy')

# Compute gaussian
g_filter = np.exp(-((xx ** 2 + yy ** 2) / (2 * sigma ** 2)))

# Normalize
g_filter /= np.sum(g_filter)

return g_filter

@staticmethod
def make_sobel_u():
"""Creates a Sobel filter for approximating the discrete derivative of the image wrt u (aka x)"""

return np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])

@staticmethod
def make_sobel_v():
"""Creates a Sobel filter for approximating the discrete derivative of the image wrt v (aka y)"""
return np.array([[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]])
Loading