Appendix A - ttt_vision.c
1: /*ttt_vision.c
2:
3: This is the main source file for the tic-tac-toe vision system.
4:
5: Programmer: Michael Zalokar
6: Date: Spring 2000
7: */
8: /*The calculation of the corners is done in the following way. The first
9: two corners are found first and named corner1 and corner2. The second
10: set of corners are found next, named corner3 and corner4. When all four
11: are found the two sets are diagnols in the object. The corners can be in
12: any orientation to each other, but they will always have the same two
13: corners adjacent and the same one as the diagnol.
14:
15: When the ttt board has been singled out from all of the objects in the
16: list, the corners are assigned into the board array as follows. The
17: assignment to the array occurs inside of the detect_ttt() function. The
18: array subscripts are listed in row major form.
19:
20: corner1
21: ___________________________ corner4
22: |[0][0] |[0][1] |[0][2] |[0][3]
23: | | | |
24: | | | |
25: | | | |
26: ----------------------------
27: |[1][0] |[1][1] |[1][2] |[1][3]
28: | | | |
29: | | | |
30: | | | |
31: ----------------------------
32: |[2][0] |[2][1] |[2][2] |[2][3]
33: | | | |
34: | | | |
35: |corner3 | | |
36: ---------------------------- corner2
37: [3][0] [3][1] [3][2] [3][3]
38:
39: The pieces array is alligned in the same way.
40:
41: Since, the corners are calculated from the chaincodes, there will always
42: the unique property that the 4th corner will be the farthest point from
43: the 1st and 2nd corners between these two points in the chaincode. The
44: third corner is either before the first corner in the chaincode or
45: after the second corner in the chaincode.
46:
47: The reason that the corners are determined using distance vs. using slope
48: is becuase, distance doesn't have a situation where the result is
49: undefined.
50:
51: I used the _OpenGL Programming for the X Window System_ "green book", _OpenGL
52: Programming Guide_ "red book" and the _OpenGL Reference Manual_ "blue book"
53: to write the OpenGL/glut windowing code.
54:
55: For the multithreading and socket programming I used the _Pthreads
56: Programming_ "catapiller book" and _UNIX systems programming for SVR4_
57: "lion book" from O'Reilly.
58: */
59:
60:
61: #include < stdio.h >
62: #include < string.h >
63: #include < math.h >
64: #include < float.h >
65: #include < GL/glut.h >
66: #include < pthread.h >
67: #include < sched.h >
68: #include < errno.h >
69: #include < stdlib.h >
70: #ifdef __linux
71: #include < getopt.h >
72: #endif
73:
74: #include "ttt_vision.h"
75: #include "sie_protocol.h"
76: #include "device_type_id_number.h"
77: #include "ttt_vision_protocol.h"
78: #include "shelley_sockets.h"
79: #include "sie_utils.h"
80: #include "colors.h"
81:
82: #define Round(v) ((int)(v+0.5))
83:
84: /*host:port:file:buffer*/
85: #define GETOPTARGS "hf:p:b"
86:
87: /*minimal size to consider an "object" as length of chaincode*/
88: #define DECENT_SIZED_OBJECT 200
89:
90: /*Used by the game detection algorithms. Represent the distance between two
91: known points as a fraction between 0 and 1.*/
92: #define ONE_THIRD (1.0 / 3.0)
93: #define ONE_HALF (1.0 / 2.0)
94: #define ONE_SIXTH (1.0 / 6.0)
95: #define ONE_TWELTH (1.0 / 12.0)
96: #define ONE_24TH (1.0 / 24.0)
97: #define ONE_32ND (1.0 / 32.0)
98:
99: /*the value used to threash the values of the moravec filter to 0 and 255*/
100: /*is the sum of the differences between the center point and its neighbors.*/
101: /*lighted*/
102: /*#define THREASH_CONSTANT 128*/
103: /*unlighted*/
104: #define THREASH_CONSTANT 30
105: /*the pratical ratio of the area searched for edge pixels of the marbles.*/
106: #define EDGE_COUNT 0.25
107: /*Since the Moravec is threashed to 0 and 255 as long as this value is between
108: the two everything will work.*/
109: #define EDGES_FOR_PIECE 128
110:
111: /*the constants that control how many pixels are inserted agains one during
112: the perspective correction*/
113: #define X_DIR_PERS_CORR 1.6
114: #define Y_DIR_PERS_CORR 1.9
115:
116: /*closeness factor to consider to marbles on the same team*/
117: #define DIFFERENCE .5
118:
119: #define DIVIDERS 4
120:
121: /*error on the board to be off of perfectly square*/
122: #define ALLOWABLE_PERCENT_ERROR 7.5
123:
124: /*structure defined as the object type. All identified objects are stored in
125: a struct of this type. This is a very general data type and is not
126: specific to a tic-tac-toe game board.*/
127: typedef struct
128: {
129: coord corner1;
130: coord corner2;
131: coord corner3;
132: coord corner4;
133: }object;
134:
135: /*This struct contains all the relavent information about the location,
136: orientation, etc. of the tic-tac-toe game board itself.*/
137: /*typedef struct
138: {*/
139: coord points[DIVIDERS][DIVIDERS];
140:
141: /*The corners of the board. corner1 and corner2 are always opposite
142: (diagnol), just like corner3 and corner4 too.*/
143: /* object corners;*/
144:
145: /*The sides. The numbers represent the to corners that the point is
146: between. The first number is the corner that it is closest to.*/
147: /* coord side14, side13, side23, side24;
148: coord side31, side32, side41, side42;*/
149:
150: /*The points surrounding the middle square. The number indicates the
151: closest corner.*/
152: /* coord middle1, middle2, middle3, middle4;*/
153: /*}ttt;
154: */
155: /*Evil globals, but not do-able otherwise.*/
156: static PGMImage *img_cur; /*current*/
157: static PGMImage *img_original; /*original*/
158: static PGMImage *img_pers_corr; /*perspective correction*/
159: static PGMImage *img_grayscale;
160: static PGMImage *img_moravec; /*moravec*/
161: /*static*/ int HSIZE; /*width*/
162: /*static*/ int VSIZE; /*height*/
163: /*static*/ int MVAL; /*max val*/
164:
165: /*pointer to a ttt structer containing the board info.*/
166: /*static ttt *ttt_board;*/
167:
168: /*pointer to an array of list_info structs which point to the first, last and
169: current nodes in each of the chain codes.*/
170: list_info* chain_codes;
171:
172: /*pointer to the array of objects found in the image. Counting starts at
173: zero and the array must be less than MAX_CHAINS in size. Unused values
174: should be set to zero.*/
175: static object *all_objects;
176:
177:
178:
179: /*used to determine if the lines connecting the corners should be
180: drawn to the screen. Set to < 0 if no abstract lines are to be drawn.
181: Otherwise is set to the number of objects found (AKA number of chaincodes).*/
182: int draw_abstract_lines = -1;
183:
184: /*used to draw the single object most likely to be considered the
185: tic-tac-toe board. Should be < 0 if this should not be drawn.
186: Should be equal to the chain-code number (from 0 to MAX_CHAINS - 1)
187: if it is to be drawn.*/
188: int draw_abstract_board = -1;
189:
190:
191: /*thread stuff*/
192: pthread_t vision_thread;
193: pthread_t conn_thread;
194: pthread_mutex_t calculate_mutex;/* = PTHREAD_MUTEX_INITIALIZER;*/
195: pthread_mutex_t images_mutex;/* = PTHREAD_MUTEX_INITIALIZER;*/
196:
197: /*command line variables*/
198: char *PGMfileName; /*string containing the filename*/
199: char *hostname;
200: int port_number;
201: int is_buffered; /* Is set to TRUE or FALSE.*/
202:
203: int socket_num;
204:
205: /*array of where the pieces are*/
206: RGB_INT *data;
207: unsigned char pieces[3][3];
208:
209: bool redraw_needed = 1;
210:
211: /*do be able to determine which team is which color when only one move has been
212: made, this variable stores that color until the second move is made to
213: compare colors*/
214: int team_first;
215:
216: /*boolean toggle for displaying time data*/
217: bool display_time = TRUE;
218: /*boolean toggle for displaying verbose text*/
219: bool display_verbose = FALSE;
220:
221: /**************Time Display funcition************************************/
222: /********************************************************************/
223: /*1st: initial time set by gettimeofday()
224: 2nd: final time set by gettimeofday();
225: 3rd: text message of the type of calculation timing*/
226: void do_time(struct timeval t0, struct timeval t1, char *message)
227: {
228: double elapsed = t1.tv_sec - t0.tv_sec;
229:
230: if(!display_time)
231: return;
232:
233: if(t1.tv_usec > t0.tv_usec)
234: elapsed += (t1.tv_usec - t0.tv_usec) / 1000000.0;
235: else
236: elapsed += (1000000.0 - abs(t1.tv_usec - t0.tv_usec)) / 1000000.0;
237:
238: if(!message)
239: printf("Processing took: %f seconds\n", elapsed);
240: else
241: printf("Processing %s took: %f seconds\n", message, elapsed);
242: }
243:
244: /**************Drawing funcitions************************************/
245: /********************************************************************/
246:
247: /*This function draws a single pixel to the screen. (0, 0) is the lower left
248: corner.*/
249: /*1st: Integer of the x pixel to be drawn.
250: 2nd: Integer of the y pixel to be drawn.
251: 3rd: RGB values as stored as integral types with values between 0 - 255.*/
252: void setCPixel(int ix, int iy, RGB_INT color)
253: {
254: float x = (ix*2.0)/HSIZE - 1.0;
255: float y = (iy*2.0)/VSIZE - 1.0;
256:
257: float red = (float)color.red/(float)MVAL;
258: float green = (float)color.green/(float)MVAL;
259: float blue = (float)color.blue/(float)MVAL;
260:
261: glColor3f(red, green, blue);
262:
263: glBegin(GL_POINTS);
264: glVertex2f (x, y);
265: glEnd();
266: }
267:
268: /*This function draws a line segment to the screen. (0, 0) is the lower
269: left corner.*/
270: /*1st: x coord. of the 1st endpoint.
271: 2nd: y coord, of the 2nd endpoint.
272: 3rd: x coord. of the 2st endpoint.
273: 4th: y coord. of the 2st endpoint.
274: 5th: RGB values as stored as integral types with values between 0 - 255.*/
275: void setCLines(int ix1, int iy1, int ix2, int iy2, RGB_INT color)
276: {
277: float x1 = (ix1*2.0)/HSIZE - 1.0;
278: float y1 = (iy1*2.0)/VSIZE - 1.0;
279: float x2 = (ix2*2.0)/HSIZE - 1.0;
280: float y2 = (iy2*2.0)/VSIZE - 1.0;
281:
282: float red = (float)color.red/(float)MVAL;
283: float green = (float)color.green/(float)MVAL;
284: float blue = (float)color.blue/(float)MVAL;
285:
286: glColor3f(red, green, blue);
287:
288: glBegin(GL_LINES);
289: glVertex2f (x1, y1);
290: glVertex2f (x2, y2);
291: glEnd();
292: }
293:
294: /*This function draws a square to the screen. (0, 0) is the lower left
295: corner.*/
296: /*1st: x coord. of the 1st endpoint.
297: 2nd: y coord, of the 2nd endpoint.
298: 3rd: x coord. of the 2st endpoint.
299: 4th: y coord. of the 2st endpoint.
300: 5th: RGB values as stored as integral types with values between 0 - 255.*/
301: void setCRect(int ix1, int iy1, int ix2, int iy2, RGB_INT color)
302: {
303: float x1 = (ix1*2.0)/HSIZE - 1.0;
304: float y1 = (iy1*2.0)/VSIZE - 1.0;
305: float x2 = (ix2*2.0)/HSIZE - 1.0;
306: float y2 = (iy2*2.0)/VSIZE - 1.0;
307:
308: float red = (float)color.red/(float)MVAL;
309: float green = (float)color.green/(float)MVAL;
310: float blue = (float)color.blue/(float)MVAL;
311:
312: glColor3f(red, green, blue);
313:
314: glBegin(GL_POLYGON);
315: glVertex2f (x1, y1);
316: glVertex2f (x1, y2);
317: glVertex2f (x2, y2);
318: glVertex2f (x2, y1);
319: glEnd();
320: }
321:
322: /*draws a string of text to the screen.*/
323: /*1st: The x coord of the lower left corner where it is to be displayed.
324: 2nd: The y coord of the lower left corner where it is to be displayed.
325: 3rd: String that is to be displayed.
326: 4th: The color to display the text in.*/
327: void drawString(int ix, int iy, char theString[256], RGB_INT color)
328: {
329: float x = (ix*2.0)/HSIZE - 1.0;
330: float y = (iy*2.0)/VSIZE - 1.0;
331: int i;
332:
333: float red = (float)color.red/(float)MVAL;
334: float green = (float)color.green/(float)MVAL;
335: float blue = (float)color.blue/(float)MVAL;
336:
337: glColor3f(red, green, blue);
338:
339: glRasterPos2f(x, y);
340: for (i = 0; theString[i] != '\0'; i++) /* draw the chars one at a time */
341: glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, theString[i]);
342: }
343:
344: /*Draws a PGMImage to the window.*/
345: /*1st: The pgm image to display.*/
346: void showColor (PGMImage *img)
347: {
348: int i, j; /*loop counting: i = y, j = x*/
349: /*This is the simplist way I found to use the fast OpenGL direct drawing
350: functions without a major rewrite of the code. First pack the data
351: into this 3D array. If that little bit of extra speed is needed, then
352: the PGMImage functionality would need to be written as a 3D array and not
353: a 2D array of structs.*/
354: GLubyte checkImage[(*img).height][(*img).width][3];
355: int test = 0;
356: for(i = 0; i < (*img).height; i++)
357: {
358: for(j = 0; j < (*img).width; j++)
359: {
360: checkImage[i][j][0] = (GLubyte) (*img).data[i][j].red;
361: checkImage[i][j][1] = (GLubyte) (*img).data[i][j].green;
362: checkImage[i][j][2] = (GLubyte) (*img).data[i][j].blue;
363: if(checkImage[i][j][0] == 0)
364: test++;
365: }
366: }
367:
368: /*tell OpenGL that the values are packed into byte sized values*/
369: glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
370: /*since OpenGL is setup for -1 to 1 size, specify (-1, -1) for the lower
371: left corner which is (0, 0) to most of the program*/
372: glRasterPos2f(-1, -1);
373: /*draw the current image*/
374: glDrawPixels((*img).width, (*img).height, GL_RGB,
375: GL_UNSIGNED_BYTE, checkImage);
376:
377: glFlush();
378: }
379:
380: /*called by showAbstract()*/
381: /*draws the (x, y) values of the coordinates specified at that coordinate*/
382: /*1st: The x coord.
383: 2nd: The y coord.
384: 3rd: Additional text to display, aside from the (x, y) determined from the
385: 1st and 2nd parameters.*/
386: void display_labels(coord point, int row, int col, char *more_text)
387: {
388: char text[40]; /*for displaying the locations in text form*/
389: int offset = 8; /*offset text # pixels from location*/
390: char null_char[1];
391:
392: if(more_text == NULL)
393: {
394: /*give more_text something to point to, to avoid seg. faults*/
395: null_char[0] = '\0';
396: more_text = null_char;
397: }
398:
399: if(row < 0 || col < 0)
400: sprintf(text, "%s (%d, %d)", more_text, point.x, point.y);
401: else
402: sprintf(text, "%s [%d][%d](%d, %d)", more_text, row, col,
403: point.x, point.y);
404: drawString(point.x + offset, point.y + offset, text, blue);
405: }
406:
407: /*display the global abstract data*/
408: void showAbstract(object* object_list, coord ttt_data[DIVIDERS][DIVIDERS])
409: {
410: int i, j;
411:
412: /*when displayeing the chaincodes, this is the temp to do that.*/
413: list_info temp;
414:
415: glPointSize(2); /*make points more visible, if desired*/
416: /*glLineWidth(4);*/
417:
418: /*draw the chaincodes*/
419: /*chain_codes is a global pointer to an array of list_info types*/
420: for(i = 0; (i < MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++)
421: {
422: memcpy(&temp, &chain_codes[i], sizeof(list_info));
423: while(RetrieveNextNode(&temp).cur)
424: {
425: setCPixel(RetrieveInfo(&temp).location.x,
426: RetrieveInfo(&temp).location.y, yellow);
427: Advance(&temp);
428: }
429: }
430:
431: /*first check for non-null pointer, next check for dereferenced pointers
432: in the array of head pointers and lastly make sure things stay in
433: bound of the max incase all MAX_CHAINS number of chains are used.*/
434: /*draw_abstract_lines is the global that holds the number of "objects"
435: to draw abstract information for*/
436: for(i = 0; i < draw_abstract_lines && i < MAX_CHAINS; i++)
437: {
438: setCLines(object_list[i].corner1.x, object_list[i].corner1.y,
439: object_list[i].corner3.x, object_list[i].corner3.y,yellow);
440: setCLines(object_list[i].corner4.x, object_list[i].corner4.y,
441: object_list[i].corner2.x, object_list[i].corner2.y,yellow);
442: setCLines(object_list[i].corner3.x, object_list[i].corner3.y,
443: object_list[i].corner2.x, object_list[i].corner2.y,yellow);
444: setCLines(object_list[i].corner4.x, object_list[i].corner4.y,
445: object_list[i].corner1.x, object_list[i].corner1.y,yellow);
446:
447: setCPixel(object_list[i].corner1.x, object_list[i].corner1.y,dk_red);
448: setCPixel(object_list[i].corner2.x, object_list[i].corner2.y,dk_blue);
449: setCPixel(object_list[i].corner3.x, object_list[i].corner3.y,white);
450: setCPixel(object_list[i].corner4.x, object_list[i].corner4.y,dk_green);
451:
452: /*labels for corner points*/
453: display_labels(object_list[i].corner1, -1, -1, "c1");
454: display_labels(object_list[i].corner2, -1, -1, "c2");
455: display_labels(object_list[i].corner3, -1, -1, "c3");
456: display_labels(object_list[i].corner4, -1, -1, "c4");
457: }
458:
459: /*if there is board to draw, draw it to the screen*/
460: if((draw_abstract_board > -1))
461: {
462: for(i = 1; i < DIVIDERS - 1; i++)
463: {
464: setCLines(points[i][0].x, points[i][0].y, points[i][DIVIDERS - 1].x,
465: points[i][DIVIDERS - 1].y, magenta);
466:
467: setCLines(points[0][i].x, points[0][i].y, points[DIVIDERS - 1][i].x,
468: points[DIVIDERS - 1][i].y, magenta);
469:
470: setCPixel(points[i][0].x, points[i][0].y, red);
471: setCPixel(points[DIVIDERS - 1][i].x, points[DIVIDERS - 1][i].y, red);
472: }
473:
474: /*lables for all ttt board points*/
475: for(i = 0; i < DIVIDERS; i++)
476: for(j = 0; j < DIVIDERS; j++)
477: {
478: display_labels(points[i][j], i, j, NULL);
479: }
480: }
481: glFlush();
482: }
483:
484: /**********************Support functions*******************************/
485: /***********************************************************************/
486:
487: /*take the source pixel and copy it to the destination pixel.*/
488: /*1st: dest is the pointer to the PGMImage that is to have the pixel changed.
489: 2nd: the row(y coord) that is to be the destination
490: 3rd: the column(x coord) that is to be the destination
491: 4th: dsrc is the pointer to the PGMImage that is to have its pixel copied.
492: 5th: the row(y coord) that is to be the source
493: 6th: the column(x coord) that is to be the source
494: */
495: void pxlcpy(PGMImage *dest, int dest_row, int dest_col,
496: PGMImage *src, int src_row, int src_col)
497: {
498: /*make sure values are within bounds*/
499: if(dest_col > 0 && dest_col < (*dest).width
500: && dest_row > 0 && dest_row < (*dest).height
501: && src_col > 0 && src_col < (*src).width
502: && src_row > 0 && src_row < (*src).height)
503: {
504: (*dest).data[dest_row][dest_col].red =
505: (*src).data[src_row][src_col].red;
506:
507: (*dest).data[dest_row][dest_col].green =
508: (*src).data[src_row][src_col].green;
509:
510: (*dest).data[dest_row][dest_col].blue =
511: (*src).data[src_row][src_col].blue;
512: }
513: }
514:
515: /*calculate the average of the red, green and blue values of a sinfle pixel.*/
516: /*1st: the RGB_INT to be averaged*/
517: /*the average of the red, green and blue is returned*/
518: int rgb_avg(RGB_INT cur_pxl)
519: {
520: /*convert each RGB to the average of the original*/
521: return ((cur_pxl.red + cur_pxl.green + cur_pxl.blue) / 3);
522: }
523:
524: /*Returns average (with RGB avg) pixel value for the image passed in.*/
525: /*1st: a pointer to a pgm image*/
526: /*the return value is the average of all the pixel in the image. Each pixel's
527: value is the average of its RGB parts.*/
528: int img_pxl_avg(PGMImage* img)
529: {
530: int i, j; /*loop counting*/
531: int sum = 0;
532: int temp;
533:
534: for(i = 0; i < (*img).height; i++)/*collumn*/
535: for(j = 0; j < (*img).width; j++)/*row*/
536: sum += rgb_avg((*img).data[i][j]);
537:
538:
539: temp = (sum / ((*img).height * (*img).width));
540:
541: return temp;
542: }
543:
544: /*determines what in the image is determined to background and foreground.*/
545: /*1st: value to compare whether more of the pixels are greater than this or
546: less than this.
547: 2nd: the image to be checked.*/
548: /*return >0 if number of pixels is greater than img. pxl. avg., return < 0 if
549: number of pixesl is less than img. pxl. avg. and return zero of equal*/
550: int background(int treash_value, PGMImage* img)
551: {
552: int i, j; /*loop counting*/
553: int pxl_less = 0, pxl_more = 0;
554:
555: for(i = 0; i < (*img).height; i++)/*collumn*/
556: for(j = 0; j < (*img).width; j++)/*row*/
557: {
558: if(rgb_avg((*img).data[i][j]) < treash_value)
559: pxl_less++;
560:
561: if(rgb_avg((*img).data[i][j]) > treash_value)
562: pxl_more++;
563: }
564:
565: if(pxl_less > pxl_more)
566: return -1;
567: else if(pxl_less < pxl_more)
568: return 1;
569: else
570: return 0;
571: }
572:
573: /*Used by showColor() and detect_pieces() to intrapolate the location of a
574: point along a line between two known points.
575: r = (1 - t)p1 + t*p2
576: This is calculated twice, once for x direction and once for y direction.*/
577: /*1st: coordinate of the closer point that t will intropolate to.
578: 2nd: coordinate of the farther point that t will intrapolate to.
579: 3rd: the fractional value, with point1 considered t = 0 and point2
580: considered t = 1 that the return value is intropolated.*/
581: /*returns the coordinate that is intropolated*/
582: coord find_dividers(coord point1, coord point2, float t)
583: {
584: coord temp;
585:
586: temp.x = (int)(((1.0 - t) * point1.x) + (t * point2.x));
587: temp.y = (int)(((1.0 - t) * point1.y) + (t * point2.y));
588:
589: return temp;
590: }
591:
592: /*takes two coordinates as x and y pairs and returns the distance betweem them
593: as a decimal*/
594: float findDist(int x1, int y1, int x2, int y2)
595: {
596: return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
597: }
598:
599:
600: /******Perspective correction*******************************************
601: ***********************************************************************/
602:
603: /*The image that we will be looking at will from the camera will likely
604: have a perspective, so look at removing the perspective effect. The x
605: direction correction starts in the middle and moves toward the sides
606: stretching the image with more stretching occuring with higher rows.
607: The y direction correction inserts more lines near the top than towards the
608: bottom of the image. There is a y direction recentering calculation
609: performed.*/
610: /*1st: The pointer to the pgm image that the image will be copied into with
611: the perspective removed.
612: 2nd: The pointer to the pgm image that will have the perspective removed.*/
613: void pers_corr(PGMImage* new_img, PGMImage* org_img)
614: {
615: /******************X direction variables*********/
616:
617: /*i and j are the left half, k and l are the right half*/
618: float i, k; /*loop counting x dir*/
619: int j, l; /*loop counting x dir*/
620: int old_i, old_k; /*x dir. counting to remove moire iterference*/
621:
622: float ins_s = X_DIR_PERS_CORR; /*insert constant starting value x dir.*/
623: float ins_k = ins_s; /*current insert x dir.*/
624:
625: /*The halfway marks in the width.*/
626: int mid_width_left;
627: int mid_width_right;
628:
629:
630: /******************Y direction variables*********/
631:
632: /*the recenter in y direction value*/
633: int recenter;
634:
635: float m; /*loop counting y dir*/
636: int n; /*loop counting y dir*/
637: int old_n = (*new_img).height, old_n_inc; /*y dir remove moire iterference*/
638:
639: float ins_t = Y_DIR_PERS_CORR; /*insert constant starting value y dir.*/
640: float ins_j = ins_t; /*current insert value y dir.*/
641:
642: /*just to be thourough clear the memory and reset maxes*/
643: memset(new_img, 0, sizeof(PGMImage));
644: (*new_img).height = (*org_img).height;
645: (*new_img).width = (*org_img).width;
646: (*new_img).maxVal = (*org_img).maxVal;
647:
648: /*setting these before the new image size was stupid. never do that again.
649: Doing so resulted in the first frame being garbaged.*/
650: mid_width_left = ((*new_img).width / 2) - 1;
651: mid_width_right = ((*new_img).width / 2);
652:
653: /*since the x dir is calculated from the middle, the middle is always
654: mapped to the middle. Since the y dir is calculated from the top the
655: image is pushed to the bottom. To correct this add to the new pixel
656: location this value to recenter the image.*/
657: recenter = ((*new_img).height / ins_t);
658:
659: /*Loop through each row from top to bottom...*/
660: for(m = n = ((*new_img).height - 1);
661: (n >= 0);
662: m -= ins_j, n--)
663: {
664: /*...reset moire interference removal counter x dir...*/
665: old_i = ((*new_img).width / 2) - 1;
666: old_k = ((*new_img).width / 2);
667:
668: /*...so each half is ajusted to remove perspective effect...*/
669: /*initalize the x and y starting conditions*/
670: for(i = j = mid_width_left, k = l = mid_width_right;
671: /*x and y ending conditions*/
672: ((j >= 0) && (l < (*new_img).width));
673: /*incremental*/
674: i -= ins_k, j--, k += ins_k, l++)
675: {
676: for(;old_i >= (int)i; old_i--) /*...in the left half...*/
677: for(old_n_inc = old_n; old_n_inc >= m; old_n_inc--)
678: pxlcpy(new_img, old_n_inc + recenter, old_i, org_img, n, j);
679:
680: for(;old_k < = (int)k; old_k++) /*...in the right half.*/
681: for(old_n_inc = old_n; old_n_inc >= m; old_n_inc--)
682: pxlcpy(new_img, old_n_inc + recenter, old_k, org_img, n, l);
683: }
684: /*Move the new image x_coord pixel counter to next new image pixel*/
685: ins_k -= ((ins_s - 1.0) / (*new_img).height);
686: ins_j -= ((ins_t - 1.0) / (*new_img).width);
687:
688: old_n = m; /*store for next row (y direction)*/
689: }
690:
691:
692: }
693:
694: /*****convert color to grayscale****************************************
695: ***********************************************************************/
696:
697: /*Turn a color image into a black and white image.*/
698: /*1st: The pointer to the pgm image that the image will be copied into with
699: the perspective removed.
700: 2nd: The pointer to the pgm image that will have the color removed.*/
701: void color_to_gray(PGMImage* new_img, PGMImage* org_img)
702: {
703: int row, col; /*loop counting*/
704: RGB_INT cur_pxl; /*current pixel*/
705:
706: /*just to be thourough clear the memory and reset maxes*/
707: memset(new_img, 0, sizeof(PGMImage));
708: (*new_img).height = (*org_img).height;
709: (*new_img).width = (*org_img).width;
710: (*new_img).maxVal = (*org_img).maxVal;
711:
712: /*Starting with the top row...*/
713: for(row = (*new_img).height - 1; row >= 0; row--)
714: for(col = 0; col < (*new_img).width - 1; col++)
715: {
716: cur_pxl = (*org_img).data[row][col]; /*more readable*/
717:
718: /*convert each RGB to the average of the original*/
719: (*new_img).data[row][col].red = rgb_avg(cur_pxl);
720: (*new_img).data[row][col].green = rgb_avg(cur_pxl);
721: (*new_img).data[row][col].blue = rgb_avg(cur_pxl);
722: }
723: }
724:
725: /*******Moravec Edge Highlighting**************************************
726: **********************************************************************/
727:
728: /*use the Moravec algorithm to highlight the edges in an image. This
729: algorithm takes the normally grayscale result and threasholds it to
730: the values of 0 and 255 with the divide at THREASH_CONSTANT*/
731: /*1st: The pointer to the pgm image that the image will be copied into with
732: the moravec filter applied.
733: 2nd: The pointer to the pgm image that will have the moravec filter*/
734: void moravec(PGMImage* new_img, PGMImage* org_img)
735: {
736: int row, col; /*loop counting*/
737: int i, j, k, l; /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/
738: int running_sum;
739: float K = 0.5; /*.125 according to org. formula, but .5 is brighter*/
740:
741: /*just to be thourough clear the memory and reset maxes*/
742: memset(new_img, 0, sizeof(PGMImage));
743: (*new_img).height = (*org_img).height;
744: (*new_img).width = (*org_img).width;
745: (*new_img).maxVal = (*org_img).maxVal;
746:
747: /*starting at the top row*/
748: /*don't count pixels along image edge to avoid segmentation fault*/
749: for(row = (*new_img).height - 1 - 1; row > 0; row--)
750: for(col = 1; col < (*new_img).width - 1; col++) /*left col start*/
751: {
752: i = row;
753: j = col;
754: running_sum = 0;
755:
756: /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/
757: for(k = i - 1; k < = i + 1; k++) /*row*/
758: for(l = j - 1; l < = j + 1; l++) /*column*/
759: running_sum += abs(rgb_avg((*org_img).data[k][l]) -
760: rgb_avg((*org_img).data[i][j]));
761:
762: /*assign the new pixel value*/
763: /*since all the data is initalized to 0, we only worry when it
764: shouldn't be*/
765: if((int)(K * running_sum) >= THREASH_CONSTANT)
766: {
767: (*new_img).data[row][col].red = 255;
768: (*new_img).data[row][col].green = 255;
769: (*new_img).data[row][col].blue = 255;
770: }
771: /*this is the original code...*/
772: /*(*new_img).data[row][col].red = (int)(K * running_sum);
773: (*new_img).data[row][col].green = (int)(K * running_sum);
774: (*new_img).data[row][col].blue = (int)(K * running_sum);*/
775: }
776: }
777:
778: /*******Corners*****************************************************
779: *******************************************************************/
780:
781: /*find the first two corners of all of the obejects in the image. This is
782: accomplished by looking at the chaincodes for the images and determining
783: the two points that are the farthest apart.*/
784: /*1st: a pointer to an array of object structs.
785: 2nd: the array of chaincodes that is searched for the farthest two points.*/
786: void findFirstTwoCorners(object *objects_array, list_info* chainCodes)
787: {
788: int i; /*loop counting*/
789: list_info temp, search; /*temp copies of the data*/
790: double max_dist, test_dist; /*temp distance holders*/
791:
792: /*printf("\nFinding first 2 corners.\n");*/
793:
794: /*as long as there are codes to check, keep checking.*/
795: for(i = 0; ((i < MAX_CHAINS) && chainCodes[i].cur); i++)
796: {
797: memcpy(&temp, &chainCodes[i], sizeof(list_info));
798:
799: max_dist = 0.0; /*reset this for each iteration*/
800:
801: while(RetrieveNextNode(&temp).cur) /*while there are nodes to check*/
802: {
803: /*set the faster moving search pointer to temp,
804: this increases the effiecency a lot compared to
805: setting it equal to the first node..*/
806: memcpy(&search, &temp, sizeof(list_info));
807:
808: while(RetrieveNextNode(&search).cur)
809: {
810: /*setCPixel(RetrieveInfo(&temp).location.x,
811: RetrieveInfo(&temp).location.y, green);*/
812:
813: /*determine if found a new maximum distance between two locations*/
814: if((test_dist = findDist(RetrieveInfo(&search).location.x,
815: RetrieveInfo(&search).location.y,
816: RetrieveInfo(&temp).location.x,
817: RetrieveInfo(&temp).location.y)) >max_dist)
818: {
819: max_dist = test_dist;
820: objects_array[i].corner1.x = RetrieveInfo(&temp).location.x;
821: objects_array[i].corner1.y = RetrieveInfo(&temp).location.y;
822: objects_array[i].corner2.x = RetrieveInfo(&search).location.x;
823: objects_array[i].corner2.y = RetrieveInfo(&search).location.y;
824: }
825: Advance(&search);
826: }
827: Advance(&temp);
828: }
829: }
830: }
831:
832: /*determine the second to corners for the objects.*/
833: /*1st: the array of objects that contains the first to corners already.
834: 2nd: the array of chaincode data*/
835: void findSecondTwoCorners(object *objects_array, list_info* chain_code_array)
836: {
837: int i; /*loop counting*/
838: list_info temp;
839: float temp_dist1, temp_dist2; /*distance between point and each corner*/
840: coord canidate_coord1, temp_coord;
841: float canidate_dist1 = 0.0, max_dist;
842: int corner_count;
843:
844: /*printf("\nFinding last 2 corners.\n\n");*/
845:
846: /*for each chain code find the corners*/
847: for(i = 0; (i < MAX_CHAINS) && chain_code_array[i].cur; i++)
848: {
849: memcpy(&temp, &chain_code_array[i], sizeof(list_info));
850:
851: /*reset these for the next chain code*/
852: max_dist = 0.0;
853: corner_count = 1;
854:
855: /*while there are nodes in the chain code to check*/
856: /*if there isn't a next node cur is NULL, which is checked*/
857: while(RetrieveNextNode(&temp).cur)
858: {
859: /*setCPixel(RetrieveInfo(&temp).location.x,
860: RetrieveInfo(&temp).location.y, color1);*/
861:
862: /*determine the first canidate coord for corner 3/4*/
863: if(((RetrieveInfo(&temp).location.x == objects_array[i].corner1.x)
864: && (RetrieveInfo(&temp).location.y == objects_array[i].corner1.y))
865: || ((RetrieveInfo(&temp).location.x == objects_array[i].corner2.x)
866: &&(RetrieveInfo(&temp).location.y == objects_array[i].corner2.y)))
867: {
868: /*if this corner found is the first of the two allready known
869: corners, then set the first canidate coord data and reset data
870: to find the next canidate corner point*/
871: if(corner_count == 1)
872: {
873: canidate_coord1.x = temp_coord.x;
874: canidate_coord1.y = temp_coord.y;
875: canidate_dist1 = max_dist;
876:
877: corner_count = 2; /*set for next corner*/
878: max_dist = 0.0;
879: }
880: else if(corner_count == 2)
881: {
882: /*the second canidate is always a corner*/
883: all_objects[i].corner4.x = temp_coord.x;
884: all_objects[i].corner4.y = temp_coord.y;
885:
886: max_dist = 0.0; /*set for next corner canidate*/
887: }
888: }
889:
890: /*calculate the distance between the current point being checked and
891: each corner point*/
892: temp_dist1 = findDist(all_objects[i].corner1.x,
893: all_objects[i].corner1.y,
894: RetrieveInfo(&temp).location.x,
895: RetrieveInfo(&temp).location.y);
896: temp_dist2 = findDist(all_objects[i].corner2.x,
897: all_objects[i].corner2.y,
898: RetrieveInfo(&temp).location.x,
899: RetrieveInfo(&temp).location.y);
900:
901: /*if the current point is the furthest away sofar, store this point
902: untill it is overridden or becomes a canidate point*/
903: if((temp_dist1 + temp_dist2) > max_dist)
904: {
905: temp_coord.x = RetrieveInfo(&temp).location.x;
906: temp_coord.y = RetrieveInfo(&temp).location.y;
907:
908: max_dist = (temp_dist1 + temp_dist2);
909: }
910:
911: Advance(&temp);
912: }
913:
914: /*from the three canidate coords find the two real corners.*/
915: /*the second canidate will always be a corner, must test 1 vs 3, where
916: three is in the variables temp_coord and max_dist.*/
917: if(canidate_dist1 > max_dist) /*first canidate*/
918: {
919: all_objects[i].corner3.x = canidate_coord1.x;
920: all_objects[i].corner3.y = canidate_coord1.y;
921: }
922: else /*third canidate*/
923: {
924: all_objects[i].corner3.x = temp_coord.x;
925: all_objects[i].corner3.y = temp_coord.y;
926: }
927: }
928: }
929:
930: /*returns the number of objects found*/
931: /*places the corner info in the global corners data*/
932: int detect_corners(list_info* current_chaincodes, object *objects_array)
933: {
934: int i; /*temporarily holds number of chaincodes*/
935:
936: if(display_verbose)
937: printf("Looking for corners.\n");
938:
939: /*clear the array of objects*/
940: memset(objects_array, 0, sizeof(object) * MAX_CHAINS);
941:
942: /*find the first two corners. they will be across a diagnal.*/
943: findFirstTwoCorners(objects_array, current_chaincodes);
944: /*find the second two corners. they will be across a diagnal too.*/
945: findSecondTwoCorners(objects_array, current_chaincodes);
946:
947: /*when this stops, i holds the number of chaincodes*/
948: for(i = 0; (i < MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++);
949:
950: return i;
951: }
952:
953: /*******Find the game board********************************************
954: **********************************************************************/
955:
956: /*compare two distances between points.*/
957: /*1st: the distance between two points
958: 2nd: the distance between two points*/
959: /*returns the difference of the lengths divided by the lengths average.
960: If there is a division by zero, the two lengths are both zero. The return
961: value in this case is FLT_MAX defined in float.h*/
962: float compare_length(float length1, float length2)
963: {
964: float denom_check = fabs((length1 + length2) / 2);
965:
966: if(denom_check == 0)
967: {
968: /*the only way to possibly pull this off is if one point is
969: is considered more than one corner. This is most likely
970: to happen where the chain was only two or three nodes long.*/
971: /*Just set the error to the maximum value obtained from float.h,
972: since such a small chain is not what we are looking for.*/
973:
974: return FLT_MAX;
975: }
976: else
977: return fabs(length1 - length2) / denom_check;
978: }
979:
980: /*determine which object is the tic-tac-toe board from those found in the
981: chaincodes and stored as objects.*/
982: /*1st: pointer the chaincode array.
983: 2nd: pointer to the array of objects.*/
984: /*return the object that is most likely the chaincode. This value is
985: the array subscript value for the object array. If none is found then
986: returns -1.*/
987: int detect_game(list_info *current_chaincodes, object *object_array)
988: {
989: float length2to4[MAX_CHAINS], length1to3[MAX_CHAINS]; /*side pairs*/
990: float length1to4[MAX_CHAINS], length2to3[MAX_CHAINS]; /*side pairs*/
991: float length1to2[MAX_CHAINS], length3to4[MAX_CHAINS]; /*diagnaols*/
992:
993: float error24to13[MAX_CHAINS], error14to23[MAX_CHAINS];/*opp. sides*/
994: float error14to13[MAX_CHAINS], error23to24[MAX_CHAINS];/*share corner*/
995: float error31to32[MAX_CHAINS], error41to42[MAX_CHAINS];/*share corner*/
996: float error12to34[MAX_CHAINS];/*diagnaols*/
997:
998: float error_avg;/*average of the errors stored in error##to## variables*/
999:
1000: int i; /*loop counting*/
1001:
1002: /*the most likely object (0 to num_of_corners) that is to be considered
1003: as the board. The float is the error associated with this object.*/
1004: int most_likely = -1;
1005: float ml_error = FLT_MAX; /*just to make sure*/
1006:
1007: if(display_verbose)
1008: printf("Finding game board. ");
1009:
1010: /*for each chaincode*/
1011: for(i = 0; (i < MAX_CHAINS) && current_chaincodes &&
1012: current_chaincodes[i].cur; i++)
1013: {
1014: /*count the number of nodes in the chaincode. Unless the size is
1015: considered long enough, skip it and move on.*/
1016: if(Length(¤t_chaincodes[i]) < DECENT_SIZED_OBJECT)
1017: continue;
1018:
1019: /*since points 1 & 2 are at a diagnal, and 3 & 4 are at a diagnol,
1020: then the dist between 2 and 4 & 1 and 3 should be close
1021: in value.*/
1022: length2to4[i] = findDist(object_array[i].corner2.x,
1023: object_array[i].corner2.y,
1024: object_array[i].corner4.x,
1025: object_array[i].corner4.y);
1026: length1to3[i] = findDist(object_array[i].corner1.x,
1027: object_array[i].corner1.y,
1028: object_array[i].corner3.x,
1029: object_array[i].corner3.y);
1030:
1031: /*the other side pair*/
1032: length1to4[i] = findDist(object_array[i].corner1.x,
1033: object_array[i].corner1.y,
1034: object_array[i].corner4.x,
1035: object_array[i].corner4.y);
1036: length2to3[i] = findDist(object_array[i].corner2.x,
1037: object_array[i].corner2.y,
1038: object_array[i].corner3.x,
1039: object_array[i].corner3.y);
1040:
1041: /*diagnols... always will be 1 & 2 and 3 & 4*/
1042: length1to2[i] = findDist(object_array[i].corner1.x,
1043: object_array[i].corner1.y,
1044: object_array[i].corner2.x,
1045: object_array[i].corner2.y);
1046: length3to4[i] = findDist(object_array[i].corner3.x,
1047: object_array[i].corner3.y,
1048: object_array[i].corner4.x,
1049: object_array[i].corner4.y);
1050:
1051: /*calculate percent errors for all edge (and diagnal) combinations*/
1052: error24to13[i] = compare_length(length2to4[i], length1to3[i]);/*op.side*/
1053: error14to23[i] = compare_length(length1to4[i], length2to3[i]);
1054: error14to13[i] = compare_length(length1to4[i], length1to3[i]);/*1 crn.*/
1055: error23to24[i] = compare_length(length2to3[i], length2to4[i]);
1056: error31to32[i] = compare_length(length1to3[i], length2to3[i]);
1057: error41to42[i] = compare_length(length1to4[i], length2to4[i]);
1058: error12to34[i] = compare_length(length1to2[i], length3to4[i]);/*diag.*/
1059:
1060: /*average all of the error values together*/
1061: error_avg = ((error24to13[i] + error14to23[i] + error14to13[i] +
1062: error23to24[i] + error31to32[i] + error41to42[i] +
1063: error12to34[i]) / 7);
1064:
1065: /*determine if the current object is considered the most likely to
1066: be the ttt board so far. Average of all the error##to##'s.
1067: If the current is */
1068: if(ml_error > error_avg)
1069: {
1070: most_likely = i;
1071: ml_error = error_avg;
1072: }
1073: }
1074: if(display_verbose)
1075: printf("Object %d is most likely the board with %f\%% error.\n",
1076: most_likely, ml_error * 100);
1077:
1078: if(ml_error* 100 < ALLOWABLE_PERCENT_ERROR)
1079: return most_likely; /*return the object number that is the board*/
1080: else
1081: return -1;
1082: }
1083:
1084: /*searches the moravec image for a cross line in the tic-tac-toe board
1085: int the neighborhood of t = 1/3 */
1086: /*1st: the first 'corner' to search from. (t = 0)
1087: 2nd: the second 'corner' to search from. (t = 1)
1088: 3rd: a pointer to the moravec image used in finding the lines.*/
1089: /*returns the coord of the location determined to be the end of the cross
1090: line being searched for. If one isn't found by looking at the image,
1091: then the return value is the theoretical location where t = 1/3 */
1092: /*note: the corners passed in are moved from the actuall corners. see
1093: find_side_points()*/
1094: coord search_moravec(coord point1, coord point2, PGMImage *moravec,
1095: float t_divider)
1096: {
1097: int i, j; /*loop counting*/
1098: float t;
1099: coord temp1, temp2;
1100: float dist = findDist(point1.x, point1.y, point2.x, point2.y);
1101: int search_size = 2; /*'radius' that defines the neighborhood of a point*/
1102:
1103: coord most_likely_corner = {-1, -1};
1104: int most_likely_sum1 = 0, most_likely_sum2 = 0;
1105: int sum_moravec_points1, sum_moravec_points2;
1106:
1107: /*calculate the coordinates where the divider edges should be.*/
1108: for(t = 0; t < ONE_TWELTH; t += (1 / dist))
1109: {
1110: /*check in both the + and - directions simultaniously.*/
1111: temp1 = find_dividers(point1, point2, t_divider + t);
1112: temp2 = find_dividers(point1, point2, t_divider - t);
1113:
1114: /*clear this before next iteration*/
1115: sum_moravec_points1 = sum_moravec_points2 = 0;
1116:
1117: /*search the neighborhood for edge 'hits' in the moravec image*/
1118: for(i = -(search_size); i < = search_size; i++)
1119: {
1120: for(j = -(search_size); j < = search_size; j++)
1121: {
1122: if(rgb_avg((*moravec).data[temp1.y + i][temp1.x + j]) > 128)
1123: {
1124: /*setCPixel(temp1.x + j, temp1.y + i, red);*/
1125: sum_moravec_points1++;
1126: }
1127: if(rgb_avg((*moravec).data[temp2.y + i][temp2.x + j]) > 128)
1128: {
1129: /*setCPixel(temp2.x + j, temp2.y + i, red);*/
1130: sum_moravec_points2++;
1131: }
1132:
1133: }
1134: }
1135:
1136: /*if the current point in the search is the best, return it*/
1137: /*reusing search_size seems good, since it is about the right size*/
1138: if((search_size < = sum_moravec_points1) &&
1139: (most_likely_sum1 < = sum_moravec_points1) &&
1140: !most_likely_sum2)
1141: {
1142: most_likely_sum1 = sum_moravec_points1;
1143: most_likely_corner = temp1;
1144: }
1145: else if(((search_size < = sum_moravec_points2) &&
1146: (most_likely_sum2 < = sum_moravec_points2) &&
1147: !most_likely_sum1))
1148: {
1149: most_likely_sum2 = sum_moravec_points2;
1150: most_likely_corner = temp2;
1151: }
1152: }
1153:
1154: /*if a sutible point is not found, return the theoretical point*/
1155: if((most_likely_corner.x < 0) || (most_likely_corner.y < 0))
1156: {
1157: most_likely_corner = find_dividers(point1, point2, ONE_THIRD);
1158: }
1159:
1160: return most_likely_corner;
1161: }
1162:
1163:
1164: /*return the intersection of two lines*/
1165: /*1st: coordinate one of first line
1166: 2nd: coordinate two of first line
1167: 3rd: coordinate one of second line
1168: 4th: coordinate two of second line*/
1169: /*return the coordinate where the lines intersect. If none-was found
1170: or an error occured in finding one return (-1, -1).*/
1171: coord find_intersection(coord line1_point1, coord line1_point2,
1172: coord line2_point1, coord line2_point2)
1173: {
1174: float line1_slope, line2_slope; /*temp slope holding variables*/
1175: float denominator, numerator; /*temp fraction holding varaibles*/
1176: float x_float; /*use to avoid obscure roundoff errors*/
1177: coord target = {-1, -1}; /*initalize temp target point*/
1178:
1179: /*find slope for first line*/
1180: if((line1_point1.x - line1_point2.x) != 0)
1181: {
1182: line1_slope = ((float)(line1_point1.y - line1_point2.y)) /
1183: ((float)(line1_point1.x - line1_point2.x));
1184: }
1185: else /*otherwise handle the undefined slope*/
1186: {
1187: /*find slope for secon line when first is undefined*/
1188: if((line2_point1.x - line2_point2.x) != 0)
1189: {
1190: line2_slope = ((float)(line2_point1.y - line2_point2.y)) /
1191: ((float)(line2_point1.x - line2_point2.x));
1192: }
1193: else /*this should never happen, but could if someone specifed the same
1194: line twice*/
1195: return target; /*target is initalized to (-1, -1)*/
1196:
1197: /*since the slope is undefined the x coord in known*/
1198: target.x = line1_point1.x;
1199: target.y = line2_slope * target.x + line2_point1.y; /*y = mx + b*/
1200: /*printf("line one has undefined slope\n");*/
1201: return target;
1202: }
1203:
1204: /*find slope for second line*/
1205: if((line2_point1.x - line2_point2.x) != 0)
1206: {
1207: line2_slope = ((float)(line2_point1.y - line2_point2.y)) /
1208: ((float)(line2_point1.x - line2_point2.x));
1209: }
1210: else
1211: {
1212: /*since the slope is undefined the x coord in known*/
1213: target.x = line2_point1.x;
1214: target.y = line1_slope * target.x + line1_point1.y; /*y = mx + b*/
1215: /*printf("line two has undefined slope\n");*/
1216: return target;
1217: }
1218:
1219: /* slope is m and defined by:
1220: (y1 - y2)
1221: m = ---------
1222: (x1 - x2)
1223:
1224: target calculated by setting Yt = M1*(Xt - Xa) + Ya equal to
1225: Yt = M2 * (Xt - Xc) + Yc to get
1226:
1227: (Xa*M1 - M2*Xc + Yc - Ya)
1228: Xt = -------------------------
1229: M1 - M2
1230:
1231: then to get the y coordinate sub Xt into:
1232:
1233: Yt = M1 * (Xt - Xa) + Ya
1234:
1235: Where line1_point1 = a
1236: line1_point2 = b
1237: line2_point1 = c
1238: line12point2 = d
1239: and are indicated as subscripts of their X or Y coordinate.
1240: M1 = line1_slope
1241: M2 = line2_slope
1242: */
1243:
1244: /*calculate the x coords nominator and demoninator*/
1245: numerator = (((float)((float)line1_point1.x * line1_slope)
1246: - (float)((float)line2_point1.x * line2_slope)
1247: + (float)line2_point1.y - (float)line1_point1.y));
1248: denominator = (float)(line1_slope - line2_slope);
1249:
1250: /*find the x coord, store is as a float to avoid obscure round off errors
1251: when using it to calculate the y coord.*/
1252: x_float = numerator / denominator;
1253:
1254: target.x = (int)x_float;
1255:
1256: /*find the y coord*/
1257: target.y = line1_slope * ((float)x_float - (float)line1_point1.x)
1258: + (float)line1_point1.y;
1259:
1260: return target;
1261: }
1262:
1263: /*move the location to start searching off of the corners a little so the
1264: search isn't right on top of the any cross lines that are perpendicular to
1265: the target line.*/
1266: /*anchor - the corner closest to the side divider point being looked for.
1267: common - the corner that the line between itself and the anchor corner contains
1268: the target divider point.
1269: anchor_opposite - the corner adjacent to the anchor corner & oppsoite common.
1270: common_opposite - the corner adjacent to the common corner & oppposite anchor.
1271: */
1272: /*return the divider point, or (-1, -1) on error*/
1273: coord find_side_points(coord anchor, coord common, coord anchor_opposite,
1274: coord common_opposite, PGMImage *moravec,
1275: float t_divider)
1276: {
1277: coord temp1, temp2;
1278:
1279: temp1 = find_dividers(anchor, anchor_opposite, ONE_24TH);
1280: temp2 = find_dividers(common, common_opposite, ONE_24TH);
1281:
1282: return search_moravec(temp1, temp2, moravec, t_divider);
1283: }
1284:
1285:
1286: /*find the ttt board's important coordinates*/
1287: /*1st: The object that is determined to be the board.
1288: 2nd: the pointer to the struct that contains all of the information about
1289: the tic-tac-toe board important coordinates.
1290: 3rd: a pointer to the Moravec pgm image*/
1291: void detect_ttt_board(object board_object,
1292: coord board_details[DIVIDERS][DIVIDERS],
1293: PGMImage *moravec)
1294: {
1295: int i, j;
1296:
1297: /*copy the corners into the ttt datatype*/
1298: if(board_object.corner1.x > board_object.corner2.x)
1299: {
1300: board_details[0][0] = board_object.corner2;
1301: board_details[DIVIDERS - 1][0] = board_object.corner2;
1302: board_details[0][DIVIDERS - 1] = board_object.corner1;
1303: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner1;
1304:
1305: if((board_object.corner3.y > board_object.corner4.y)
1306: && (board_object.corner3.x > board_object.corner4.x))
1307: {
1308: board_details[DIVIDERS - 1][0] = board_object.corner4;
1309: board_details[0][DIVIDERS - 1] = board_object.corner3;
1310: }
1311: else if((board_object.corner3.y > board_object.corner4.y)
1312: && (board_object.corner3.x < board_object.corner4.x))
1313: {
1314: board_details[0][0] = board_object.corner3;
1315: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner4;
1316: }
1317: else if((board_object.corner3.y < board_object.corner4.y)
1318: && (board_object.corner3.x > board_object.corner4.x))
1319: {
1320: board_details[0][0] = board_object.corner4;
1321: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner3;
1322: }
1323: else
1324: {
1325: board_details[DIVIDERS - 1][0] = board_object.corner3;
1326: board_details[0][DIVIDERS - 1] = board_object.corner4;
1327: }
1328:
1329: }
1330: else
1331: {
1332: board_details[0][0] = board_object.corner1;
1333: board_details[DIVIDERS - 1][0] = board_object.corner1;
1334: board_details[0][DIVIDERS - 1] = board_object.corner2;
1335: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner2;
1336:
1337: if((board_object.corner3.y > board_object.corner4.y)
1338: && (board_object.corner3.x > board_object.corner4.x))
1339: {
1340: board_details[DIVIDERS - 1][0] = board_object.corner4;
1341: board_details[0][DIVIDERS - 1] = board_object.corner3;
1342: }
1343: else if((board_object.corner3.y > board_object.corner4.y)
1344: && (board_object.corner3.x < board_object.corner4.x))
1345: {
1346: board_details[0][0] = board_object.corner3;
1347: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner4;
1348: }
1349: else if((board_object.corner3.y < board_object.corner4.y)
1350: && (board_object.corner3.x > board_object.corner4.x))
1351: {
1352: board_details[0][0] = board_object.corner4;
1353: board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner3;
1354: }
1355: else
1356: {
1357: board_details[DIVIDERS - 1][0] = board_object.corner3;
1358: board_details[0][DIVIDERS - 1] = board_object.corner4;
1359: }
1360: }
1361:
1362: /*loop along the four sides, finding the side points for the cross lines.*/
1363: for(i = 1; i < DIVIDERS - 1; i++)
1364: {
1365: /*"side"*/
1366: board_details[i][0] =
1367: find_side_points(board_details[0][0],
1368: board_details[DIVIDERS - 1][0],
1369: board_details[0][DIVIDERS - 1],
1370: board_details[DIVIDERS - 1][DIVIDERS - 1],
1371: moravec, i / (DIVIDERS - 1.0));
1372:
1373: /*side opposite "side"*/
1374: board_details[i][DIVIDERS - 1] =
1375: find_side_points(board_details[0][DIVIDERS - 1],
1376: board_details[DIVIDERS - 1][DIVIDERS - 1],
1377: board_details[0][0],
1378: board_details[DIVIDERS - 1][0],
1379: moravec, i / (DIVIDERS - 1.0));
1380:
1381: /*side to the left of "side"*/
1382: board_details[0][i] =
1383: find_side_points(board_details[0][0],
1384: board_details[0][DIVIDERS - 1],
1385: board_details[DIVIDERS - 1][0],
1386: board_details[DIVIDERS - 1][DIVIDERS - 1],
1387: moravec, i / (DIVIDERS - 1.0));
1388:
1389: /*side opposite the side to the left of "side"*/
1390: board_details[DIVIDERS - 1][i] =
1391: find_side_points(board_details[DIVIDERS - 1][0],
1392: board_details[DIVIDERS - 1][DIVIDERS - 1],
1393: board_details[0][0],
1394: board_details[0][DIVIDERS - 1],
1395: moravec, i / (DIVIDERS - 1.0));
1396: }
1397:
1398:
1399: /*correct the length of the lines that is shortend to calculate them
1400: in the first place above. simply find the intersection of the *short*
1401: line with that of the outside line between the corners.*/
1402:
1403: for(i = 1; i < DIVIDERS - 1; i++)
1404: {
1405: board_details[i][0] = find_intersection(board_details[i][0],
1406: board_details[i][DIVIDERS - 1],
1407: board_details[0][0],
1408: board_details[DIVIDERS - 1][0]);
1409:
1410: board_details[i][DIVIDERS - 1] =
1411: find_intersection(board_details[i][DIVIDERS - 1],
1412: board_details[i][0],
1413: board_details[0][DIVIDERS - 1],
1414: board_details[DIVIDERS - 1][DIVIDERS - 1]);
1415:
1416:
1417: board_details[0][i] = find_intersection(board_details[0][i],
1418: board_details[DIVIDERS - 1][i],
1419: board_details[0][0],
1420: board_details[0][DIVIDERS - 1]);
1421:
1422:
1423: board_details[DIVIDERS - 1][i] =
1424: find_intersection(board_details[DIVIDERS - 1][i],
1425: board_details[0][i],
1426: board_details[DIVIDERS - 1][0],
1427: board_details[DIVIDERS - 1][DIVIDERS - 1]);
1428: }
1429:
1430: /*now that the sides are found, find the intersections to find the
1431: middle points*/
1432: for(i = 1; i < DIVIDERS - 1; i++)
1433: for(j = 1; j < DIVIDERS - 1; j++)
1434: board_details[i][j] =
1435: find_intersection(board_details[0][j],
1436: board_details[DIVIDERS - 1][j],
1437: board_details[i][0],
1438: board_details[i][DIVIDERS - 1]);
1439:
1440: /*print all the coords*/
1441: /*for(i = 0; i < DIVIDERS; i++)
1442: for(j = 0; j < DIVIDERS; j++)
1443: printf("points[%d][%d]: (%d, %d)\n", i, j, points[i][j].x,
1444: points[i][j].y);*/
1445: }
1446:
1447: /*not a callback, called by menu() and buffer()*/
1448: int showGame()
1449: {
1450: int object; /*temp variable*/
1451: struct timeval t0, t1;
1452:
1453: printf("\n\n");
1454:
1455: /*correct image perspective*/
1456: pthread_mutex_lock(&images_mutex);
1457:
1458: img_cur = img_original;
1459: gettimeofday(&t0, NULL);
1460: pers_corr(img_pers_corr, img_cur);
1461: gettimeofday(&t1, NULL);
1462: do_time(t0, t1, "perspective correction");
1463:
1464: img_cur = img_pers_corr;
1465:
1466: pthread_mutex_unlock(&images_mutex);
1467:
1468:
1469:
1470: /*find chain codes*/
1471: pthread_mutex_lock(&calculate_mutex);
1472: pthread_mutex_lock(&images_mutex);
1473:
1474: gettimeofday(&t0, NULL);
1475: showChain(img_pers_corr, &chain_codes); /*chain codes*/
1476: gettimeofday(&t1, NULL);
1477: do_time(t0, t1, "chain codes");
1478:
1479: pthread_mutex_unlock(&images_mutex);
1480: pthread_mutex_unlock(&calculate_mutex);
1481:
1482:
1483:
1484: /*find corners*/
1485: pthread_mutex_lock(&calculate_mutex);
1486:
1487: gettimeofday(&t0, NULL);
1488: detect_corners(chain_codes, all_objects);
1489: gettimeofday(&t1, NULL);
1490: do_time(t0, t1, "corner detection");
1491:
1492: pthread_mutex_unlock(&calculate_mutex);
1493:
1494:
1495:
1496:
1497: /*detect the ttt game board*/
1498: pthread_mutex_lock(&calculate_mutex);
1499:
1500: gettimeofday(&t0, NULL);
1501: object = detect_game(chain_codes, all_objects);
1502: gettimeofday(&t1, NULL);
1503: do_time(t0, t1, "game detection");
1504:
1505: pthread_mutex_unlock(&calculate_mutex);
1506:
1507:
1508: if(object == -1)
1509: {
1510: if(display_verbose)
1511: printf("No board was found\n");
1512: return object;
1513: }
1514:
1515:
1516: /*do moravec first, then perspective to prevent the loss of edge sharpness
1517: from doing the perspective first*/
1518: pthread_mutex_lock(&images_mutex);
1519:
1520: gettimeofday(&t0, NULL);
1521: moravec(img_grayscale, img_original);
1522: pers_corr(img_moravec, img_grayscale);
1523: gettimeofday(&t1, NULL);
1524: do_time(t0, t1, "Moravec filter");
1525:
1526: pthread_mutex_unlock(&images_mutex);
1527:
1528:
1529:
1530:
1531: /*if a board was found, find the specific information*/
1532: pthread_mutex_lock(&images_mutex);
1533: pthread_mutex_lock(&calculate_mutex);
1534:
1535: gettimeofday(&t0, NULL);
1536: detect_ttt_board(all_objects[object], points, img_moravec);
1537: gettimeofday(&t1, NULL);
1538: do_time(t0, t1, "detect tic-tac-toe");
1539:
1540: pthread_mutex_unlock(&calculate_mutex);
1541: pthread_mutex_unlock(&images_mutex);
1542:
1543:
1544: return object;
1545: }
1546:
1547:
1548: /*****Find the pieces***************************************************
1549: ***********************************************************************/
1550:
1551: /*Takes four points that must be in order going around the region. Also,
1552: needs a pointer to an image with the moravec filter applied.*/
1553: /*1st - 4th: The coordinates in order (starting point arbitrary, as long
1554: as they are in order) that the area inside of is searched for pieces.
1555: 5th: a pointer to the Moravec image to be used.
1556: 6th: a pointer to the memory location were the averages of the pixels
1557: vals for the marble are stored. (only valid when return value is TRUE)
1558: */
1559: /*returns TRUE if a marble exists at the location, FALSE otherwise.*/
1560: bool search_area(coord point1, coord point2, coord point3, coord point4,
1561: PGMImage *moravec, PGMImage *color, RGB_INT *area_avg)
1562: {
1563: /*The following variables are used to calculate the existence of a marble
1564: at a location on the board or not. Uses the following basic formula
1565: new_point = ((t-1) * 1st point) + (t * 2nd point)
1566: where the start & end coords are opposite sides of the area being
1567: searched. If you take the entire area of a single position on a ttt
1568: board, the area that is searched is the region that is bouned by the
1569: points halfway between those passed in to this function.*/
1570:
1571: int search_size = 2, search_area, i, j;
1572:
1573: /*cant use pixel_sum directly becuase it is only */
1574: RGB_INT pixel_sum = {0, 0, 0};
1575: int pixel_sum_red = 0, pixel_sum_green = 0, pixel_sum_blue = 0;
1576:
1577: float t, s, r; /*hold values incrementing from 0 to 1*/
1578: coord t_start, t_end, s_start, s_end;
1579: coord temp_t, temp_s, temp_r;
1580: float dist_t, dist_s, dist_r;
1581:
1582: /*pixel count of those over the threashhold and total pixels*/
1583: int pixel_count = 0, moravec_count = 0;
1584:
1585: /*running storage for the middle of the marble*/
1586: coord sum_locations = {0, 0}; /*sum of x and y coords for average location*/
1587:
1588: coord move_start1, move_start2, move_start3, move_start4;
1589:
1590: /*this translation of moving the starting points skews the search area
1591: off enough to avoid counting "hit" boundry pixels that exist along the
1592: divders/edges and not the marble itself.*/
1593: move_start1 = find_dividers(point1, point2, ONE_THIRD);
1594: move_start2 = find_dividers(point2, point3, ONE_THIRD);
1595: move_start3 = find_dividers(point3, point4, ONE_THIRD);
1596: move_start4 = find_dividers(point4, point4, ONE_THIRD);
1597:
1598: t_start = find_dividers(move_start1, move_start2, ONE_HALF);
1599: t_end = find_dividers(move_start1, move_start4, ONE_HALF);
1600:
1601: s_start = find_dividers(move_start3, move_start2, ONE_HALF);
1602: s_end = find_dividers(move_start3, move_start4, ONE_HALF);
1603:
1604: dist_t = findDist(t_start.x, t_start.y, t_end.x, t_end.y);
1605: dist_s = findDist(s_start.x, s_start.y, s_end.x, s_end.y);
1606:
1607: /*march two points along that parallel each other in the search area*/
1608: for(t = 0.0, s = 0.0; t < = 1.0; t += (1.0 / dist_t), s += (1.0 / dist_s))
1609: {
1610: temp_t = find_dividers(t_start, t_end, t);
1611:
1612: temp_s = find_dividers(s_start, s_end, s);
1613:
1614: dist_r = findDist(temp_t.x, temp_t.y, temp_s.x, temp_s.y);
1615:
1616: /*march a single point along that starts at temp_s and ends
1617: at temp_t. This fills in the region for the search.*/
1618: for(r = 0.0; r < = 1.0; r += (1 / dist_r))
1619: {
1620: temp_r = find_dividers(temp_s, temp_t, r);
1621:
1622: /*setCPixel(temp_r.x, temp_r.y, red);*/
1623: /*talley the number of edge points found (rgb. avg. >= 128)*/
1624: if(rgb_avg((*moravec).data[temp_r.y][temp_r.x]) >= EDGES_FOR_PIECE)
1625: {
1626: sum_locations.x += temp_r.x;
1627: sum_locations.y += temp_r.y;
1628: /*setCPixel(temp_r.x, temp_r.y, blue);*/
1629: moravec_count++;
1630: }
1631: pixel_count++; /*increment the number of pixels*/
1632: }
1633:
1634: }
1635:
1636: if(moravec_count)
1637: {
1638: sum_locations.x /= moravec_count;
1639: sum_locations.y /= moravec_count;
1640: }
1641:
1642: /*if the number of edge pixels is greater than 25% of the total, then
1643: this is an edge of marble. This is based on the fact the the percentage
1644: of pixels highlighted theoritically be 25% of the total count. By
1645: dividing the circumfernce of the marble (in cm) by the area of the search.
1646: Since the radius is 2cm and the length and width are 5cm the math works
1647: out to 25% of the total (theoretically). However, since the starting
1648: points are moved 1/3 off the corners this theo. percentage jumps to
1649: 45% for the max. For practical implementation EDGE_COUNT is set
1650: to .25 or 25 percent.*/
1651: /*printf("ttt fp mc: %d tc: %d per: %f\n", moravec_count, pixel_count,
1652: ((float)moravec_count / (float)pixel_count) * 100.0);*/
1653:
1654: if(moravec_count >= EDGE_COUNT * pixel_count)
1655: {
1656: for(i = -search_size; i < = search_size; i++) /*y dir*/
1657: for(j = -search_size; j < = search_size; j++) /*x dir*/
1658: {
1659: pixel_sum_red += (*color).data
1660: [sum_locations.y + i][sum_locations.x + j].red;
1661: pixel_sum_green += (*color).data
1662: [sum_locations.y + i][sum_locations.x + j].green;
1663: pixel_sum_blue += (*color).data
1664: [sum_locations.y + i][sum_locations.x + j].blue;
1665:
1666: /*setCPixel(sum_locations.x + j, sum_locations.y + i, yellow);*/
1667:
1668: }
1669: /*setCPixel(sum_locations.x, sum_locations.y, green);
1670: glFlush();*/
1671:
1672: search_area = (((search_size * 2) + 1) * ((search_size * 2) + 1));
1673:
1674: pixel_sum.red =
1675: pixel_sum_red / search_area;
1676: pixel_sum.green =
1677: pixel_sum_green / search_area;
1678: pixel_sum.blue =
1679: pixel_sum_blue / search_area;
1680:
1681: memcpy(area_avg, &pixel_sum, sizeof(RGB_INT));
1682: /* printf("pxl avg: %d\n", pixel_sum);*/
1683: return TRUE;
1684: }
1685:
1686: /*otherwise a marble isn't here*/
1687: return FALSE;
1688: }
1689:
1690:
1691: int weight_colors(RGB_INT color)
1692: {
1693: int red_to_green = abs(color.red - color.green);
1694: int red_to_blue = abs(color.red - color.blue);
1695: int green_to_blue = abs(color.green - color.blue);
1696:
1697: return red_to_green + red_to_blue + green_to_blue;
1698: }
1699:
1700:
1701: /*looks for the tic-tac-toe pieces on the board*/
1702: /*1st: the pointer to the board information.
1703: 2nd: the pointer to the Moravec image used to find the edges.*/
1704: void detect_pieces(coord board_data[DIVIDERS][DIVIDERS],
1705: PGMImage *moravec, PGMImage *color)
1706: {
1707: int i, j; /*loop counting*/
1708: int teams_pixel_avg, team1_val = 255, team2_val = 0;
1709: RGB_INT store[3][3]; /*area pixel averages*/
1710: int team1_assign = 1, team2_assign = 2;
1711:
1712: if(display_verbose)
1713: printf("Finding pieces.\n");
1714:
1715: memset(pieces, 0, 9 * sizeof(unsigned char));
1716: memset(store, 0, 9 * sizeof(RGB_INT));
1717:
1718: for(i = 0; i < DIVIDERS - 1; i++) /*y*/
1719: for(j = 0; j < DIVIDERS - 1; j++) /*x*/
1720: {
1721: pieces[i][j] = search_area(board_data[i][j],
1722: board_data[i + 1][j],
1723: board_data[i + 1][j + 1],
1724: board_data[i][j + 1],
1725: moravec, color, &store[i][j]);
1726: }
1727:
1728: /*first position to check*********************************/
1729: for(i = 0; i < DIVIDERS - 1; i++) /*y*/
1730: {
1731: for(j = 0; j < DIVIDERS - 1; j++) /*x*/
1732: {
1733: if(!pieces[i][j]) /*if there isn't a piece*/
1734: continue;
1735:
1736: pieces[i][j] = weight_colors(store[i][j]);
1737:
1738: if(pieces[i][j] < team1_val)
1739: {
1740: team1_val = pieces[i][j];
1741: }
1742:
1743: if(pieces[i][j] > team2_val)
1744: {
1745: team2_val = pieces[i][j];
1746: }
1747: }
1748: } /*for*******************************************************/
1749:
1750: if(team1_val == team2_val) /*only one move made so far*/
1751: /*at this point we don't know which color each team is. So, store
1752: this into the team_first global.*/
1753: team_first = team1_val;
1754:
1755: teams_pixel_avg = (team1_val + team2_val) / 2;
1756:
1757: /*the first team to go is assigned team 1. If the color determining
1758: sets it to team 2, then this loop sets what needs to be switched.*/
1759: if(abs(team_first - team1_val) > abs((team1_val - team2_val) / 2))
1760: {
1761: team1_assign = 2;
1762: team2_assign = 1;
1763: }
1764:
1765: for(i = 0; i < 3; i++) /*y*/
1766: {
1767: for(j = 0; j < 3; j++) /*x*/
1768: {
1769: if(pieces[i][j])
1770: {
1771: /*check if team 1*/
1772: if(pieces[i][j] < = teams_pixel_avg)
1773: {
1774: pieces[i][j] = team1_assign;
1775: }
1776: /*check if team 2*/
1777: else if(pieces[i][j] >= teams_pixel_avg)
1778: {
1779: pieces[i][j] = team2_assign;
1780: }
1781: /*to many different colored teams*/
1782: else
1783: {
1784: pieces[i][j] = -1;
1785: }
1786: } /*if(pieces[i][j]*/
1787: }
1788: }
1789: }
1790:
1791: /*display the state of the board in ascii graphics*/
1792: void display_board()
1793: {
1794: int i, j;
1795:
1796: printf(" ");
1797: for(j = 0; j < DIVIDERS - 1; j++)
1798: printf(" [%d] ", j);
1799: printf("\n-------------------\n");
1800:
1801: for(i = 0; i < DIVIDERS - 1; i++)
1802: {
1803: printf("[%d] ", i);
1804: for(j = 0; j < DIVIDERS - 1; j++)
1805: {
1806: printf(" %d ", pieces[i][j]);
1807: }
1808: printf("\n");
1809: }
1810: }
1811:
1812: void showPieces()
1813: {
1814: struct timeval t0, t1;
1815: /*if we get here there is a likely canidate found*/
1816: pthread_mutex_lock(&calculate_mutex);
1817: pthread_mutex_lock(&images_mutex);
1818:
1819: gettimeofday(&t0, NULL);
1820: detect_pieces(points, img_moravec, img_cur);
1821: gettimeofday(&t1, NULL);
1822: do_time(t0, t1, "detect peices");
1823:
1824: if(display_verbose)
1825: display_board();
1826:
1827: pthread_mutex_unlock(&images_mutex);
1828: pthread_mutex_unlock(&calculate_mutex);
1829: }
1830:
1831:
1832: /* =================================================================
1833: * Callback functions.
1834: *
1835: * color = displayed graphics in window
1836: * menu = menu event handling
1837: * keyboard = deyboard event handling
1838: * ----------------------------------------------------------------- */
1839: void color(void)
1840: {
1841: glutReshapeWindow(HSIZE, VSIZE);
1842:
1843: if(pthread_mutex_trylock(&images_mutex) == EBUSY)
1844: {
1845: return;
1846: }
1847:
1848: /*show the current image*/
1849: showColor(img_cur);
1850: pthread_mutex_unlock(&images_mutex);
1851:
1852: if(pthread_mutex_trylock(&calculate_mutex) == EBUSY)
1853: {
1854: return;
1855: }
1856: /*show the current abstract information*/
1857: showAbstract(all_objects, points);
1858: pthread_mutex_unlock(&calculate_mutex);
1859: }
1860:
1861: void sie(void)
1862: {
1863: glutReshapeWindow(HSIZE, VSIZE);
1864:
1865: if(redraw_needed)
1866: {
1867: pthread_mutex_lock(&images_mutex);
1868: showColor(img_cur);
1869: redraw_needed = FALSE;
1870: pthread_mutex_unlock(&images_mutex);
1871: }
1872: else
1873: sched_yield();
1874: }
1875:
1876: void* buffer(void *param)
1877: {
1878: while(is_buffered)
1879: {
1880: /*the mutexes for these calcs are done inside showGame()*/
1881: draw_abstract_board = showGame();
1882:
1883: if(draw_abstract_board != -1)
1884: {
1885: showPieces();
1886: }
1887: }
1888:
1889: return NULL;
1890: }
1891:
1892:
1893:
1894: #define RESTART 0
1895: #define PERS_CORR 3
1896: #define COLOR_TO_GRAY 4
1897: #define MORAVEC 5
1898: #define EDGES 6
1899: #define CORNERS 7
1900: #define BUFFERS 8
1901: #define GAME 9
1902: #define PIECES 10
1903:
1904: /*this is not a callback function, but is used inside menu() for setting
1905: new states of execution*/
1906: void reset_state(PGMImage* new_current)
1907: {
1908: img_cur = new_current;
1909: draw_abstract_lines = -1;
1910: draw_abstract_board = -1;
1911: free_chaincodes(&chain_codes);
1912: }
1913:
1914: void menu(int selection)
1915: {
1916: if(selection == RESTART)
1917: {
1918: reset_state(img_original);
1919: }
1920: if(selection == PERS_CORR)
1921: {
1922: pers_corr(img_pers_corr, img_cur);
1923: reset_state(img_pers_corr);
1924: }
1925: if(selection == COLOR_TO_GRAY)
1926: {
1927: color_to_gray(img_grayscale, img_cur);
1928: reset_state(img_grayscale);
1929: }
1930: if(selection == MORAVEC)
1931: {
1932: moravec(img_moravec, img_cur);
1933: reset_state(img_moravec);
1934: }
1935: if(selection == EDGES)
1936: {
1937: showChain(img_cur, &chain_codes);
1938: }
1939: if(selection == CORNERS)
1940: {
1941: showChain(img_cur, &chain_codes);
1942:
1943: draw_abstract_lines = detect_corners(chain_codes, all_objects);
1944: }
1945: if(selection == BUFFERS)
1946: {
1947: if(is_buffered == FALSE)
1948: {
1949: is_buffered = TRUE;
1950: pthread_create(&vision_thread, NULL, &buffer, NULL);
1951: glutIdleFunc(&color);
1952: }
1953: else
1954: {
1955: is_buffered = FALSE;
1956: pthread_join(vision_thread, NULL);
1957: glutIdleFunc(NULL);
1958: }
1959: }
1960: if(selection == GAME)
1961: {
1962: draw_abstract_board = showGame();
1963: }
1964: if(selection == PIECES)
1965: {
1966: draw_abstract_board = showGame();
1967:
1968: if(draw_abstract_board != -1)
1969: showPieces();
1970: }
1971:
1972: glutPostRedisplay(); /*redraw the image*/
1973: }
1974:
1975: void keyboard(unsigned char key, int x, int y)
1976: {
1977: switch (key)
1978: {
1979: case 27: /*Esc*/
1980: exit(0);
1981: break;
1982:
1983: /*save current image to file image1.pgm*/
1984: case 115: /*s*/
1985: pthread_mutex_lock(&images_mutex);
1986: save(img_cur);
1987: pthread_mutex_unlock(&images_mutex);
1988: break;
1989:
1990: /*save original image to file image1.pgm*/
1991: case 111: /*o*/
1992: pthread_mutex_lock(&images_mutex);
1993: save(img_original);
1994: pthread_mutex_unlock(&images_mutex);
1995: break;
1996:
1997: /*enable display of calculation times.*/
1998: case 116: /*t*/
1999: if(display_time)
2000: display_time = FALSE;
2001: else
2002: display_time = TRUE;
2003: break;
2004:
2005: /*enable display of verbose text.*/
2006: case 118: /*v*/
2007: if(display_verbose)
2008: display_verbose = FALSE;
2009: else
2010: display_verbose = TRUE;
2011: break;
2012: }
2013: }
2014:
2015: void mouse(int button, int state, int x, int y)
2016: {
2017: char temp[50];
2018:
2019: if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
2020: {
2021: sprintf(temp, "(x, y): (%d, %d) red: %d green: %d blue: %d\n",
2022: x, VSIZE - y, (*img_cur).data[VSIZE - y][x].red,
2023: (*img_cur).data[VSIZE - y][x].green,
2024: (*img_cur).data[VSIZE - y][x].blue);
2025: setCRect(0, 0, 200, 12, black);
2026: glColor3f(1.0, 0.0, 0.0);
2027: drawString(0, 0, temp, red);
2028: glFlush();
2029: }
2030: }
2031:
2032:
2033: /* --------------------------------------------------------------------
2034: Connection functions for work with sie.
2035: ------------------------------------------------------------------ */
2036:
2037: /* now connect to the administrator */
2038: int admin_connect(int argc, const char** argv)
2039: {
2040: int sock;
2041:
2042: sock = device_handshake (argc, argv,
2043: TTT_VISION_DEVICE_TYPE_ID_NUMBER, 1);
2044:
2045: if (sock == -1)
2046: {
2047: printf ("failed in handshake; aborting...\n");
2048: exit (1);
2049: }
2050:
2051: return sock;
2052: }
2053:
2054: void* sock_com(void* ptr)
2055: {
2056: PGMImage *img = (PGMImage*)ptr;
2057: int sock = socket_num;
2058: int8 value8, command;
2059: int32 value32, return_val;
2060: int x, y;
2061:
2062: struct timeval t0, t1; /*time 0 and finished time*/
2063:
2064: /******* main loop ******/
2065: command = TTT_VISION_INIT_VALUE;
2066: while (command != ADMIN_DISCONNECT_DEVICE)
2067: {
2068: /* block until we get the next command */
2069: if ((shelley_sockets_read (sock, (char*) &command, sizeof (int8))) == -1)
2070: exit (0);
2071:
2072: else
2073: {
2074: /*printf("ttt command: %d\n", command);*/
2075: switch (command)
2076: {
2077: case ADMIN_QUERY_DEVICE_STATUS:
2078: value8 = DEVICE_A_OK;
2079: shelley_sockets_write (sock, &value8, 1);
2080: break;
2081:
2082: case TTT_VISION_SET_RESOLUTION:
2083: shelley_sockets_read (sock, (char*) &value32, sizeof (int32));
2084: (*img).width = HSIZE = value32;
2085: shelley_sockets_read (sock, (char*) &value32, sizeof (int32));
2086: (*img).height = VSIZE = value32;
2087: (*img).maxVal = MVAL = 255;
2088:
2089: /*we need to use a buffer the same size as the data being read
2090: in since the PGMImage type declares the type as MAX by MAX
2091: in size. Without this buffer the data could not be unpacked
2092: from the socket stream and into the PGMImage in the correct
2093: way.*/
2094: data = (RGB_INT*)malloc(HSIZE * VSIZE * sizeof(RGB_INT));
2095:
2096: return_val = 1;
2097: shelley_sockets_write (sock, (char*) &return_val,
2098: sizeof (int32));
2099: value8 = 99; /*the admin expects something always returned*/
2100: shelley_sockets_write (sock, (char*) &value8, sizeof (int8));
2101: break;
2102:
2103: case TTT_VISION_FIND_TTT:
2104: /*read the data into a region of memory before placing
2105: it into the image data in the correct way.*/
2106: value32 = shelley_sockets_read (sock, (unsigned char*) data,
2107: HSIZE * VSIZE * 3);
2108:
2109: pthread_mutex_lock(&images_mutex);
2110:
2111: /*repack the data into the correct storage size.*/
2112: for (y = 0; y < VSIZE; y++)
2113: {
2114: for (x = 0; x < HSIZE; x++)
2115: {
2116: (*img).data[y][x].red = data[(y * HSIZE) + x].red;
2117: (*img).data[y][x].green = data[(y * HSIZE) + x].green;
2118: (*img).data[y][x].blue = data[(y * HSIZE) + x].blue;
2119: }
2120: }
2121: pthread_mutex_unlock(&images_mutex);
2122:
2123: img_cur = img;
2124:
2125: /*do the timing stuff*/
2126: gettimeofday(&t0, NULL);
2127: menu(PIECES); /*using the menu function is the lazy way*/
2128: gettimeofday(&t1, NULL);
2129: do_time(t0, t1, "total");
2130:
2131: value32 = 9 * sizeof(unsigned char);
2132:
2133: shelley_sockets_write (sock, (char*) &value32, sizeof(int32));
2134: shelley_sockets_write (sock, (char*) pieces, value32);
2135: break;
2136:
2137: case ADMIN_DISCONNECT_DEVICE:
2138: /* we're supposed to quit, now */
2139: exit (0);
2140: break;
2141:
2142: default:
2143: printf("TTT_VISION: unknown command: %d\n", command);
2144: }
2145: }
2146: }
2147: pthread_exit(NULL);
2148: return NULL; /*compilers complain otherwise*/
2149: }
2150:
2151:
2152: /* =================================================================
2153: * init() & parse_command_line()
2154: * initalize none-OpenGL related things.
2155: * ----------------------------------------------------------------- */
2156:
2157: void usage(const char* name)
2158: {
2159: printf("Usage: %s < [-h < hostname > -p < port_number >] [-f < filename >] > [-b]\n"
2160: " -h - remote host\n"
2161: " -p - port number\n"
2162: " -f - file name\n"
2163: " -b - buffer\n"
2164: "\n", name);
2165: }
2166:
2167: /*set global flag variables from things specified at commandline.*/
2168: /*return the filename specified, if none specified exit().*/
2169: void parse_command_line(int argc, const char** argv)
2170: {
2171: /*int c;*/
2172: hostname = (char*)malloc (1024);
2173: port_number = SIE_DEFAULT_PORT_NUMBER;/*needs to be default sie port*/
2174: PGMfileName = NULL;
2175: is_buffered = FALSE;
2176:
2177: strcpy(hostname, "localhost");
2178:
2179: if(argc == 2)
2180: PGMfileName = (char*)argv[1];
2181:
2182: /*parse the command line*/
2183: /*while((c = getopt(argc, argv, GETOPTARGS)) != EOF)
2184: {
2185: switch(c)
2186: {
2187: case 'h':
2188: strcpy(hostname, optarg);
2189: is_buffered = TRUE;
2190: break;
2191:
2192: case 'p':
2193: port_number = atoi(optarg);
2194: break;
2195:
2196: case 'f':
2197: PGMfileName = optarg;
2198: break;
2199:
2200: case 'b':
2201: is_buffered = TRUE;
2202: break;
2203:
2204: default:
2205: usage(argv[0]);
2206: exit(1);
2207: }
2208: }*/
2209:
2210: }
2211:
2212: void init_image(PGMImage *image)
2213: {
2214: memset(image, 0, sizeof(PGMImage));
2215: (*image).width = HSIZE;
2216: (*image).height = VSIZE;
2217: (*image).maxVal = MVAL;
2218: }
2219:
2220:
2221: void init (int argc, const char **argv)
2222: {
2223: int threaded;
2224:
2225: /*parse the command line into globals*/
2226: parse_command_line(argc, argv);
2227:
2228:
2229:
2230: /*
2231: * Read in image file. - note: sets our global values, too.
2232: * ----------------------------------------------------------------- */
2233:
2234: /*allocate memory for original image*/
2235: img_original = (PGMImage*) malloc(sizeof(PGMImage));
2236: memset(img_original, 0, sizeof(PGMImage));
2237: img_pers_corr = (PGMImage*) malloc(sizeof(PGMImage));
2238: memset(img_pers_corr, 0, sizeof(PGMImage));
2239: img_grayscale = (PGMImage*) malloc(sizeof(PGMImage));
2240: memset(img_grayscale, 0, sizeof(PGMImage));
2241: img_moravec = (PGMImage*) malloc(sizeof(PGMImage));
2242: memset(img_moravec, 0, sizeof(PGMImage));
2243:
2244: if(argc == 3)
2245: {
2246: socket_num = admin_connect(argc, argv);
2247: threaded = pthread_create(&conn_thread, NULL, sock_com,
2248: (void*)img_original);
2249: sleep(1); /*give new thread time to make copy of sock_number*/
2250: /*set the filename so the text in the window says the hostname*/
2251: PGMfileName = (char*)malloc(1024); /*this better be enough*/
2252: strcpy(PGMfileName, hostname);
2253:
2254: img_cur = img_original; /*VERY IMPORTANT to set these*/
2255: HSIZE = 100;
2256: VSIZE = 100;
2257: MVAL = 255;
2258: /*
2259: (*img_original).width = HSIZE;
2260: (*img_original).height = VSIZE;
2261: (*img_original).maxVal = MVAL;
2262:
2263: (*img_pers_corr).width = HSIZE;
2264: (*img_pers_corr).height = VSIZE;
2265: (*img_pers_corr).maxVal = MVAL;
2266:
2267: (*img_grayscale).width = HSIZE;
2268: (*img_grayscale).height = VSIZE;
2269: (*img_grayscale).maxVal = MVAL;
2270:
2271: (*img_moravec).width = HSIZE;
2272: (*img_moravec).height = VSIZE;
2273: (*img_moravec).maxVal = MVAL;
2274: */
2275: }
2276: else if(argc == 2)
2277: {
2278: getPGMfile(PGMfileName, img_original);
2279: HSIZE = (*img_original).width;
2280: VSIZE = (*img_original).height;
2281: MVAL = (*img_original).maxVal;
2282:
2283: img_cur = img_original; /*VERY IMPORTANT to set this*/
2284:
2285: /*allocate memory for pers. corr. image*/
2286:
2287: init_image(img_pers_corr);
2288: init_image(img_grayscale);
2289: init_image(img_moravec);
2290: }
2291: else
2292: {
2293: usage(argv[0]);
2294: exit(1);
2295: }
2296:
2297: all_objects = (object*) malloc(sizeof(object) * MAX_CHAINS);
2298: memset(all_objects, 0, sizeof(object) * MAX_CHAINS);
2299:
2300: memset(points, 0, sizeof(points));
2301: memset(pieces, 0, sizeof(pieces));
2302: }
2303:
2304:
2305: int main(int argc, const char** argv)
2306: {
2307: int WindowID; /*unique window id, there is only one used*/
2308:
2309: /*
2310: * Call our init function to define non-OpenGL related things.
2311: * ----------------------------------------------------------------- */
2312: init (argc, argv);
2313:
2314: /*
2315: * Initialize the glut package.
2316: * ----------------------------------------------------------------- */
2317:
2318: glutInit(&argc, (char**)argv);
2319: glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
2320:
2321: /*
2322: * Define a new window (its size, position and title).
2323: * ----------------------------------------------------------------- */
2324: /*glutInitWindowSize (HSIZE, VSIZE);*/
2325: glutInitWindowPosition (10, 10);
2326: WindowID = glutCreateWindow (PGMfileName);
2327: glutSetWindow(WindowID);
2328: glutDisplayFunc(color);
2329:
2330: /*
2331: * select clearing color - white
2332: */
2333:
2334: glClearColor (1.0, 1.0, 1.0, 0.0);
2335:
2336:
2337: /*
2338: * initialize viewport values
2339: */
2340: glMatrixMode(GL_PROJECTION);
2341: glLoadIdentity();
2342: glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
2343:
2344: /*add menus*/
2345: if(argc == 2)
2346: {
2347: glutCreateMenu(menu);
2348: glutAddMenuEntry("restart", RESTART);
2349: glutAddMenuEntry("perspective correction", PERS_CORR);
2350: glutAddMenuEntry("color to grayscale", COLOR_TO_GRAY);
2351: glutAddMenuEntry("Moravec filter", MORAVEC);
2352: glutAddMenuEntry("detect edges", EDGES);
2353: glutAddMenuEntry("detect corners", CORNERS);
2354: glutAddMenuEntry("detect game", GAME);
2355: glutAddMenuEntry("detect pieces", PIECES);
2356: glutAddMenuEntry("toggle buffering", BUFFERS);
2357: glutAttachMenu(GLUT_RIGHT_BUTTON);
2358: }
2359: else
2360: glutIdleFunc(sie);
2361:
2362: glutMouseFunc(mouse);
2363: glutKeyboardFunc(keyboard);
2364:
2365: glutMainLoop();
2366:
2367: /*
2368: * When we reach here, we've left the event loop and are ready to
2369: * exit.
2370: * ----------------------------------------------------------------- */
2371: return 0;
2372: }
2373: