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.