Amanda Bernsohn: @ITP | Images | Statement | CV | Projects

 



Final Final - Pinhole Painter Camera

I changed the concept for my final twice. In the end, because it ended up being more programming heavy than we had originally intended, I combined my PComp and ICM finals - working solely on the Pinhole Painter Camera with Chris Cerrito and Eduardo Lytton.

Our development Wiki can be found here:
http://pcomp.wetpaint.com/

The Pinhole Painter Camera is a lo-fi digital camera intended to combine aspects of digital and film photography. Our team was interested in the affordability and accessibility of digital imaging, but also think that it brings up some interesting concerns.

Our goals were to:
Learn more about programming
Learn more about sensors and electricity
Understand the basic ways in which cameras (and ccds) work
Bring serendipity and the unexpected to digital photography
Play with the ways in which users can become more involved in picture taking
Add a time element to digital picture taking - in early photography, taking a picture took a long time. Our thinking was that this commitment to the time that photographing took informed the experience of image capture.

Our process began with a lot of discussion around photography and the aspects of it that we appreciate in both film and digital. We agreed that we appreciate the unknowing of film photography, as well as the time it takes to craft an image and to receive the endeavor's results.

We approached the process from a number of different ways in the beginning. We were unclear about whether we would hack an existing digital camera, create a camera obscura and place a digital camera inside of it, or hack a scanner. In the end we agreed to create our own rudimentary ccd out of a grid of 16 (4x4) photocells. The image plane would be projected through the pinhole, onto the image plane (as an inverted image) and be recorded as light values (0-255) by an array of photo cells. Instead of having a grid of 1000 or so photo cells, we decided to use only sixteen, and to allow the user to have a part in moving that sensor around to capture the full image in the way that they want.

As the project stands, it leaves room for adding color values to the light readings by switching color filters in and out (R,G,B) and composing the images out of those values using processing.

The programming in this project was three-fold really. After crafting the array of photocells, we needed to write a program in Arduino that would send values to processing. The program is constantly sending sets of 18 ASCII values to processing. They are delineated twice, by an asterisk separating the photocell values from the IR readings, and then by commas separating the photocell values themselves. The Arduino program takes five readings for the IR values and prints the average in DEC, to be read by processing.

The Arduino code is below:

//ARDUINO CODE -- ASCII SENDER
int val[16];
int analog1[5];
int h, j;
long average1;
int analog2[5];
int p, q;
long average2;

int a= 3; // SELECT PIN A0 GOING INTO PIN 2
int b= 4; // SELECT PIN A1 GOING INTO PIN 3
int c= 5; // SELECT PIN A2 GOING INTO PIN 4
int d= 6; // SELECT PIN A3 GOING INTO PIN 5
//we are using three analog pins
int analog0 = 0;
//int analog1 = 0;
//int analog2 = 0;

void setup() {
Serial.begin(9600);
pinMode(analog0, INPUT);
pinMode(a,OUTPUT);
pinMode(b,OUTPUT);
pinMode(c,OUTPUT);
pinMode(d,OUTPUT);
//pinMode(analog1, INPUT);
//pinMode(analog2, INPUT);
}

void loop() {

for (int i = 0; i < 16; i++) {

// determine the four address pin values from i:
// mask off bit 0:
int pinOne = 1 & i;
// shift value 1 bit to the right, and mask all but bit 0:
int pinTwo = 1 & (i >> 1) ;
// shift value 2 bits to the right, and mask all but bit 0:
int pinThree = 1 & (i >> 2);
// shift value 3 bits to the right, and mask all but bit 0:
int pinFour = 1 & (i >> 3);

// set the address pins:
digitalWrite(a,pinOne);
digitalWrite(b,pinTwo);
digitalWrite(c,pinThree);
digitalWrite(d,pinFour);

// read the analog input and store it in the value array:
val[i] = analogRead(analog0);

//val[i] = i;
//Serial.print(val[i], DEC);
if (i < 15) {
//Serial.print(",");
//For IR 1

analog1[(h++ %5)] = analogRead(1);
average1 = 0;
for (j =0; j<5; j ++){
// add samples
average1 += analog1[j];
}
average1 = average1 / 5;
//For IR 2
analog2[(p++ %5)] = analogRead(2);
average2 = 0;
for (q =0; q<5; q ++){
// add samples
average2 += analog2[q];
}
average2 = average2 / 5;
//End Averaging
}
}
Serial.print("*");
Serial.print(average1, DEC);
Serial.print(",");
Serial.println(average2, DEC);
}

