Loading autocnet/graph/network.py +40 −43 Original line number Diff line number Diff line import operator from functools import reduce import operator as op import os import networkx as nx Loading @@ -12,6 +13,7 @@ from autocnet.control.control import C from autocnet.fileio import io_json from autocnet.fileio.io_gdal import GeoDataset from autocnet.matcher import feature_extractor as fe # extract features from image from autocnet.matcher import outlier_detector as od class CandidateGraph(nx.Graph): """ Loading Loading @@ -185,7 +187,7 @@ class CandidateGraph(nx.Graph): destination_key = dest_group['destination_image'].values[0] try: edge = self[source_key][destination_key] except: except: # pragma: no cover edge = self[destination_key][source_key] if 'matches' in edge.keys(): Loading @@ -194,18 +196,17 @@ class CandidateGraph(nx.Graph): else: edge['matches'] = dest_group def compute_homography(self, source_key, destination_key, outlier_algorithm=cv2.RANSAC): def compute_homographies(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): """ For each edge in the (sub) graph, compute the homography Parameters ---------- source_key : str The identifier for the source node destination_key : str The identifier for the destination node outlier_algorithm : object An openCV outlier detections algorithm, e.g. cv2.RANSAC clean_keys : list of string keys to masking arrays (created by calling outlier detection) Returns ------- transformation_matrix : ndarray Loading @@ -213,43 +214,39 @@ class CandidateGraph(nx.Graph): mask : ndarray Boolean array of the outliers """ A tuple of the form (transformation matrix, bad entry mask) The returned tuple is empty if there is no edge between the source and destination nodes or if it exists, but has not been populated with a matches dataframe. for source, destination, attributes in self.edges_iter(data=True): matches = attributes['matches'] """ if self.has_edge(source_key, destination_key): try: edge = self[source_key][destination_key] except: edge = self[destination_key][source_key] if 'matches' in edge.keys(): source_keypoints = [] destination_keypoints = [] for i, row in edge['matches'].iterrows(): source_idx = row['source_idx'] src_keypoint = [self.node[source_key]['keypoints'][int(source_idx)].pt[0], self.node[source_key]['keypoints'][int(source_idx)].pt[1]] destination_idx = row['destination_idx'] dest_keypoint = [self.node[destination_key]['keypoints'][int(destination_idx)].pt[0], self.node[destination_key]['keypoints'][int(destination_idx)].pt[1]] source_keypoints.append(src_keypoint) destination_keypoints.append(dest_keypoint) transformation_matrix, mask = cv2.findHomography(np.array(source_keypoints), np.array(destination_keypoints), outlier_algorithm, 5.0) mask = mask.astype(bool) return transformation_matrix, mask else: return ('', '') else: return ('','') if clean_keys: mask = np.prod([attributes[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) s_coords = np.empty((len(matches), 2)) d_coords = np.empty((len(matches), 2)) for i, (idx, row) in enumerate(matches.iterrows()): s_idx = int(row['source_idx']) d_idx = int(row['destination_idx']) s_coords[i] = self.node[source]['keypoints'][s_idx].pt d_coords[i] = self.node[destination]['keypoints'][d_idx].pt transformation_matrix, ransac_mask = od.compute_homography(s_coords, d_coords) ransac_mask = ransac_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask if clean_keys: mask[full_mask] = ransac_mask else: mask = ransac_mask attributes['homography'] = transformation_matrix attributes['ransac'] = mask def to_cnet(self, clean_keys=[]): """ Loading @@ -270,7 +267,7 @@ class CandidateGraph(nx.Graph): # Merge all of the masks if clean_keys: mask = np.array(list(map(operator.mul, *[attributes[i] for i in clean_keys]))) mask = np.prod([attributes[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] kp1 = self.node[source]['keypoints'] Loading autocnet/matcher/outlier_detector.py +36 −0 Original line number Diff line number Diff line import cv2 import numpy as np Loading Loading @@ -112,3 +113,38 @@ def mirroring_test(matches): duplicates.astype(bool, copy=False) return duplicates def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold=5.0): """ Given two arrays of keypoints compute a homography Parameters ---------- kp1 : ndarray (n, 2) of coordinates from the source image kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : object The openCV algorithm to use for outlier detection reproj_threshold : float The RANSAC reprojection threshold Returns ------- transformation_matrix : ndarray The 3x3 transformation matrix mask : ndarray Boolean array of the outliers """ transformation_matrix, mask = cv2.findHomography(kp1, kp2, outlier_algorithm, reproj_threshold) mask = mask.astype(bool) return transformation_matrix, mask No newline at end of file functional_tests/test_two_image.py +9 −7 Original line number Diff line number Diff line Loading @@ -46,9 +46,9 @@ class TestTwoImageMatching(unittest.TestCase): self.assertEqual(1, cg.number_of_edges()) # Step: Extract image data and attribute nodes cg.extract_features(50) cg.extract_features(500) for node, attributes in cg.nodes_iter(data=True): self.assertIn(len(attributes['keypoints']), [49, 50, 51]) self.assertIn(len(attributes['keypoints']), range(490, 511)) # Step: Then apply a FLANN matcher fl = FlannMatcher() Loading @@ -65,19 +65,21 @@ class TestTwoImageMatching(unittest.TestCase): matches = attributes['matches'] # Perform the symmetry check symmetry_mask = od.mirroring_test(matches) self.assertIn(symmetry_mask.sum(), range(45, 50)) self.assertIn(symmetry_mask.sum(), range(430, 461)) attributes['symmetry'] = symmetry_mask # Perform the ratio test ratio_mask = od.distance_ratio(matches, ratio=0.95) self.assertIn(ratio_mask.sum(), range(30,45)) self.assertIn(ratio_mask.sum(), range(400, 451)) attributes['ratio'] = ratio_mask mask = np.array(ratio_mask * symmetry_mask) self.assertIn(len(matches.loc[mask]), range(4,10)) # Step: And create a C object cnet = cg.to_cnet(clean_keys=['symmetry', 'ratio']) self.assertIn(len(matches.loc[mask]), range(75,101)) cg.compute_homographies(clean_keys=['symmetry', 'ratio']) # Step: And create a C object cnet = cg.to_cnet(clean_keys=['symmetry', 'ratio', 'ransac']) # Step update the serial numbers nid_to_serial = {} for node, attributes in cg.nodes_iter(data=True): Loading Loading
autocnet/graph/network.py +40 −43 Original line number Diff line number Diff line import operator from functools import reduce import operator as op import os import networkx as nx Loading @@ -12,6 +13,7 @@ from autocnet.control.control import C from autocnet.fileio import io_json from autocnet.fileio.io_gdal import GeoDataset from autocnet.matcher import feature_extractor as fe # extract features from image from autocnet.matcher import outlier_detector as od class CandidateGraph(nx.Graph): """ Loading Loading @@ -185,7 +187,7 @@ class CandidateGraph(nx.Graph): destination_key = dest_group['destination_image'].values[0] try: edge = self[source_key][destination_key] except: except: # pragma: no cover edge = self[destination_key][source_key] if 'matches' in edge.keys(): Loading @@ -194,18 +196,17 @@ class CandidateGraph(nx.Graph): else: edge['matches'] = dest_group def compute_homography(self, source_key, destination_key, outlier_algorithm=cv2.RANSAC): def compute_homographies(self, outlier_algorithm=cv2.RANSAC, clean_keys=[]): """ For each edge in the (sub) graph, compute the homography Parameters ---------- source_key : str The identifier for the source node destination_key : str The identifier for the destination node outlier_algorithm : object An openCV outlier detections algorithm, e.g. cv2.RANSAC clean_keys : list of string keys to masking arrays (created by calling outlier detection) Returns ------- transformation_matrix : ndarray Loading @@ -213,43 +214,39 @@ class CandidateGraph(nx.Graph): mask : ndarray Boolean array of the outliers """ A tuple of the form (transformation matrix, bad entry mask) The returned tuple is empty if there is no edge between the source and destination nodes or if it exists, but has not been populated with a matches dataframe. for source, destination, attributes in self.edges_iter(data=True): matches = attributes['matches'] """ if self.has_edge(source_key, destination_key): try: edge = self[source_key][destination_key] except: edge = self[destination_key][source_key] if 'matches' in edge.keys(): source_keypoints = [] destination_keypoints = [] for i, row in edge['matches'].iterrows(): source_idx = row['source_idx'] src_keypoint = [self.node[source_key]['keypoints'][int(source_idx)].pt[0], self.node[source_key]['keypoints'][int(source_idx)].pt[1]] destination_idx = row['destination_idx'] dest_keypoint = [self.node[destination_key]['keypoints'][int(destination_idx)].pt[0], self.node[destination_key]['keypoints'][int(destination_idx)].pt[1]] source_keypoints.append(src_keypoint) destination_keypoints.append(dest_keypoint) transformation_matrix, mask = cv2.findHomography(np.array(source_keypoints), np.array(destination_keypoints), outlier_algorithm, 5.0) mask = mask.astype(bool) return transformation_matrix, mask else: return ('', '') else: return ('','') if clean_keys: mask = np.prod([attributes[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] full_mask = np.where(mask == True) s_coords = np.empty((len(matches), 2)) d_coords = np.empty((len(matches), 2)) for i, (idx, row) in enumerate(matches.iterrows()): s_idx = int(row['source_idx']) d_idx = int(row['destination_idx']) s_coords[i] = self.node[source]['keypoints'][s_idx].pt d_coords[i] = self.node[destination]['keypoints'][d_idx].pt transformation_matrix, ransac_mask = od.compute_homography(s_coords, d_coords) ransac_mask = ransac_mask.ravel() # Convert the truncated RANSAC mask back into a full length mask if clean_keys: mask[full_mask] = ransac_mask else: mask = ransac_mask attributes['homography'] = transformation_matrix attributes['ransac'] = mask def to_cnet(self, clean_keys=[]): """ Loading @@ -270,7 +267,7 @@ class CandidateGraph(nx.Graph): # Merge all of the masks if clean_keys: mask = np.array(list(map(operator.mul, *[attributes[i] for i in clean_keys]))) mask = np.prod([attributes[i] for i in clean_keys], axis=0, dtype=np.bool) matches = matches[mask] kp1 = self.node[source]['keypoints'] Loading
autocnet/matcher/outlier_detector.py +36 −0 Original line number Diff line number Diff line import cv2 import numpy as np Loading Loading @@ -112,3 +113,38 @@ def mirroring_test(matches): duplicates.astype(bool, copy=False) return duplicates def compute_homography(kp1, kp2, outlier_algorithm=cv2.RANSAC, reproj_threshold=5.0): """ Given two arrays of keypoints compute a homography Parameters ---------- kp1 : ndarray (n, 2) of coordinates from the source image kp2 : ndarray (n, 2) of coordinates from the destination image outlier_algorithm : object The openCV algorithm to use for outlier detection reproj_threshold : float The RANSAC reprojection threshold Returns ------- transformation_matrix : ndarray The 3x3 transformation matrix mask : ndarray Boolean array of the outliers """ transformation_matrix, mask = cv2.findHomography(kp1, kp2, outlier_algorithm, reproj_threshold) mask = mask.astype(bool) return transformation_matrix, mask No newline at end of file
functional_tests/test_two_image.py +9 −7 Original line number Diff line number Diff line Loading @@ -46,9 +46,9 @@ class TestTwoImageMatching(unittest.TestCase): self.assertEqual(1, cg.number_of_edges()) # Step: Extract image data and attribute nodes cg.extract_features(50) cg.extract_features(500) for node, attributes in cg.nodes_iter(data=True): self.assertIn(len(attributes['keypoints']), [49, 50, 51]) self.assertIn(len(attributes['keypoints']), range(490, 511)) # Step: Then apply a FLANN matcher fl = FlannMatcher() Loading @@ -65,19 +65,21 @@ class TestTwoImageMatching(unittest.TestCase): matches = attributes['matches'] # Perform the symmetry check symmetry_mask = od.mirroring_test(matches) self.assertIn(symmetry_mask.sum(), range(45, 50)) self.assertIn(symmetry_mask.sum(), range(430, 461)) attributes['symmetry'] = symmetry_mask # Perform the ratio test ratio_mask = od.distance_ratio(matches, ratio=0.95) self.assertIn(ratio_mask.sum(), range(30,45)) self.assertIn(ratio_mask.sum(), range(400, 451)) attributes['ratio'] = ratio_mask mask = np.array(ratio_mask * symmetry_mask) self.assertIn(len(matches.loc[mask]), range(4,10)) # Step: And create a C object cnet = cg.to_cnet(clean_keys=['symmetry', 'ratio']) self.assertIn(len(matches.loc[mask]), range(75,101)) cg.compute_homographies(clean_keys=['symmetry', 'ratio']) # Step: And create a C object cnet = cg.to_cnet(clean_keys=['symmetry', 'ratio', 'ransac']) # Step update the serial numbers nid_to_serial = {} for node, attributes in cg.nodes_iter(data=True): Loading