def __init__(self, pdmodel, glmodel_pyramid): self.pdmodel = pdmodel self.glmodel_pyramid = glmodel_pyramid # initialise examining/fitting/aligning classes self.fitter = Fitter(pdmodel) self.examiner = Examiner(glmodel_pyramid) self.aligner = Aligner()
class Fitter(object): def __init__(self, pdmodel): self.pdmodel = pdmodel self.aligner = Aligner() self.start_pose = () 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
class ActiveShapeModel(object): ''' Algorithm examines a region close to the initial position. For each point X_i in this region, find the shape/pose parameters of the deformable model that fits the examined region (keeping the shape parameter within a 3*sqrt(eigval) bound). Repeat until convergence. in: PointDistributionModel pdmodel list of gray-level models per resolution level ''' def __init__(self, pdmodel, glmodel_pyramid): self.pdmodel = pdmodel self.glmodel_pyramid = glmodel_pyramid # initialise examining/fitting/aligning classes self.fitter = Fitter(pdmodel) self.examiner = Examiner(glmodel_pyramid) self.aligner = Aligner() # def __init__(self, pdmodel): # self.pdmodel = pdmodel # # initialise examining/fitting/aligning classes # self.fitter = Fitter(pdmodel) # self.aligner = Aligner() 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 search(self, image, region, t=0, level=0, max_level=False, max_iter=5, n=None): ''' Perform the Active Shape Model algorithm in input region. in: array image; input image 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 level; level in the gaussian pyramid int max_iter; amount to stop iterations at each level int n; fitting parameter out: array points; approximation of the target ''' if max_level: # get initial parameters pose_para = self.aligner.get_pose_parameters( self.pdmodel.mean, region) # set initial fitting pose parameters self.fitter.start_pose = pose_para # align model mean with region points = self.aligner.transform(self.pdmodel.mean, pose_para) else: points = region # set examiner image self.examiner.set_image(image) # perform algorithm i = 0 movement = np.zeros_like(points.length) while np.sum(movement) / points.length <= 0.5: # examine t pixels on the normals of all points in the model # plot.render_shape_to_image(np.uint8(image), points) adjustments, movement = self.examiner.examine(points, t=t, pyramid_level=level) # find the best parameters to fit the model to the examined points pose_para, c = self.fitter.fit(points, adjustments, pyramid_level=level, n=n) # add constraints to the shape parameter c = self.constrain(c) # transform the model according to the new parameters points = self.transform(pose_para, c) plt.imshow(image) plt.plot(points.x, points.y, 'r.') plt.show() print('**** LEVEL ---', str(level)) print('**** ITER ---', str(i)) print('(constr shape param)', c[:4]) print('(pose params)', pose_para) # keep iteration count i += 1 if i == max_iter: break return points def transform(self, pose_para, b): ''' Transform the model to the image by inserting the most suitable pose and shape parameters ''' mode = self.pdmodel.deform(b) return self.aligner.transform(mode, pose_para) def constrain(self, vector): ''' Add constraints to shape parameter proportional to the eigenvalues of the point distribution model. According to Cootes et al., all elements of the vector should agree to the following constraint: |v_i| < 3*sqrt(eigenval_i) ''' uplimit = 3 * np.sqrt(self.pdmodel.eigenvalues) lowlimit = -1 * uplimit vector[vector > uplimit] = uplimit[np.where(vector > uplimit)] vector[vector < lowlimit] = lowlimit[np.where(vector < lowlimit)] return vector
gaussian_pyramid(self.images[i], levels=levels) for i in range(self.images.shape[0]) ]) # create list of gray-level models glmodels = [] for l in range(levels): glmodels.append( self.graylevelmodel(multi_res[:, l], k=k, reduction_factor=2**l)) print('---Created gray-level model of level ' + str(l)) return glmodels aligner = Aligner() loader = [] training_set = [] test_set = [] trainlandmarks = [] asm = [] pca = [] def Train(): global loader, training_set, test_set, trainlandmarks, pca # ------------- LOAD DATA -------------- # loader = DataLoader() training_set, test_set = loader.leave_one_out(test_index=0)
def run(imageslice, centroid): ''' Main method of the package. ''' # ------------- LOAD DATA -------------- # loader = DataLoader() training_set, test_set = loader.leave_one_out(test_index=1) # --------------- TRAINING ---------------- # trainlandmarks = training_set[1] # build and train an Active Shape Model asm = ASMTraining(training_set, k=3, levels=3) pca = asm.activeshape.pdmodel t = 0 for i in range(len(pca.eigenvalues)): if sum(pca.eigenvalues[:i]) / sum(pca.eigenvalues) < 0.99: t = t + 1 else: break print("Constructed model with {0} modes of variation".format(t)) # --------------- TESTING ----------------- # aligner = Aligner() testimage, testlandmarks = test_set test = Shape(testlandmarks) # remove some noise from the test image testimage = remove_noise(testimage) plt.imshow(testimage) plt.plot(test.x, test.y, 'r.') plt.show() plt.imshow(imagestest) plt.plot(test.x, test.y, "r.") pose_para = aligner.get_pose_parameters(pca.mean, test) lst = list(pose_para) lst[0] = 0 lst[1] = 0 lst[2] = pose_para[2] lst[3] = 0 t = tuple(lst) points = aligner.transform(pca.mean, t) meanShapeCentroid = (np.sum(points.x) / 30, np.sum(points.y) / 30) # centroid= tran.set_clicked_center(np.uint8(testimage)) matches1 = tran.initalizeShape(centroid, meanShapeCentroid, points.matrix.T) if not isinstance(matches1, Shape): matches1 = Shape(matches1.T) plt.imshow(imagestest) plt.plot(matches1.x, matches1.y, '.') plt.show() new_fit = asm.activeshape.multiresolution_search(imagestest, matches1, t=10, max_level=0, max_iter=20, n=0.1) # Find the target that the new fit represents in order # to compute the error. This is done by taking the smallest # MSE of all targets. mse = mean_squared_error(testlandmarks, new_fit) # implement maximally tolerable error if int(mse) < MSE_THRESHOLD: print('MSE:', mse) # plot.render_shape_to_image(np.uint8(testimage), trainlandmarks[best_fit_index], color=(0, 0, 0)) else: print('Bad fit. Needs to restart.')
def __init__(self, pdmodel): self.pdmodel = pdmodel self.aligner = Aligner() self.start_pose = ()