--------------------------------------------
The Processing program had several incarnations throughout the project. The first program was coded using hard numbers and calling each separate place in the array. The x & y positions of the large "pixels" (which were really 20x20) were coded using sensorValue1 and sensorValue2 as x and y respectively, and then offsetting each subsequent value by 20 pixels to create the grid effect. The results of this program were fine and the coding was simple, but far from dynamic. If we had wanted to re-scale the image or change the pixel size, we would have needed to change all of these values each time. It was important and helpful to begin in this way because it helped us to identify each separate cell and to know exactly when we were calling the value, dividing it, assigning it a variable, etc.. The later iteration of the program uses object orientation and arrays to achieve a similar visual effect, while being more dynamic and scalable.

The Processing code as it stands now is below (still in progress):
---
The A-Mazing Pinhole Painter Camera Processing Code ! ! ! !
Project by Amanda Bernsohn, Chris Cerrito, Eduardo Lytton
Fall 2007 ITP

// 3 arrays: PicPixArray (the big picture), cursorArray (our photosensor cursor), and vals
// range of values coming in from our photo sensors


import processing.serial.*;
Serial myPort;
//Declare your main string5
String inString;
boolean gotFirstString = false;



int cols = 40; //# of columns
int rows = 40; //# of rows
int videoScale = width/cols; //resolution of the overall picture grid - how wide a "bigPixel" is
int[] vals = new int[16]; //Arduino array of gray values coming in serially
int irOne; //IR sensor value for X
int irTwo; //IR sensor value for Y
int []pots; //the string leftover, what will contain IR x,y values
Cell[][] PicPixArray; //2D array of objects -- the overall grid of our image plane
Cell[][] cursorArray; //2D array of objects -- the "cursor" array
int gridScale; //resolution of the overall picture grid - how wide a "bigPixel" is
int gridCols = 4; //Number of columns in the grid
int gridRows = 4; //Number of rows in the grid
//int grayGridMasterValue = 5; //for NON-SERIAL COMMUN



//PImage img;

void setup() {
size (800, 800);
background(255);
println(Serial.list());
myPort = new Serial(this, Serial.list()[0], 9600);
//set a buffer - serialEvent will not happen until \n (new line feed)
//so serialEvent will always start on the first of our values
myPort.bufferUntil('\n'); // SERIAL COMMUNICATION

gridScale = width/cols;
cursorArray = new Cell[gridCols][gridRows];
//this is where we initialize the cursorArray a.k.a "cursor" or "photo sensors array"
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
//intialize each object
//int locLinearArray = i+j*4;
cursorArray [i][j] = new Cell(i*gridScale,j*gridScale,gridScale,gridScale,-1);//values[locLinearArray]);
//cursorArray[i][j].getArduinoValues();
//print(values[i] + "\t");
}
}

PicPixArray = new Cell[cols][rows];
//this is where we initialize the PicPixArray a.k.a "image plane" or "the big picture"

for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int x = i*gridScale; //x position
int y = j*gridScale; //y position
int tempbigPixelGray = 150;
PicPixArray [i][j] = new Cell(x, y, gridScale, gridScale, tempbigPixelGray);
//PicPixArray [i][j].fillinPicPix();


}

//img = createImage(width,height,RGB);
}


//failed attempts / other ideas below
// PicPixArray[x][y] = values[(x*4) + y];
//PicPixArray [values [16] / 10 + x][values [17] / 10 + y] = cursorArray [x][y];


}

