Loading autocnet/graph/network.py +83 −13 Original line number Diff line number Diff line Loading @@ -2,11 +2,9 @@ import os import warnings import networkx as nx import numpy as np import pandas as pd import cv2 from pysal.cg.shapes import Polygon import numpy as np from scipy.misc import bytescale from autocnet.control.control import C Loading @@ -16,9 +14,9 @@ from autocnet.matcher.matcher import FlannMatcher from autocnet.matcher import feature_extractor as fe from autocnet.matcher import outlier_detector as od from autocnet.matcher import subpixel as sp from autocnet.matcher.homography import Homography from autocnet.cg.cg import convex_hull_ratio, overlapping_polygon_area from autocnet.vis.graph_view import plot_node, plot_edge from autocnet.vis.graph_view import plot_node, plot_edge, plot_graph class Edge(object): """ Loading Loading @@ -105,7 +103,35 @@ class Edge(object): else: raise AttributeError('No matches have been computed for this edge.') def compute_homography(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): def compute_fundamental_matrix(self, clean_keys=[], **kwargs): if hasattr(self, 'matches'): matches = self.matches else: raise AttributeError('Matches have not been computed for this edge') if clean_keys: mask = np.prod([self._mask_arrays[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) s_keypoints = self.source.keypoints.iloc[matches['source_idx'].values] d_keypoints = self.destination.keypoints.iloc[matches['destination_idx'].values] transformation_matrix, fundam_mask = od.compute_fundamental_matrix(s_keypoints[['x', 'y']].values, d_keypoints[['x', 'y']].values, **kwargs) fundam_mask = fundam_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask if clean_keys: mask[full_mask] = fundam_mask else: mask = fundam_mask self.masks = ('fundamental', mask) self.fundamental_matrix = transformation_matrix def compute_homography(self, method='ransac', clean_keys=[], **kwargs): """ For each edge in the (sub) graph, compute the homography Parameters Loading Loading @@ -139,7 +165,8 @@ class Edge(object): d_keypoints = self.destination.keypoints.iloc[matches['destination_idx'].values] transformation_matrix, ransac_mask = od.compute_homography(s_keypoints[['x', 'y']].values, d_keypoints[['x', 'y']].values) d_keypoints[['x', 'y']].values, **kwargs) ransac_mask = ransac_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask Loading @@ -148,7 +175,20 @@ class Edge(object): else: mask = ransac_mask self.masks = ('ransac', mask) self.homography = transformation_matrix self.homography = Homography(transformation_matrix, s_keypoints[ransac_mask][['x', 'y']], d_keypoints[ransac_mask][['x', 'y']]) @property def homography_determinant(self): """ If the determinant of the homography is close to zero, this is indicative of a validation issue, i.e., the homography might be bad. """ if not hasattr(self, 'homography'): raise AttributeError('No homography has been computed for this edge.') return np.linalg.det(self.homography) def compute_subpixel_offset(self, clean_keys=[], threshold=0.8, upsampling=16, template_size=19, search_size=53): Loading Loading @@ -653,23 +693,33 @@ class CandidateGraph(nx.Graph): for s, d, edge in self.edges_iter(data=True): edge.ratio_check(ratio=ratio) def compute_homographies(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): def compute_homographies(self, clean_keys=[], **kwargs): """ Compute homographies for all edges using identical parameters Parameters ---------- outlier_algorithm : object Function to apply for outlier detection clean_keys : list Of keys in the mask dict """ for s, d, edge in self.edges_iter(data=True): edge.compute_homography(clean_keys=clean_keys, **kwargs) def compute_fundamental_matrices(self, clean_keys=[], **kwargs): """ Compute fundamental matrices for all edges using identical parameters Parameters ---------- clean_keys : list Of keys in the mask dict """ for s, d, edge in self.edges_iter(data=True): edge.compute_homography(outlier_algorithm=outlier_algorithm, clean_keys=clean_keys) edge.compute_fundamental_matrix(clean_keys=clean_keys, **kwargs) def compute_subpixel_offsets(self, clean_keys=[], threshold=0.8, upsampling=10, template_size=9, search_size=27): Loading Loading @@ -848,3 +898,23 @@ class CandidateGraph(nx.Graph): A list of connected sub-graphs of nodes, with the largest sub-graph first. Each subgraph is a set. """ return sorted(nx.connected_components(self), key=len, reverse=True) # TODO: The Edge object requires a get method in order to be plottable, probably Node as well. # This is a function of being a dict in NetworkX ''' def plot(self, ax=None, **kwargs): """ Plot the graph object Parameters ---------- ax : object A MatPlotLib axes object. Returns ------- : object A MatPlotLib axes object """ return plot_graph(self, ax=ax, **kwargs) ''' No newline at end of file autocnet/graph/tests/test_network.py +0 −2 Original line number Diff line number Diff line Loading @@ -74,5 +74,3 @@ class TestNode(unittest.TestCase): # Convex hull computation is checked lower in the hull computation node = self.graph.node[0] self.assertRaises(AttributeError, node.coverage_ratio) autocnet/matcher/homography.py 0 → 100644 +62 −0 Original line number Diff line number Diff line import numpy as np from autocnet.utils.utils import make_homogeneous from autocnet.utils import evaluation_measures class Homography(np.ndarray): """ A homography or planar transformation matrix Attributes ---------- determinant : float The determinant of the matrix condition : float The condition computed as SVD[0] / SVD[-1] rmse : float The root mean square error computed using a set of given input points """ def __new__(cls, inputarr, x1, x2): obj = np.asarray(inputarr).view(cls) if not isinstance(inputarr, np.ndarray): raise TypeError('The homography must be an ndarray') if not inputarr.shape[0] == 3 and not inputarr.shape[1] == 3: raise ValueError('The homography must be a 3x3 matrix.') obj.x1 = make_homogeneous(x1) obj.x2 = make_homogeneous(x2) return obj @property def determinant(self): if not hasattr(self, '_determinant'): self._determinant = np.linalg.det(self) return self._determinant @property def condition(self): if not hasattr(self, '_condition'): s = np.linalg.svd(self, compute_uv=False) self._condition = s[0] / s[1] return self._condition @property def rmse(self): if not hasattr(self, '_rmse'): # TODO: Vectorize this for performance t_kps = np.empty((self.x1.shape[0], 3)) for i, j in enumerate(self.x1): proj_point = self.dot(j) proj_point /= proj_point[-1] # normalize t_kps[i] = proj_point self._rmse = evaluation_measures.rmse(self.x2, t_kps) return self._rmse autocnet/matcher/outlier_detector.py +76 −19 Original line number Diff line number Diff line Loading @@ -79,19 +79,6 @@ def distance_ratio(matches, ratio=0.8): mask[counter: counter + group_size] = False counter += group_size ''' # won't work if there's only 1 match for each queryIdx if len(group) < 2: mask.append(True) else: if group['distance'].iloc[0] < ratio * group['distance'].iloc[1]: # this means distance _0_ is good and can drop all other distances mask.append(True) for i in range(len(group['distance']-1)): mask.append(False) else: for i in range(len(group['distance'])): mask.append(False) ''' return mask Loading Loading @@ -122,9 +109,9 @@ def mirroring_test(matches): return duplicates def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold=5.0): def compute_fundamental_matrix(kp1, kp2, method='ransac', reproj_threshold=5.0, confidence=0.99): """ Given two arrays of keypoints compute a homography Given two arrays of keypoints compute the fundamental matrix Parameters ---------- Loading @@ -134,12 +121,15 @@ def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold= kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : object outlier_algorithm : {'ransac', 'lmeds', 'normal'} The openCV algorithm to use for outlier detection reproj_threshold : float The RANSAC reprojection threshold The maximum distances in pixels a reprojected points can be from the epipolar line to be considered an inlier confidence : float [0, 1] that the estimated matrix is correct Returns ------- Loading @@ -148,12 +138,79 @@ def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold= mask : ndarray Boolean array of the outliers Notes ----- While the method is user definable, if the number of input points is < 7, normal outlier detection is automatically used, if 7 > n > 15, least medians is used, and if 7 > 15, ransac can be used. """ if method == 'ransac': method_ = cv2.FM_RANSAC elif method == 'lmeds': method_ = cv2.FM_LMEDS elif method == 'normal': method_ = cv2.FM_7POINT else: raise ValueError("Unknown outlier detection method. Choices are: 'ransac', 'lmeds', or 'normal'.") transformation_matrix, mask = cv2.findFundamentalMat(kp1, kp2, method_, reproj_threshold, confidence) mask = mask.astype(bool) return transformation_matrix, mask def compute_homography(kp1, kp2, method='ransac', **kwargs): """ Given two arrays of keypoints compute the homography Parameters ---------- kp1 : ndarray (n, 2) of coordinates from the source image kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : {'ransac', 'lmeds', 'normal'} The openCV algorithm to use for outlier detection reproj_threshold : float The maximum distances in pixels a reprojected points can be from the epipolar line to be considered an inlier Returns ------- transformation_matrix : ndarray The 3x3 perspective transformation matrix mask : ndarray Boolean array of the outliers Notes ----- While the method is user definable, if the number of input points is < 7, normal outlier detection is automatically used, if 7 > n > 15, least medians is used, and if 7 > 15, ransac can be used. """ if method == 'ransac': method_ = cv2.RANSAC elif method == 'lmeds': method_ = cv2.LMEDS elif method == 'normal': method_ = 0 # Normal method else: raise ValueError("Unknown outlier detection method. Choices are: 'ransac', 'lmeds', or 'normal'.") transformation_matrix, mask = cv2.findHomography(kp1, kp2, outlier_algorithm, reproj_threshold) method_, **kwargs) mask = mask.astype(bool) return transformation_matrix, mask Loading autocnet/matcher/tests/test_homography.py 0 → 100644 +36 −0 Original line number Diff line number Diff line import os import numpy as np import unittest import sys sys.path.insert(0, os.path.abspath('..')) import numpy.testing from .. import homography class TestHomography(unittest.TestCase): def setUp(self): pass def test_Homography(self): nbr_inliers = 20 fp = np.array(np.random.standard_normal((nbr_inliers,2))) #inliers # homography to transform fp static_H = np.array([[4,0.5,10],[0.25,1,5],[0.2,0.1,1]]) #Make homogeneous fph = np.hstack((fp,np.ones((nbr_inliers, 1)))) tp = static_H.dot(fph.T) # normalize hom. coordinates tp /= tp[-1,:np.newaxis] H = homography.Homography(static_H, fp, tp.T[:,:2]) self.assertAlmostEqual(H.determinant, 0.6249999, 5) self.assertAlmostEqual(H.condition, 7.19064438, 5) numpy.testing.assert_array_almost_equal(H.rmse, np.array([0, 0, 0.0])) Loading
autocnet/graph/network.py +83 −13 Original line number Diff line number Diff line Loading @@ -2,11 +2,9 @@ import os import warnings import networkx as nx import numpy as np import pandas as pd import cv2 from pysal.cg.shapes import Polygon import numpy as np from scipy.misc import bytescale from autocnet.control.control import C Loading @@ -16,9 +14,9 @@ from autocnet.matcher.matcher import FlannMatcher from autocnet.matcher import feature_extractor as fe from autocnet.matcher import outlier_detector as od from autocnet.matcher import subpixel as sp from autocnet.matcher.homography import Homography from autocnet.cg.cg import convex_hull_ratio, overlapping_polygon_area from autocnet.vis.graph_view import plot_node, plot_edge from autocnet.vis.graph_view import plot_node, plot_edge, plot_graph class Edge(object): """ Loading Loading @@ -105,7 +103,35 @@ class Edge(object): else: raise AttributeError('No matches have been computed for this edge.') def compute_homography(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): def compute_fundamental_matrix(self, clean_keys=[], **kwargs): if hasattr(self, 'matches'): matches = self.matches else: raise AttributeError('Matches have not been computed for this edge') if clean_keys: mask = np.prod([self._mask_arrays[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) s_keypoints = self.source.keypoints.iloc[matches['source_idx'].values] d_keypoints = self.destination.keypoints.iloc[matches['destination_idx'].values] transformation_matrix, fundam_mask = od.compute_fundamental_matrix(s_keypoints[['x', 'y']].values, d_keypoints[['x', 'y']].values, **kwargs) fundam_mask = fundam_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask if clean_keys: mask[full_mask] = fundam_mask else: mask = fundam_mask self.masks = ('fundamental', mask) self.fundamental_matrix = transformation_matrix def compute_homography(self, method='ransac', clean_keys=[], **kwargs): """ For each edge in the (sub) graph, compute the homography Parameters Loading Loading @@ -139,7 +165,8 @@ class Edge(object): d_keypoints = self.destination.keypoints.iloc[matches['destination_idx'].values] transformation_matrix, ransac_mask = od.compute_homography(s_keypoints[['x', 'y']].values, d_keypoints[['x', 'y']].values) d_keypoints[['x', 'y']].values, **kwargs) ransac_mask = ransac_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask Loading @@ -148,7 +175,20 @@ class Edge(object): else: mask = ransac_mask self.masks = ('ransac', mask) self.homography = transformation_matrix self.homography = Homography(transformation_matrix, s_keypoints[ransac_mask][['x', 'y']], d_keypoints[ransac_mask][['x', 'y']]) @property def homography_determinant(self): """ If the determinant of the homography is close to zero, this is indicative of a validation issue, i.e., the homography might be bad. """ if not hasattr(self, 'homography'): raise AttributeError('No homography has been computed for this edge.') return np.linalg.det(self.homography) def compute_subpixel_offset(self, clean_keys=[], threshold=0.8, upsampling=16, template_size=19, search_size=53): Loading Loading @@ -653,23 +693,33 @@ class CandidateGraph(nx.Graph): for s, d, edge in self.edges_iter(data=True): edge.ratio_check(ratio=ratio) def compute_homographies(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): def compute_homographies(self, clean_keys=[], **kwargs): """ Compute homographies for all edges using identical parameters Parameters ---------- outlier_algorithm : object Function to apply for outlier detection clean_keys : list Of keys in the mask dict """ for s, d, edge in self.edges_iter(data=True): edge.compute_homography(clean_keys=clean_keys, **kwargs) def compute_fundamental_matrices(self, clean_keys=[], **kwargs): """ Compute fundamental matrices for all edges using identical parameters Parameters ---------- clean_keys : list Of keys in the mask dict """ for s, d, edge in self.edges_iter(data=True): edge.compute_homography(outlier_algorithm=outlier_algorithm, clean_keys=clean_keys) edge.compute_fundamental_matrix(clean_keys=clean_keys, **kwargs) def compute_subpixel_offsets(self, clean_keys=[], threshold=0.8, upsampling=10, template_size=9, search_size=27): Loading Loading @@ -848,3 +898,23 @@ class CandidateGraph(nx.Graph): A list of connected sub-graphs of nodes, with the largest sub-graph first. Each subgraph is a set. """ return sorted(nx.connected_components(self), key=len, reverse=True) # TODO: The Edge object requires a get method in order to be plottable, probably Node as well. # This is a function of being a dict in NetworkX ''' def plot(self, ax=None, **kwargs): """ Plot the graph object Parameters ---------- ax : object A MatPlotLib axes object. Returns ------- : object A MatPlotLib axes object """ return plot_graph(self, ax=ax, **kwargs) ''' No newline at end of file
autocnet/graph/tests/test_network.py +0 −2 Original line number Diff line number Diff line Loading @@ -74,5 +74,3 @@ class TestNode(unittest.TestCase): # Convex hull computation is checked lower in the hull computation node = self.graph.node[0] self.assertRaises(AttributeError, node.coverage_ratio)
autocnet/matcher/homography.py 0 → 100644 +62 −0 Original line number Diff line number Diff line import numpy as np from autocnet.utils.utils import make_homogeneous from autocnet.utils import evaluation_measures class Homography(np.ndarray): """ A homography or planar transformation matrix Attributes ---------- determinant : float The determinant of the matrix condition : float The condition computed as SVD[0] / SVD[-1] rmse : float The root mean square error computed using a set of given input points """ def __new__(cls, inputarr, x1, x2): obj = np.asarray(inputarr).view(cls) if not isinstance(inputarr, np.ndarray): raise TypeError('The homography must be an ndarray') if not inputarr.shape[0] == 3 and not inputarr.shape[1] == 3: raise ValueError('The homography must be a 3x3 matrix.') obj.x1 = make_homogeneous(x1) obj.x2 = make_homogeneous(x2) return obj @property def determinant(self): if not hasattr(self, '_determinant'): self._determinant = np.linalg.det(self) return self._determinant @property def condition(self): if not hasattr(self, '_condition'): s = np.linalg.svd(self, compute_uv=False) self._condition = s[0] / s[1] return self._condition @property def rmse(self): if not hasattr(self, '_rmse'): # TODO: Vectorize this for performance t_kps = np.empty((self.x1.shape[0], 3)) for i, j in enumerate(self.x1): proj_point = self.dot(j) proj_point /= proj_point[-1] # normalize t_kps[i] = proj_point self._rmse = evaluation_measures.rmse(self.x2, t_kps) return self._rmse
autocnet/matcher/outlier_detector.py +76 −19 Original line number Diff line number Diff line Loading @@ -79,19 +79,6 @@ def distance_ratio(matches, ratio=0.8): mask[counter: counter + group_size] = False counter += group_size ''' # won't work if there's only 1 match for each queryIdx if len(group) < 2: mask.append(True) else: if group['distance'].iloc[0] < ratio * group['distance'].iloc[1]: # this means distance _0_ is good and can drop all other distances mask.append(True) for i in range(len(group['distance']-1)): mask.append(False) else: for i in range(len(group['distance'])): mask.append(False) ''' return mask Loading Loading @@ -122,9 +109,9 @@ def mirroring_test(matches): return duplicates def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold=5.0): def compute_fundamental_matrix(kp1, kp2, method='ransac', reproj_threshold=5.0, confidence=0.99): """ Given two arrays of keypoints compute a homography Given two arrays of keypoints compute the fundamental matrix Parameters ---------- Loading @@ -134,12 +121,15 @@ def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold= kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : object outlier_algorithm : {'ransac', 'lmeds', 'normal'} The openCV algorithm to use for outlier detection reproj_threshold : float The RANSAC reprojection threshold The maximum distances in pixels a reprojected points can be from the epipolar line to be considered an inlier confidence : float [0, 1] that the estimated matrix is correct Returns ------- Loading @@ -148,12 +138,79 @@ def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold= mask : ndarray Boolean array of the outliers Notes ----- While the method is user definable, if the number of input points is < 7, normal outlier detection is automatically used, if 7 > n > 15, least medians is used, and if 7 > 15, ransac can be used. """ if method == 'ransac': method_ = cv2.FM_RANSAC elif method == 'lmeds': method_ = cv2.FM_LMEDS elif method == 'normal': method_ = cv2.FM_7POINT else: raise ValueError("Unknown outlier detection method. Choices are: 'ransac', 'lmeds', or 'normal'.") transformation_matrix, mask = cv2.findFundamentalMat(kp1, kp2, method_, reproj_threshold, confidence) mask = mask.astype(bool) return transformation_matrix, mask def compute_homography(kp1, kp2, method='ransac', **kwargs): """ Given two arrays of keypoints compute the homography Parameters ---------- kp1 : ndarray (n, 2) of coordinates from the source image kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : {'ransac', 'lmeds', 'normal'} The openCV algorithm to use for outlier detection reproj_threshold : float The maximum distances in pixels a reprojected points can be from the epipolar line to be considered an inlier Returns ------- transformation_matrix : ndarray The 3x3 perspective transformation matrix mask : ndarray Boolean array of the outliers Notes ----- While the method is user definable, if the number of input points is < 7, normal outlier detection is automatically used, if 7 > n > 15, least medians is used, and if 7 > 15, ransac can be used. """ if method == 'ransac': method_ = cv2.RANSAC elif method == 'lmeds': method_ = cv2.LMEDS elif method == 'normal': method_ = 0 # Normal method else: raise ValueError("Unknown outlier detection method. Choices are: 'ransac', 'lmeds', or 'normal'.") transformation_matrix, mask = cv2.findHomography(kp1, kp2, outlier_algorithm, reproj_threshold) method_, **kwargs) mask = mask.astype(bool) return transformation_matrix, mask Loading
autocnet/matcher/tests/test_homography.py 0 → 100644 +36 −0 Original line number Diff line number Diff line import os import numpy as np import unittest import sys sys.path.insert(0, os.path.abspath('..')) import numpy.testing from .. import homography class TestHomography(unittest.TestCase): def setUp(self): pass def test_Homography(self): nbr_inliers = 20 fp = np.array(np.random.standard_normal((nbr_inliers,2))) #inliers # homography to transform fp static_H = np.array([[4,0.5,10],[0.25,1,5],[0.2,0.1,1]]) #Make homogeneous fph = np.hstack((fp,np.ones((nbr_inliers, 1)))) tp = static_H.dot(fph.T) # normalize hom. coordinates tp /= tp[-1,:np.newaxis] H = homography.Homography(static_H, fp, tp.T[:,:2]) self.assertAlmostEqual(H.determinant, 0.6249999, 5) self.assertAlmostEqual(H.condition, 7.19064438, 5) numpy.testing.assert_array_almost_equal(H.rmse, np.array([0, 0, 0.0]))