Jump to content

Create 3D photographs with Processing

+ 2
  odewahn1's Photo
Posted May 10 2010 12:00 PM

Stereoscopy is a technique for creating the illusion of depth in a 2D image. While the specifics vary, the general principle is that two images are taken of the same scene, but at slight offsets of about 2-3 inches. (The width between most people's eyes). Then, the two images are fused together into a single image in a way that creates an appearance of depth. There are many, many stereoscopic fusion techniques, ranging from simply crossing your eyes (seriously!) to wearing funky glasses with red and blue lenses.

This Answer shows you how to use a technique called "Wiggle Stereoscopy" to create 3D-ish images like this one:

Attached Image

In wiggle stereoscopy, the 2 images are aligned so that the "subject" overlaps, and then the right and left images are alternated quickly. Past a certain frequency, the brain interprets the alternations as parallax motion, which creates the illusion of depth. (It's important to note that this is not really a 3D image, but rather a visual trick. True 3D is more complex.)

The first step is to take 2 photographs; fairly simple objects work best. Take the first picture of the object and then (and this is the important part!) slide the camera to the other eye and take the same picture, but at an offset. This is the tricky part -- you don't want to change the angle of the camera or move your position in any way. You simply want to slide the camera from one eye to the other, keeping the lens in the same plane. With practice, you'll get the hang of it. (And, of course, there are also special cameras you can buy, like this 3D camera from Fuji). Once you have the images, you can use this Processing script at the bottom of the page to align the subjects and then export the image to an animated Gif. (You'll need to install the gifAnimation Processing Library.)

Here's an example. I took these two images of my son's tricycle. Here's the right hand image:

Attached Image

And here's the left hand image:

Attached Image

I imported them into iPhoto, and then exported them on the medium size setting, naming them "tricycle_r.png" and "tricycle_l.png" respectively. I then used the Processing script at the end of this post to align and fuse the images. Use the "k" and "l" keys to move the image to the left and right, and the "q" and "a" keys to move the image up and down. The goal is to superimpose the subject of the two images (i.e., the tricycle) on top of each other. When you get it right, the image will suddenly pop off the screen, as shown in this example:



Once the alignment is right, press "e" to export an animated gif. (Remember to install the gifAnimation library). Here's the final example, as well as a few more pictures I've created. You'll quickly see why this is called "wiggle stereoscopy"!

Attached Image

A cactus:

Attached Image

A toy firetruck:
Attached Image

Here's the Processing script. Hope you enjoy it, and look forward to seeing your own examples in the comments.

//Be sure to insall gifAnimation library from:
//   http://www.extrapixel.ch/processing/gifAnimation/
//
import gifAnimation.*;

PImage left = loadImage("/Users/odewahn/Desktop/cactus_l.png");
PImage right = loadImage("/Users/odewahn/Desktop/cactus_r.png");

int rx = 0;  // Horizontal offset for the right image
int ry = 0;  // Vertical offest for the right image
int pause = 75;  //Number of MS to pause b/t left and right images

int rflag = -1;  // toggle that controls which to display

//Set up drawing area
void setup() {
  size(left.width, left.height);
}


void exportGif() {
  // Set up the gif
  GifMaker gifExport = new GifMaker(this, "/Users/odewahn/Desktop/out.gif");
  gifExport.setSize(width-abs(rx), height-abs(ry));  //Sets a clip region
  gifExport.setRepeat(0);  //Make this repeat infinitely
  gifExport.setDelay(75);  //Set the pause rate to 75ms b/t images
  // Draw the right image
  image(right,rx,ry);
  gifExport.addFrame();
  //Draw the left frame
  image(left,0,0);
  gifExport.addFrame();
  //Save the file
  gifExport.finish();
}

//Simple control scheme  
void keyPressed() {
  switch (key) {
     case 'q':  //shift up
        ry -= 2;
        break;
     case 'a':  //shift down
        ry += 2;
        break;
     case 'l':
        rx += 2;  //shift right
        break;
     case 'k':
        rx -= 2;  //shift left
        break;
     case '1':
        pause = 75; //fast display mode
        break;
     case '2':   //slow display mode
        pause = 500;
        break;
     case 'e':  //export an animated gif
        exportGif();
        break;
  }
}

void draw() {
  delay(pause);
  println(rx + " , " + ry);
  if (rflag == -1) {
     image(right,rx,ry);  //Draw the right image at the current offest position
  } else {
     image (left,0,0);  //Draw the left image
  }
  rflag *= -1;  // toggle the image
}  



Tags:
2 Subscribe


4 Replies

 : Nov 21 2010 08:58 PM
what are using as your sketch book being; trying to get this to work, keep getting a nullpointer exception
0
  odewahn1's Photo
Posted Nov 24 2010 06:52 AM

Hi, vid91. I'd need a bit more info to help you, but the first thing I'd try is to upgrade to the latest version of Processing, which you'll find here:

http://www.processing.org/download/

Often these weird error messages come from that. Give that a shot and then see if you still have the problem. Also, are you on a Mac or a PC?

Andrew
0
  allogarage's Photo
Posted Dec 06 2011 08:51 AM

It's the first time I see this technique of Wiggle Stereoscopy. Very clever and works well.
0
  BenVC's Photo
Posted Mar 10 2012 06:59 PM

I made some alterations to the code here to give support for jps files that come out of an EVO 3D(although, it should work for any jps file...). all you need to do is drop the files in the sketch folder and use the proper field for JPS_FILEPATH and you should be good to go. The export function has been tweeked to do some better cropping on the outputted .gif file and to timestamp the output filename so you accidentally override your last export. The HEIGHT and WIDTH fields let you pick the output size as well.

Happy Gif'ing! :)



//
//
//Be sure to insall gifAnimation library from:
//   http://www.extrapixel.ch/processing/gifAnimation/

//
//controls:
//     'q':shift up
//     'a':shift down
//     'l':shift right
//     'k':shift left
//     '1':fast display mode
//     '2':slow display mode
//     'e':export an animated gif
//     
// from Create 3D photographs with Processing, 
// http://answers.oreilly.com/topic/1463-create-3d-photographs-with-processing/
// by odewahn1
//
// support for JPL files by Benjamin Van Citters (march-2012)
// these values will be the resolution that final gif 
//will be output in.
//typical images from the evo 3d are 3840x1080
//so each frame is 1920x1080

private static final int WIDTH = 1920/3;
private static final int HEIGHT = 1080/3;

// note that this sketch is only guaranteed to work on jpl/jpg files.
// processing assumes the files are in the same folder as as this .pde file
private static final String JPS_FILEPATH = "IMAG0050.jps";

//////////////////////
//code
//////////////
import gifAnimation.*;
import java.nio.channels.FileChannel;
PGraphics both; 
PGraphics lf; 
PGraphics rt;
File tmpJpg;
int rx = 0;  // Horizontal offset for the right image
int ry = 0;  // Vertical offest for the right image
int pause = 75;  //Number of MS to pause b/t left and right images

int rflag = -1;  // toggle that controls which to display

//Set up drawing area
void setup() {
  //typical images from the evo 3d are 3840x1080
  //so each frame is 1920x1080
 
  File tmpJpl = new File(sketchPath(JPS_FILEPATH));
  String newfilePath = tmpJpl.getAbsolutePath();
  newfilePath = newfilePath.substring(0, newfilePath.indexOf('.')) + "_tmp.jpg";
  //println("newfilePath: " + newfilePath);
  tmpJpg = new File(newfilePath);
  try{
    copyFile(tmpJpl,tmpJpg);
  }
  catch(Exception e){
    println(e); 
    tmpJpg.delete();
    println("temporary file removed."); 
    exit();
    return;
  };
  PImage both = loadImage(newfilePath);//JPG_FILEPATH);
  lf = createGraphics(both.width/2,both.height,P2D);
  rt = createGraphics(both.width/2,both.height,P2D);

  lf.image(both,0,0);
  //shift over and blt to crop off the left
  rt.image(both,-both.width/2.0,0);
//  size(lf.width, lf.height);
  size(WIDTH,HEIGHT);
}


void exportGif() {
  // Set up the gif
  SimpleDateFormat dtFomat = new SimpleDateFormat("yyyyMMddHHmmssZ");
  String fileName = JPS_FILEPATH + "_" + dtFomat.format(new Date()) + ".gif";
  GifMaker gifExport = new GifMaker(this, fileName,256);
  //gifExport.setSize(width-abs(rx), height-abs(ry));  //Sets a clip region
  gifExport.setSize(width, height);  //Sets a clip region
  gifExport.setRepeat(0);  //Make this repeat infinitely
  gifExport.setDelay(75);  //Set the pause rate to 75ms b/t images
  // Draw the right image
  float rtRect[] = new float[]{0,0};
  float lfRect[] = new float[]{0,0};
  getOffsets(rtRect,lfRect);
  image(rt,rtRect[0],rtRect[1],width+abs(rx),height+abs(ry));//  image(rt,rx,ry,width,height);
  gifExport.addFrame();
  //Draw the left frame
  image(lf, lfRect[0],lfRect[1],width+abs(rx),height+abs(ry)); //image(lf,-max(0,rx),-max(0,ry),width,height);
  gifExport.addFrame();
//  println("rt: {" + rx+", " +ry+", " +width+", " +height+"}");
//  println("lf: {" + -max(0,rx)+", " + -max(0,ry)+ ", " + width + ", " + height);
  
  //Save the file
  gifExport.finish();
}
void getOffsets(float[] rtRect,float[] lfRect)
{
  rtRect[0] = (rx > 0) ? 0 : rx;
  rtRect[1] = (ry > 0) ? 0 : ry;
  
  lfRect[0] = (rx < 0) ? 0 : -rx;
  lfRect[1] = (ry < 0) ? 0 : -ry;  
}
//Simple control scheme  
void keyPressed() {
  switch (key) {
     case 'q':  //shift up
        ry -= 2;
        break;
     case 'a':  //shift down
        ry += 2;
        break;
     case 'l':
        rx += 2;  //shift right
        break;
     case 'k':
        rx -= 2;  //shift left
        break;
     case '1':
        pause = 75; //fast display mode
        break;
     case '2':   //slow display mode
        pause = 500;
        break;
     case 'e':  //export an animated gif
        exportGif();
        break;
  }
}

void draw() {
  delay(pause);
  //println(rx + " , " + ry);
  if (rflag == -1) {
     image(rt,rx,ry,width,height);  //Draw the right image at the current offest position
  } else {
     image (lf,0,0,width,height);  //Draw the left image
  }
  rflag *= -1;  // toggle the image
}  

// from: http://stackoverflow.com/questions/106770/standard-concise-way-to-copy-a-file-in-java
// retrieved march 10th 2012
// author unknown :(
void copyFile(File sourceFile, File destFile) throws IOException {
    if(!destFile.exists()) {
        destFile.createNewFile();
    }

    FileChannel source = null;
    FileChannel destination = null;

    try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destFile).getChannel();
        destination.transferFrom(source, 0, source.size());
    }
    finally {
        if(source != null) {
            source.close();
        }
        if(destination != null) {
            destination.close();
        }
    }
}


void stop(){
  tmpJpg.delete();
  println("temporary file removed."); 
 super.stop();
}