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
178
179
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
|
package com.hackingroomba.roombacomm;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.lang.Thread;
import java.util.*;
import java.lang.Math;
public class FrameProcessor {
private static final long serialVersionUID = 1L;
private static final int lumBufHeight = 32;
// socket variables
int portNum = 5005;
Socket videoSocket;
BufferedInputStream in;
BufferedOutputStream out;
RoboRealmAPI rr;
// video variables
byte[] readBuf; // where we read raw network data into
byte[] vidBuf; // raw pixel bytes
int[] vidDispBuf, sliceBufInt;
int[] lumBuf = new int[256*lumBufHeight];
int[] qDisplayBuf, q; // the quantized array, qDisplayBuf is video data, q is 0 or 1 in each int
int[] ltBuf; // the graphic markers for current middle & bundaries of tracking
String server = "";
int frameSize;
int imgWidth;
int imgHeight;
Container contentPane;
Image img, lumImg, quantizedImg, sliceImg, trackingImg;
JFrame frame;
Insets insets;
int qStartRow, qEndRow;
int thresholdOverride = 0;
int frameCount = 0;
// line variables. Units are pixels distance from center of image. Negative value is left of center
class LineBoundary {
int start;
int middle;
int end;
};
ArrayList<LineBoundary> lineList;
LineBoundary currentLine;
int first;
int[] smoothSlice; // a quantized slice across the image, averaged to remove noise
int minLineThickness, maxLineThickness;
public FrameProcessor(String server,int portnum, int width, int height, int minLine, int maxline, int thresholdOv)
{
imgWidth = width;
imgHeight = height;
minLineThickness = minLine;
maxLineThickness = maxline;
lineList = new ArrayList<LineBoundary>();
currentLine = new LineBoundary();
first = 1;
smoothSlice = new int[width];
ltBuf = new int[4*imgWidth];
thresholdOverride = thresholdOv;
this.server = server;
this.portNum = portnum;
}
public void makeImagePane()
{
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
void createAndShowGUI() {
//Create and set up the window.
frame = new JFrame("Raw Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT, 20, 20));
//Display the window.
frame.pack();
frame.setVisible(true);
insets = frame.getInsets();
frame.setMinimumSize(new Dimension(400,350));
}
public void displayFrame()
{
Graphics g = frame.getGraphics();
// display the raw image
img = frame.createImage(new MemoryImageSource(imgWidth,imgHeight,vidDispBuf, 0, imgWidth));
g.drawImage(img, insets.left+2, insets.top+2, null);
// display the slice
sliceImg = frame.createImage(new MemoryImageSource(imgWidth, qEndRow-qStartRow, sliceBufInt, 0, imgWidth));
g.drawImage(sliceImg, insets.left, insets.top + 20 + imgHeight, null);
// display the quantized image
quantizedImg = frame.createImage(new MemoryImageSource(imgWidth, qEndRow-qStartRow, qDisplayBuf, 0, imgWidth));
g.drawImage(quantizedImg, insets.left, insets.top + 40 + imgHeight, null);
// display the line tracking markers
trackingImg = frame.createImage(new MemoryImageSource(imgWidth, 4, ltBuf, 0, imgWidth));
g.drawImage(trackingImg, insets.left, insets.top + 60 + imgHeight, null);
//lumImg = frame.createImage(new MemoryImageSource(256,lumBufHeight,lumBuf, 0, 256));
//g.drawImage(lumImg, insets.left, insets.top + 20 + imgHeight, null);
}
public Boolean frame2Roborealm()
{
return rr.setImage(vidBuf, imgWidth, imgHeight);
}
public String getShapeData()
{
String rrString = rr.getVariable("SHAPES");
//System.out.println(rrString);
return rrString;
}
public void connect(boolean connectRoborealm)
{
try {
videoSocket = new Socket(server, portNum);
in = new BufferedInputStream(videoSocket.getInputStream());
out = new BufferedOutputStream(videoSocket.getOutputStream());
} catch (UnknownHostException e) {
System.out.println("Unknown host: " + server + ":" + portNum);
System.exit(-1);
} catch(IOException e) {
System.out.println("I/O exception");
e.printStackTrace();
System.exit(-1);
}
System.out.println("connected to video");
// connect to RoboRealm if requested
if (connectRoborealm) {
rr = new RoboRealmAPI();
if (!rr.connect("localhost"))
{
System.out.println("Could not connect to RoboRealm on localhost! Exiting...");
System.exit(-1);
}
}
}
public void disconnect()
{
try {
// do io streams need to be closed first?
if (in != null) in.close();
} catch (Exception e) {
e.printStackTrace();
}
in = null;
try {
if (videoSocket != null) videoSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
videoSocket = null;
System.out.println("disconnected from Video");
}
/**
* Read a video frame from the network device
* @param width in pixels
* @param height in pixels
* @param captureType 0 = monochrome, 1 = rgb color
* @return number of pixels read or negative for error during read (e.g. size doesn't match frame size)
*/
public int readFrame(int width, int height, int captureType)
{
int bytesPerPixel = 1;
int readLength = 0;
int rxImageSize;
int rgbShiftSize = 16;
int pixel = 255 << 24;
if (captureType == 1) // if rgb color, 3 bytes/pixel, otherwise 1 for grayscale
bytesPerPixel = 3;
frameSize = width * height * bytesPerPixel;
int maxReadSize = frameSize;
readBuf = new byte[frameSize]; // analysis form - temp buffer for raw received data
vidBuf = new byte[frameSize]; // raw image bytes
vidDispBuf = new int[frameSize]; // display version (alpha set, bytes replicated if needed)
int vidDispBufIx = 0;
int vidBufIx = 0;
readBuf[0] = (byte)(200 + captureType); // send the "capture" command
byte[] t = new byte[4];
try {
out.write(readBuf, 0, 1);
out.flush();
readLength = in.read(t, 0, 4); // read imgWidth
if (readLength != 4)
return -4;
//System.out.println(t[0] + " " + t[1] + " " + t[2] + " " + t[3]);
imgWidth = ((t[2] << 8) & 0xff00) | t[3] & 0xff; // I think this way of getting byte to int is broken
if (imgWidth != width)
return -1;
readLength = in.read(t, 0, 4); // read imgHeight
//System.out.println(t[0] + " " + t[1] + " " + t[2] + " " + t[3]);
if (readLength != 4)
return -5;
imgHeight = (int)(t[2] & 0xff) << 8 | t[3] & 0xff;
//System.out.println(t[0] + " " + t[1] + " " + t[2] + " " + t[3]);
if (imgHeight != height)
return -2;
readLength = in.read(t, 0, 4); // read imgLength
if (readLength != 4)
return -6;
rxImageSize = ((t[1] << 16) & 0xff0000) | ((t[2] << 8) & 0xff00) | (int)t[3] & 0xff;
//System.out.println(t[0] + " " + t[1] + " " + t[2] + " " + t[3]);
if (rxImageSize != frameSize)
return -3;
//System.out.println("Chumby reports image size " + imgWidth + "x" + imgHeight + " length " + rxImageSize + " for frameSize " + frameSize);
// ACHTUNG - readBuf gets overwritten at the beginning by multiple read buffers - use vidDispBuf or vidBuf for image
for (int i=0; i<50; i++) {
readLength = in.read(readBuf, 0, maxReadSize);
maxReadSize -= readLength;
for (int j=0; j<readLength; j++) {
vidBuf[vidBufIx++] = readBuf[j]; // copy video bytes into the video buffer
if (captureType == 0) {
// copy pixel luminance into r, g, b bytes in vidDispBuf & set alpha to opaque
vidDispBuf[vidDispBufIx++] = (255 << 24) | (int)(readBuf[j] & 0xff) << 16
| (int)(readBuf[j] & 0xff) << 8 | (readBuf[j] & 0xff);
vidBuf[vidBufIx++] = readBuf[j];
} else {
switch (rgbShiftSize) {
case 16:
pixel |= (readBuf[j] << rgbShiftSize) & 0xff0000;
rgbShiftSize = 8;
break;
case 8:
pixel |= (readBuf[j] << rgbShiftSize) & 0xff00;
rgbShiftSize = 0;
break;
case 0:
pixel |= (readBuf[j]) & 0xff;
vidDispBuf[vidDispBufIx++] = pixel;
pixel = 255 << 24;
rgbShiftSize = 16;
break;
default:
System.err.println("Illegal value for rgbShiftSize " + rgbShiftSize);
return -100;
}
}
}
if (maxReadSize == 0)
break;
try {
Thread.sleep(20);
//System.out.println("Have " + vidBufIx + ", trying again");
} catch (Exception e) {
System.out.print(e);
}
}
} catch (IOException e) {
System.out.println("I/O exception");
e.printStackTrace();
System.exit(-1);
}
//System.out.println("readFrame read " + frameSize + " bytes");
System.out.print(".");
frameCount++;
if (frameCount%128 == 0)
System.out.println();
return (vidDispBufIx);
}
/*
* Develop a normalization array that compensates for illumination variations in each voxel.
* This method returns an array of doubles, one per voxel, containing the factor by which each
* voxel must be multiplied to normalize its value to 0xc0. getFrame must have been called with the
* camera looking at white before this is called. It processes vidDispBuf at the requested rows.
*/
public double [] normalizeRows(int startRow, int endRow)
{
int start, end;
assert (endRow <= startRow);
start = startRow * imgWidth;
end = endRow * imgWidth;
double [] normalizeArray = new double[end - start];
for (int i=start; i<end; i++) {
normalizeArray[i] = 192.0 / (double)(vidDispBuf[start + i]);
}
return (normalizeArray);
}
/*
* quantize the start row through end row-1 (looking down the image)
* See UTD Prof Schweitzer's notes on "Thresholding by Quantization"
*/
public int quantizeRows (int startRow, int endRow)
{
assert (endRow <= startRow);
qStartRow = startRow;
qEndRow = endRow;
return(quantize(startRow*imgWidth, endRow*imgWidth));
}
// take a start & end offset into the readBuf array
private int quantize(int start, int end) {
int[] h, xh; // the histogram of the slice, and x * histogram
int[] q1, q2, e; // array of possible quantization values & Error for each t
int t, tMin, tMax;
int eMin, threshold;
int sigmaXhQ1, sigmaHQ1, sigmaXhQ2, sigmaHQ2;
int sigmaXQ1H, sigmaXQ2H;
int x;
int qtmp;
if (end <= start) {
System.out.println("error: quantize end < start");
System.exit(0);
}
// array values zeroed on create
h = new int[256];
xh = new int[256];
q1 = new int[256];
q2 = new int[256];
e = new int[256]; // E term (error) at t value
qDisplayBuf = new int[(end-start)];
q = new int[(end-start)];
sliceBufInt = new int[(end-start)];
// mark the live image with start/end
for (int i=(start-imgWidth); i<start; i++) {
if (i < 0) break;
vidDispBuf[i] = 0xff<<24 | 0xff;
}
for (int i=end; i<(end+imgWidth); i++) {
vidDispBuf[i] = 0xff<<24 | 0xff;
}
// build the histogram
for (int i=(start), j=0; i<(end); i++, j++) {
x = (int)(vidDispBuf[i])& 0xff;
h[x]++; // increment the appropriate histogram bucket for this image value
sliceBufInt[j] = x | ((x<<8)&0xff00) | ((x<<16)& 0xff0000) | (0xff<<24);
}
// calculate x * h(x) & store in xh, and print histogram values for testing
//System.out.println("Histogram: x, h, xh");
tMin = 0;
tMax = 0;
for (int j=0; j<h.length; j++) {
xh[j] = j * h[j];
if (tMin == 0) { // initialize tMin to the next t value after the first non-zero histogram bucket (avoid divide-by-zero)
if (h[j] != 0)
tMin = j+1;
} else {
if (h[j] != 0) {
tMax = j-1;
}
}
//if (h[j] != 0) System.out.println(j + "\t" + h[j] + "\t" + xh[j]);
}
if ((tMax - tMin) < 3) {
System.out.println("Error: image is too uniform in value - abandoning quantization");
return(-1);
}
// build arrays of q1, q2. Start summation at the first non-zero histogram index
t = tMin;
sigmaXhQ1 = xh[tMin-1];
sigmaHQ1 = h[tMin-1];
// initialize the q2 summations
sigmaXhQ2 = 0;
sigmaHQ2 = 0;
for (x=tMin; x<256; x++) {
sigmaXhQ2 += xh[x];
sigmaHQ2 += h[x];
}
// calculate q1 & q2 arrays for t = 1 to t = 254
do {
q1[t] = sigmaXhQ1 / sigmaHQ1;
q2[t] = sigmaXhQ2 / sigmaHQ2;
sigmaXhQ1 += xh[t]; // incrementing t means sigma**Q1 gets one more histogram value, and
sigmaHQ1 += h[t]; // sigma**Q2 loses that same one
sigmaXhQ2-= xh[t];
sigmaHQ2 -= h[t];
if (sigmaXhQ2 == 0) { // if we reach the highest luminance value, set tMax & bail
tMax = t;
break;
}
t++;
} while (t<255);
// calculate e array for t=1 to t=254
//System.out.println("\nt\tq1\tq2\tsgmQ1H\tsgmQ2H\te"); // print the header for diagnostic prints
for (t=tMin; t<tMax; t++) {
for (x=tMin-1, qtmp = q1[t], sigmaXQ1H=0; x<t; x++) {
sigmaXQ1H += (Math.pow((x - qtmp),2)) * h[x];
}
for (x=t, qtmp = q2[t], sigmaXQ2H=0; x<tMax; x++) {
sigmaXQ2H += (java.lang.Math.pow((x - qtmp),2)) * h[x];
}
e[t] = sigmaXQ1H + sigmaXQ2H;
//System.out.println(t + "\t" + q1[t] + "\t" + q2[t] + "\t" + sigmaXQ1H + "\t" + sigmaXQ2H + "\t" + e[t]);
}
// find minimum e & corresponding t
eMin = (int)2E9; // close to max positive number
threshold = 1;
for (t=tMin; t<tMax; t++) {
if (e[t] < eMin) {
eMin = e[t];
threshold = t;
}
}
//System.out.println("Threshold = " + threshold + " q1 = " + q1[t] + " q2 = " + q2[t] + "\n");
// Create the quantized image
x = 0;
if (thresholdOverride != 0)
threshold = thresholdOverride;
for (int srcIx=start; srcIx<end; srcIx++, x++) {
qDisplayBuf[x] = ((vidDispBuf[srcIx] & 0xff) < threshold) ? 0xff<<24 : -1; // assign quantized values to each pixel
q[x] = ((vidDispBuf[srcIx] & 0xff) < threshold) ? 0 : 1; // assign quantized values to each pixel
}
return(0);
}
/*
* segment the image into lines by doing an initial smoothing & averaging which ignores regions of black with fewer than 3
* black pixels in a 4-pixel vertical line. This produces a 1-line array of ints representing black or white at that
* portion of the image (called a smoothSlice). Then scan the smoothSlice and extract white-black-white transitions
* into an array container of found line boundaries.
* Note: this is designed to track black on white, but this is where tracking white on black would be supported
*/
public int segmentImage()
{
int off1, off2, off3; // offsets into quantized array
boolean inBlack = false; // initially assume we're in white (virtual white at beginning of slice)
int blackStart, blackEnd;
lineList.clear(); // clear out previous lines
// average 4 vertical pixels to decide whether this point of the slice is white or black
off1 = imgWidth;
off2 = imgWidth * 2;
off3 = imgWidth * 3;
for (int i=0; i<imgWidth; i++) {
int whiteCnt = q[i] + q[i+off1] + q[i+off2] + q[i+off3];
if (whiteCnt < 3)
smoothSlice[i] = 0;
else
smoothSlice[i] = 1;
}
// scan the slice & pick out the black regions > minLineThickness & create a LineBoundary for each
blackStart = blackEnd = 0;
for (int i=0; i<imgWidth; i++) {
if (inBlack) { // we're in a black part of the image (can never happen on element 0)
if ((smoothSlice[i] == 1) || (i == imgWidth-1)) { // were in black, just transitioned to white, or end of array
blackEnd = i-1;
inBlack = false;
if (((blackEnd - blackStart) > minLineThickness) && ((blackEnd - blackStart) < maxLineThickness)) {
LineBoundary lb = new LineBoundary();
lb.start = blackStart - (imgWidth/2);
lb.end = blackEnd - (imgWidth/2);
lb.middle = ((lb.start + lb.end)/2);
lineList.add(lb);
} // else ignore this as a false line (noise) - we're in white now
}
} else { // inWhite
if (smoothSlice[i] == 0) {
inBlack = true; // were in white, just transitioned to black (can happen on element 0)
blackStart = i;
}
}
}
// print found lines
// System.out.print("Found " + lineList.size() + " lines: ");
// for (LineBoundary l : lineList) {
// System.out.print(l.start + " " + l.middle + " " + l.end + " ");
// }
// System.out.println();
return(0);
}
/*
* Find the line we should be following. The very first time this runs, or if it loses the line & backs up
* it will pick the line closest to center. Thereafter it chooses the first found line who's center is within
* the boundaries of the last line it chose. If it can't find one, it returns an error. Therefore it will always take
* a left fork. It returns the middle value of the chosen line, or a large value if error.
*/
public int trackLine()
{
int m;
LineBoundary lTmp = new LineBoundary();
// clear out current markers in the tracking display
m = currentLine.middle + imgWidth/2;
ltBuf[m + 3*imgWidth] = ltBuf[m + 2*imgWidth] = ltBuf[m + imgWidth] = ltBuf[m] = 0;
if (lineList.size() == 0) {
if (currentLine.middle >= 0) {
System.out.println("Error: line disappeared to right, last seen at " + currentLine.middle);
return(100);
} else {
System.out.println("Error: line disappeared to left, last seen at " + currentLine.middle);
return (-100);
}
}
if (first == 1) {
lTmp.middle = 100; // any found line will be closer than this
for (LineBoundary lb : lineList) {
if (Math.abs(lb.middle) < Math.abs(lTmp.middle)) {
lTmp = lb; // save the new lineBoundary with the lowest absolute value of middle
}
}
currentLine = lTmp;
first = 0;
System.out.print("Picked line center at " + lTmp.middle);
return(lTmp.middle);
} else {
if (lineList.size() == 1) {
System.out.println("tracking line center at " + lineList.get(0).middle + " width: " + (lineList.get(0).end-lineList.get(0).start));
currentLine = lineList.get(0);
return (lineList.get(0).middle);
}
for (LineBoundary lb : lineList) {
if ((lb.middle > currentLine.start-20) && (lb.middle < currentLine.end + 20)) {
System.out.println("tracking line center at " + lb.middle + " width: " + (lb.end-lb.start));
currentLine = lb;
// write the image of the new currentLine middle
m = lb.middle+(imgWidth/2);
ltBuf[m] = 0xff<<24 | 0xff<<16;
ltBuf[m + 3*imgWidth] = ltBuf[m + 2*imgWidth] = ltBuf[m + imgWidth] = ltBuf[m];
return (lb.middle);
}
}
}
System.out.println("Error: Lost the line I was tracking");
first = 1;
return(2001);
}
public void testQuantization()
{
byte[] testArray1 =
{6, 6, 6, 10,
6, 6, 6, 10,
17, 17, 17, 17,
17, 17, 17, 88};
for (int i=0; i<testArray1.length; i++) {
readBuf[i] = testArray1[i];
}
quantize(0, testArray1.length);
}
void dumpVideo(int row)
{
System.out.print("row " + row + " ");
for (int col=0; col<160; col++) {
System.out.print((int)readBuf[row*160 + col] + " ");
}
System.out.println();
}
}
|