void serialEvent(Serial myPort) {
String myString = myPort.readStringUntil('\n');

if(myString != null) {
myString = trim(myString);
//println(myString);
//split your longer string, using the asterisk as a delimiter, creating two smaller strings
String[] strings = split(myString,'*');

if (strings.length > 1) {
//make an array of vals by splitting the first string wherever there is a commma
//you know it's the first string cause it's called strings[0]
vals = int(split(strings[0], ','));

//do the same things for the pots, split the second string up
//wherever there is a comma
pots = int(split(strings[1], ','));
//irOne is a variable, whose value is going to be the first value from the pots array
irOne = pots[0];
//irTwo is a variable, whose value is going to be the second value from the pots array
irTwo = pots[1];
//same thing here with val0, etc... they will eventually be val0-val15, which are variable
println(vals + "\t");

for(int i = 0; i < vals.length; i++){
vals[i] = vals[i]/4;
print(vals[i] + "\t");
}
println();

println("IRS");
println(irOne + "\t" + irTwo);
println();
//then print whichever cell values you would like to look at
println(vals[0] + "\t" + vals[1]);
}
}
}

//void keyPressed() {






void mousePressed() {
// img.save("test.jpg");

}

void draw() {
// background (0);
//irOne = mouseX; //change to include IR sensor value array value [16] - x
//irTwo = mouseY; //change to include IR sensor value array value [17] - y

//img.loadPixels();
noStroke();

for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
int locLinearArray = i+j*4;
cursorArray [i][j].setArduinoValue(vals[locLinearArray]);

//cursorArray [i][j].setArduinoValue(150);
cursorArray [i][j].setPixel();
}
}
//img.updatePixels();




//image(img,0,0);
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
cursorArray [i][j].displayCursor();
}
}

/* for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
PicPixArray [i][j].display();
}
}*/

/*
for (int i = 0; i < gridCols; i++) {
for (int j = 0; j < gridRows; j++) {
int locLinearArrayCursor = i+j*4;
vals [i] = ((vals[locLinearArrayCursor])/4);
//this is the line that needs to be change to receive arduino bytes!
//vals[i] = int(random(255));//to test NON-SERIAL COMMUNICATION
// vals[i] = int(random(255)); //SERIAL COMMUNICATION
}
}*/
}

========
THE "CURSOR CELL" TAB


//a cell object
class Cell {
float xcell,ycell; //x,y location
float w,h; //width and height
int grayGridValue; // gray value for fill for each cursor Cell


//cell constructor
Cell(float tempxcell, float tempycell, float tempW, float tempH, int tempGrayGridValue) {
xcell = tempxcell;
ycell = tempycell;
w = tempW;
h = tempH;
grayGridValue = tempGrayGridValue;
}


void setArduinoValue(int temp) {
grayGridValue = temp;
}


void setPixel() {
//rect(irOne + xcell, irTwo + ycell,gridScale,gridScale); //replace mouseX with irOne value

/*int x = irOne + int(xcell)/gridScale;
int y = irTwo + int(ycell)/gridScale;

int loc = x+y*width; // NOT CORRECT
img.pixels[loc] = color(grayGridValue);*/


PicPixArray[int(xcell+irOne)/gridScale][int(ycell+irTwo)/gridScale].setArduinoValue(grayGridValue);

}


//void fillInPicPix() {
// //PicPixArray[i][j] = PicPixArray[irOne / videoScale + x][irTwo/ videoScale + y];
//


void displayCursor () {
noStroke(); //color and move to mouse position
color c = grayGridValue;
fill(c);
rect(irOne + xcell, irTwo + ycell,gridScale,gridScale); //replace mouseX with irOne value
}

void display () {
noStroke(); //color and move to mouse position
color c = grayGridValue;
fill(c);
rect(xcell,ycell,gridScale,gridScale); //replace mouseX with irOne value
}
}

Several images documenting our process and our results can be found below:
One of Chris Cerrito's early drawings of the concept.


We started with five photocells on the board, using analog ins, but no multiplexer.


Building the photocell array (something we ended up doing in its entirety twice, and are still tweaking)





We needed to use a multiplexer to increase the number of analog ins on the Arduino. we used one, which gave us an additional 15 analog ins, and only used one analog pin (and four digital). The code for the MUX (which involved bitshifting) ended up taking several days to work out.


We tried a number of different sensors to track the x and y positions of the cells inside the camera body. We ended up using IR sensors, one of which is shown below.



An early screenshot from the processing program, using ellipses with brightness values corresponding to available light. This is unfocused, ambiant light and the values are quite dark.

posted by Amanda @ 12/11/2007 11:29:00 PM,  

0 Comments:

Post a Comment

<< Home