Subpixel Matching - Part II

[1]:
import math
import os
import sys

sys.path.insert(0, os.path.abspath('/data/autocnet'))

import autocnet
# Enable the GPU
autocnet.cuda(enable=True)

from autocnet import CandidateGraph

# The GPU based extraction library that contains SIFT extraction and matching
import cudasift as cs
# A method to resize the images on the fly.
from scipy.misc import imresize

%pylab inline
figsize(16,4)
Populating the interactive namespace from numpy and matplotlib

Create the CandidateGraph

Just like the other notebooks this cell creates the candidate graph. This also patches in functionality to the object on the fly. To get a handle on what is going on, checkout the Advanced 1. Extending the CandidateGraph notebook.

[2]:
# Create the candidate graph and enable a GPU
a = 'AS15-P-0111_CENTER_LRG_CROPPED.png'
b = 'AS15-P-0112_CENTER_LRG_CROPPED.png'

adj = {a:[b],
       b:[a]}

cg = CandidateGraph.from_adjacency(adj)

# Define a function to do the feature extraction.
def extract_features(self, arr, downsample_amount=None, **kwargs):
    total_size = arr.shape[0] * arr.shape[1]
    if not downsample_amount:
        downsample_amount = math.ceil(total_size / 12500**2)
    shape = (int(arr.shape[0] / downsample_amount), int(arr.shape[1] / downsample_amount))
    # Downsample
    arr = imresize(arr, shape, interp='lanczos')

    npts = max(arr.shape) / 3.5
    sd = cs.PySiftData(npts)
    cs.ExtractKeypoints(arr, sd, **kwargs)
    kp, des = sd.to_data_frame()
    kp = kp[['x', 'y', 'scale', 'sharpness', 'edgeness', 'orientation', 'score', 'ambiguity']]
    kp['score'] = 0.0
    kp['ambiguity'] = 0.0

    # Match the interface defined in the edge.
    self.keypoints = kp
    self.descriptors = des

    self['downsample_amount'] = downsample_amount

# Import the class and update it.  This updates the current instance of the CandidateGraph
from autocnet.graph.node import Node
Node.extract_features = extract_features

# Extract the features
cg.extract_features(thresh=1)

Match and Find Outliers

[3]:
# Match
cg.match()

# Apply outlier detection - see the above linked notebook if you are curious about what is going on here.
for s,d,e in cg.edges_iter(data=True):
    e.masks['ratio'] = e.matches.ambiguity <= e.matches.ambiguity.quantile(0.015)
    e.masks['score'] = e.matches.score >= e.matches.score.quantile(0.85)

# Compute the F Matrix
cg.compute_fundamental_matrices(clean_keys=['ratio', 'score'])

Subpixel Refinement

The last notebook looked at the results of subpixel refinement to explore the result quality. In this notebook, subpixel refinement is simply applied using the built-in syntax sugar.

Caveat: AutoCNet does not (yet?) natively support up and down sampling so the user has to manually rescale the correspondences. The dev. team is thinking about if, and how best to implement multi-resolution tracking without adding lots of complexity.

Subpixel Parameters :

Since the Apollo Pan data was originally processed at a reduced resolution, the max_x_shift and max_y_shift parameters are allowed to be larger than the normal 1-pixel constraint. Aslo, clean_keys are passed to subpixel register only the best matches currently found and tiled is set to true indicating that the entire image is not to be read into memory.

[4]:
import cv2
# Scale the keypoints coordinates back to full resolution.
for i, n in cg.nodes_iter(data=True):
    n.keypoints.x *= n['downsample_amount']
    n.keypoints.y *= n['downsample_amount']

cg.subpixel_register(clean_keys=['fundamental'], tiled=True, func=cv2.TM_CCORR_NORMED, max_x_shift=7, max_y_shift=7)

# Scale the keypoint coordinates back to the reduced resolution
for i, n in cg.nodes_iter(data=True):
    n.keypoints.x /= n['downsample_amount']
    n.keypoints.y /= n['downsample_amount']

