The goal of P4 was to create an image morph from 2 basic photos; 1 from someone famous, and 1 from myself. People used to tell me I
looked like Moby (I don't really think I do, at least not without a completly bald head).
So I figured I'd take this opportunity to either prove or disprove everyone.
Finding a usable photo of Moby took a little bit of time, but I finally was able to find the one to the left. I figured if I was going to morph
into Moby, I mine as well dress the part (pictured on the far right). All it took was a little "Photoshop Magic" and we were set to start the deformation process.
The first and third images are what I used as my base to create the animation. I took those 2 images and made 6 key frames out of them,
following the flow pattern outlined below.
To create the Deformations of each photo, I used the turbo morph applet (provided by Professor Jarek Rossignac).
I then modified the save/load routines to both output the points into a file format, and to output the current screen render as a JPG image. Also, I decided I would need a consistant facial map to use
when creating each keyframe (that way I always had the same number of points, in the same order, making my interpolation step trivial). By lining up these map-points between key facial features in
each of the images, I was able to create my key-frames.
 |
 |
 |
 |
 |
 |
| moby-deformation |
moby |
moby-choyce-map |
choyce-moby-map |
choyce |
choyce-deformation |
Facial map used to create the key frames.
|
To create the morphing in between each of these key-frames, I just ran 30 iterations of my custom interpolations tool:
void animate_step( )
{
// loop through all the keyframes
int frame = startStep;
int nextFrame = frame+1;
float newX, newY;
// looping through all the points
for( int i = 0; i < cn[frame]; i++ )
{
// get out starting and ending points
pt ptStart = C[frame][i].make();
pt ptEnd = C[nextFrame][i].make();
newX = ptStart.x + t * (ptEnd.x - ptStart.x)/4;
newY = ptStart.y + t * (ptEnd.y - ptStart.y)/4;
// now update our starting point.
C[frame][i].setTo(newX, newY, 0);
G[I[frame][i]][J[frame][i]].setTo(C[frame][i]);
} // end cn loop
t += timeStep;
animationFrameCount++;
// smooth it!
smoothing=true; sfairInit(); fstp=0;
imgSaveAnimationFrame();
}
|
I ended up keeping this as a manual iteration because the timing between to smoothing call and the saving routine would get off, so sometimes
the smoothing wouldn't finish before the save was called --not good! I would like to go back and automate this so you could just start the process
with your keyframes defined, and it would do all the iteration for you while you sat back, ate some popcorn and watched Veronica Mars.
In order to get the middle part of the animation working correctly, I had to blend together the image of Moby with the image of myself.
I combined both a the interpolation method above, and an alpha cross-fade between the two images. I choose a function which ramped up quickly
and then slowed down as it came to an end (basically the same function we used in P1a: F3).
What I tired to do was map the the moby photo's facial points to about where my facial points were for the moby-choyce blend, and then map my facial points to
Moby's for the choyce-moby blend. While this worked, I noticed afterwards: I should have paid more attention to the glasses. His are significantly larger than
mine in this photo, and when I mapped the eyes and ears together, I messed up the glasses mapping. His glass get bigger when they should get smaller but, time permitting,
I would like to fix this.
void animate_blend( )
{
String mBase = "../frames/m1-m2/m1-m2-";
String cBase = "../frames/m3-m4/m3-m4-";
mImage = loadImage( mBase + currentBlendIndex + ".jpg");
cImage = loadImage( cBase + currentBlendIndex + ".jpg");
alphaChannel = blendRampUp( );
int pixelLength = mImage.height * mImage.width;
color mColor, cColor;
for( int i = 0; i < pixelLength; i++ )
{
mColor = color(
red(mImage.pixels[i]),
green(mImage.pixels[i]),
blue(mImage.pixels[i]),
255-alphaChannel
);
cColor = color(
red(cImage.pixels[i]),
green(cImage.pixels[i]),
blue(cImage.pixels[i]),
alphaChannel
);
mImage.pixels[i] = blendColor( mColor, cColor, BLEND );
}
imgSaveBlendFrame();
currentBlendIndex++;
}
// changes opacity fast and then slows down
float blendRampUp( )
{
float retVal = 0;
float t = (float)currentBlendIndex/30;
retVal = -cos(3.14159265*t);
retVal += 1.0;
retVal /= 2.0;
return retVal*255;
}
|

1/3 through the moby-choyce blend.

1/2 through the moby-choyce blend.

2/3 through the moby-choyce blend.
|
Once I had all these images create, I had to resize them all to get a useable images. Rather then running the interpolation and
blending over and over each time I wanted a different size animation, I just simple created a simple tool in processing to resize
them to any width I would need. This resize process also removed the moby-choyce images and choyce-moby images (which are now combined
into a blend) and re-numbered them to make the animation easier.
Click to toggle resize code display.
int framesPerChapter = 30;
int numChapters = 3;
int numFrames = framesPerChapter * numChapters;
boolean loadedImages = false;
boolean loadStarted = false;
int dimension = 600;
// made these global so we can give user feedback on how many images have been loaded
int chapterIndex = 0, frameIndex = 0, loadedCount = 0;
int frame = 0;
PImage[] images = new PImage[numFrames];
void setup()
{
size(dimension, dimension);
frameRate(10);
PFont fontA = loadFont("TrebuchetMS-Bold-32.vlw");
textFont(fontA, 32);
}
void draw()
{
if( !loadedImages )
{
loadImages( );
}
else
{
background(0);
text("Finished!", 0, height/2 );
}
}
void loadImages( )
{
if( loadedCount >= numFrames )
loadedImages = true;
else
{
String basePath = "";
// Update our counters
if( frameIndex >= framesPerChapter )
{
if( chapterIndex == 0 )
chapterIndex = 2;
else if( chapterIndex == 2 )
chapterIndex = 4;
frameIndex = 0;
}
String _path = "m" + chapterIndex + "-m" + (chapterIndex+1); // in form "m0-m1"
basePath = "../frames/" + _path + "/" + _path + "-";
loadedCount++;
print("loaded count:" + loadedCount + "\n");
print("loading:" + basePath + (frameIndex+1) + ".jpg" + "\n");
PImage temp = loadImage( basePath + (frameIndex+1) + ".jpg");
image( temp, 0, 0, dimension, dimension);
String imgName = loadedCount < 10 ? "00" + str(loadedCount) :
loadedCount < 100 ? "0" + str(loadedCount) : str(loadedCount);
imgSave( imgName );
frameIndex++;
}
}
void imgSave( String FileName )
{
// we want to copy the current screen output, pixel by pixel, into our new image
PImage output = createImage(
width,
height, RGB);
loadPixels( );
for( int i = 0; i < width * height; i++ )
output.pixels[i] = pixels[i];
output.save("../frames" + dimension + "x" + dimension + "/" + FileName + ".jpg");
print("Saved:" + FileName + "\n" );
}
Then came time to animate it all. I could have just imported all of my animation files into Adobe Premiere, but in the spirt of Processing
(and because I didn't have access to it at the time) I simply created an applet which loaded up all the files and then played the animation
in a loop. This ended up working out nicely since I did a reverse loop so that the animation just cycles back and forth like a pendulum.
Click to toggle animation code display.
int framesPerChapter = 30;
int numChapters = 3;
int numFrames = framesPerChapter * numChapters;
boolean loadedImages = false;
boolean loadStarted = false;
boolean animateReverse = false;
int dimension = 600;
// made these global so we can give user feedback on how many images have been loaded
int chapterIndex = 0, frameIndex = 0, loadedCount = 0;
int frame = 0;
PImage[] images = new PImage[numFrames];
void setup()
{
size(dimension, dimension);
frameRate(10);
PFont fontA = loadFont("TrebuchetMS-Bold-32.vlw");
textFont(fontA, 32);
}
void draw()
{
if( !loadedImages )
{
background(0);
text("Loading...", 0, 100 );
text( loadedCount + " of " + numFrames, 0, 150 );
loadImages( );
}
else
{
image(images[frame], 0, 0);
if( frame >= numFrames-1 )
animateReverse = true;
else if( frame <= 0 )
animateReverse = false;
frame += (animateReverse ? -1 : 1); // go forwards and back
}
}
void loadImages( )
{
if( loadedCount >= numFrames-1 )
loadedImages = true;
else
{
String basePath = "../frames" + dimension + "x" + dimension+ "/";
int localCounter = 0; // keeps track of how many we've loaded this pass
for( ; loadedCount < numFrames && localCounter < 10; frameIndex++ )
{
loadedCount++;
String imgName = loadedCount < 10 ? "00" + str(loadedCount)
: loadedCount < 100 ? "0" + str(loadedCount) : str(loadedCount);
images[(loadedCount-1)] = loadImage( basePath + imgName + ".jpg");
localCounter++;
}
}
}
Here is all the source code I used to generate these images:
- animation.pde
- resize.pde
- choyce-p4-fast.zip *
* fast is a modified version of that tubro-warp source code. Changes include:
- IO.pde -- custom functions for loading and saving data.
- MyImage.pde -- image manipulation and saving functions.
- animate.pde -- all the functions which generated the animated images.
- fast.pde -- modifications made to support the animation.
- /keyframes -- files needed to load up the different keyframes of the animation.
- output-[0-9].txt, output-[0-9].jpg -- used to load up the different sets of constraints.
|