In this project, I will be morphing one face to another and also do some experimentation with population mean faces.
For the first morph, I decided to morph George Clooney’s face to Mark Zuckerberg’s face and vice versa. Photos are from Martin Schoeller’s portfolio.
I created a triangular mask for each photo using Delaunay triangulation. Both photos contain the same triangular structure as each other, so translating corresponding keypoints and triangluar regions is easier.
george clooney |
george clooney with keypoints mask |
mark zuckerberg |
mark zuckerberg with keypoints mask |
To compute the “midway face”:
Although the relative structure of the triangular masks are the same, the coordinates are not exactly the same and therefore we need a triangular mask that is the composed of the average of the keypoints and their respective positions.
For each triangle in the triangular mask:
- Find the affine transformation matrix from avg keypoints to img keypoints. This can be done by using
np.linalg.solve
, wherea
is the avg keypoints matrix andb
is the img keypoints matrix. Make sure that each keypoint is in this format:[x, y, 1]
, sincenp.linalg.solve
solvesax = b
forx
. The resulting affine transformation matrix will beA = x.T
.- Use the affine matrix to find the points that correspond to the region in the avg triangle in the img triangle by doing
A @ avg_points_matrix
.- Use nearest neighbor interpolation to find the resulting img coordinates for transformed coordinates that are in between coordinates (aka are floats and not integers)
- Place the image values that are located at the img coordinates derived from
3.
into the warped image array.
clooney |
clooney and mark avg keypoints |
zuck |
clooney warped |
clooney and zuck mean face |
zuck warped |
Creating a morph sequence is similar to calculating the midway face but instead of keeping a constant warp alpha and dissolve alpha at 0.50, we do a linear interoplation of the the two constants to get a smooth transistion of morphs.
To create this linear interpolation, I just used np.linspace
to create 45 alphas that can be in between the range of [0, 1]
, since I want to create 45 frames of in-between morph sequences. Each frame uses the same alpha for warping and cross-dissolving, which seems to work fine. However, I think a smoother transition can be done if the warp alpha was a function of dissolve alpha for some function f
. This probably needs a bit more testing and research before much more can be said.
clooney-zuck-morph.gif
I generated a mean face using the Danes dataset of annotated faces (30 males, 7 females).
To generate the mean face:
danes mean face
swift warped to danes mean face |
danes mean face warped to swift |
07-1m |
12-1f |
23-1m |
31-1m |
Caricatures of a face can be derived from this formula: caricature = alpha * (average_keypoints - original_keypoints) + original_keypoints
. Here are some with examples of Taylor Swift being more or less Danish by tuning the alpha.
alpha=-0.75 |
alpha=-0.25 |
alpha=0 |
alpha=-0.25 |
alpha=0.75 |
I created a female Mark Zuckerberg photo by morphing the Zuckerberg photo to the average Australian Women photo. To morph only shape, the warp alpha was set to 0.5
while the dissolve alpha was set to 0.0
. To morph only appearance, the warp alpha was set to 0.0
while the dissolve alpha was set to 0.5
. To morph everything together, the both warp and dissolve alphas were set to 0.5
.
zuck |
avg Australian women keypoints |
avg Australian women keypoints |
avg Australian women face |
warp=0.5, dissolve=0.0 |
warp=0.0, dissolve=0.5 |
warp=0.5, dissolve=0.5 |
pretty fun project over all and learned more the applications of affine transformations in computer vision.