Loading autocnet/graph/network.py +58 −25 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): """ Loading Loading @@ -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. Loading @@ -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): Loading @@ -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] Loading Loading @@ -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'] Loading @@ -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, Loading autocnet/graph/tests/test_network.py +1 −1 Original line number Diff line number Diff line Loading @@ -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) Loading autocnet/matcher/feature_extractor.py +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 Loading @@ -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. Loading @@ -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) autocnet/matcher/subpixel.py +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 autocnet/matcher/tests/test_feature_extractor.py +8 −8 Original line number Diff line number Diff line Loading @@ -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 Loading
autocnet/graph/network.py +58 −25 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): """ Loading Loading @@ -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. Loading @@ -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): Loading @@ -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] Loading Loading @@ -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'] Loading @@ -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, Loading
autocnet/graph/tests/test_network.py +1 −1 Original line number Diff line number Diff line Loading @@ -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) Loading
autocnet/matcher/feature_extractor.py +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 Loading @@ -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. Loading @@ -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)
autocnet/matcher/subpixel.py +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
autocnet/matcher/tests/test_feature_extractor.py +8 −8 Original line number Diff line number Diff line Loading @@ -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