Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* Using a camera, approximate the 2-D location of the area illuminated by each LED.
* Instead of illuminating each LED in sequence, this uses a binary pattern to quickly
* identify large numbers of LEDs with few test patterns.
*
* This is still really experimental! Currently it's just a minimal proof-of-concept :)
*
* Micah Elizabeth Scott, 2013
*/
import processing.video.*;
//int MAX_PIXELS = 0xFFFF / 3; // Maximum number of pixels
int MAX_PIXELS = 16;
int BRIGHTNESS = 130; // How bright should our test pattern be?
float MSE_THRESHOLD = 30; // Mean squared error below which frames are "identical"
int S_WAITING = 0;
int S_START_BIT = 1;
int S_SETTLE_BIT = 2;
int S_FINALIZE = 3;
int state;
int bit;
int numBitsInUse;
OPCLowLevel opc;
Capture video;
int[][] bitImages;
void setup()
{
size(640, 480);
video = new Capture(this, width, height);
video.start();
bitImages = new int[16][width * height];
opc = new OPCLowLevel("127.0.0.1", 7890, MAX_PIXELS);
opc.firmwareConfig = 0x07; // Disable dithering, interpolation, and status LED
}
void allPixelsOff()
{
for (int i = 0; i < MAX_PIXELS; i++) {
opc.setPixel(i, 0, 0, 0);
}
opc.sendPixels();
}
void sendBitPattern()
{
// Send an LED pattern in which red/blue correspond to 1/0 for the indicated bit position.
for (int i = 0; i < MAX_PIXELS; i++) {
boolean b = 0 != ((i >> bit) & 1);
opc.setPixel(i, b ? BRIGHTNESS : 0, 0, b ? 0 : BRIGHTNESS);
}
opc.sendPixels();
}
void keyPressed()
{
if (key == ' ' && state == S_WAITING) {
// Start!
bit = 0;
state = S_START_BIT;
}
if (keyCode == DELETE || keyCode == BACKSPACE) {
// Start over
state = S_WAITING;
}
}
void sText(String s, int x, int y)
{
// Text with a shadow
fill(0);
text(s, x+2, y+2);
fill(255);
text(s, x, y);
}
float frameDifference(int[] a, int[] b)
{
// Mean squared distance between frames
float total = 0;
for (int i = 0; i < a.length; i++) {
int aPixel = a[i];
int bPixel = b[i];
int diffR = ((aPixel >> 16) & 0xFF) - ((bPixel >> 16) & 0xFF);
int diffG = ((aPixel >> 8) & 0xFF) - ((bPixel >> 8) & 0xFF);
int diffB = (aPixel & 0xFF) - (bPixel & 0xFF);
total += diffR*diffR + diffG*diffG + diffB*diffB;
}
return total / (width * height);
}
PVector bitSimilarityImage(int[] output, int pattern)
{
/*
* Generate an image which maps how similar each pixel is, across all bits, to
* the indicated bit pattern. One way to think of this: we're calculating a
* probability that each pixel in question was in fact pointed at the pattern
* we're looking for. Each bit gives us a single probability, and those probabilities
* are multiplied together to give a pattern probability.
*
* Returns the weighted centroid of the image.
*/
float xTotal = 0, xWeight = 0;
float yTotal = 0, yWeight = 0;
for (int i = 0; i < output.length; i++) {
int x = i % width;
int y = i / width;
float probability = 1.0;
for (int b = 0; b < numBitsInUse; b++) {
int pixel = bitImages[b][i];
boolean patternBit = 1 == ((pattern >> b) & 1);
// We might want to do something smarter here, like histogram backprojection
// based on actual colors sampled from the device. This is pretty stupid.
int diffR = (patternBit ? 0xFF : 0x00) - ((pixel >> 16) & 0xFF);
int diffB = (patternBit ? 0x00 : 0xFF) - (pixel & 0xFF);
probability /= 1 + (diffR*diffR + diffB*diffB) * 1e-4;
}
// Update weighted centroid
float w = probability * probability;
xTotal += x * w;
xWeight += w;
yTotal += y * w;
yWeight += w;
int gray = constrain(int(probability * 1e3), 0, 255);
output[i] = gray | (gray << 8) | (gray << 16);
}
return new PVector(xTotal / xWeight, yTotal / yWeight);
}
void showFrameSequence()
{
// Show our captured frames in sequence
int i = (millis() / 500) % numBitsInUse;
loadPixels();
arrayCopy(bitImages[i], pixels);
updatePixels();
textSize(25);
sText("Bit " + i, 10, 30);
}
void showPatternSequence()
{
// Show pattern similarity measurements in sequence
int i = (millis() / 500) % MAX_PIXELS;
loadPixels();
PVector peak = bitSimilarityImage(pixels, i);
updatePixels();
textSize(25);
sText("Similarity to pattern " + i, 10, 30);
// Draw crosshairs at the peak probability location
stroke(128, 255, 128);
int size = 100;
line(peak.x, peak.y - size, peak.x, peak.y + size);
line(peak.x - size, peak.y, peak.x + size, peak.y);
}
void draw()
{
if (state == S_FINALIZE) {
if (mousePressed)
showFrameSequence();
else
showPatternSequence();
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
return;
}
if (!video.available())
return;
video.read();
image(video, 0, 0, width, height);
video.loadPixels();
textSize(25);
if (state == S_WAITING) {
sText("Press [SPACE] to start mapping", 10, 30);
allPixelsOff();
} else {
sText("Mapping bit " + bit + "...", 10, 30);
}
textSize(16);
if (state == S_START_BIT) {
// Update the LEDs, and discard this video frame.
sendBitPattern();
state = S_SETTLE_BIT;
} else if (state == S_SETTLE_BIT) {
// Keep capturing frames until the frames are similar enough.
// This gives the camera's brightness control time to settle.
float d = frameDifference(video.pixels, bitImages[bit]);
sText("Settling... MSE: " + d, 10, 60);
arrayCopy(video.pixels, bitImages[bit]);
if (d < MSE_THRESHOLD) {
// Settled enough! Ready to move on.
if (((MAX_PIXELS - 1) >> (bit + 1) == 0)) {
// No more pixels possible beyond this bit. We're done.
numBitsInUse = bit + 1;
state = S_FINALIZE;
} else {
// More bits
bit++;
state = S_START_BIT;
}
}
}
}