Unverified Commit 4c916693 authored by jlaura's avatar jlaura Committed by GitHub
Browse files

🚧 Algorithm in, working on tests (#421)

* Algorithm in, working on tests

* Updates tests

* Adds doc string
parent 4234e7cb
Loading
Loading
Loading
Loading
+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
    ----------
+8 −3
Original line number Diff line number Diff line
@@ -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
@@ -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.
@@ -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)
+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),
@@ -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,