Loading autocnet/matcher/naive_template.py +85 −1 Original line number Diff line number Diff line from math import floor import cv2 import numpy as np from scipy.ndimage.interpolation import zoom def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, func=cv2.TM_CCORR_NORMED): """ Call an arbitrary pattern matcher using a subpixel approach where a center of gravity using the correlation coefficients are used for subpixel alignment. Parameters ---------- template : ndarray The input search template used to 'query' the destination image image : ndarray The image or sub-image to be searched subpixel_size : int An odd integer that defines the window size used to compute the moments max_scaler : float The percentage offset to apply to the delta between the maximum correlation and the maximum edge correlation. func : object The function to be used to perform the template based matching Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED} In testing the first two options perform significantly better with Apollo data. Returns ------- x : float The x offset y : float The y offset max_corr : float The strength of the correlation in the range [-1, 1]. """ result = cv2.matchTemplate(image, template, method=func) if func == cv2.TM_SQDIFF or func == cv2.TM_SQDIFF_NORMED: y, x = np.unravel_index(np.argmin(result, axis=None), result.shape) else: y, x = np.unravel_index(np.argmax(result, axis=None), result.shape) max_corr = result[(y,x)] upper = int(2 + floor(subpixel_size / 2)) lower = upper - 1 # x, y are the location of the upper left hand corner of the template in the image area = result[y-lower:y+upper, x-lower:x+upper] if area.shape != (subpixel_size+2, subpixel_size+2): print("Max correlation is too close to the boundary.") return None, None, 0 # Find the max on the edges, scale just like autoreg (but why?) edge_max = np.max(np.vstack([area[0], area[-1], area[:,0], area[:,-1]])) internal = area[1:-1, 1:-1] mask = (internal > edge_max + max_scaler * (edge_max-max_corr)).flatten() empty = np.column_stack([np.repeat(np.arange(0,subpixel_size),subpixel_size), np.tile(np.arange(0,subpixel_size),subpixel_size), np.zeros(subpixel_size*subpixel_size)]) empty[:,-1] = internal.ravel() to_weight = empty[mask, :] # Average is the shift from y, x form average = np.average(to_weight[:,:2], axis=0, weights=to_weight[:,2]) # The center of the 3x3 window is 1.5,1.5, so the shift needs to be recentered to 0,0 y += (subpixel_size/2 - average[0]) x += (subpixel_size/2 - average[1]) # Compute the idealized shift (image center) y -= (image.shape[0] / 2) - (template.shape[0] / 2) x -= (image.shape[1] / 2) - (template.shape[1] / 2) return x, y, max_corr def pattern_match(template, image, upsampling=16, func=cv2.TM_CCORR_NORMED, error_check=False): """ Call an arbitrary pattern matcher Call an arbitrary pattern matcher using a subpixel approach where the template and image are upsampled using a third order polynomial. Parameters ---------- Loading autocnet/matcher/subpixel.py +8 −3 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from redis import StrictRedis from plurmy import Slurm from autocnet import Session, config from autocnet.matcher.naive_template import pattern_match from autocnet.matcher.naive_template import pattern_match, pattern_match_autoreg from autocnet.matcher import ciratefi from autocnet.io.db.model import Measures, Points, Images, JsonEncoder from autocnet.graph.node import NetworkNode Loading Loading @@ -165,7 +165,12 @@ def subpixel_phase(template, search, **kwargs): (y_shift, x_shift), error, diffphase = register_translation(search, template, **kwargs) return x_shift, y_shift, (error, diffphase) def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), template_size=(51,51), **kwargs): def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), template_size=(51,51), func=pattern_match, **kwargs): """ 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. Loading Loading @@ -224,7 +229,7 @@ def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), templ if (s_image is None) or (d_template is None): return None, None, None shift_x, shift_y, metrics = pattern_match(d_template, s_image, **kwargs) shift_x, shift_y, metrics = func(d_template, s_image, **kwargs) dx = (dx - shift_x + dxr) dy = (dy - shift_y + dyr) Loading autocnet/matcher/tests/test_naive_template.py +53 −24 Original line number Diff line number Diff line import pytest import unittest from .. import naive_template from numpy import array from numpy import uint8 import numpy as np class TestNaiveTemplateAutoReg(unittest.TestCase): def setUp(self): self._test_image = np.array(((0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 1, 1, 0, 0, 0), (0, 0, 0, 0, 0, 1, 0, 0, 0), (0, 0, 0, 1, 1, 1, 0, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0)), dtype=np.uint8) self._shape = np.array(((1, 1, 1), (1, 0, 1), (1, 1, 1)), dtype=np.uint8) def test_subpixel_shift(self): result_x, result_y, result_strength = naive_template.pattern_match_autoreg(self._shape, self._test_image) self.assertEqual(result_x, 0.5) self.assertEqual(result_y, -1.5) self.assertGreaterEqual(result_strength, 0.8) class TestNaiveTemplate(unittest.TestCase): def setUp(self): # Center is (5, 6) self._test_image = array(((0, 0, 0, 0, 0, 0, 0, 1, 0), self._test_image = np.array(((0, 0, 0, 0, 0, 0, 0, 1, 0), (0, 0, 0, 0, 0, 0, 0, 1, 0), (1, 1, 1, 0, 0, 0, 0, 1, 0), (0, 1, 0, 0, 0, 0, 0, 0, 0), Loading @@ -20,29 +49,29 @@ class TestNaiveTemplate(unittest.TestCase): (0, 1, 1, 1, 0, 0, 1, 0, 1), (0, 1, 0, 1, 0, 0, 1, 0, 1), (0, 1, 1, 1, 0, 0, 1, 0, 1), (0, 0, 0, 0, 0, 0, 1, 1, 1)), dtype=uint8) (0, 0, 0, 0, 0, 0, 1, 1, 1)), dtype=np.uint8) # Should yield (-3, 3) offset from image center self._t_shape = array(((1, 1, 1), self._t_shape = np.array(((1, 1, 1), (0, 1, 0), (0, 1, 0)), dtype=uint8) (0, 1, 0)), dtype=np.uint8) # Should be (3, -4) self._rect_shape = array(((1, 1, 1), self._rect_shape = np.array(((1, 1, 1), (1, 0, 1), (1, 0, 1), (1, 0, 1), (1, 1, 1)), dtype=uint8) (1, 1, 1)), dtype=np.uint8) # Should be (-2, -4) self._square_shape = array(((1, 1, 1), self._square_shape = np.array(((1, 1, 1), (1, 0, 1), (1, 1, 1)), dtype=uint8) (1, 1, 1)), dtype=np.uint8) # Should be (3, 5) self._vertical_line = array(((0, 1, 0), self._vertical_line = np.array(((0, 1, 0), (0, 1, 0), (0, 1, 0)), dtype=uint8) (0, 1, 0)), dtype=np.uint8) def test_t_shape(self): result_x, result_y, result_strength = naive_template.pattern_match(self._t_shape, Loading Loading
autocnet/matcher/naive_template.py +85 −1 Original line number Diff line number Diff line from math import floor import cv2 import numpy as np from scipy.ndimage.interpolation import zoom def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, func=cv2.TM_CCORR_NORMED): """ Call an arbitrary pattern matcher using a subpixel approach where a center of gravity using the correlation coefficients are used for subpixel alignment. Parameters ---------- template : ndarray The input search template used to 'query' the destination image image : ndarray The image or sub-image to be searched subpixel_size : int An odd integer that defines the window size used to compute the moments max_scaler : float The percentage offset to apply to the delta between the maximum correlation and the maximum edge correlation. func : object The function to be used to perform the template based matching Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED} In testing the first two options perform significantly better with Apollo data. Returns ------- x : float The x offset y : float The y offset max_corr : float The strength of the correlation in the range [-1, 1]. """ result = cv2.matchTemplate(image, template, method=func) if func == cv2.TM_SQDIFF or func == cv2.TM_SQDIFF_NORMED: y, x = np.unravel_index(np.argmin(result, axis=None), result.shape) else: y, x = np.unravel_index(np.argmax(result, axis=None), result.shape) max_corr = result[(y,x)] upper = int(2 + floor(subpixel_size / 2)) lower = upper - 1 # x, y are the location of the upper left hand corner of the template in the image area = result[y-lower:y+upper, x-lower:x+upper] if area.shape != (subpixel_size+2, subpixel_size+2): print("Max correlation is too close to the boundary.") return None, None, 0 # Find the max on the edges, scale just like autoreg (but why?) edge_max = np.max(np.vstack([area[0], area[-1], area[:,0], area[:,-1]])) internal = area[1:-1, 1:-1] mask = (internal > edge_max + max_scaler * (edge_max-max_corr)).flatten() empty = np.column_stack([np.repeat(np.arange(0,subpixel_size),subpixel_size), np.tile(np.arange(0,subpixel_size),subpixel_size), np.zeros(subpixel_size*subpixel_size)]) empty[:,-1] = internal.ravel() to_weight = empty[mask, :] # Average is the shift from y, x form average = np.average(to_weight[:,:2], axis=0, weights=to_weight[:,2]) # The center of the 3x3 window is 1.5,1.5, so the shift needs to be recentered to 0,0 y += (subpixel_size/2 - average[0]) x += (subpixel_size/2 - average[1]) # Compute the idealized shift (image center) y -= (image.shape[0] / 2) - (template.shape[0] / 2) x -= (image.shape[1] / 2) - (template.shape[1] / 2) return x, y, max_corr def pattern_match(template, image, upsampling=16, func=cv2.TM_CCORR_NORMED, error_check=False): """ Call an arbitrary pattern matcher Call an arbitrary pattern matcher using a subpixel approach where the template and image are upsampled using a third order polynomial. Parameters ---------- Loading
autocnet/matcher/subpixel.py +8 −3 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from redis import StrictRedis from plurmy import Slurm from autocnet import Session, config from autocnet.matcher.naive_template import pattern_match from autocnet.matcher.naive_template import pattern_match, pattern_match_autoreg from autocnet.matcher import ciratefi from autocnet.io.db.model import Measures, Points, Images, JsonEncoder from autocnet.graph.node import NetworkNode Loading Loading @@ -165,7 +165,12 @@ def subpixel_phase(template, search, **kwargs): (y_shift, x_shift), error, diffphase = register_translation(search, template, **kwargs) return x_shift, y_shift, (error, diffphase) def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), template_size=(51,51), **kwargs): def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), template_size=(51,51), func=pattern_match, **kwargs): """ 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. Loading Loading @@ -224,7 +229,7 @@ def subpixel_template(sx, sy, dx, dy, s_img, d_img, image_size=(251, 251), templ if (s_image is None) or (d_template is None): return None, None, None shift_x, shift_y, metrics = pattern_match(d_template, s_image, **kwargs) shift_x, shift_y, metrics = func(d_template, s_image, **kwargs) dx = (dx - shift_x + dxr) dy = (dy - shift_y + dyr) Loading
autocnet/matcher/tests/test_naive_template.py +53 −24 Original line number Diff line number Diff line import pytest import unittest from .. import naive_template from numpy import array from numpy import uint8 import numpy as np class TestNaiveTemplateAutoReg(unittest.TestCase): def setUp(self): self._test_image = np.array(((0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 1, 1, 0, 0, 0), (0, 0, 0, 0, 0, 1, 0, 0, 0), (0, 0, 0, 1, 1, 1, 0, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0, 0), (0, 0, 0, 1, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0)), dtype=np.uint8) self._shape = np.array(((1, 1, 1), (1, 0, 1), (1, 1, 1)), dtype=np.uint8) def test_subpixel_shift(self): result_x, result_y, result_strength = naive_template.pattern_match_autoreg(self._shape, self._test_image) self.assertEqual(result_x, 0.5) self.assertEqual(result_y, -1.5) self.assertGreaterEqual(result_strength, 0.8) class TestNaiveTemplate(unittest.TestCase): def setUp(self): # Center is (5, 6) self._test_image = array(((0, 0, 0, 0, 0, 0, 0, 1, 0), self._test_image = np.array(((0, 0, 0, 0, 0, 0, 0, 1, 0), (0, 0, 0, 0, 0, 0, 0, 1, 0), (1, 1, 1, 0, 0, 0, 0, 1, 0), (0, 1, 0, 0, 0, 0, 0, 0, 0), Loading @@ -20,29 +49,29 @@ class TestNaiveTemplate(unittest.TestCase): (0, 1, 1, 1, 0, 0, 1, 0, 1), (0, 1, 0, 1, 0, 0, 1, 0, 1), (0, 1, 1, 1, 0, 0, 1, 0, 1), (0, 0, 0, 0, 0, 0, 1, 1, 1)), dtype=uint8) (0, 0, 0, 0, 0, 0, 1, 1, 1)), dtype=np.uint8) # Should yield (-3, 3) offset from image center self._t_shape = array(((1, 1, 1), self._t_shape = np.array(((1, 1, 1), (0, 1, 0), (0, 1, 0)), dtype=uint8) (0, 1, 0)), dtype=np.uint8) # Should be (3, -4) self._rect_shape = array(((1, 1, 1), self._rect_shape = np.array(((1, 1, 1), (1, 0, 1), (1, 0, 1), (1, 0, 1), (1, 1, 1)), dtype=uint8) (1, 1, 1)), dtype=np.uint8) # Should be (-2, -4) self._square_shape = array(((1, 1, 1), self._square_shape = np.array(((1, 1, 1), (1, 0, 1), (1, 1, 1)), dtype=uint8) (1, 1, 1)), dtype=np.uint8) # Should be (3, 5) self._vertical_line = array(((0, 1, 0), self._vertical_line = np.array(((0, 1, 0), (0, 1, 0), (0, 1, 0)), dtype=uint8) (0, 1, 0)), dtype=np.uint8) def test_t_shape(self): result_x, result_y, result_strength = naive_template.pattern_match(self._t_shape, Loading