Source code for aam

"""
.. module:: active_appearance_model
   :platform: Unix, Windows
   :synopsis: Contains the aam data format abstraction layer

"""

import logging
import numpy as np
from matplotlib.tri import Triangulation
import cv2

# local imports
import pca
import reconstruction.texture as tx


logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(name)s: %(message)s'
)
logger = logging.getLogger(__name__)


[docs]class AAMPoints(): """ Object to store AAM points / landmarks. Tries to keep the scaling of these points transparent. """ def __init__(self, normalized_flattened_points_list=None, points_list=None, actual_shape=()): """ Args: normalized_flattened_points_list(ndarray): flattened list of points. This means that if the points consist of x,y coordinates, then all this list will be: [x1, y1, x2, y2, ... xi, yi] points_list(ndarray): this list is the same points but then not flattened, [[x1, y1], [x2, y2], ... [xi, yi]]. You either create this object with this argument or the normalized_flattened_points_list actual_shape(tuple): this is important if you want to reconstruct the original list, see get_scaled_points() for usage. """ self.normalized_flattened_points_list = normalized_flattened_points_list self.points_list = points_list self.actual_shape = actual_shape self.bounding_box = None
[docs] def get_bounding_box(self): """ Get the bounding box around the points. Returns: OpenCV rectangle: x, y, w, h """ if self.bounding_box is None: return self.calculate_bounding_box() return self.bounding_box
[docs] def get_scaled_points(self, width_height_dimensions, rescale=False): """ Scale the normalized flattened points list to a scale given by 'shape'. The x and y values should be scaled to the width and height of the image. Args: shape(tuple): (height, width) rescal(boolean): flag if we should rescale or not because if we already scaled, we are not going to do it again by default. Returns: ndarray scaled to 'shape' width and height. """ if self.points_list is None or rescale: self.points_list = self.normalized_flattened_points_list # shape into [[x, y(, z)], [x, y, (,z)]] # we use the 'actual_shape' which is known from the creation of # this object. if len(self.actual_shape): self.points_list = self.points_list.reshape(self.actual_shape) h = width_height_dimensions[0] w = width_height_dimensions[1] self.points_list[:, 0] = self.points_list[:, 0] * w self.points_list[:, 1] = self.points_list[:, 1] * h return self.points_list
[docs] def calculate_bounding_box(self): """ Calculate bounding box in the **scaled** points list The empasis on on scaled because the convexHull does not support small values, the normalized_flattened_points_list does not work. Use get_scaled_points first, with a shape that is needed. The shape should be the dimensions of the out image, example (480, 640), ie., (height, width) """ assert self.points_list is not None, \ 'the list points already need to be scaled order to correctly work,\ this requires that get_scaled_points is executed first.' hull = cv2.convexHull(self.points_list, returnPoints=True) return cv2.boundingRect(hull)
# TODO: impove by not using opencv but just min-max of the lists def get_bounding_box_2(self): pass
#hull = cv2.convexHull(self.points_list, returnPoints=True) #x, y, w_slice, h_slice = cv2.boundingRect(hull) #return cv2.boundingRect()
[docs]def get_mean(vector): """ Construct a mean from a matrix of x,y values Args: points(numpy array) that follows the following structure: Returns: mean_values (numpy array) Example: Input observations: 0. [[x_0_0, y_0_0], ... , [x_0_m, y_0_m]], 1. [[x_1_0, y_1_0], ... , [x_1_m, y_1_m]], 2. [[x_2_0, y_2_0], ... , [x_2_m, y_2_m]], 3. [[x_3_0, y_3_0], ... , [x_3_m, y_3_m]] .... .... ..... n. [[x_4_0, y_4_0], ... , [x_n_m, y_n_m]] This vector containts the mean values of the corresponding column, like so: 0. [[x_0_0, y_0_0], ... , [x_0_k, y_0_k]], 1. [[x_1_0, y_1_0], ... , [x_1_k, y_1_k]], 2. [[x_2_0, y_2_0], ... , [x_2_k, y_2_k]], 3. [[x_3_0, y_3_0], ... , [x_3_k, y_3_k]] .... .... ..... n. [[x_4_0, y_4_0], ... , [x_n_k, y_n_k]] mean. [[x_mean_0, y_mean_0], ... [x_mean_n, y_mean_n]] """ return np.mean(vector, axis=0)
[docs]def get_triangles(x_vector, y_vector): """ Perform triangulation between two 2d vectors Args: x_vector(ndarray): list of x locations y_vector(ndarray): list of y locations """ return Triangulation(x_vector, y_vector).triangles
[docs]def build_shape_feature_vectors(files, get_points, flattened=False): """ Gets the aam points from the files and appends them seperately to one array. Args: files (list): list files get_points(function): function that gets the points/landmarks given a list of files. Returns: list. List of feature vectors """ points = get_points(files) if flattened: points = pca.flatten_feature_vectors(points, dim=0) return points
[docs]def sample_from_triangles(src, points2d_src, points2d_dst, triangles, dst): """ Get pixels from within the triangles [[p1, p2, p3]_0, .. [p1, p2, p3]_n]. Args: src(ndarray, dtype=uint8): input image points2d_src(ndarray, dtype=np.int32): shape array [[x, y], ... [x, y]] points2d_dst(ndarray, dtype=np.int32): shape array [[x, y], ... [x, y]] triangles(ndarray, ndim=3, dtype=np.int32): shape array [[p1, p2, p3]_0, .. [p1, p2, p3]_n]. """ for tri in triangles: src_p1, src_p2, src_p3 = points2d_src[tri] dst_p1, dst_p2, dst_p3 = points2d_dst[tri] tx.fill_triangle_src_dst( src, dst, src_p1[0], src_p1[1], src_p2[0], src_p2[1], src_p3[0], src_p3[1], dst_p1[0], dst_p1[1], dst_p2[0], dst_p2[1], dst_p3[0], dst_p3[1] )
[docs]def build_texture_feature_vectors(files, get_image_with_points, mean_points, triangles): """ Args: files (list): list files get_image_with_points (function): That can return the image together with the location. mean_points(AAMPoints): AAMPoints object Returns: list: list of feature vectors """ mean_texture = [] image, points = get_image_with_points(files[0]) mean_points.get_scaled_points(image.shape) # improve this, see issue #1 x, y, w_slice, h_slice = mean_points.get_bounding_box() for i, f in enumerate(files): image, points = get_image_with_points(f) Points = AAMPoints( normalized_flattened_points_list=points, actual_shape=(58, 2) ) # empty colored image dst = np.full((image.shape[0], image.shape[1], 3), fill_value=0, dtype=np.uint8) sample_from_triangles( image, Points.get_scaled_points(image.shape), mean_points.get_scaled_points(image.shape), triangles, dst ) dst_flattened = dst[y: y + h_slice, x: x + w_slice].flatten() mean_texture.append(dst_flattened) logger.info('processed file: {} {}/{}'.format(f, i, len(files))) return np.asarray(mean_texture)
[docs]def get_pixel_values(image, points): """ deprecated """ h, w, c = image.shape points[:, 0] = points[:, 0] * w points[:, 1] = points[:, 1] * h image = cv2.blur(image, (3, 3)) hull = cv2.convexHull(points, returnPoints=True) rect = cv2.boundingRect(hull) x, y, w, h = rect for i in np.linspace(0, 1, num=150): for j in np.linspace(0, 1, num=150): y_loc_g = int(i * h + y) x_loc_g = int(j * w + x) if cv2.pointPolygonTest(hull, (x_loc_g, y_loc_g), measureDist=False) >= 0: image[y_loc_g][x_loc_g][0] = 0 image[y_loc_g][x_loc_g][1] = 0 image[y_loc_g][x_loc_g][2] = 0 # return np.asarray(pixels, dtype=np.uint8), hull return image, hull