def scale_and_rotate(self, subject, s, theta, inverse=False): '''Rotate over theta and scale by s''' rotation_matrix = np.array( [[s * math.cos(theta), -1 * s * math.sin(theta)], [s * math.sin(theta), s * math.cos(theta)]]) if inverse: return Shape(np.dot(rotation_matrix.T, subject.matrix)) else: return Shape(np.dot(rotation_matrix, subject.matrix))
def normalize(self, shape): ''' Perform isomorphic scaling in order to normalize shapes See Amy Ross on GPA p.5 in: target Shape out: scaled Shape object ''' return Shape([shape.vector / np.linalg.norm(shape.vector)])
def __init__(self, eigenvalues, eigenvectors, mean): self.dimension = eigenvalues.size self.eigenvalues = eigenvalues self.eigenvectors = eigenvectors self.mean = Shape(mean) # create a set of scaled eigenvectors self.scaled_eigenvectors = np.dot(self.eigenvectors, np.diag(self.eigenvalues))
def deform(self, shape_param): ''' Reconstruct a shape based on principal components and a set of parameters that define a deformable model (see Cootes p. 6 eq. 2) in: Tx1 vector deformable model b out: 1xC deformed image ''' return Shape(self.mean.vector + self.scaled_eigenvectors.dot(shape_param))
def examine(self, model_points, t=0, pyramid_level=0): ''' Examines points normal to model points and compare its grey-levels with the grey-level model. in: matrix of pixels image array of model points (x1, x2, ..., xN, y1, ..., yN) int t amount of pixels examined either side of the normal (t > k) out: Shape with adjustments (dx, dy) to better approximate target ''' if not isinstance(model_points, Shape): model_points = Shape(model_points) new_points = model_points.matrix # keep track of large movements movement = np.zeros((model_points.length, 1)) # get greylevel model for requested pyramid level glmodel = self.glmodel_pyramid[pyramid_level] # determine reduction based on pyramid level reduction = 2**pyramid_level i = 0 for m in range(model_points.length): i += 1 # set model index (for mean/cov) glmodel.set_evaluation_index(m - 1) # choose model points according to pyramid level prev, curr, nex = m - 2, m - 1, m reduced_points = model_points / reduction # get current, previous and next points = np.array([ reduced_points.get(prev), reduced_points.get(curr), reduced_points.get(nex) ]) # get point that best matches gray levels new_points[:, curr], movement[curr] = self.get_best_match(glmodel, points, t=t) print 'Number of points examined:', str(i) return Shape(new_points) * reduction, movement
def fit(self, prev_shape, new_shape, pyramid_level=0, n=None): ''' Algorithm that finds the best shape parameters that match identified image points. In: PointDistributionModel instance pdm, array of new image points (x1, x2, ..., xN, y1, y2,..., yN) Out: the pose params (Tx, Ty, s, theta) and shape parameter (c) to fit the model to the image ''' if not isinstance(new_shape, Shape): new_shape = Shape(new_shape) if not isinstance(prev_shape, Shape): prev_shape = Shape(prev_shape) if not self.start_pose: raise ValueError('No inital pose parameters found.') # find pose parameters to align with new image points Tx, Ty, s, theta = self.start_pose dx, dy, ds, dTheta = self.aligner.get_pose_parameters( prev_shape, new_shape) changed_pose = (Tx + dx, Ty + dy, s * (1 + ds), theta + dTheta) # align image with model y = self.aligner.invert_transform(new_shape, changed_pose) # SVD on scaled eigenvectors of the model u, w, v = np.linalg.svd(self.pdmodel.scaled_eigenvectors, full_matrices=False) W = np.zeros_like(w) # define weight vector n if n is None: last_eigenvalue = self.pdmodel.eigenvalues[-1] n = last_eigenvalue**2 if last_eigenvalue**2 >= 0 else 0 # calculate the shape vector W = np.diag(w / ((w**2) + n)) c = (v.T).dot(W).dot(u.T).dot(y.vector) return changed_pose, c
def _parse(self, path): ''' Parse the data from path directory and return arrays of x and y coordinates Data should be in the form (x1, y1) in: String pathdirectory with list of landmarks (x1, y1, ..., xN, yN) out: 1xc array (x1, ..., xN, y1, ..., yN) ''' data = np.loadtxt(path) x = np.absolute(data[::2, ]) y = data[1::2, ] return Shape(np.hstack((x, y)))
def render_shape_to_image(img, shape, color=(255, 0, 0), title='Image'): ''' Draw shape over image ''' if not isinstance(shape, Shape): shape = Shape(shape) for i in range(shape.length - 1): cv2.line(img, (int(shape.x[i]), int(shape.y[i])), (int(shape.x[i + 1]), int(shape.y[i + 1])), color, 5) render(img)
def align(self, shape): ''' Align shape with the mean shape and return the aligned shape. All arrays in form (x1, ..., xC, ..., y1, ..., yC) In: 1xC array shape Out: 1xC aligned shape ''' # perform aligning translated = self.translate_to_origin(Shape(shape)) scaled = self.normalize(translated) aligned = self.rotate_to_target(scaled, self.mean_shape) return aligned.vector
def translate_to_origin(self, shape): ''' Move all shapes to a common center, most likely the origin (0,0) In: array x array y Out = array, array ''' # compute centroid centr_x, centr_y = shape.centroid() # translate w.r.t. centroid return Shape([shape.x - centr_x, shape.y - centr_y])
def rotate_to_target(self, subject, target): ''' Rotate shape such that it aligns with the target shape in: Shapes out: rotated Rx2 matrix of subject ''' # perform singular value decomposition (svd) to get U, S, V' u, s, v = np.linalg.svd(target.matrix.dot(np.transpose( subject.matrix))) # multiply VU' with subject to get the rotated matrix vu = np.transpose(v).dot(np.transpose(u)) return Shape(vu.dot(subject.matrix))
def slice_images(images, landmarks_per_image): ''' Slice the landmarks from the images ''' imagecount, shapecount, landmarkcount = landmarks_per_image.shape for i in range(imagecount): image = images[i] for s in range(shapecount): landmark = Shape(landmarks_per_image[i, s, :]) # slice the landmarks from the images landmark_image = image[min(landmark.y) - 5:max(landmark.y) + 5, min(landmark.x) - 5:max(landmark.x) + 5] cv2.imwrite( '../Project Data/_Data/Slicings2/' + str(i) + '-' + str(s) + '.png', landmark_image)
def multiresolution_search(self, image, region, t=0, max_level=0, max_iter=5, n=None): ''' Perform Multi-resolution Search ASM algorithm. in: np array of training image np array region; array of coordinates that gives a rough estimation of the target in form (x1, ..., xN, y1, ..., yN) int t; amount of pixels to be examined on each side of the normal of each point during an iteration (t>k) int max_levels; max amount of levels to be searched int max_iter; amount to stop iterations at each level int n; fitting parameter out: Shape region; approximation of the target ''' if not isinstance(region, Shape): region = Shape(region) # create Gaussian pyramid of input image image_pyramid = gaussian_pyramid(image, levels=len(self.glmodel_pyramid)) # allow examiner to render the largest image (for testing) self.examiner.bigImage = image_pyramid[0] level = max_level max_level = True while level >= 0: # get image at level resolution image = image_pyramid[level] # search in the image region = self.search(image, region, t=t, level=level, max_level=max_level, max_iter=max_iter, n=n) # descend the pyramid level -= 1 max_level = False return region
def scan_region(self, landmarks, diff=0, searchStep=30): ''' Scan landmark centroids to find a good search region for the feature detector. in: np array of landmarks Shapes int diff; narrows down the search space int seachStep; a larger search step fastens search, but also increases the risk of missing matches. ''' centroids = np.zeros((landmarks.shape[0], 2)) for l in range(landmarks.shape[0]): centroids[l] = Shape(landmarks[l]).centroid() x = (int(min(centroids[:, 0])) + diff, int(max(centroids[:, 0])) - diff) y = (int(min(centroids[:, 1])) + diff, int(max(centroids[:, 1])) - diff) return (y, x, searchStep)
def translate(self, shape, Tx, Ty): ''' Translate a shape according to translation parameters ''' return Shape([shape.x + Tx, shape.y + Ty])
def render_shape(shape): if not isinstance(shape, Shape): shape = Shape(shape) plt.plot(shape.x, shape.y, marker='o') plt.show()
def _ellipse(self, center, amount_of_points=LANDMARK_AMOUNT): ''' Returns points along the ellipse around a center. ''' ellipse = cv2.ellipse2Poly(tuple(center), (80, 210), 90, 0, 360, 4) return Shape(np.hstack(ellipse[:amount_of_points, :].T))
def set_mean_shape(self, shape): self.mean_shape = Shape(shape)