Slit-Scan Camera: video to create timeline images.
in
Share your Work
•
10 months ago
I have created my first useful program in processing and have placed it on a new
website which explains more detail and sample output.l. It is pretty simple in concept - It processes a video (.mov) to an image (.tiff) file. A selected column of pixels is taken from each frame and these are appended to create a timeline image. Pretty arty!
As I haven't used processing before Any constructive comments would be very welcome!
THANKS!
Here is the code Just put a .mov movie in the data folder and the .tiff image appears in the same folder as the sketch:
// Slit Camera - Movie procesed to a timeline image.
// Copyright Martin Dixon 2012
// http:// wwww.slitcam.com
//
// Program Map:
// declarations
// setup() called first
// draw() called every time screen refreshed
// movieEvent() called every movie frame read.
import processing.video.*;
// Change the behaviour of the program here:
//+++start
// if you want to change the display area (width, height pixels on PC screen) then change the first line (size) of 'setup' below.
String moviefilename = "t22.mov"; // input .mov movie file in sketch data folder
String outfilename = "t22.tiff"; // output image file (normally TIFF or PNG) created in sketch folder
boolean horizontal_slit = true; // use horizontal slit (otherwise vertical)
// choose one of:
float fixedslit = 0.5; // value between 0 & 1. Use column (or row) of frame pixels this fraction across
// // the image (0.5 = central slit).
boolean scanR = false; // start with left (or top) column (or row). Overrides fixedslit.
boolean scanL = false; // start with right (or bottom). Overrides fixedslit.
int startpoint = 0; // Start from this frame.
int maxframes = 8000; // Stop after this many frames processed (or end of movie or end of scan).
// // Equals max output image width.
int scanSlow = 1; // The default is 1, which means the scan "slit" will move along 1 pixel for each
// // frame processed. 2 is move along 1 pixel every 2 frames etc.
int closeafter = 15000; // Close window after this many miliseconds. Default 15000. 0 for never (user) close.
// // More experimental parameters:
boolean difference = false; // show change from last slit pixel by pixel. Just a idea.
int average = 1; // Average adjacent pixels. 1 = no average, 2, 4 etc will use average of this number of
// // pixels perpendicular to the slit. Intended to remove some noise - not sure if effective.
float speed = 1; // Does not appear to have any affect!
// // Speed of movie - might want to reduce to reduce jittery video rendition
// // (should not affect output) if PC is very slow.
// float scanspeed = 1.0 // Not needed, use scanSlow. I thought it would be handy to control the number number of pixels
// // to move slit or each frame, however in most cases this would mean cubic interpolation
// // would be required and I am prety sure this is exacty equivalent to re-sizing the final
// // image in photoshop etc.
//+++end
// you probably dont want to change anything except possibly (window) "size" in "setup" below here.
// define global variables
int frame, lastframe, lastframecount, outcol;
int SlitPixels;
int slitOffset = 0;
int scanWidth = 0;
int scanInc = 0;
long lastTime = 0;
// Variable to hold onto Video (potentially you could change this program to use a live "Capture" object);
Movie video;
PImage outimg;
int videodisplaywidth = 10;
int videodisplayheight = 10;
String infotext = "";
String infotext2 = "";
boolean startscan = false;
boolean ending = false;
PImage currslit;
int[] prevslit, tempslit;
int startpointn = 0;
int scanSlowN = -1;
int dsplWidth = width-350;
int dsplHeight = height / 2;
void setup() {
size(1300, 1024); // you might want to change the screen window width, height.
// size(800, 500); // e.g. smaller window.
// a little validation
if (average < 2) average=1;
if (speed<=0) speed=1;
// Initialize columns and rows
video = new Movie(this, moviefilename);
video.speed(speed); // this seems to have no affect for 1st play
video.play();
outcol = -1;
frame = -1;
lastframe = -1;
lastframecount=0;
dsplWidth = width-350;
dsplHeight = height / 2;
textFont(createFont("Verdana", 24));
// Note much initialisation occurs in movieEvent as 1st frame needs too be read before frame size is known.
}
void draw() { // show stuff on screen
String s = "";
background(200);
// Show image so far and current video frame
if (outimg!=null) image(outimg, 0, 0, dsplWidth, dsplHeight);
image(video, 0, dsplHeight, dsplWidth, dsplHeight);
// draw a line to indicate slit on video display
if (startscan && !ending) {
stroke (30, 256, 0, 180);
int offsetDone;
int hh, ww;
if (horizontal_slit) {
hh = dsplHeight;
ww = dsplWidth;
}
else {
hh = dsplWidth;
ww = dsplHeight;
}
if (scanR) offsetDone = hh * slitOffset / scanWidth;
else if (scanL) offsetDone = hh * slitOffset / scanWidth;
else offsetDone = int(hh * fixedslit);
if (horizontal_slit) line (0, hh + offsetDone, dsplWidth, hh + offsetDone);
else line (offsetDone, ww, offsetDone, ww + ww);
}
// ending, re-size and output
if (startscan) {
if (ending) {
if (closeafter > 0) if ( millis () - lastTime > closeafter ) exit();
}
else if (lastframecount<20) { // is nothing changing?
if (lastframe==frame)lastframecount++;
else lastframecount=0;
}
else { // nothing changed output and end
if (outimg!=null) {
outimg = outimg.get(0, 0, frame+1, outimg.height); //possibly crop image
outimg.save(outfilename);
infotext2 = "\n\n" + "Output image file (" + frame + " x " + outimg.height +"px) has been written to sketch directory:\n" + outfilename;
}
else s += "\n\nError: no image generated";
ending = true;
lastTime = millis();
}
lastframe = frame;
}
// display text information
if (frame>0) s = infotext + "\n\n" + "Frame: " + frame + s;
else s = infotext + "\n\n" + "Wait for frame " + startpoint + " : " + startpointn + s;
textSize(12);
fill(10);
text(s + infotext2, width-330, height-330, 320, 320);
}
// Called every time a new frame is available to read
void movieEvent(Movie m) {
int r = 0;
int g = 0;
int b = 0;
if ( frame + 1 >= maxframes) return;
m.read();
if (startpointn < startpoint) {
startpointn++;
return;
}
frame++;
video.loadPixels();
if (frame == 0) { // initialisation start
videodisplaywidth = video.width;
videodisplayheight = video.height;
if (video.height > height/2) {
videodisplaywidth = videodisplaywidth;
videodisplayheight = video.height;
}
infotext = "Input movie file (" + video.width + " x " + video.height + "px):\n" + moviefilename + "\nSlit: ";
if (horizontal_slit) {
infotext += "Horizontal " ;
scanWidth = video.height;
SlitPixels = video.width;
if (scanR) infotext += "Scan top to bottom. ";
else if (scanL) infotext += "Scan bottom to top. ";
else infotext += "Fixed row " + fixedslit * 100 + "% down from top";
}
else {
scanWidth = video.width;
SlitPixels = video.height;
if (scanR) infotext += "Scan left to right. ";
else if (scanL) infotext += "Scan right to left. ";
else infotext += "Fixed column " + fixedslit * 100 + "% across from left";
}
if ( (scanL || scanR) && scanSlow > 1) infotext += "Scan moves 1px every " + scanSlow + " frames. ";
currslit = createImage(1, SlitPixels, RGB);
if (scanR) {
slitOffset = - 1;
scanInc = 1;
}
else if (scanL) {
slitOffset = scanWidth + 1 - average;
scanInc = -1;
}
else slitOffset = int(scanWidth * fixedslit);
if (difference) infotext += "Difference ";
if (startpoint>0) infotext += "Startframe: " + startpoint + " ";
if (average>1) infotext += " Average " + average + "pixels ";
if (scanR || scanL) {
if (horizontal_slit) maxframes = video.height * scanSlow;
else maxframes = video.width * scanSlow;
maxframes = maxframes + 1 - average;
}
outimg = createImage(maxframes, SlitPixels, RGB);
startscan = true;
} // initialisation end
// Begin loop for rows
int k = slitOffset;
if (scanSlow < 2) k += scanInc;
else {
scanSlowN ++;
if (scanSlowN % scanSlow == 0) k += scanInc;
}
slitOffset = k;
for (int j = 0; j < SlitPixels; j++) {
// Looking up the appropriate color in the pixel array
color c = 0;
if (average == 1) {
if (horizontal_slit) c = video.pixels[SlitPixels * k + j ]; // row
else c = video.pixels[k + j * scanWidth]; // column
}
else { //average the colours
r=g=b=0;
for (int n = 0 ; n < average; n++ ) {
if (horizontal_slit) c = video.pixels[SlitPixels * (k + n) + j ]; // row
else c = video.pixels[k + n + j * scanWidth]; // column
r += (c >> 16) & 0xFF;
g += (c >> 8) & 0xFF;
b += c & 0xFF;
}
c = color(r / average, g / average, b / average);
}
currslit.pixels[j] = c;
}
currslit.updatePixels();
// End loop for rows
if (difference) { // just show difference from last slit column.
if (prevslit==null) {
prevslit = new int[SlitPixels];
tempslit = new int[SlitPixels];
arraycopy(currslit.pixels, prevslit);
}
else {
arraycopy(currslit.pixels, tempslit);
// Begin loop for rows
color c;
color d;
for (int j = 0; j < SlitPixels; j++) {
c = currslit.pixels[j];
d = prevslit[j];
r = 127 + (((c >> 16) & 0xFF) - ((d >> 16) & 0xFF)) / 2;
g = 127 + (((c >> 8) & 0xFF) - ((d >> 8) & 0xFF)) / 2;
b = 127 + ((c & 0xFF) - (d & 0xFF) ) / 2;
currslit.pixels[j] = color(r, g, b);
}
currslit.updatePixels();
arraycopy(tempslit, prevslit);
}
}
// add column to the output image
outimg.copy(currslit, 0, 0, 1, SlitPixels, frame, 0, 1, SlitPixels);
}
// Copyright Martin Dixon 2012
// wwww.slitcam.com
As I haven't used processing before Any constructive comments would be very welcome!
THANKS!
Here is the code Just put a .mov movie in the data folder and the .tiff image appears in the same folder as the sketch:
// Slit Camera - Movie procesed to a timeline image.
// Copyright Martin Dixon 2012
// http:// wwww.slitcam.com
//
// Program Map:
// declarations
// setup() called first
// draw() called every time screen refreshed
// movieEvent() called every movie frame read.
import processing.video.*;
// Change the behaviour of the program here:
//+++start
// if you want to change the display area (width, height pixels on PC screen) then change the first line (size) of 'setup' below.
String moviefilename = "t22.mov"; // input .mov movie file in sketch data folder
String outfilename = "t22.tiff"; // output image file (normally TIFF or PNG) created in sketch folder
boolean horizontal_slit = true; // use horizontal slit (otherwise vertical)
// choose one of:
float fixedslit = 0.5; // value between 0 & 1. Use column (or row) of frame pixels this fraction across
// // the image (0.5 = central slit).
boolean scanR = false; // start with left (or top) column (or row). Overrides fixedslit.
boolean scanL = false; // start with right (or bottom). Overrides fixedslit.
int startpoint = 0; // Start from this frame.
int maxframes = 8000; // Stop after this many frames processed (or end of movie or end of scan).
// // Equals max output image width.
int scanSlow = 1; // The default is 1, which means the scan "slit" will move along 1 pixel for each
// // frame processed. 2 is move along 1 pixel every 2 frames etc.
int closeafter = 15000; // Close window after this many miliseconds. Default 15000. 0 for never (user) close.
// // More experimental parameters:
boolean difference = false; // show change from last slit pixel by pixel. Just a idea.
int average = 1; // Average adjacent pixels. 1 = no average, 2, 4 etc will use average of this number of
// // pixels perpendicular to the slit. Intended to remove some noise - not sure if effective.
float speed = 1; // Does not appear to have any affect!
// // Speed of movie - might want to reduce to reduce jittery video rendition
// // (should not affect output) if PC is very slow.
// float scanspeed = 1.0 // Not needed, use scanSlow. I thought it would be handy to control the number number of pixels
// // to move slit or each frame, however in most cases this would mean cubic interpolation
// // would be required and I am prety sure this is exacty equivalent to re-sizing the final
// // image in photoshop etc.
//+++end
// you probably dont want to change anything except possibly (window) "size" in "setup" below here.
// define global variables
int frame, lastframe, lastframecount, outcol;
int SlitPixels;
int slitOffset = 0;
int scanWidth = 0;
int scanInc = 0;
long lastTime = 0;
// Variable to hold onto Video (potentially you could change this program to use a live "Capture" object);
Movie video;
PImage outimg;
int videodisplaywidth = 10;
int videodisplayheight = 10;
String infotext = "";
String infotext2 = "";
boolean startscan = false;
boolean ending = false;
PImage currslit;
int[] prevslit, tempslit;
int startpointn = 0;
int scanSlowN = -1;
int dsplWidth = width-350;
int dsplHeight = height / 2;
void setup() {
size(1300, 1024); // you might want to change the screen window width, height.
// size(800, 500); // e.g. smaller window.
// a little validation
if (average < 2) average=1;
if (speed<=0) speed=1;
// Initialize columns and rows
video = new Movie(this, moviefilename);
video.speed(speed); // this seems to have no affect for 1st play
video.play();
outcol = -1;
frame = -1;
lastframe = -1;
lastframecount=0;
dsplWidth = width-350;
dsplHeight = height / 2;
textFont(createFont("Verdana", 24));
// Note much initialisation occurs in movieEvent as 1st frame needs too be read before frame size is known.
}
void draw() { // show stuff on screen
String s = "";
background(200);
// Show image so far and current video frame
if (outimg!=null) image(outimg, 0, 0, dsplWidth, dsplHeight);
image(video, 0, dsplHeight, dsplWidth, dsplHeight);
// draw a line to indicate slit on video display
if (startscan && !ending) {
stroke (30, 256, 0, 180);
int offsetDone;
int hh, ww;
if (horizontal_slit) {
hh = dsplHeight;
ww = dsplWidth;
}
else {
hh = dsplWidth;
ww = dsplHeight;
}
if (scanR) offsetDone = hh * slitOffset / scanWidth;
else if (scanL) offsetDone = hh * slitOffset / scanWidth;
else offsetDone = int(hh * fixedslit);
if (horizontal_slit) line (0, hh + offsetDone, dsplWidth, hh + offsetDone);
else line (offsetDone, ww, offsetDone, ww + ww);
}
// ending, re-size and output
if (startscan) {
if (ending) {
if (closeafter > 0) if ( millis () - lastTime > closeafter ) exit();
}
else if (lastframecount<20) { // is nothing changing?
if (lastframe==frame)lastframecount++;
else lastframecount=0;
}
else { // nothing changed output and end
if (outimg!=null) {
outimg = outimg.get(0, 0, frame+1, outimg.height); //possibly crop image
outimg.save(outfilename);
infotext2 = "\n\n" + "Output image file (" + frame + " x " + outimg.height +"px) has been written to sketch directory:\n" + outfilename;
}
else s += "\n\nError: no image generated";
ending = true;
lastTime = millis();
}
lastframe = frame;
}
// display text information
if (frame>0) s = infotext + "\n\n" + "Frame: " + frame + s;
else s = infotext + "\n\n" + "Wait for frame " + startpoint + " : " + startpointn + s;
textSize(12);
fill(10);
text(s + infotext2, width-330, height-330, 320, 320);
}
// Called every time a new frame is available to read
void movieEvent(Movie m) {
int r = 0;
int g = 0;
int b = 0;
if ( frame + 1 >= maxframes) return;
m.read();
if (startpointn < startpoint) {
startpointn++;
return;
}
frame++;
video.loadPixels();
if (frame == 0) { // initialisation start
videodisplaywidth = video.width;
videodisplayheight = video.height;
if (video.height > height/2) {
videodisplaywidth = videodisplaywidth;
videodisplayheight = video.height;
}
infotext = "Input movie file (" + video.width + " x " + video.height + "px):\n" + moviefilename + "\nSlit: ";
if (horizontal_slit) {
infotext += "Horizontal " ;
scanWidth = video.height;
SlitPixels = video.width;
if (scanR) infotext += "Scan top to bottom. ";
else if (scanL) infotext += "Scan bottom to top. ";
else infotext += "Fixed row " + fixedslit * 100 + "% down from top";
}
else {
scanWidth = video.width;
SlitPixels = video.height;
if (scanR) infotext += "Scan left to right. ";
else if (scanL) infotext += "Scan right to left. ";
else infotext += "Fixed column " + fixedslit * 100 + "% across from left";
}
if ( (scanL || scanR) && scanSlow > 1) infotext += "Scan moves 1px every " + scanSlow + " frames. ";
currslit = createImage(1, SlitPixels, RGB);
if (scanR) {
slitOffset = - 1;
scanInc = 1;
}
else if (scanL) {
slitOffset = scanWidth + 1 - average;
scanInc = -1;
}
else slitOffset = int(scanWidth * fixedslit);
if (difference) infotext += "Difference ";
if (startpoint>0) infotext += "Startframe: " + startpoint + " ";
if (average>1) infotext += " Average " + average + "pixels ";
if (scanR || scanL) {
if (horizontal_slit) maxframes = video.height * scanSlow;
else maxframes = video.width * scanSlow;
maxframes = maxframes + 1 - average;
}
outimg = createImage(maxframes, SlitPixels, RGB);
startscan = true;
} // initialisation end
// Begin loop for rows
int k = slitOffset;
if (scanSlow < 2) k += scanInc;
else {
scanSlowN ++;
if (scanSlowN % scanSlow == 0) k += scanInc;
}
slitOffset = k;
for (int j = 0; j < SlitPixels; j++) {
// Looking up the appropriate color in the pixel array
color c = 0;
if (average == 1) {
if (horizontal_slit) c = video.pixels[SlitPixels * k + j ]; // row
else c = video.pixels[k + j * scanWidth]; // column
}
else { //average the colours
r=g=b=0;
for (int n = 0 ; n < average; n++ ) {
if (horizontal_slit) c = video.pixels[SlitPixels * (k + n) + j ]; // row
else c = video.pixels[k + n + j * scanWidth]; // column
r += (c >> 16) & 0xFF;
g += (c >> 8) & 0xFF;
b += c & 0xFF;
}
c = color(r / average, g / average, b / average);
}
currslit.pixels[j] = c;
}
currslit.updatePixels();
// End loop for rows
if (difference) { // just show difference from last slit column.
if (prevslit==null) {
prevslit = new int[SlitPixels];
tempslit = new int[SlitPixels];
arraycopy(currslit.pixels, prevslit);
}
else {
arraycopy(currslit.pixels, tempslit);
// Begin loop for rows
color c;
color d;
for (int j = 0; j < SlitPixels; j++) {
c = currslit.pixels[j];
d = prevslit[j];
r = 127 + (((c >> 16) & 0xFF) - ((d >> 16) & 0xFF)) / 2;
g = 127 + (((c >> 8) & 0xFF) - ((d >> 8) & 0xFF)) / 2;
b = 127 + ((c & 0xFF) - (d & 0xFF) ) / 2;
currslit.pixels[j] = color(r, g, b);
}
currslit.updatePixels();
arraycopy(tempslit, prevslit);
}
}
// add column to the output image
outimg.copy(currslit, 0, 0, 1, SlitPixels, frame, 0, 1, SlitPixels);
}
// Copyright Martin Dixon 2012
// wwww.slitcam.com