Commit 727c0fd1 authored by kberry's avatar kberry
Browse files

Merge with upstream and more documentation tweaks

parents c330a18d de291e94
Loading
Loading
Loading
Loading
+58 −25
Original line number Diff line number Diff line
@@ -5,7 +5,8 @@ import pandas as pd
import cv2
import numpy as np

from scipy.misc import bytescale # store image array
from scipy.misc import bytescale, imresize


from autocnet.control.control import C
from autocnet.fileio import io_json
@@ -151,41 +152,59 @@ class CandidateGraph(nx.Graph):
        #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter
        self.node_counter += 1

    def get_geodataset(self, nodeIndex):
    def get_geodataset(self, nodeindex):
        """
        Constructs a GeoDataset object from the given node image and assigns the 
        dataset and its NumPy array to the 'handle' and 'image' node attributes.

        Parameters
        ----------
        nodeIndex : int
        nodeindex : int
                    The index of the node.

        """
        self.node[nodeIndex]['handle'] = GeoDataset(self.node[nodeIndex]['image_path'])
        self.node[nodeIndex]['image'] = bytescale(self.node[nodeIndex]['handle'].read_array())
        self.node[nodeindex]['handle'] = GeoDataset(self.node[nodeindex]['image_path'])

    def extract_features(self, nfeatures) :
    def get_array(self, nodeindex, downsampling=1):
        """
        Downsample the input image file by some amount using bicubic interpolation
        in order to reduce data sizes for visualization and analysis, e.g. feature detection

        Parameters
        ----------
        nodeindex : hashable
                    The index into the node containing a geodataset object

        downsampling : int
                       [1, infinity] downsampling
        """

        array = self.node[nodeindex]['handle'].read_array()
        newx_size = int(array.shape[0] / downsampling)
        newy_size = int(array.shape[1] / downsampling)

        resized_array = imresize(array, (newx_size, newy_size), interp='bicubic')
        self.node[nodeindex]['image'] = bytescale(resized_array)
        self.node[nodeindex]['image_downsampling'] = downsampling

    def extract_features(self, extractor_parameters={}, downsampling=1):
        """
        Extracts features from each image in the graph and uses the result to assign the
        node attributes for 'handle', 'image', 'keypoints', and 'descriptors'.

        Parameters
        ----------
        nfeatures : int
                    The number of features to be extracted.
        extractor_parameters : dict
                               A dictionary containing OpenCV SIFT parameters names and values.

        downsampling : int
                       The divisor to image_size to down sample the input image.
        """
        # Loop through the nodes (i.e. images) on the graph and fill in their attributes.
        # These attributes are...
        #      geo dataset (handle and image)
        #      features (keypoints and descriptors)
        for node, attributes in self.nodes_iter(data=True):
        
            self.get_geodataset(node)
            extraction_params = {'nfeatures' : nfeatures}
            self.get_array(node, downsampling=downsampling)
            attributes['keypoints'], attributes['descriptors'] = fe.extract_features(attributes['image'],
                                                                                     extraction_params)
                                                                                     extractor_parameters)

    def add_matches(self, matches):
        """
@@ -279,7 +298,8 @@ class CandidateGraph(nx.Graph):
            attributes['homography'] = transformation_matrix
            attributes['ransac'] = mask

    def compute_subpixel_offsets(self, clean_keys=[], threshold=0.8, upsampling=10):
    def compute_subpixel_offsets(self, clean_keys=[], threshold=0.8, upsampling=10,
                                 template_size=9, search_size=27):
        """
        For the entire graph, compute the subpixel offsets using pattern-matching and add the result
        as an attribute to each edge of the graph.
@@ -294,6 +314,16 @@ class CandidateGraph(nx.Graph):
                    On the range [-1, 1].  Values less than or equal to
                    this threshold are masked and can be considered
                    outliers

        upsampling : int
                     The multiplier to the template and search shapes to upsample
                     for subpixel accuracy

        template_size : int
                        The size of the template in pixels, must be odd

        search_size : int
                      The size of the search
        """

        for source, destination, attributes in self.edges_iter(data=True):
@@ -317,11 +347,18 @@ class CandidateGraph(nx.Graph):
            for i, (idx, row) in enumerate(matches.iterrows()):
                s_idx = int(row['source_idx'])
                d_idx = int(row['destination_idx'])
                src_keypoint = self.node[source]['keypoints'][s_idx]
                dest_keypoint = self.node[destination]['keypoints'][d_idx]

                # Compute the subpixel offset
                edge_offsets[i] = sp.subpixel_offset(src_keypoint, dest_keypoint, src_image, dest_image, upsampling=upsampling)
                s_node = self.node[source]
                d_node = self.node[destination]

                s_keypoint = s_node['keypoints'][s_idx].pt
                d_keypoint = d_node['keypoints'][d_idx].pt

                # Get the template and search windows
                s_template = sp.clip_roi(src_image, s_keypoint, template_size)
                d_search = sp.clip_roi(dest_image, d_keypoint, search_size)

                edge_offsets[i] = sp.subpixel_offset(s_template, d_search, upsampling=upsampling)

            # Compute the mask for correlations less than the threshold
            threshold_mask = edge_offsets[edge_offsets[:,-1] >= threshold]
@@ -394,7 +431,6 @@ class CandidateGraph(nx.Graph):

            if 'subpixel' in clean_keys:
                offsets = attributes['subpixel_offsets'][attributes['subpixel']]
                print(offsets)
            kp1 = self.node[source]['keypoints']
            kp2 = self.node[destination]['keypoints']

@@ -415,11 +451,8 @@ class CandidateGraph(nx.Graph):
                kp2y = kp2[m2[1]].pt[1]

                if 'subpixel' in clean_keys:
                    print(idx)
                    print(kp2x, kp2y)
                    kp2x += offsets['x_offset'].values[i]
                    kp2y += offsets['y_offset'].values[i]
                    print(kp2x, kp2y)
                values.append([kp2x,
                               kp2y,
                               m2,
+1 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ class TestCandidateGraph(unittest.TestCase):

    def test_extract_features(self):
        # also tests get_geodataset() and get_keypoints
        self.graph.extract_features(10)
        self.graph.extract_features(extractor_parameters={'nfeatures':10})
        node_number = self.graph.node_name_map['AS15-M-0297_SML.png']
        node = self.graph.node[node_number]
        self.assertEquals(len(node['image']), 1012)
+5 −7
Original line number Diff line number Diff line
import cv2
from scipy import misc


def extract_features(image_array, extractor_parameters):
def extract_features(array, extractor_parameters):
    """
    This method finds and extracts features from an image using the given dictionary of keyword arguments. 
    The input image is represented as NumPy array and the output features are represented as keypoint IDs 
