This is a reimplementation of Johny Lee's Automatic Projector Calibration in collaboration with James A. Ferwerda. To make it easier for students to work on this project a sensor board with four IF-D92 was developed that connects to a regular Arduino. Code examples for the Arduino microcontroller and for Processing were also provided.
| Level | Binary Calibration | Gray Code Calibration |
|---|---|---|
| 0 | ![]() |
![]() |
| 1 | ![]() |
![]() |
| 2 | ![]() |
![]() |
| 3 | ![]() |
![]() |
| 4 | ![]() |
![]() |
| 5 | ![]() |
![]() |
| 6 | ![]() |
![]() |
| 7 | ![]() |
![]() |
The calibration images for the y position are similar, but just flipped.
unsigned int binaryToGray(unsigned int number)
{
return (number >> 1) ^ number;
}
unsigned int grayToBinary(unsigned int gray)
{
gray ^= gray >> 8; // for 16 bit and more
gray ^= gray >> 4; // for 8 bit and more
gray ^= gray >> 2; // for 4 bit and more
gray ^= gray >> 1; // for 2 bit and more
return gray;
}
| Bit/Frame | Block | Value |
|---|---|---|
| 0 | Start | White |
| 1 | Start | Black |
| 2 | Start | White |
| 3 | Start | Black |
| 4 | Start | White |
| 5 | Start | Black |
| 6 | x | 10 |
| 7 | x | 9 |
| 8 | x | 8 |
| 9 | x | 7 |
| 10 | x | 6 |
| 11 | x | 5 |
| 12 | x | 4 |
| 13 | x | 3 |
| 14 | x | 2 |
| 15 | x | 1 |
| 16 | x | 0 |
| 17 | Separator | White |
| 18 | y | 10 |
| 19 | y | 9 |
| 20 | y | 8 |
| 21 | y | 7 |
| 22 | y | 6 |
| 23 | y | 5 |
| 24 | y | 4 |
| 25 | y | 3 |
| 26 | y | 2 |
| 27 | y | 1 |
| 28 | y | 0 |
| 29 | End | White |
The Arduino code:
/**
* Sample.ino
*
* James A. Ferwerda and Lars Schumann
* More information at: https://larsi.org/projects/ProjectorCalibration/
*/
// Fast sampling with slightly lower precission
#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
/** 3 frames a second = 333ms a frame */
const unsigned long SAMPLE_TIME = 333;
/** default thresholds for all 4 points - these get sampled with the 'l' command */
unsigned int threshold0 = 255;
unsigned int threshold1 = 255;
unsigned int threshold2 = 255;
unsigned int threshold3 = 255;
/** Converts a reflected binary Gray code number to a binary number */
unsigned int grayToBinary(unsigned int gray)
{
gray ^= gray >> 8; // for 16 bit and more
gray ^= gray >> 4; // for 8 bit and more
gray ^= gray >> 2; // for 4 bit and more
gray ^= gray >> 1; // for 2 bit and more
return gray;
}
/**
* This function should be called, whike a white image is displayed.
* It samples the fibers for a while to find a good threshold value.
*/
void level()
{
unsigned long sum0 = 0;
unsigned long sum1 = 0;
unsigned long sum2 = 0;
unsigned long sum3 = 0;
unsigned long cnt = 0;
unsigned long t = millis() + SAMPLE_TIME;
while (millis() < t) {
sum0 += analogRead(0);
sum1 += analogRead(1);
sum2 += analogRead(2);
sum3 += analogRead(3);
cnt++;
}
Serial.print(cnt);
Serial.print(": ");
cnt <<= 1; // double, so that the threshold is set to average / 2
threshold0 = sum0 / cnt;
threshold1 = sum1 / cnt;
threshold2 = sum2 / cnt;
threshold3 = sum3 / cnt;
Serial.print(threshold0);
Serial.print(",");
Serial.print(threshold1);
Serial.print(",");
Serial.print(threshold2);
Serial.print(",");
Serial.print(threshold3);
Serial.println();
}
/**
* Starts sampling the calibration sequence. Shifts in x and y and
* sends the coordinated back.
*/
byte sample()
{
unsigned int x0 = 0;
unsigned int y0 = 0;
unsigned int x1 = 0;
unsigned int y1 = 0;
unsigned int x2 = 0;
unsigned int y2 = 0;
unsigned int x3 = 0;
unsigned int y3 = 0;
unsigned long t = millis();
for (byte index = 0; index < 30; index++) {
unsigned int lows0 = 0;
unsigned int lows1 = 0;
unsigned int lows2 = 0;
unsigned int lows3 = 0;
unsigned int cnt = 0;
t += SAMPLE_TIME;
while (millis() < t) {
if (analogRead(0) < threshold0) { lows0++; }
if (analogRead(1) < threshold1) { lows1++; }
if (analogRead(2) < threshold2) { lows2++; }
if (analogRead(3) < threshold3) { lows3++; }
cnt++;
}
cnt >>= 1;
byte current0 = (lows0 < cnt) ? 1 : 0;
byte current1 = (lows1 < cnt) ? 1 : 0;
byte current2 = (lows2 < cnt) ? 1 : 0;
byte current3 = (lows3 < cnt) ? 1 : 0;
byte all = (current3 << 3) | (current2 << 2) | (current1 << 1) | current0;
if (index == 0 || index == 2 || index == 5 || index == 17 || index == 29) {
// should be a white frame
if (all != 0x0F) return index;
} else if (index == 1 || index == 3 || index == 4) {
// should be a black frame
if (all != 0x00) return index;
} else if (index > 5 && index < 17) {
// shift in x
x0 = (x0 << 1) | current0;
x1 = (x1 << 1) | current1;
x2 = (x2 << 1) | current2;
x3 = (x3 << 1) | current3;
} else if (index > 17 && index < 29) {
// shift in y
y0 = (y0 << 1) | current0;
y1 = (y1 << 1) | current1;
y2 = (y2 << 1) | current2;
y3 = (y3 << 1) | current3;
}
}
Serial.print(grayToBinary(x0));
Serial.print(",");
Serial.print(grayToBinary(y0));
Serial.print(",");
Serial.print(grayToBinary(x1));
Serial.print(",");
Serial.print(grayToBinary(y1));
Serial.print(",");
Serial.print(grayToBinary(x2));
Serial.print(",");
Serial.print(grayToBinary(y2));
Serial.print(",");
Serial.print(grayToBinary(x3));
Serial.print(",");
Serial.print(grayToBinary(y3));
Serial.println();
return 30;
}
void setup()
{
// serial port speed - must match computers settings
Serial.begin(115200);
#if FASTADC
// set prescale to 16
sbi(ADCSRA,ADPS2);
cbi(ADCSRA,ADPS1);
cbi(ADCSRA,ADPS0);
#endif
}
void loop()
{
if (Serial.available()) {
char c = Serial.read();
if (c == 'l') {
level();
} else if (c == 's') {
byte cnt = sample();
if (cnt < 30) {
Serial.print("-1,");
Serial.print((int)cnt);
Serial.print(",0,0,0,0,0,0");
Serial.println();
}
}
}
}
Processing/Java example code:
/**
* GrayCodeImage.java
*
* James A. Ferwerda and Lars Schumann
* More information at: https://larsi.org/projects/ProjectorCalibration/
*/
package org.larsi.jim;
import org.larsi.codes.Binary2Gray;
import org.larsi.protocols.serial.Serial;
import processing.core.PApplet;
import processing.core.PImage;
@SuppressWarnings("serial")
public class GrayCodeImage extends PApplet
{
/** serial port speed - must match Arduino settings */
static int BAUD = 115200;
/** true means gray code, false means binary code */
static boolean doImage = false;
/** true means gray code, false means binary code */
static boolean grayCode = true;
static int x0 = 0;
static int y0 = 0;
static int x1 = 0;
static int y1 = 0;
static int x2 = 0;
static int y2 = 0;
static int x3 = 0;
static int y3 = 0;
static Serial serial;
static PImage img;
public static String getLine()
{
String temp = null;
while ((temp = serial.readStringUntil('\n') ) == null) {
}
return temp.replaceAll("[\n\r]", "");
}
/** Processing: setup() */
@Override
public void setup()
{
size(1024, 768);
//size(1024, 768, P3D);
frameRate(3);
serial = new Serial(null, Serial.getSerialPort(), BAUD);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
img = loadImage("berlin-1.jpg");
}
/** Processing: draw() */
@Override
public void draw()
{
final int f = (frameCount - 1) % 50;
// start sampling
if (f == 0)
serial.write('s');
if (f == 0 || f == 2 || f == 5 || f == 17 || f == 29) {
// white frame
background(0xFFFFFFFF);
} else if (f == 1 || f == 3 || f == 4 || f > 46) {
// black frame
background(0xFF000000);
} else if (f > 5 && f < 17) {
background(0xFF000000);
// shifting x out
int i = 16 - f;
int mask = 1 << i;
for (int x = 0; x < width; x++) {
int g = grayCode ? Binary2Gray.binaryToGray(x) : x;
stroke((g & mask) > 0 ? 0xFFFFFFFF : 0xFF000000);
line(x, 0, x, height);
}
} else if (f > 17 && f < 29) {
background(0xFF000000);
// shifting y out
int i = 28 - f;
int mask = 1 << i;
for (int y = 0; y < height; y++) {
int g = grayCode ? Binary2Gray.binaryToGray(y) : y;
stroke((g & mask) > 0 ? 0xFFFFFFFF : 0xFF000000);
line(0, y, width, y);
}
} else if (f == 30) {
// collecting results
background(0xFF000000);
String[] elements = getLine().split(",");
if (elements.length != 8)
System.out.println("???");
x0 = Integer.parseInt(elements[0]);
y0 = Integer.parseInt(elements[1]);
x1 = Integer.parseInt(elements[2]);
y1 = Integer.parseInt(elements[3]);
x2 = Integer.parseInt(elements[4]);
y2 = Integer.parseInt(elements[5]);
x3 = Integer.parseInt(elements[6]);
y3 = Integer.parseInt(elements[7]);
System.out.println(x0 + "," + y0);
System.out.println(x1 + "," + y1);
System.out.println(x2 + "," + y2);
System.out.println(x3 + "," + y3);
} else {
// show found corner points
background(0xFF000000);
stroke(0xFFFFFFFF);
if (doImage) {
beginShape();
texture(img);
vertex(x0, y0, 0, 0);
vertex(x1, y1, img.width, 0);
vertex(x3, y3, img.width, img.height);
vertex(x2, y2, 0, img.height);
endShape();
}
line(x0, y0, x1, y1);
line(x1, y1, x3, y3);
line(x3, y3, x2, y2);
line(x2, y2, x0, y0);
}
// save calibration image
//if (f < 30) save((grayCode ? "gray" : "binary") + "_" + f + ".png");
}
}
Watch (running time 00:20)
This movie shows the first test, with the fiber directly on the screen.



