Loading autocnet/matcher/cpu_decompose.py +84 −45 Original line number Diff line number Diff line Loading @@ -6,8 +6,7 @@ from autocnet.matcher.feature_matcher import match from autocnet.transformation.decompose import coupled_decomposition def decompose(self, subset=False, k=2, maxiteration=2, size=18, buf_dist=3, ndv=None, **kwargs): def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into $4^{maxiteration}$ subimages and applys matching between each sub-image. Loading @@ -22,6 +21,10 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, k : int The number of neighbors to find method : {'coupled', 'whole'} whether to utilize coupled decomposition or match the whole image maxiteration : int When using coupled decomposition, the number of recursive divisions to apply. The total number of resultant Loading @@ -46,28 +49,26 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, the (sub)image a point must be in order to be used as a partioning point. The smaller the distance, the more likely percision errors can results in erroneous partitions. ndv : float The no data value that will be masked when computing the radial correlation. """ def func(group): ratio = 0.8 res = [False] * len(group) if len(res) == 1: return [single] if group.iloc[0] < group.iloc[1] * ratio: res[0] = True return res # Grab the original image arrays sdata = self.source.get_array() ddata = self.destination.get_array() ssize = sdata.shape dsize = ddata.shape sdata[sdata == ndv] = np.nan ddata[ddata == ndv] = np.nan matches, _ = self.clean(['ratio']) sidx = matches['source_idx'] didx = matches['destination_idx'] # Grab all the available candidate keypoints skp = self.source.get_keypoints().loc[sidx] dkp = self.destination.get_keypoints().loc[didx] skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) Loading @@ -75,9 +76,12 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, self.smembership[:] = -1 self.dmembership[:] = -1 pcounter = 0 # FLANN Matcher fl= FlannMatcher() for k in range(maxiteration): partitions = np.unique(self.smembership) npartitions = len(partitions) for p in partitions: sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) Loading @@ -94,55 +98,90 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Clip the sub image from the full images (this is a MBR) # Clip the sub image from the full images asub = sdata[minsy:maxsy, minsx:maxsx] bsub = ddata[mindy:maxdy, mindx:maxdx] # Approximate the mid point of the partition as the mean of the matched keypoints sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 decompose = False while True: sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Check the size to ensure a valid return if len(sub_skp) == 0: break # No valid keypoints in this (sub)image if size > len(sub_skp): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') candidate_matches = matches[mask] match_idx = candidate_matches['source_idx'].astype(np.int) # Extract those matches that pass the ratio check sub_skp = skp.iloc[match_idx] # Check that valid points remain if len(sub_skp) == 0: break # Locate the candidate closest to the middle of all of the matches smx, smy = sub_skp[['x', 'y']].mean() mid = np.array([[smx, smy]]) dists = cdist(mid, sub_skp[['x', 'y']]) closest = sub_skp.iloc[np.argmin(dists)] closest_idx = closest.name soriginx, soriginy = closest[['x', 'y']] # Grab the corresponding point in the destination dest_idx = matches[matches['source_idx'] == closest_idx]['destination_idx'] dest_pt = dkp.loc[dest_idx] doriginx, doriginy = dest_pt[['x', 'y']].values[0] # Sub image origin is assumed to be 0,0 (local sub-image space), while match point origins are # in the full image space. Shift the match point orign to be in the sub-image space if needed soriginx -= minsx soriginy -= minsy doriginx -= mindx doriginy -= mindy # Apply coupled decomposition q = candidate_matches.query('source_idx == {}'.format(closest.name)) dest_idx = int(q['destination_idx'].iat[0]) doriginx = dkp.at[dest_idx, 'x'] doriginy = dkp.at[dest_idx, 'y'] if mindy + buf_dist <= doriginy <= maxdy - buf_dist\ and mindx + 3 <= doriginx <= maxdx - 3: # Point is good to split on decompose = True break else: scounter += 1 if scounter >= maxiteration: break # Clear the Flann matcher for reuse fl.clear() # Check that the identified match falls within the (sub)image # This catches most bad matches that have passed the ratio check if not (buf_dist <= doriginx - mindx <= bsub.shape[1] - buf_dist) or not\ (buf_dist <= doriginy - mindy <= bsub.shape[0] - buf_dist): decompose = False if decompose: # Apply coupled decomposition, shifting the origin to the sub-image s_submembership, d_submembership = coupled_decomposition(asub, bsub, sorigin=(soriginx, soriginy), dorigin=(doriginx, doriginy)) sorigin=(soriginx - minsx, soriginy - minsy), dorigin=(doriginx - mindx, doriginy - mindy), **kwargs) # Shift the returned membership counters to a set of unique numbers s_submembership += pcounter d_submembership += pcounter # And assign membership self.smembership[minsy:maxsy, minsx:maxsx] = s_submembership sdy = dy_part - min(dy_part) sdx = dx_part - min(dx_part) self.dmembership[dy_part, dx_part] = d_submembership[sdy, sdx] self.dmembership[mindy:maxdy, mindx:maxdx] = d_submembership pcounter += 4 def decompose_and_match(self, **kwargs): decompose(self, **kwargs) # Now match the decomposed segments to one another for p in np.unique(self.smembership): sy_part, sx_part = np.where(self.smembership == p) Loading autocnet/matcher/cuda_extractor.py +3 −2 Original line number Diff line number Diff line import warnings import cudasift as cs def extract_features(array, nfeatures=None, **kwargs): def extract_features(array, nfeatures=None): if not nfeatures: nfeatures = int(max(array.shape) / 1.75) else: warnings.warn('NFeatures specified with the CudaSift implementation. Please ensure the distribution of keypoints is what you expect.') siftdata = cs.PySiftData(nfeatures) cs.ExtractKeypoints(array, siftdata, **kwargs) cs.ExtractKeypoints(array, siftdata) keypoints, descriptors = siftdata.to_data_frame() keypoints = keypoints[['x', 'y', 'scale', 'sharpness', 'edgeness', 'orientation', 'score', 'ambiguity']] # Set the columns that have unfilled values to zero to avoid confusion Loading autocnet/matcher/cuda_matcher.py +1 −1 Original line number Diff line number Diff line Loading @@ -33,4 +33,4 @@ def match(self, ratio=0.8, **kwargs): # Set the matches and set the 'ratio' (ambiguity) mask self.matches = df self.masks['ratio'] = df['ambiguity'] <= ratio self.masks = ('ratio', df['ambiguity'] <= ratio) autocnet/matcher/feature_matcher.py +0 −12 Original line number Diff line number Diff line import pandas as pd from autocnet.matcher.feature import FlannMatcher def match(self, k=2, **kwargs): Loading Loading @@ -69,19 +68,8 @@ def match(self, k=2, **kwargs): _add_matches(matches) fl.clear() # TODO: This should be converted to a decorator on the class # TODO: This entire method should never have access to the class self.masks = pd.DataFrame() fl = FlannMatcher() mono_matches(self.source, self.destination, **kwargs) # Since this matches bidirectionally if 'aidx' in kwargs.keys(): if not 'bidx' in kwargs.keys(): kwargs['bidx'] = None kwargs['aidx'], kwargs['bidx'] = kwargs['bidx'], kwargs['aidx'] mono_matches(self.destination, self.source, **kwargs) self.matches.sort_values(by=['distance']) Loading
autocnet/matcher/cpu_decompose.py +84 −45 Original line number Diff line number Diff line Loading @@ -6,8 +6,7 @@ from autocnet.matcher.feature_matcher import match from autocnet.transformation.decompose import coupled_decomposition def decompose(self, subset=False, k=2, maxiteration=2, size=18, buf_dist=3, ndv=None, **kwargs): def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs): """ Similar to match, this method first decomposed the image into $4^{maxiteration}$ subimages and applys matching between each sub-image. Loading @@ -22,6 +21,10 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, k : int The number of neighbors to find method : {'coupled', 'whole'} whether to utilize coupled decomposition or match the whole image maxiteration : int When using coupled decomposition, the number of recursive divisions to apply. The total number of resultant Loading @@ -46,28 +49,26 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, the (sub)image a point must be in order to be used as a partioning point. The smaller the distance, the more likely percision errors can results in erroneous partitions. ndv : float The no data value that will be masked when computing the radial correlation. """ def func(group): ratio = 0.8 res = [False] * len(group) if len(res) == 1: return [single] if group.iloc[0] < group.iloc[1] * ratio: res[0] = True return res # Grab the original image arrays sdata = self.source.get_array() ddata = self.destination.get_array() ssize = sdata.shape dsize = ddata.shape sdata[sdata == ndv] = np.nan ddata[ddata == ndv] = np.nan matches, _ = self.clean(['ratio']) sidx = matches['source_idx'] didx = matches['destination_idx'] # Grab all the available candidate keypoints skp = self.source.get_keypoints().loc[sidx] dkp = self.destination.get_keypoints().loc[didx] skp = self.source.get_keypoints() dkp = self.destination.get_keypoints() # Set up the membership arrays self.smembership = np.zeros(sdata.shape, dtype=np.int16) Loading @@ -75,9 +76,12 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, self.smembership[:] = -1 self.dmembership[:] = -1 pcounter = 0 # FLANN Matcher fl= FlannMatcher() for k in range(maxiteration): partitions = np.unique(self.smembership) npartitions = len(partitions) for p in partitions: sy_part, sx_part = np.where(self.smembership == p) dy_part, dx_part = np.where(self.dmembership == p) Loading @@ -94,55 +98,90 @@ def decompose(self, subset=False, k=2, maxiteration=2, size=18, mindx = np.min(dx_part) maxdx = np.max(dx_part) + 1 # Clip the sub image from the full images (this is a MBR) # Clip the sub image from the full images asub = sdata[minsy:maxsy, minsx:maxsx] bsub = ddata[mindy:maxdy, mindx:maxdx] # Approximate the mid point of the partition as the mean of the matched keypoints sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Utilize the FLANN matcher to find a match to approximate a center fl.add(self.destination.descriptors, self.destination['node_id']) fl.train() scounter = 0 decompose = False while True: sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)) # Check the size to ensure a valid return if len(sub_skp) == 0: break # No valid keypoints in this (sub)image if size > len(sub_skp): size = len(sub_skp) candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False) candidates = self.source.descriptors[candidate_idx] matches = fl.query(candidates, self.source['node_id'], k=3, index=candidate_idx) # Apply Lowe's ratio test to try to find a 'good' starting point mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool') candidate_matches = matches[mask] match_idx = candidate_matches['source_idx'].astype(np.int) # Extract those matches that pass the ratio check sub_skp = skp.iloc[match_idx] # Check that valid points remain if len(sub_skp) == 0: break # Locate the candidate closest to the middle of all of the matches smx, smy = sub_skp[['x', 'y']].mean() mid = np.array([[smx, smy]]) dists = cdist(mid, sub_skp[['x', 'y']]) closest = sub_skp.iloc[np.argmin(dists)] closest_idx = closest.name soriginx, soriginy = closest[['x', 'y']] # Grab the corresponding point in the destination dest_idx = matches[matches['source_idx'] == closest_idx]['destination_idx'] dest_pt = dkp.loc[dest_idx] doriginx, doriginy = dest_pt[['x', 'y']].values[0] # Sub image origin is assumed to be 0,0 (local sub-image space), while match point origins are # in the full image space. Shift the match point orign to be in the sub-image space if needed soriginx -= minsx soriginy -= minsy doriginx -= mindx doriginy -= mindy # Apply coupled decomposition q = candidate_matches.query('source_idx == {}'.format(closest.name)) dest_idx = int(q['destination_idx'].iat[0]) doriginx = dkp.at[dest_idx, 'x'] doriginy = dkp.at[dest_idx, 'y'] if mindy + buf_dist <= doriginy <= maxdy - buf_dist\ and mindx + 3 <= doriginx <= maxdx - 3: # Point is good to split on decompose = True break else: scounter += 1 if scounter >= maxiteration: break # Clear the Flann matcher for reuse fl.clear() # Check that the identified match falls within the (sub)image # This catches most bad matches that have passed the ratio check if not (buf_dist <= doriginx - mindx <= bsub.shape[1] - buf_dist) or not\ (buf_dist <= doriginy - mindy <= bsub.shape[0] - buf_dist): decompose = False if decompose: # Apply coupled decomposition, shifting the origin to the sub-image s_submembership, d_submembership = coupled_decomposition(asub, bsub, sorigin=(soriginx, soriginy), dorigin=(doriginx, doriginy)) sorigin=(soriginx - minsx, soriginy - minsy), dorigin=(doriginx - mindx, doriginy - mindy), **kwargs) # Shift the returned membership counters to a set of unique numbers s_submembership += pcounter d_submembership += pcounter # And assign membership self.smembership[minsy:maxsy, minsx:maxsx] = s_submembership sdy = dy_part - min(dy_part) sdx = dx_part - min(dx_part) self.dmembership[dy_part, dx_part] = d_submembership[sdy, sdx] self.dmembership[mindy:maxdy, mindx:maxdx] = d_submembership pcounter += 4 def decompose_and_match(self, **kwargs): decompose(self, **kwargs) # Now match the decomposed segments to one another for p in np.unique(self.smembership): sy_part, sx_part = np.where(self.smembership == p) Loading
autocnet/matcher/cuda_extractor.py +3 −2 Original line number Diff line number Diff line import warnings import cudasift as cs def extract_features(array, nfeatures=None, **kwargs): def extract_features(array, nfeatures=None): if not nfeatures: nfeatures = int(max(array.shape) / 1.75) else: warnings.warn('NFeatures specified with the CudaSift implementation. Please ensure the distribution of keypoints is what you expect.') siftdata = cs.PySiftData(nfeatures) cs.ExtractKeypoints(array, siftdata, **kwargs) cs.ExtractKeypoints(array, siftdata) keypoints, descriptors = siftdata.to_data_frame() keypoints = keypoints[['x', 'y', 'scale', 'sharpness', 'edgeness', 'orientation', 'score', 'ambiguity']] # Set the columns that have unfilled values to zero to avoid confusion Loading
autocnet/matcher/cuda_matcher.py +1 −1 Original line number Diff line number Diff line Loading @@ -33,4 +33,4 @@ def match(self, ratio=0.8, **kwargs): # Set the matches and set the 'ratio' (ambiguity) mask self.matches = df self.masks['ratio'] = df['ambiguity'] <= ratio self.masks = ('ratio', df['ambiguity'] <= ratio)
autocnet/matcher/feature_matcher.py +0 −12 Original line number Diff line number Diff line import pandas as pd from autocnet.matcher.feature import FlannMatcher def match(self, k=2, **kwargs): Loading Loading @@ -69,19 +68,8 @@ def match(self, k=2, **kwargs): _add_matches(matches) fl.clear() # TODO: This should be converted to a decorator on the class # TODO: This entire method should never have access to the class self.masks = pd.DataFrame() fl = FlannMatcher() mono_matches(self.source, self.destination, **kwargs) # Since this matches bidirectionally if 'aidx' in kwargs.keys(): if not 'bidx' in kwargs.keys(): kwargs['bidx'] = None kwargs['aidx'], kwargs['bidx'] = kwargs['bidx'], kwargs['aidx'] mono_matches(self.destination, self.source, **kwargs) self.matches.sort_values(by=['distance'])