@@ -10,8 +9,9 @@ def extract_features(image_array, extractor_parameters):

    Parameters
    ----------
    image_array : ndarray
    array : ndarray
            a NumPy array that represents an image

    extractor_parameters : dict
                           A dictionary containing OpenCV SIFT parameters names and values. 

@@ -22,6 +22,4 @@ def extract_features(image_array, extractor_parameters):
    """

    sift = cv2.xfeatures2d.SIFT_create(**extractor_parameters)
    converted_array = misc.bytescale(image_array)

    return sift.detectAndCompute(converted_array, None)
    return sift.detectAndCompute(array, None)
+45 −33
Original line number Diff line number Diff line
import pandas as pd
from autocnet.matcher import matcher
import numpy as np

from scipy.misc import imresize
from autocnet.matcher import matcher

# TODO: look into KeyPoint.size and perhaps use to determine an appropriately-sized search/template.
# TODO: do not allow even sizes

def subpixel_offset(template_kp, search_kp, template_img, search_img, template_size=9, search_size=27, upsampling=10):
def clip_roi(img, center, img_size):
    """
    Given an input image, clip a square region of interest
    centered on some pixel at some size.

    Parameters
    ----------
    img : ndarray or file handle
          The input image to be clipped

    center : tuple
             (y,x) coordinates to center the roi

    img_size : int
               Odd, total image size

    Returns
    -------
    clipped_img : ndarray
                  The clipped image
    """
    if img_size % 2 == 0:
            raise ValueError('Image size must be odd.')

    i = (img_size - 1) / 2

    y, x = map(int, center)

    if isinstance(img, np.ndarray):
        clipped_img = img[y - i:y + i,
                          x - i:x + i]

    return clipped_img


def subpixel_offset(template, search, upsampling=10):
    """
    Uses a pattern-matcher on subsets of two images determined from the passed-in keypoints and optional sizes to
    compute an x and y offset from the search keypoint to the template keypoint and an associated strength.

    Parameters
    ----------
    template_kp : KeyPoint
                  The KeyPoint to match the search_kp to.
    search_kp : KeyPoint
                The KeyPoint to match to the template_kp
    template_img : numpy array
    template : numpy array
                   The entire image that the template chip to match to will be taken out of.
    search_img : numpy array
    search : numpy array
                 The entire image that the search chip to match to the template chip will be taken out of.
    template_size : int
                    The length of one side of the square subset of the template image that will actually be used for
                    the subpixel registration. Default is 9.
                    Must be odd.
    search_size : int
                  The length of one side of the square subset of the search image that will be used for subpixel
                  registration. Default is 13. Must be odd.
    upsampling: int
                The amount to upsample the image. 
    Returns
    -------
    : tuple
      The returned tuple is of form: (x_offset, y_offset, strength). The offsets are from the search to the template
      keypoint.
    """
    # Get the x,y coordinates
    temp_x, temp_y = map(int, template_kp.pt)
    search_x, search_y = map(int, search_kp.pt)

    # Convert desired template and search sizes to offsets to get the bounding box
    t = int(template_size/2) #index offset for template
    s = int(search_size/2) #index offset for search

    template = template_img[temp_y-t:temp_y+t, temp_x-t:temp_x+t]
    search = search_img[search_y-s:search_y+s, search_x-s:search_x+s]

    results = (None, None, None)

    try:
        results = matcher.pattern_match(template, search, upsampling=upsampling)
        return results
    except ValueError:
        # the match fails if the template or search point is near an edge of the image
        # TODO: come up with a better solution?
        print('Template Keypoint ({},{}) cannot be pattern matched'.format(str(temp_x), str(temp_y)))

    return results
        print('Can not subpixel match point.')
        return
+8 −8
Original line number Diff line number Diff line
@@ -14,10 +14,10 @@ from autocnet.fileio import io_gdal
class TestFeatureExtractor(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        self.dataset = io_gdal.GeoDataset(get_path('AS15-M-0295_SML.png'))
        self.data_array = self.dataset.read_array()
        self.parameters = {"nfeatures" : 10,
    def setUpClass(cls):
        cls.dataset = io_gdal.GeoDataset(get_path('AS15-M-0295_SML.png'))
        cls.data_array = cls.dataset.read_array(dtype='uint8')
        cls.parameters = {"nfeatures": 10,
                          "nOctaveLayers": 3,
                          "contrastThreshold": 0.02,
                          "edgeThreshold": 10,
Loading