Subpixel Results

The subpixel matcher updates the masks with three new entries for (1) shift: has the correspondenced moved too far, (2) threshold: is the correlation good enough, and subpixel: the combination of (1) and (2).

[6]:
cg.edge[0][1].masks.head(3)
[6]:
ratio score fundamental shift threshold subpixel
0 False True False True False False
1 False True False True False False
2 False False False True False False
[10]:
# Use the `clean` method to get back the subpixel registered correspondences.
matches, mask = cg.edge[0][1].clean(['subpixel'])
matches
[10]:
source_image source_idx destination_image destination_idx score ambiguity y_offset x_offset reference correlation
17 0.0 17 1.0 296 0.985350 0.861263 0.1250 6.3750 0.0 0.999713
21 0.0 21 1.0 291 0.979893 0.816333 -1.2500 4.8750 0.0 0.999727
283 0.0 283 1.0 1173 0.978444 0.890681 3.0000 5.1875 0.0 0.999637
291 0.0 291 1.0 1230 0.958613 0.936126 3.0000 -2.0000 0.0 0.999747
309 0.0 309 1.0 1175 0.977205 0.930651 -3.1875 2.3125 0.0 0.999675
338 0.0 338 1.0 1295 0.971325 0.911120 -3.5625 6.7500 0.0 0.999822
345 0.0 345 1.0 1290 0.971112 0.907802 -6.6875 -2.3125 0.0 0.999785
353 0.0 353 1.0 1261 0.984677 0.789020 -6.0625 5.4375 0.0 0.999745
356 0.0 356 1.0 1262 0.979318 0.899721 4.8125 -4.7500 0.0 0.999745
366 0.0 366 1.0 1272 0.985263 0.876244 -5.4375 -1.1250 0.0 0.999751
[14]:
# Use the `clean` method to get back the subpixel registered correspondences.
matches, mask = cg.edge[0][1].clean(['threshold'])
matches
[14]:
source_image source_idx destination_image destination_idx score ambiguity y_offset x_offset reference correlation
17 0.0 17 1.0 296 0.985350 0.861263 0.1250 6.3750 0.0 0.999713
21 0.0 21 1.0 291 0.979893 0.816333 -1.2500 4.8750 0.0 0.999727
283 0.0 283 1.0 1173 0.978444 0.890681 3.0000 5.1875 0.0 0.999637
291 0.0 291 1.0 1230 0.958613 0.936126 3.0000 -2.0000 0.0 0.999747
293 0.0 293 1.0 1235 0.985849 0.871732 -9.0000 -2.0000 0.0 0.999773
296 0.0 296 1.0 1239 0.967968 0.927719 -7.6250 -4.5000 0.0 0.999759
298 0.0 298 1.0 1238 0.964188 0.898417 -10.6875 13.1250 0.0 0.999724
309 0.0 309 1.0 1175 0.977205 0.930651 -3.1875 2.3125 0.0 0.999675
338 0.0 338 1.0 1295 0.971325 0.911120 -3.5625 6.7500 0.0 0.999822
345 0.0 345 1.0 1290 0.971112 0.907802 -6.6875 -2.3125 0.0 0.999785
353 0.0 353 1.0 1261 0.984677 0.789020 -6.0625 5.4375 0.0 0.999745
355 0.0 355 1.0 1267 0.965001 0.915773 -14.0000 15.3750 0.0 0.999711
356 0.0 356 1.0 1262 0.979318 0.899721 4.8125 -4.7500 0.0 0.999745
366 0.0 366 1.0 1272 0.985263 0.876244 -5.4375 -1.1250 0.0 0.999751
386 0.0 386 1.0 1248 0.984362 0.885029 15.8750 17.0000 0.0 0.999772
[13]:
cg.edge[0][1].plot(clean_keys=['threshold'], downsampling=True)
[13]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f4674d24a58>
../../../_images/users_tutorials_apollopan_6b._Subpixel_Matching_-_Part_II_12_1.png