1: /*research.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: The middle points of the tic-tac-toe crosses are named after the corner 16: they are closest too. Thus the middle point near corner1 is middle1. 17: 18: The sides of the tic-tac-toe crosses are named for the line segment they 19: are on defined by the two corners that is is on. There are two numbers 20: given, the first is the closer corner of the two. (i.e. side13) 21: 22: An example of the numbering is here. Remeber the name is based on which 23: points are determined to be the first and second corners. (m = middle) 24: 25: corner2 side24 side42 26: __________________________ corner4 27: | | | | 28: | | | | 29: | | | | 30: |s23 | m2 | m4 | 31: -------------------------- side41 32: | | | | 33: | | | | 34: | | | | 35: |s32 | | | 36: -------------------------- side14 37: | | m3 | m1 | 38: | | | | 39: | | | | 40: | | | | 41: -------------------------- corner1 42: corner3 side31 side13 43: 44: Since, the corners are calculated from the chaincodes, there will always 45: the unique property that the 4th corner will be the farthest point from 46: the 1st and 2nd corners between these two points in the chaincode. The 47: third corner is either before the first corner in the chaincode or 48: after the second corner in the chaincode. 49: 50: The reason that the corners are determined using distance vs. using slope 51: is becuase, distance doesn't have a situation where the result is 52: undefined. 53: 54: */ 55: 56: #include <stdlib.h> 57: #include <stdio.h> 58: #include <string.h> 59: #include <math.h> 60: #include <float.h> 61: #include <GL/glut.h> 62: 63: #include "research.h" 64: 65: #define Round(v) ((int)(v+0.5)) 66: 67: /*minimal size to consider an "object" as length of chaincode*/ 68: #define DECENT_SIZED_OBJECT 200 69: 70: /*Used by the game detection algorithms. Represent the distance between two 71: known points as a fraction between 0 and 1.*/ 72: #define ONE_THIRD (1.0 / 3.0) 73: #define ONE_HALF (1.0 / 2.0) 74: #define ONE_SIXTH (1.0 / 6.0) 75: #define ONE_TWELTH (1.0 / 12.0) 76: #define ONE_24TH (1.0 / 24.0) 77: 78: /*the value used to threash the values of the moravec filter to 0 and 255*/ 79: #define THREASH_CONSTANT 128 80: 81: /*structure defined as the object type. All identified objects are stored in 82: a struct of this type. This is a very general data type and is not 83: specific to a tic-tac-toe game board.*/ 84: typedef struct 85: { 86: coord corner1; 87: coord corner2; 88: coord corner3; 89: coord corner4; 90: }object; 91: 92: /*This struct contains all the relavent information about the location, 93: orientation, etc. of the tic-tac-toe game board itself.*/ 94: typedef struct 95: { 96: /*The corners of the board. corner1 and corner2 are always opposite 97: (diagnol), just like corner3 and corner4 too.*/ 98: object corners; 99: 100: /*The sides. The numbers represent the to corners that the point is 101: between. The first number is the corner that it is closest to.*/ 102: coord side14, side13, side23, side24; 103: coord side31, side32, side41, side42; 104: 105: /*The points surrounding the middle square. The number indicates the 106: closest corner.*/ 107: coord middle1, middle2, middle3, middle4; 108: }ttt; 109: 110: /*Evil globals, but not do-able otherwise.*/ 111: static PGMImage *img_cur; /*current*/ 112: static PGMImage *img_original; /*original*/ 113: static PGMImage *img_pers_corr; /*perspective correction*/ 114: static PGMImage *img_grayscale; 115: static PGMImage *img_moravec; /*moravec*/ 116: static int HSIZE; /*width*/ 117: static int VSIZE; /*height*/ 118: static int MVAL; /*max val*/ 119: 120: /*pointer to a ttt structer containing the board info.*/ 121: static ttt *ttt_board; 122: 123: /*pointer to an array of list_info structs which point to the first, last and 124: current nodes in each of the chain codes.*/ 125: list_info* chain_codes; 126: 127: /*pointer to the array of objects found in the image. Counting starts at 128: zero and the array must be less than MAX_CHAINS in size. Unused values 129: should be set to zero.*/ 130: static object *all_objects; 131: 132: /*use double buffered output and keep running enable idle callback to call 133: detect_corners() over and over simulating realtime response. 134: Is set to TRUE or FALSE.*/ 135: int is_buffered; 136: 137: /*used to determine if the lines connecting the corners should be 138: drawn to the screen. Set to <0 if no abstract lines are to be drawn. 139: Otherwise is set to the number of objects found (AKA number of chaincodes).*/ 140: int draw_abstract_lines = -1; 141: 142: /*used to draw the single object most likely to be considered the 143: tic-tac-toe board. Should be <0 if this should not be drawn. 144: Should be equal to the chain-code number (from 0 to MAX_CHAINS - 1) 145: if it is to be drawn.*/ 146: int draw_abstract_board = -1; 147: 148: 149: /**************Drawing funcitions************************************/ 150: /********************************************************************/ 151: 152: /*This function draws a single pixel to the screen. (0, 0) is the lower left 153: corner.*/ 154: /*1st: Integer of the x pixel to be drawn. 155: 2nd: Integer of the y pixel to be drawn. 156: 3rd: RGB values as stored as integral types with values between 0 - 255.*/ 157: void setCPixel(int ix, int iy, RGB_INT color) 158: { 159: float x = (ix*2.0)/HSIZE - 1.0; 160: float y = (iy*2.0)/VSIZE - 1.0; 161: 162: float red = (float)color.red/(float)MVAL; 163: float green = (float)color.green/(float)MVAL; 164: float blue = (float)color.blue/(float)MVAL; 165: 166: glColor3f(red, green, blue); 167: 168: glBegin(GL_POINTS); 169: glVertex2f (x, y); 170: glEnd(); 171: } 172: 173: /*This function draws a line segment to the screen. (0, 0) is the lower 174: left corner.*/ 175: /*1st: x coord. of the 1st endpoint. 176: 2nd: y coord, of the 2nd endpoint. 177: 3rd: x coord. of the 2st endpoint. 178: 4th: y coord. of the 2st endpoint. 179: 5th: RGB values as stored as integral types with values between 0 - 255.*/ 180: void setCLines(int ix1, int iy1, int ix2, int iy2, RGB_INT color) 181: { 182: float x1 = (ix1*2.0)/HSIZE - 1.0; 183: float y1 = (iy1*2.0)/VSIZE - 1.0; 184: float x2 = (ix2*2.0)/HSIZE - 1.0; 185: float y2 = (iy2*2.0)/VSIZE - 1.0; 186: 187: float red = (float)color.red/(float)MVAL; 188: float green = (float)color.green/(float)MVAL; 189: float blue = (float)color.blue/(float)MVAL; 190: 191: glColor3f(red, green, blue); 192: 193: glBegin(GL_LINES); 194: glVertex2f (x1, y1); 195: glVertex2f (x2, y2); 196: glEnd(); 197: } 198: 199: /*This function draws a square to the screen. (0, 0) is the lower left 200: corner.*/ 201: /*1st: x coord. of the 1st endpoint. 202: 2nd: y coord, of the 2nd endpoint. 203: 3rd: x coord. of the 2st endpoint. 204: 4th: y coord. of the 2st endpoint. 205: 5th: RGB values as stored as integral types with values between 0 - 255.*/ 206: void setCRect(int ix1, int iy1, int ix2, int iy2, RGB_INT color) 207: { 208: float x1 = (ix1*2.0)/HSIZE - 1.0; 209: float y1 = (iy1*2.0)/VSIZE - 1.0; 210: float x2 = (ix2*2.0)/HSIZE - 1.0; 211: float y2 = (iy2*2.0)/VSIZE - 1.0; 212: 213: float red = (float)color.red/(float)MVAL; 214: float green = (float)color.green/(float)MVAL; 215: float blue = (float)color.blue/(float)MVAL; 216: 217: glColor3f(red, green, blue); 218: 219: glBegin(GL_POLYGON); 220: glVertex2f (x1, y1); 221: glVertex2f (x1, y2); 222: glVertex2f (x2, y2); 223: glVertex2f (x2, y1); 224: glEnd(); 225: } 226: 227: /*draws a string of text to the screen.*/ 228: /*1st: The x coord of the lower left corner where it is to be displayed. 229: 2nd: The y coord of the lower left corner where it is to be displayed. 230: 3rd: String that is to be displayed. 231: 4th: The color to display the text in.*/ 232: void drawString(int ix, int iy, char theString[256], RGB_INT color) 233: { 234: float x = (ix*2.0)/HSIZE - 1.0; 235: float y = (iy*2.0)/VSIZE - 1.0; 236: int i; 237: 238: float red = (float)color.red/(float)MVAL; 239: float green = (float)color.green/(float)MVAL; 240: float blue = (float)color.blue/(float)MVAL; 241: 242: glColor3f(red, green, blue); 243: 244: glRasterPos2f(x, y); 245: for (i = 0; theString[i] != '\0'; i++) /* draw the chars one at a time */ 246: glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, theString[i]); 247: } 248: 249: /*Draws a PGMImage to the window.*/ 250: /*1st: The pgm image to display.*/ 251: void showColor (PGMImage *img) 252: { 253: int i, j; /*loop counting: i = y, j = x*/ 254: 255: /*This is the simplist way I found to use the fast OpenGL direct drawing 256: functions without a major rewrite of the code. First pack the data 257: into this 3D array. If that little bit of extra speed is needed, then 258: the PGMImage functionality would need to be written as a 3D array and not 259: a 2D array of structs.*/ 260: GLubyte checkImage[(*img).height][(*img).width][3]; 261: for(i = 0; i < (*img).height; i++) 262: { 263: for(j = 0; j < (*img).width; j++) 264: { 265: checkImage[i][j][0] = (GLubyte) (*img).data[i][j].red; 266: checkImage[i][j][1] = (GLubyte) (*img).data[i][j].green; 267: checkImage[i][j][2] = (GLubyte) (*img).data[i][j].blue; 268: } 269: } 270: 271: /*tell OpenGL that the values are packed into byte sized values*/ 272: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 273: /*since OpenGL is setup for -1 to 1 size, specify (-1, -1) for the lower 274: left corner which is (0, 0) to most of the program*/ 275: glRasterPos2f(-1, -1); 276: /*draw the current image*/ 277: glDrawPixels((*img).width, (*img).height, GL_RGB, 278: GL_UNSIGNED_BYTE, checkImage); 279: 280: glFlush(); 281: } 282: 283: /*called by showAbstract()*/ 284: /*draws the (x, y) values of the coordinates specified at that coordinate*/ 285: /*1st: The x coord. 286: 2nd: The y coord. 287: 3rd: Additional text to display, aside from the (x, y) determined from the 288: 1st and 2nd parameters.*/ 289: void display_labels(int x_coord, int y_coord, char* more_text) 290: { 291: char text[40]; /*for displaying the locations in text form*/ 292: int offset = 8; /*offset text # pixels from location*/ 293: 294: sprintf(text, "%s (%d, %d)\0", more_text, x_coord, y_coord); 295: drawString(x_coord + offset, y_coord + offset, text, blue); 296: } 297: 298: /*display the global abstract data*/ 299: void showAbstract(object* object_list, ttt *ttt_data) 300: { 301: int i; 302: 303: /* coord hor_top1, hor_top2; 304: coord hor_bot1, hor_bot2; 305: coord ver_rgt1, ver_rgt2; 306: coord ver_lft1, ver_lft2; 307: */ 308: list_info temp; 309: 310: glPointSize(2); /*make points more visible, if desired*/ 311: /*glLineWidth(4);*/ 312: 313: /*draw the chaincodes*/ 314: /*chain_codes is a global pointer to an array of list_info types*/ 315: for(i = 0; (i < MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++) 316: { 317: memcpy(&temp, &chain_codes[i], sizeof(list_info)); 318: while(RetrieveNextNode(&temp).cur) 319: { 320: setCPixel(RetrieveInfo(&temp).location.x, 321: RetrieveInfo(&temp).location.y, gray); 322: Advance(&temp); 323: } 324: } 325: 326: /*first check for non-null pointer, next check for dereferenced pointers 327: in the array of head pointers and lastly make sure things stay in 328: bound of the max incase all MAX_CHAINS number of chains are used.*/ 329: /*draw_abstract_lines is the global that holds the number of "objects" 330: to draw abstract information for*/ 331: for(i = 0; i < draw_abstract_lines && i < MAX_CHAINS; i++) 332: { 333: setCLines(object_list[i].corner1.x, object_list[i].corner1.y, 334: object_list[i].corner3.x, object_list[i].corner3.y,yellow); 335: setCLines(object_list[i].corner4.x, object_list[i].corner4.y, 336: object_list[i].corner2.x, object_list[i].corner2.y,yellow); 337: setCLines(object_list[i].corner3.x, object_list[i].corner3.y, 338: object_list[i].corner2.x, object_list[i].corner2.y,yellow); 339: setCLines(object_list[i].corner4.x, object_list[i].corner4.y, 340: object_list[i].corner1.x, object_list[i].corner1.y,yellow); 341: 342: setCPixel(object_list[i].corner1.x, object_list[i].corner1.y,dk_red); 343: setCPixel(object_list[i].corner2.x, object_list[i].corner2.y,dk_blue); 344: setCPixel(object_list[i].corner3.x, object_list[i].corner3.y,white); 345: setCPixel(object_list[i].corner4.x, object_list[i].corner4.y,dk_green); 346: 347: /*labels for corner points*/ 348: display_labels(object_list[i].corner1.x, object_list[i].corner1.y, "c1"); 349: display_labels(object_list[i].corner2.x, object_list[i].corner2.y, "c2"); 350: display_labels(object_list[i].corner3.x, object_list[i].corner3.y, "c3"); 351: display_labels(object_list[i].corner4.x, object_list[i].corner4.y, "c4"); 352: } 353: 354: /*if there is board to draw and just make sure there isn't a NULL pointer*/ 355: if((draw_abstract_board > -1) && ttt_data) 356: { 357: /*This code should draw the bounding box*/ 358: /*setCLines(ttt_data->corners.corner1.x, 359: ttt_data->corners.corner1.y, 360: ttt_data->corners.corner3.x, 361: ttt_data->corners.corner3.y,blue); 362: setCLines(ttt_data->corners.corner4.x, 363: ttt_data->corners.corner4.y, 364: ttt_data->corners.corner2.x, 365: ttt_data->corners.corner2.y,blue); 366: setCLines(ttt_data->corners.corner3.x, 367: ttt_data->corners.corner3.y, 368: ttt_data->corners.corner2.x, 369: ttt_data->corners.corner2.y,blue); 370: setCLines(ttt_data->corners.corner4.x, 371: ttt_data->corners.corner4.y, 372: ttt_data->corners.corner1.x, 373: ttt_data->corners.corner1.y,blue); 374: setCPixel(ttt_data->corners.corner1.x, 375: ttt_data->corners.corner1.y, red); 376: setCPixel(ttt_data->corners.corner2.x, 377: ttt_data->corners.corner2.y, red); 378: setCPixel(ttt_data->corners.corner3.x, 379: ttt_data->corners.corner3.y, red); 380: setCPixel(ttt_data->corners.corner4.x, 381: ttt_data->corners.corner4.y, red); 382: */ 383: 384: setCLines(ttt_data->side13.x, 385: ttt_data->side13.y, 386: ttt_data->side42.x, 387: ttt_data->side42.y,blue); 388: setCLines(ttt_data->side31.x, 389: ttt_data->side31.y, 390: ttt_data->side24.x, 391: ttt_data->side24.y,blue); 392: setCLines(ttt_data->side23.x, 393: ttt_data->side23.y, 394: ttt_data->side41.x, 395: ttt_data->side41.y,blue); 396: setCLines(ttt_data->side32.x, 397: ttt_data->side32.y, 398: ttt_data->side14.x, 399: ttt_data->side14.y,blue); 400: 401: setCPixel(ttt_data->middle1.x, ttt_data->middle1.y, red); 402: setCPixel(ttt_data->middle2.x, ttt_data->middle2.y, red); 403: setCPixel(ttt_data->middle3.x, ttt_data->middle3.y, red); 404: setCPixel(ttt_data->middle4.x, ttt_data->middle4.y, red); 405: 406: setCPixel(ttt_data->side13.x, ttt_data->side13.y, red); 407: setCPixel(ttt_data->side14.x, ttt_data->side14.y, red); 408: setCPixel(ttt_data->side23.x, ttt_data->side23.y, red); 409: setCPixel(ttt_data->side24.x, ttt_data->side24.y, red); 410: setCPixel(ttt_data->side31.x, ttt_data->side31.y, red); 411: setCPixel(ttt_data->side32.x, ttt_data->side32.y, red); 412: setCPixel(ttt_data->side41.x, ttt_data->side41.y, red); 413: setCPixel(ttt_data->side42.x, ttt_data->side42.y, red); 414: 415: /*labels for middle points*/ 416: display_labels(ttt_data->middle1.x, ttt_data->middle1.y, "m1"); 417: display_labels(ttt_data->middle2.x, ttt_data->middle2.y, "m2"); 418: display_labels(ttt_data->middle3.x, ttt_data->middle3.y, "m3"); 419: display_labels(ttt_data->middle4.x, ttt_data->middle4.y, "m4"); 420: 421: /*lables for side points*/ 422: display_labels(ttt_data->side13.x, ttt_data->side13.y, "s13"); 423: display_labels(ttt_data->side14.x, ttt_data->side14.y, "s14"); 424: display_labels(ttt_data->side23.x, ttt_data->side23.y, "s23"); 425: display_labels(ttt_data->side24.x, ttt_data->side24.y, "s24"); 426: display_labels(ttt_data->side31.x, ttt_data->side31.y, "s31"); 427: display_labels(ttt_data->side32.x, ttt_data->side32.y, "s32"); 428: display_labels(ttt_data->side41.x, ttt_data->side41.y, "s41"); 429: display_labels(ttt_data->side42.x, ttt_data->side42.y, "s42"); 430: } 431: glFlush(); 432: } 433: 434: /**********************Support functions*******************************/ 435: /***********************************************************************/ 436: 437: /*take the source pixel and copy it to the destination pixel.*/ 438: /*1st: dest is the pointer to the PGMImage that is to have the pixel changed. 439: 2nd: the row(y coord) that is to be the destination 440: 3rd: the column(x coord) that is to be the destination 441: 4th: dsrc is the pointer to the PGMImage that is to have its pixel copied. 442: 5th: the row(y coord) that is to be the source 443: 6th: the column(x coord) that is to be the source 444: */ 445: void pxlcpy(PGMImage *dest, int dest_row, int dest_col, 446: PGMImage *src, int src_row, int src_col) 447: { 448: /*make sure values are within bounds*/ 449: if(dest_col > 0 && dest_col < (*dest).width 450: && dest_row > 0 && dest_row < (*dest).height 451: && src_col > 0 && src_col < (*src).width 452: && src_row > 0 && src_row < (*src).height) 453: { 454: (*dest).data[dest_row][dest_col].red = 455: (*src).data[src_row][src_col].red; 456: 457: (*dest).data[dest_row][dest_col].green = 458: (*src).data[src_row][src_col].green; 459: 460: (*dest).data[dest_row][dest_col].blue = 461: (*src).data[src_row][src_col].blue; 462: } 463: } 464: 465: /*calculate the average of the red, green and blue values of a sinfle pixel.*/ 466: /*1st: the RGB_INT to be averaged*/ 467: /*the average of the red, green and blue is returned*/ 468: int rgb_avg(RGB_INT cur_pxl) 469: { 470: /*convert each RGB to the average of the original*/ 471: return ((cur_pxl.red + cur_pxl.green + cur_pxl.blue) / 3); 472: } 473: 474: /*Returns average (with RGB avg) pixel value for the image passed in.*/ 475: /*1st: a pointer to a pgm image*/ 476: /*the return value is the average of all the pixel in the image. Each pixel's 477: value is the average of its RGB parts.*/ 478: int img_pxl_avg(PGMImage* img) 479: { 480: int i, j; /*loop counting*/ 481: int sum = 0; 482: int temp; 483: 484: for(i = 0; i < (*img).height; i++)/*collumn*/ 485: for(j = 0; j < (*img).width; j++)/*row*/ 486: sum += rgb_avg((*img).data[i][j]); 487: 488: 489: temp = (sum / ((*img).height * (*img).width)); 490: 491: return temp; 492: } 493: 494: /*determines what in the image is determined to background and foreground.*/ 495: /*1st: value to compare whether more of the pixels are greater than this or 496: less than this. 497: 2nd: the image to be checked.*/ 498: /*return >0 if number of pixels is greater than img. pxl. avg., return <0 if 499: number of pixesl is less than img. pxl. avg. and return zero of equal*/ 500: int background(int treash_value, PGMImage* img) 501: { 502: int i, j; /*loop counting*/ 503: int pxl_less = 0, pxl_more = 0; 504: 505: for(i = 0; i < (*img).height; i++)/*collumn*/ 506: for(j = 0; j < (*img).width; j++)/*row*/ 507: { 508: if(rgb_avg((*img).data[i][j]) < treash_value) 509: pxl_less++; 510: 511: if(rgb_avg((*img).data[i][j]) > treash_value) 512: pxl_more++; 513: } 514: 515: if(pxl_less > pxl_more) 516: return -1; 517: else if(pxl_less < pxl_more) 518: return 1; 519: else 520: return 0; 521: } 522: 523: /*Used by showColor() and detect_pieces() to intrapolate the location of a 524: point along a line between two known points. 525: r = (1 - t)p1 + t*p2 526: This is calculated twice, once for x direction and once for y direction.*/ 527: /*1st: coordinate of the closer point that t will intropolate to. 528: 2nd: coordinate of the farther point that t will intrapolate to. 529: 3rd: the fractional value, with point1 considered t = 0 and point2 530: considered t = 1 that the return value is intropolated.*/ 531: /*returns the coordinate that is intropolated*/ 532: coord find_dividers(coord point1, coord point2, float t) 533: { 534: coord temp; 535: 536: temp.x = (int)(((1.0 - t) * point1.x) + (t * point2.x)); 537: temp.y = (int)(((1.0 - t) * point1.y) + (t * point2.y)); 538: 539: return temp; 540: } 541: 542: /*takes two coordinates as x and y pairs and returns the distance betweem them 543: as a decimal*/ 544: float findDist(int x1, int y1, int x2, int y2) 545: { 546: return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2))); 547: } 548: 549: 550: /******Perspective correction******************************************* 551: ***********************************************************************/ 552: 553: /*The image that we will be looking at will from the camera will likely 554: have a perspective, so look at removing the perspective effect. The x 555: direction correction starts in the middle and moves toward the sides 556: stretching the image with more stretching occuring with higher rows. 557: The y direction correction inserts more lines near the top than towards the 558: bottom of the image. There is a y direction recentering calculation 559: performed.*/ 560: /*1st: The pointer to the pgm image that the image will be copied into with 561: the perspective removed. 562: 2nd: The pointer to the pgm image that will have the perspective removed.*/ 563: void pers_corr(PGMImage* new_img, PGMImage* org_img) 564: { 565: /******************X direction variables*********/ 566: 567: /*i and j are the left half, k and l are the right half*/ 568: float i, k; /*loop counting x dir*/ 569: int j, l, row; /*loop counting x dir*/ 570: int old_i, old_k; /*x dir. counting to remove moire iterference*/ 571: 572: float ins_s = 2.2; /*insert constant starting value x dir.*/ 573: float ins_k = ins_s; /*current insert x dir.*/ 574: 575: /*The halfway marks in the width.*/ 576: int mid_width_left = ((*new_img).width / 2) - 1; 577: int mid_width_right = ((*new_img).width / 2); 578: 579: /******************Y direction variables*********/ 580: 581: float m; /*loop counting y dir*/ 582: int n, col; /*loop counting y dir*/ 583: int old_n = (*new_img).height, old_n_inc; /*y dir remove moire iterference*/ 584: 585: float ins_t = 2.0; /*insert constant starting value y dir.*/ 586: float ins_j = ins_t; /*current insert value y dir.*/ 587: 588: /*since the x dir is calculated from the middle, the middle is always 589: mapped to the middle. Since the y dir is calculated from the top the 590: image is pushed to the bottom. To correct this add to the new pixel 591: location this value to recenter the image.*/ 592: int recenter = ((*new_img).height / ins_t); 593: 594: /*just to be thourough clear the memory and reset maxes*/ 595: memset(new_img, 0, sizeof(PGMImage)); 596: (*new_img).height = (*org_img).height; 597: (*new_img).width = (*org_img).width; 598: (*new_img).maxVal = (*org_img).maxVal; 599: 600: /*Loop through each row from top to bottom...*/ 601: /*for(row = ((*new_img).height - 1); row >= 0; row--)*/ 602: for(m = n = ((*new_img).height - 1); 603: m >= 0, n >= 0; 604: m -= ins_j, n--) 605: { 606: /*...reset moire interference removal counter x dir...*/ 607: old_i = ((*new_img).width / 2) - 1; 608: old_k = ((*new_img).width / 2); 609: 610: /*...so each half is ajusted to remove perspective effect...*/ 611: /*initalize the x and y starting conditions*/ 612: for(i = j = mid_width_left, k = l = mid_width_right; 613: /*x ending conditions*/ 614: i >= 0, j >= 0, k < (*new_img).width, l < (*new_img).width; 615: /*incremental*/ 616: i -= ins_k, j--, k += ins_k, l++) 617: { 618: for(;old_i >= (int)i; old_i--) /*...in the left half...*/ 619: for(old_n_inc = old_n; old_n_inc >= m; old_n_inc--) 620: pxlcpy(new_img, old_n_inc + recenter, old_i, org_img, n, j); 621: 622: for(;old_k <= (int)k; old_k++) /*...in the right half.*/ 623: for(old_n_inc = old_n; old_n_inc >= m; old_n_inc--) 624: pxlcpy(new_img, old_n_inc + recenter, old_k, org_img, n, l); 625: } 626: /*Move the new image x_coord pixel counter to next new image pixel*/ 627: ins_k -= ((ins_s - 1.0) / (*new_img).height); 628: ins_j -= ((ins_t - 1.0) / (*new_img).width); 629: 630: old_n = m; /*store for next row (y direction)*/ 631: } 632: 633: 634: } 635: 636: /*****convert color to grayscale**************************************** 637: ***********************************************************************/ 638: 639: /*Turn a color image into a black and white image.*/ 640: /*1st: The pointer to the pgm image that the image will be copied into with 641: the perspective removed. 642: 2nd: The pointer to the pgm image that will have the color removed.*/ 643: void color_to_gray(PGMImage* new_img, PGMImage* org_img) 644: { 645: int row, col; /*loop counting*/ 646: RGB_INT cur_pxl; /*current pixel*/ 647: 648: /*just to be thourough clear the memory and reset maxes*/ 649: memset(new_img, 0, sizeof(PGMImage)); 650: (*new_img).height = (*org_img).height; 651: (*new_img).width = (*org_img).width; 652: (*new_img).maxVal = (*org_img).maxVal; 653: 654: /*Starting with the top row...*/ 655: for(row = (*new_img).height - 1; row >= 0; row--) 656: for(col = 0; col < (*new_img).width - 1; col++) 657: { 658: cur_pxl = (*org_img).data[row][col]; /*more readable*/ 659: 660: /*convert each RGB to the average of the original*/ 661: (*new_img).data[row][col].red = rgb_avg(cur_pxl); 662: (*new_img).data[row][col].green = rgb_avg(cur_pxl); 663: (*new_img).data[row][col].blue = rgb_avg(cur_pxl); 664: } 665: } 666: 667: /*******Moravec Edge Highlighting************************************** 668: **********************************************************************/ 669: 670: /*use the Moravec algorithm to highlight the edges in an image. This 671: algorithm takes the normally grayscale result and threasholds it to 672: the values of 0 and 255 with the divide at THREASH_CONSTANT*/ 673: /*1st: The pointer to the pgm image that the image will be copied into with 674: the moravec filter applied. 675: 2nd: The pointer to the pgm image that will have the moravec filter*/ 676: void moravec(PGMImage* new_img, PGMImage* org_img) 677: { 678: int row, col; /*loop counting*/ 679: int i, j, k, l; /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/ 680: int running_sum; 681: float K = .5; /*.125 according to org. formula, but .5 is brighter*/ 682: int max_val = 0, row_max, col_max; /* max porportion value in image*/ 683: 684: /*just to be thourough clear the memory and reset maxes*/ 685: memset(new_img, 0, sizeof(PGMImage)); 686: (*new_img).height = (*org_img).height; 687: (*new_img).width = (*org_img).width; 688: (*new_img).maxVal = (*org_img).maxVal; 689: 690: /*starting at the top row*/ 691: for(row = (*new_img).height - 1 - 1; row > 0; row--) 692: for(col = 1; col < (*new_img).width - 1; col++) /*left col start*/ 693: { 694: i = row; 695: j = col; 696: running_sum = 0; 697: 698: /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/ 699: for(k = i - 1; k <= i + 1; k++) /*row*/ 700: for(l = j - 1; l <= j + 1; l++) /*column*/ 701: running_sum += abs(rgb_avg((*org_img).data[k][l]) - 702: rgb_avg((*org_img).data[i][j])); 703: 704: /*assign the new pixel value*/ 705: /*since it all the data is initalized to 0, we only worry when it 706: shouldn't be*/ 707: if((int)(K * running_sum) >= THREASH_CONSTANT) 708: { 709: (*new_img).data[row][col].red = 255; 710: (*new_img).data[row][col].green = 255; 711: (*new_img).data[row][col].blue = 255; 712: } 713: /*this is the original code...*/ 714: /*(*new_img).data[row][col].red = (int)(K * running_sum); 715: (*new_img).data[row][col].green = (int)(K * running_sum); 716: (*new_img).data[row][col].blue = (int)(K * running_sum);*/ 717: } 718: } 719: 720: /*******Corners***************************************************** 721: *******************************************************************/ 722: 723: /*find the first two corners of all of the obejects in the image. This is 724: accomplished by looking at the chaincodes for the images and determining 725: the two points that are the farthest apart.*/ 726: /*1st: a pointer to an array of object structs. 727: 2nd: the array of chaincodes that is searched for the farthest two points.*/ 728: void findFirstTwoCorners(object *objects_array, list_info* chainCodes) 729: { 730: int i; /*loop counting*/ 731: list_info temp, search; /*temp copies of the data*/ 732: double max_dist, test_dist; /*temp distance holders*/ 733: 734: /*printf("\nFinding first 2 corners.\n");*/ 735: 736: /*as long as there are codes to check, keep checking.*/ 737: for(i = 0; ((i < MAX_CHAINS) && chainCodes[i].cur); i++) 738: { 739: memcpy(&temp, &chainCodes[i], sizeof(list_info)); 740: 741: max_dist = 0.0; /*reset this for each iteration*/ 742: 743: while(RetrieveNextNode(&temp).cur) /*while there are nodes to check*/ 744: { 745: /*set the faster moving search pointer to temp, 746: this increases the effiecency a lot compared to 747: setting it equal to the first node..*/ 748: memcpy(&search, &temp, sizeof(list_info)); 749: 750: while(RetrieveNextNode(&search).cur) 751: { 752: /*setCPixel(RetrieveInfo(&temp).location.x, 753: RetrieveInfo(&temp).location.y, green);*/ 754: 755: /*determine if found a new maximum distance between two locations*/ 756: if((test_dist = findDist(RetrieveInfo(&search).location.x, 757: RetrieveInfo(&search).location.y, 758: RetrieveInfo(&temp).location.x, 759: RetrieveInfo(&temp).location.y))>max_dist) 760: { 761: max_dist = test_dist; 762: objects_array[i].corner1.x = RetrieveInfo(&temp).location.x; 763: objects_array[i].corner1.y = RetrieveInfo(&temp).location.y; 764: objects_array[i].corner2.x = RetrieveInfo(&search).location.x; 765: objects_array[i].corner2.y = RetrieveInfo(&search).location.y; 766: } 767: Advance(&search); 768: } 769: Advance(&temp); 770: } 771: } 772: } 773: 774: /*determine the second to corners for the objects.*/ 775: /*1st: the array of objects that contains the first to corners already. 776: 2nd: the array of chaincode data*/ 777: void findSecondTwoCorners(object *objects_array, list_info* chain_code_array) 778: { 779: int i; /*loop counting*/ 780: list_info temp; 781: float temp_dist1, temp_dist2; /*distance between point and each corner*/ 782: coord canidate_coord1, temp_coord; 783: float canidate_dist1, max_dist; 784: int corner_count; 785: 786: /*printf("\nFinding last 2 corners.\n\n");*/ 787: 788: /*for each chain code find the corners*/ 789: for(i = 0; (i < MAX_CHAINS) && chain_code_array[i].cur; i++) 790: { 791: memcpy(&temp, &chain_code_array[i], sizeof(list_info)); 792: 793: /*reset these for the next chain code*/ 794: max_dist = 0.0; 795: corner_count = 1; 796: 797: /*while there are nodes in the chain code to check*/ 798: /*if there isn't a next node cur is NULL, which is checked*/ 799: while(RetrieveNextNode(&temp).cur) 800: { 801: /*setCPixel(RetrieveInfo(&temp).location.x, 802: RetrieveInfo(&temp).location.y, color1);*/ 803: 804: /*determine the first canidate coord for corner 3/4*/ 805: if(((RetrieveInfo(&temp).location.x == objects_array[i].corner1.x) 806: && (RetrieveInfo(&temp).location.y == objects_array[i].corner1.y)) 807: || ((RetrieveInfo(&temp).location.x == objects_array[i].corner2.x) 808: &&(RetrieveInfo(&temp).location.y == objects_array[i].corner2.y))) 809: { 810: /*if this corner found is the first of the two allready known 811: corners, then set the first canidate coord data and reset data 812: to find the next canidate corner point*/ 813: if(corner_count == 1) 814: { 815: canidate_coord1.x = temp_coord.x; 816: canidate_coord1.y = temp_coord.y; 817: canidate_dist1 = max_dist; 818: 819: corner_count = 2; /*set for next corner*/ 820: max_dist = 0.0; 821: } 822: else if(corner_count == 2) 823: { 824: /*the second canidate is always a corner*/ 825: all_objects[i].corner4.x = temp_coord.x; 826: all_objects[i].corner4.y = temp_coord.y; 827: 828: max_dist = 0.0; /*set for next corner canidate*/ 829: } 830: } 831: 832: /*calculate the distance between the current point being checked and 833: each corner point*/ 834: temp_dist1 = findDist(all_objects[i].corner1.x, 835: all_objects[i].corner1.y, 836: RetrieveInfo(&temp).location.x, 837: RetrieveInfo(&temp).location.y); 838: temp_dist2 = findDist(all_objects[i].corner2.x, 839: all_objects[i].corner2.y, 840: RetrieveInfo(&temp).location.x, 841: RetrieveInfo(&temp).location.y); 842: 843: /*if the current point is the furthest away sofar, store this point 844: untill it is overridden or becomes a canidate point*/ 845: if((temp_dist1 + temp_dist2) > max_dist) 846: { 847: temp_coord.x = RetrieveInfo(&temp).location.x; 848: temp_coord.y = RetrieveInfo(&temp).location.y; 849: 850: max_dist = (temp_dist1 + temp_dist2); 851: } 852: 853: Advance(&temp); 854: } 855: 856: /*from the three canidate coords find the two real corners.*/ 857: /*the second canidate will always be a corner, must test 1 vs 3, where 858: three is in the variables temp_coord and max_dist.*/ 859: if(canidate_dist1 > max_dist) /*first canidate*/ 860: { 861: all_objects[i].corner3.x = canidate_coord1.x; 862: all_objects[i].corner3.y = canidate_coord1.y; 863: } 864: else /*third canidate*/ 865: { 866: all_objects[i].corner3.x = temp_coord.x; 867: all_objects[i].corner3.y = temp_coord.y; 868: } 869: } 870: } 871: 872: /*returns the number of objects found*/ 873: /*places the corner info in the global corners data*/ 874: int detect_corners(list_info* current_chaincodes, object *objects_array) 875: { 876: int i; /*temporarily holds number of chaincodes*/ 877: 878: /*clear the array of objects*/ 879: memset(objects_array, 0, sizeof(object) * MAX_CHAINS); 880: 881: /*find the first two corners. they will be across a diagnal.*/ 882: findFirstTwoCorners(objects_array, current_chaincodes); 883: /*find the second two corners. they will be across a diagnal too.*/ 884: findSecondTwoCorners(objects_array, current_chaincodes); 885: 886: /*when this stops, i holds the number of chaincodes*/ 887: for(i = 0; (i < MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++); 888: 889: return i; 890: } 891: 892: /*******Find the game board******************************************** 893: **********************************************************************/ 894: 895: /*compare two distances between points.*/ 896: /*1st: the distance between two points 897: 2nd: the distance between two points*/ 898: /*returns the difference of the lengths divided by the lengths average. 899: If there is a division by zero, the two lengths are both zero. The return 900: value in this case is FLT_MAX defined in float.h*/ 901: float compare_length(float length1, float length2) 902: { 903: float denom_check = fabs((length1 + length2) / 2); 904: 905: if(denom_check == 0) 906: { 907: /*the only way to possibly pull this off is if one point is 908: is considered more than one corner. This is most likely 909: to happen where the chain was only two or three nodes long.*/ 910: /*Just set the error to the maximum value obtained from float.h, 911: since such a small chain is not what we are looking for.*/ 912: 913: return FLT_MAX; 914: } 915: else 916: return fabs(length1 - length2) / denom_check; 917: } 918: 919: /*determine which object is the tic-tac-toe board from those found in the 920: chaincodes and stored as objects.*/ 921: /*1st: pointer the chaincode array. 922: 2nd: pointer to the array of objects.*/ 923: /*return the object that is most likely the chaincode. This value is 924: the array subscript value for the object array. If none is found then 925: returns -1.*/ 926: int detect_game(list_info *current_chaincodes, object *object_array) 927: { 928: float length2to4[MAX_CHAINS], length1to3[MAX_CHAINS]; /*side pairs*/ 929: float length1to4[MAX_CHAINS], length2to3[MAX_CHAINS]; /*side pairs*/ 930: float length1to2[MAX_CHAINS], length3to4[MAX_CHAINS]; /*diagnaols*/ 931: 932: float error24to13[MAX_CHAINS], error14to23[MAX_CHAINS];/*opp. sides*/ 933: float error14to13[MAX_CHAINS], error23to24[MAX_CHAINS];/*share corner*/ 934: float error31to32[MAX_CHAINS], error41to42[MAX_CHAINS];/*share corner*/ 935: float error12to34[MAX_CHAINS];/*diagnaols*/ 936: 937: float error_avg;/*average of the errors stored in error##to## variables*/ 938: 939: int i, k; /*loop counting*/ 940: list* temp; 941: 942: /*the most likely object (0 to num_of_corners) that is to be considered 943: as the board. The float is the error associated with this object.*/ 944: int most_likely = -1; 945: float ml_error = FLT_MAX; /*just to make sure*/ 946: 947: /*for each chaincode*/ 948: for(i = 0; (i < MAX_CHAINS) && current_chaincodes && 949: current_chaincodes[i].cur; i++) 950: { 951: /*count the number of nodes in the chaincode. Unless the size is 952: considered long enough, skip it and move on.*/ 953: if(Length(¤t_chaincodes[i]) < DECENT_SIZED_OBJECT) 954: continue; 955: 956: /*since points 1 & 2 are at a diagnal, and 3 & 4 are at a diagnol, 957: then the dist between 2 and 4 & 1 and 3 should be close 958: in value.*/ 959: length2to4[i] = findDist(object_array[i].corner2.x, 960: object_array[i].corner2.y, 961: object_array[i].corner4.x, 962: object_array[i].corner4.y); 963: length1to3[i] = findDist(object_array[i].corner1.x, 964: object_array[i].corner1.y, 965: object_array[i].corner3.x, 966: object_array[i].corner3.y); 967: 968: /*the other side pair*/ 969: length1to4[i] = findDist(object_array[i].corner1.x, 970: object_array[i].corner1.y, 971: object_array[i].corner4.x, 972: object_array[i].corner4.y); 973: length2to3[i] = findDist(object_array[i].corner2.x, 974: object_array[i].corner2.y, 975: object_array[i].corner3.x, 976: object_array[i].corner3.y); 977: 978: /*diagnols... always will be 1 & 2 and 3 & 4*/ 979: length1to2[i] = findDist(object_array[i].corner1.x, 980: object_array[i].corner1.y, 981: object_array[i].corner2.x, 982: object_array[i].corner2.y); 983: length3to4[i] = findDist(object_array[i].corner3.x, 984: object_array[i].corner3.y, 985: object_array[i].corner4.x, 986: object_array[i].corner4.y); 987: 988: /*calculate percent errors for all edge (and diagnal) combinations*/ 989: error24to13[i] = compare_length(length2to4[i], length1to3[i]);/*op.side*/ 990: error14to23[i] = compare_length(length1to4[i], length2to3[i]); 991: error14to13[i] = compare_length(length1to4[i], length1to3[i]);/*1 crn.*/ 992: error23to24[i] = compare_length(length2to3[i], length2to4[i]); 993: error31to32[i] = compare_length(length1to3[i], length2to3[i]); 994: error41to42[i] = compare_length(length1to4[i], length2to4[i]); 995: error12to34[i] = compare_length(length1to2[i], length3to4[i]);/*diag.*/ 996: 997: /*average all of the error values together*/ 998: error_avg = ((error24to13[i] + error14to23[i] + error14to13[i] + 999: error23to24[i] + error31to32[i] + error41to42[i] + 1000: error12to34[i]) / 7); 1001: 1002: /*determine if the current object is considered the most likely to 1003: be the ttt board so far. Average of all the error##to##'s. 1004: If the current is */ 1005: if(ml_error > error_avg) 1006: { 1007: most_likely = i; 1008: ml_error = error_avg; 1009: } 1010: } 1011: 1012: printf("Object %d is most likely the board with %f\%% error.\n", 1013: most_likely, ml_error * 100); 1014: 1015: return most_likely; /*return the object number that is the board*/ 1016: } 1017: 1018: /*searches the moravec image for a cross line in the tic-tac-toe board 1019: int the neighborhood of t = 1/3 */ 1020: /*1st: the first 'corner' to search from. (t = 0) 1021: 2nd: the second 'corner' to search from. (t = 1) 1022: 3rd: a pointer to the moravec image used in finding the lines.*/ 1023: /*returns the coord of the location determined to be the end of the cross 1024: line being searched for. If one isn't found by looking at the image, 1025: then the return value is the theoretical location where t = 1/3 */ 1026: /*note: the corners passed in are moved from the actuall corners. see 1027: find_side_points()*/ 1028: coord search_moravec(coord point1, coord point2, PGMImage *moravec) 1029: { 1030: int i, j; /*loop counting*/ 1031: float t; 1032: coord temp1, temp2; 1033: float dist = findDist(point1.x, point1.y, point2.x, point2.y); 1034: int search_size = 3; /*'radius' that defines the neighborhood of a point*/ 1035: 1036: coord most_likely_corner = {-1, -1}; 1037: int most_likely_sum1 = 0, most_likely_sum2 = 0; 1038: int sum_moravec_points1, sum_moravec_points2; 1039: 1040: /*calculate the coordinates where the divider edges should be.*/ 1041: for(t = 0; t < ONE_TWELTH;t += (1 / dist)) 1042: { 1043: /*check in both the + and - directions simultaniously.*/ 1044: temp1 = find_dividers(point1, point2, ONE_THIRD + t); 1045: temp2 = find_dividers(point1, point2, ONE_THIRD - t); 1046: 1047: /*clear this before next iteration*/ 1048: sum_moravec_points1 = sum_moravec_points2 = 0; 1049: 1050: /*search the neighborhood for edge 'hits' in the moravec image*/ 1051: for(i = -(search_size); i <= search_size; i++) 1052: { 1053: for(j = -(search_size); j <= search_size; j++) 1054: { 1055: if(rgb_avg((*moravec).data[temp1.y + i][temp1.x + j]) > 128) 1056: { 1057: /*setCPixel(temp1.x + j, temp1.y + i, red);*/ 1058: sum_moravec_points1++; 1059: } 1060: if(rgb_avg((*moravec).data[temp2.y + i][temp2.x + j]) > 128) 1061: { 1062: /*setCPixel(temp2.x + j, temp2.y + i, red);*/ 1063: sum_moravec_points2++; 1064: } 1065: 1066: } 1067: } 1068: 1069: /*if the current point in the search is the best, return it*/ 1070: /*reusing search_size seems good, since it is about the right size*/ 1071: if((search_size <= sum_moravec_points1) && 1072: (most_likely_sum1 <= sum_moravec_points1) && 1073: !most_likely_sum2) 1074: { 1075: most_likely_sum1 = sum_moravec_points1; 1076: most_likely_corner = temp1; 1077: } 1078: else if(((search_size <= sum_moravec_points2) && 1079: (most_likely_sum2 <= sum_moravec_points2) && 1080: !most_likely_sum1)) 1081: { 1082: most_likely_sum2 = sum_moravec_points2; 1083: most_likely_corner = temp2; 1084: } 1085: } 1086: 1087: /*if a sutible point is not found, return the theoretical point*/ 1088: if((most_likely_corner.x < 0) || (most_likely_corner.y < 0)) 1089: { 1090: most_likely_corner = find_dividers(point1, point2, ONE_THIRD); 1091: } 1092: 1093: return most_likely_corner; 1094: } 1095: 1096: 1097: /*return the intersection of two lines*/ 1098: /*1st: coordinate one of first line 1099: 2nd: coordinate two of first line 1100: 3rd: coordinate one of second line 1101: 4th: coordinate two of second line*/ 1102: /*return the coordinate where the lines intersect. If none-was found 1103: or an error occured in finding one return (-1, -1).*/ 1104: coord find_intersection(coord line1_point1, coord line1_point2, 1105: coord line2_point1, coord line2_point2) 1106: { 1107: float line1_slope, line2_slope; /*temp slope holding variables*/ 1108: float denominator, numerator; /*temp fraction holding varaibles*/ 1109: float x_float; /*use to avoid obscure roundoff errors*/ 1110: coord target = {-1, -1}; /*initalize temp target point*/ 1111: 1112: /*find slope for first line*/ 1113: if((line1_point1.x - line1_point2.x) != 0) 1114: { 1115: line1_slope = ((float)(line1_point1.y - line1_point2.y)) / 1116: ((float)(line1_point1.x - line1_point2.x)); 1117: } 1118: else /*otherwise handle the undefined slope*/ 1119: { 1120: /*find slope for secon line when first is undefined*/ 1121: if((line2_point1.x - line2_point2.x) != 0) 1122: { 1123: line2_slope = ((float)(line2_point1.y - line2_point2.y)) / 1124: ((float)(line2_point1.x - line2_point2.x)); 1125: } 1126: else /*this should never happen, but could if someone specifed the same 1127: line twice*/ 1128: return target; /*target is initalized to (-1, -1)*/ 1129: 1130: /*since the slope is undefined the x coord in known*/ 1131: target.x = line1_point1.x; 1132: target.y = line2_slope * target.x + line2_point1.y; /*y = mx + b*/ 1133: printf("line one has undefined slope\n"); 1134: return target; 1135: } 1136: 1137: /*find slope for second line*/ 1138: if((line2_point1.x - line2_point2.x) != 0) 1139: { 1140: line2_slope = ((float)(line2_point1.y - line2_point2.y)) / 1141: ((float)(line2_point1.x - line2_point2.x)); 1142: } 1143: else 1144: { 1145: /*since the slope is undefined the x coord in known*/ 1146: target.x = line2_point1.x; 1147: target.y = line1_slope * target.x + line1_point1.y; /*y = mx + b*/ 1148: printf("line two has undefined slope\n"); 1149: return target; 1150: } 1151: 1152: /*if both lines have defined slopes find the target point*/ 1153: /* slope is m and defined by: 1154: (y1 - y2) 1155: m = --------- 1156: (x1 - x2) 1157: 1158: target calculated by setting Yt = M1*(Xt - Xa) + Ya equal to 1159: Yt = M2 * (Xt - Xc) + Yc to get 1160: 1161: (Xa*M1 - M2*Xc + Yc - Ya) 1162: Xt = ------------------------- 1163: M1 - M2 1164: 1165: then to get the y coordinate sub Xt into: 1166: 1167: Yt = M1 * (Xt - Xa) + Ya 1168: 1169: Where line1_point1 = a 1170: line1_point2 = b 1171: line2_point1 = c 1172: line12point2 = d 1173: and are indicated as subscripts of their X or Y coordinate. 1174: M1 = line1_slope 1175: M2 = line2_slope 1176: */ 1177: 1178: /*calculate the x coords nominator and demoninator*/ 1179: numerator = (((float)((float)line1_point1.x * line1_slope) 1180: - (float)((float)line2_point1.x * line2_slope) 1181: + (float)line2_point1.y - (float)line1_point1.y)); 1182: denominator = (float)(line1_slope - line2_slope); 1183: 1184: /*find the x coord, store is as a float to avoid obscure round off errors 1185: when using it to calculate the y coord.*/ 1186: x_float = numerator / denominator; 1187: 1188: target.x = (int)x_float; 1189: 1190: /*find the y coord*/ 1191: target.y = line1_slope * ((float)x_float - (float)line1_point1.x) 1192: + (float)line1_point1.y; 1193: 1194: return target; 1195: } 1196: 1197: /*move the location to start searching off of the corners a little so the 1198: search isn't right on top of the any cross lines that are perpendicular to 1199: the target line.*/ 1200: /*anchor - the corner closest to the side divider point being looked for. 1201: common - the corner that the line between itself and the anchor corner contains 1202: the target divider point. 1203: anchor_opposite - the corner adjacent to the anchor corner & oppsoite common. 1204: common_opposite - the corner adjacent to the common corner & oppposite anchor. 1205: */ 1206: /*return the divider point, or (-1, -1) on error*/ 1207: coord find_side_points(coord anchor, coord common, coord anchor_opposite, 1208: coord common_opposite, PGMImage *moravec) 1209: { 1210: coord temp1, temp2; 1211: 1212: temp1 = find_dividers(anchor, anchor_opposite, ONE_24TH); 1213: temp2 = find_dividers(common, common_opposite, ONE_24TH); 1214: 1215: return search_moravec(temp1, temp2, moravec); 1216: } 1217: 1218: 1219: /*find the ttt board's important coordinates*/ 1220: /*1st: The object that is determined to be the board. 1221: 2nd: the pointer to the struct that contains all of the information about 1222: the tic-tac-toe board important coordinates. 1223: 3rd: a pointer to the Moravec pgm image*/ 1224: void detect_ttt_board(object board_object, ttt *board_details, 1225: PGMImage *moravec) 1226: { 1227: /*copy the corners into the ttt datatype*/ 1228: memcpy(&(board_details->corners), &board_object, sizeof(object)); 1229: 1230: board_details->side13 = find_side_points(board_object.corner1, 1231: board_object.corner3, 1232: board_object.corner2, 1233: board_object.corner4, moravec); 1234: 1235: board_details->side14 = find_side_points(board_object.corner1, 1236: board_object.corner4, 1237: board_object.corner2, 1238: board_object.corner3, moravec); 1239: 1240: board_details->side23 = find_side_points(board_object.corner2, 1241: board_object.corner3, 1242: board_object.corner1, 1243: board_object.corner4, moravec); 1244: 1245: board_details->side24 = find_side_points(board_object.corner2, 1246: board_object.corner4, 1247: board_object.corner1, 1248: board_object.corner3, moravec); 1249: 1250: board_details->side31 = find_side_points(board_object.corner3, 1251: board_object.corner1, 1252: board_object.corner4, 1253: board_object.corner2, moravec); 1254: 1255: board_details->side32 = find_side_points(board_object.corner3, 1256: board_object.corner2, 1257: board_object.corner4, 1258: board_object.corner1, moravec); 1259: 1260: board_details->side41 = find_side_points(board_object.corner4, 1261: board_object.corner1, 1262: board_object.corner3, 1263: board_object.corner2, moravec); 1264: 1265: board_details->side42 = find_side_points(board_object.corner4, 1266: board_object.corner2, 1267: board_object.corner3, 1268: board_object.corner1, moravec); 1269: 1270: /*correct the length of the lines that is shortend to calculate them 1271: in the first place above. simply find the intersection of the *short* 1272: line with that of the outside line between the corners.*/ 1273: board_details->side13 = find_intersection(board_details->side13, 1274: board_details->side42, 1275: board_details->corners.corner1, 1276: board_details->corners.corner3); 1277: board_details->side14 = find_intersection(board_details->side14, 1278: board_details->side32, 1279: board_details->corners.corner1, 1280: board_details->corners.corner4); 1281: board_details->side23 = find_intersection(board_details->side23, 1282: board_details->side41, 1283: board_details->corners.corner2, 1284: board_details->corners.corner3); 1285: board_details->side24 = find_intersection(board_details->side24, 1286: board_details->side31, 1287: board_details->corners.corner2, 1288: board_details->corners.corner4); 1289: board_details->side31 = find_intersection(board_details->side31, 1290: board_details->side24, 1291: board_details->corners.corner3, 1292: board_details->corners.corner1); 1293: board_details->side32 = find_intersection(board_details->side32, 1294: board_details->side14, 1295: board_details->corners.corner3, 1296: board_details->corners.corner2); 1297: board_details->side41 = find_intersection(board_details->side41, 1298: board_details->side23, 1299: board_details->corners.corner4, 1300: board_details->corners.corner1); 1301: board_details->side42 = find_intersection(board_details->side42, 1302: board_details->side13, 1303: board_details->corners.corner4, 1304: board_details->corners.corner2); 1305: 1306: /*now that the sides are found, find the intersections to find the 1307: middle points*/ 1308: 1309: board_details->middle1 = find_intersection(board_details->side14, 1310: board_details->side32, 1311: board_details->side13, 1312: board_details->side42); 1313: 1314: board_details->middle2 = find_intersection(board_details->side23, 1315: board_details->side41, 1316: board_details->side24, 1317: board_details->side31); 1318: 1319: board_details->middle3 = find_intersection(board_details->side31, 1320: board_details->side24, 1321: board_details->side32, 1322: board_details->side14); 1323: 1324: board_details->middle4 = find_intersection(board_details->side41, 1325: board_details->side23, 1326: board_details->side42, 1327: board_details->side13); 1328: 1329: } 1330: 1331: /*not a callback, called by menu() and buffer()*/ 1332: int showGame() 1333: { 1334: int object; /*temp variable*/ 1335: 1336: /*need an image with the moravec filter applied*/ 1337: /*don't calculating this if it is the current image!!! 1338: Bad things happen like memseting to zero the current image.*/ 1339: /*if(img_cur != img_pers_corr)*/ 1340: img_cur = img_original; 1341: pers_corr(img_pers_corr, img_cur); 1342: 1343: showChain(img_pers_corr, &chain_codes); 1344: detect_corners(chain_codes, all_objects); 1345: 1346: /*do moravec first, then perspective to prevent the loss of edges 1347: from doing the perspective first*/ 1348: moravec(img_moravec, img_cur); 1349: pers_corr(img_pers_corr, img_moravec); 1350: 1351: /*detect the ttt game board*/ 1352: object = detect_game(chain_codes, all_objects); 1353: detect_ttt_board(all_objects[object], ttt_board, 1354: img_pers_corr); 1355: 1356: pers_corr(img_pers_corr, img_cur); 1357: img_cur = img_pers_corr; 1358: 1359: return object; 1360: } 1361: 1362: 1363: /*****Find the pieces*************************************************** 1364: ***********************************************************************/ 1365: 1366: /*Takes four points that must be in order going around the region. Also, 1367: needs a pointer to an image with the moravec filter applied.*/ 1368: /*1st - 4th: The coordinates in order (starting point arbitrary, as long 1369: as they are in order) that the area inside of is searched for pieces. 1370: 5th: a pointer to the Moravec image to be used.*/ 1371: int search_area(coord point1, coord point2, coord point3, coord point4, 1372: PGMImage *moravec) 1373: { 1374: /*The following variables are used to calculate the existence of a marble 1375: at a location on the board or not. Uses the following basic formula 1376: new_point = ((t-1) * 1st point) + (t * 2nd point) 1377: where the start & end coords are opposite sides of the area being 1378: searched. If you take the entire area of a single position on a ttt 1379: board, the area that is searched is the region that is bouned by the 1380: points halfway between those passed in to this function.*/ 1381: 1382: float t, s, r; /*hold values incrementing from 0 to 1*/ 1383: coord t_start, t_end, s_start, s_end; 1384: coord temp_t, temp_s, temp_r; 1385: float dist_t, dist_s, dist_r; 1386: 1387: /*pixel count of those over the threashhold and total pixels*/ 1388: int pixel_count = 0, moravec_count = 0; 1389: 1390: t_start = find_dividers(point1, point2, ONE_HALF); 1391: t_end = find_dividers(point1, point4, ONE_HALF); 1392: 1393: s_start = find_dividers(point3, point2, ONE_HALF); 1394: s_end = find_dividers(point3, point4, ONE_HALF); 1395: 1396: dist_t = findDist(t_start.x, t_start.y, t_end.x, t_end.y); 1397: dist_s = findDist(s_start.x, s_start.y, s_end.x, s_end.y); 1398: 1399: /*march two points along that parallel each other in the search area*/ 1400: for(t = 0.0, s = 0.0; t <= 1.0; t += (1.0 / dist_t), s += (1.0 / dist_s)) 1401: { 1402: temp_t = find_dividers(t_start, t_end, t); 1403: 1404: temp_s = find_dividers(s_start, s_end, s); 1405: 1406: dist_r = findDist(temp_t.x, temp_t.y, temp_s.x, temp_s.y); 1407: 1408: /*march a single point along that starts at temp_s and ends 1409: at temp_t. This fills in the region for the search.*/ 1410: for(r = 0.0; r <= 1.0; r += (1 / dist_r)) 1411: { 1412: temp_r = find_dividers(temp_s, temp_t, r); 1413: 1414: /*talley the number of edge points found (rgb. avg. >= 128)*/ 1415: if(rgb_avg((*moravec).data[temp_r.y][temp_r.x]) >= 128) 1416: { 1417: /*setCPixel(temp_r.x, temp_r.y, blue);*/ 1418: moravec_count++; 1419: } 1420: pixel_count++; /*increment the number of pixels*/ 1421: } 1422: 1423: } 1424: 1425: /*if the number of edge pixels is greater than 10% of the total, then 1426: this is an edge of marble. The percent is there because there could be 1427: a few small blobs that get counted when the shouldn't that otherwise 1428: would give a positive hit that a marble was found*/ 1429: if(moravec_count >= (pixel_count / 20)) 1430: return TRUE; 1431: 1432: /*otherwise a marble isn't here*/ 1433: return FALSE; 1434: } 1435: 1436: /*looks for the tic-tac-toe pieces on the board*/ 1437: /*1st: the pointer to the board information. 1438: 2nd: the pointer to the Moravec image used to find the edges.*/ 1439: void detect_pieces(ttt *board_data, PGMImage* moravec) 1440: { 1441: if(search_area(board_data->middle1, board_data->middle3, 1442: board_data->middle2, board_data->middle4, moravec)) 1443: printf("middle has a piece\n"); 1444: else 1445: printf("middle has no piece\n"); 1446: 1447: 1448: if(search_area(board_data->corners.corner1, board_data->side13, 1449: board_data->middle1, board_data->side14, moravec)) 1450: printf("corner 1 has a piece\n"); 1451: else 1452: printf("corner 1 has no piece\n"); 1453: 1454: 1455: if(search_area(board_data->corners.corner2, board_data->side23, 1456: board_data->middle2, board_data->side24, moravec)) 1457: printf("corner 2 has a piece\n"); 1458: else 1459: printf("corner 2 has no piece\n"); 1460: 1461: 1462: if(search_area(board_data->corners.corner3, board_data->side31, 1463: board_data->middle3, board_data->side32, moravec)) 1464: printf("corner 3 has a piece\n"); 1465: else 1466: printf("corner 3 has no piece\n"); 1467: 1468: 1469: if(search_area(board_data->corners.corner4, board_data->side41, 1470: board_data->middle4, board_data->side42, moravec)) 1471: printf("corner 4 has a piece\n"); 1472: else 1473: printf("corner 4 has no piece\n"); 1474: 1475: 1476: if(search_area(board_data->side31, board_data->side13, 1477: board_data->middle1, board_data->middle3, moravec)) 1478: printf("side 1-3 has a piece\n"); 1479: else 1480: printf("side 1-3 has no piece\n"); 1481: 1482: if(search_area(board_data->side41, board_data->side14, 1483: board_data->middle1, board_data->middle4, moravec)) 1484: printf("side 1-4 has a piece\n"); 1485: else 1486: printf("side 1-4 has no piece\n"); 1487: 1488: if(search_area(board_data->side42, board_data->side24, 1489: board_data->middle2, board_data->middle4, moravec)) 1490: printf("side 2-4 has a piece\n"); 1491: else 1492: printf("side 2-4 has no piece\n"); 1493: 1494: if(search_area(board_data->side32, board_data->side23, 1495: board_data->middle2, board_data->middle3, moravec)) 1496: printf("side 2-3 has a piece\n"); 1497: else 1498: printf("side 2-3 has no piece\n"); 1499: 1500: } 1501: 1502: 1503: /* ================================================================= 1504: * Callback functions. 1505: * 1506: * color = displayed graphics in window 1507: * menu = menu event handling 1508: * keyboard = deyboard event handling 1509: * ----------------------------------------------------------------- */ 1510: void color(void) 1511: { 1512: /*glClear (GL_COLOR_BUFFER_BIT);*/ 1513: 1514: /*show the current image*/ 1515: showColor(img_cur); 1516: 1517: /*show the current abstract information*/ 1518: showAbstract(all_objects, ttt_board); 1519: 1520: } 1521: 1522: 1523: void buffer() 1524: { 1525: /*if the drawing state of all objects is enabled, recalculate*/ 1526: if(draw_abstract_lines >= 0) 1527: draw_abstract_lines = detect_corners(chain_codes, all_objects); 1528: 1529: /*if the drawing state of the object most likely to be the board is 1530: found, recalulate*/ 1531: if(draw_abstract_board >= 0) 1532: draw_abstract_board = showGame(); 1533: 1534: glutPostRedisplay(); 1535: } 1536: 1537: #define RESTART 0 1538: #define PERS_CORR 3 1539: #define COLOR_TO_GRAY 4 1540: #define MORAVEC 5 1541: #define EDGES 6 1542: #define CORNERS 7 1543: #define BUFFERS 8 1544: #define GAME 9 1545: #define PIECES 10 1546: 1547: /*this is not a callback function, but is used inside menu() for setting 1548: new states of execution*/ 1549: void reset_state(PGMImage* new_current) 1550: { 1551: img_cur = new_current; 1552: draw_abstract_lines = -1; 1553: draw_abstract_board = -1; 1554: free_chaincodes(&chain_codes); 1555: } 1556: 1557: void menu(int selection) 1558: { 1559: if(selection == RESTART) 1560: { 1561: reset_state(img_original); 1562: } 1563: if(selection == PERS_CORR) 1564: { 1565: pers_corr(img_pers_corr, img_cur); 1566: reset_state(img_pers_corr); 1567: } 1568: if(selection == COLOR_TO_GRAY) 1569: { 1570: color_to_gray(img_grayscale, img_cur); 1571: reset_state(img_grayscale); 1572: } 1573: if(selection == MORAVEC) 1574: { 1575: moravec(img_moravec, img_cur); 1576: reset_state(img_moravec); 1577: } 1578: if(selection == EDGES) 1579: { 1580: showChain(img_cur, &chain_codes); 1581: } 1582: if(selection == CORNERS) 1583: { 1584: showChain(img_cur, &chain_codes); 1585: 1586: draw_abstract_lines = detect_corners(chain_codes, all_objects); 1587: } 1588: if(selection == BUFFERS) 1589: { 1590: if(is_buffered == FALSE) 1591: { 1592: glutIdleFunc(buffer); 1593: is_buffered = TRUE; 1594: } 1595: else 1596: { 1597: glutIdleFunc(NULL); 1598: is_buffered = FALSE; 1599: } 1600: } 1601: if(selection == GAME) 1602: { 1603: draw_abstract_board = showGame(); 1604: } 1605: if(selection == PIECES) 1606: { 1607: draw_abstract_board = showGame(); 1608: 1609: detect_pieces(ttt_board, img_pers_corr); 1610: } 1611: 1612: glutPostRedisplay(); /*redraw the image*/ 1613: } 1614: 1615: void keyboard(unsigned char key, int x, int y) 1616: { 1617: switch (key) 1618: { 1619: case 27: 1620: exit(0); 1621: break; 1622: } 1623: } 1624: 1625: void mouse(int button, int state, int x, int y) 1626: { 1627: char temp[50]; 1628: 1629: if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) 1630: { 1631: sprintf(temp, "(x, y): (%d, %d) red: %d green: %d blue: %d\n", 1632: x, VSIZE - y, (*img_cur).data[VSIZE - y][x].red, 1633: (*img_cur).data[VSIZE - y][x].green, 1634: (*img_cur).data[VSIZE - y][x].blue); 1635: setCRect(0, 0, 200, 12, black); 1636: glColor3f(1.0, 0.0, 0.0); 1637: drawString(0, 0, temp, red); 1638: glFlush(); 1639: } 1640: } 1641: 1642: 1643: /* ================================================================= 1644: * init() & parse_command_line() 1645: * initalize none-OpenGL related things. 1646: * ----------------------------------------------------------------- */ 1647: 1648: /*set global flag variables from things specified at commandline.*/ 1649: /*return the filename specified, if none specified exit().*/ 1650: char* parse_command_line(int argc, char** argv) 1651: { 1652: /*parse the command line*/ 1653: if(argc == 1) 1654: { 1655: printf("To few parameters.\n"); 1656: printf("Usage: research <file.pgm>\n"); 1657: exit(1); 1658: } 1659: else if(argc == 2) 1660: { 1661: return argv[1]; 1662: } 1663: else 1664: { 1665: printf("To many parameters.\n"); 1666: printf("Usage: research <file.pgm>\n"); 1667: exit(1); 1668: } 1669: } 1670: 1671: char* init (int argc, char** argv) 1672: { 1673: char* PGMfileName;/*pointer to the string containing the filename*/ 1674: 1675: /*parse the command line*/ 1676: PGMfileName = parse_command_line(argc, argv); 1677: 1678: /* 1679: * Read in image file. - note: sets our global values, too. 1680: * ----------------------------------------------------------------- */ 1681: 1682: /*allocate memory for original image*/ 1683: img_original = (PGMImage*) malloc(sizeof(PGMImage)); 1684: getPGMfile(PGMfileName, img_original); 1685: HSIZE = (*img_original).width; 1686: VSIZE = (*img_original).height; 1687: MVAL = (*img_original).maxVal; 1688: 1689: img_cur = img_original; /*VERY IMPORTANT to set this*/ 1690: 1691: /*allocate memory for pers. corr. image*/ 1692: img_pers_corr = (PGMImage*) malloc(sizeof(PGMImage)); 1693: (*img_pers_corr).width = HSIZE; 1694: (*img_pers_corr).height = VSIZE; 1695: (*img_pers_corr).maxVal = 255; 1696: 1697: img_grayscale = (PGMImage*) malloc(sizeof(PGMImage)); 1698: (*img_grayscale).width = HSIZE; 1699: (*img_grayscale).height = VSIZE; 1700: (*img_grayscale).maxVal = 255; 1701: 1702: img_moravec = (PGMImage*) malloc(sizeof(PGMImage)); 1703: (*img_moravec).width = HSIZE; 1704: (*img_moravec).height = VSIZE; 1705: (*img_moravec).maxVal = 255; 1706: 1707: 1708: all_objects = (object*) malloc(sizeof(object) * MAX_CHAINS); 1709: memset(all_objects, 0, sizeof(object) * MAX_CHAINS); 1710: 1711: ttt_board = (ttt*) malloc(sizeof(ttt)); 1712: memset(ttt_board, 0, sizeof(ttt)); 1713: 1714: return PGMfileName; 1715: } 1716: 1717: 1718: int main(int argc, char** argv) 1719: { 1720: char* PGMfileName; 1721: int WindowID; /*unique window id, there is only one used*/ 1722: int i; /*looping variable*/ 1723: 1724: /* 1725: * Call our init function to define non-OpenGL related things. 1726: * Have init return a pointer to the filename, used to display the 1727: * filename in the titlebar of the window. 1728: * ----------------------------------------------------------------- */ 1729: PGMfileName = init (argc, argv); 1730: 1731: /* 1732: * Initialize the glut package. 1733: * ----------------------------------------------------------------- */ 1734: glutInit(&argc, argv); 1735: glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); 1736: 1737: /* 1738: * Define a new window (its size, position and title). 1739: * ----------------------------------------------------------------- */ 1740: glutInitWindowSize (HSIZE, VSIZE); /*size*/ 1741: glutInitWindowPosition (10, 10); /*position*/ 1742: WindowID = glutCreateWindow (PGMfileName); /*title*/ 1743: glutSetWindow(WindowID); 1744: glutDisplayFunc(color); 1745: 1746: /* 1747: * select clearing color - white 1748: */ 1749: glClearColor (1.0, 1.0, 1.0, 0.0); 1750: 1751: /* 1752: * initialize viewport values 1753: */ 1754: glMatrixMode(GL_PROJECTION); 1755: glLoadIdentity(); 1756: glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); 1757: 1758: /*add menus*/ 1759: glutCreateMenu(menu); 1760: glutAddMenuEntry("Restart", RESTART); 1761: glutAddMenuEntry("perspective correction", PERS_CORR); 1762: glutAddMenuEntry("color to gray", COLOR_TO_GRAY); 1763: glutAddMenuEntry("moravec", MORAVEC); 1764: glutAddMenuEntry("edges", EDGES); 1765: glutAddMenuEntry("corners", CORNERS); 1766: glutAddMenuEntry("detect game", GAME); 1767: glutAddMenuEntry("detect pieces", PIECES); 1768: glutAddMenuEntry("toggle buffering", BUFFERS); 1769: glutAttachMenu(GLUT_RIGHT_BUTTON); 1770: 1771: glutMouseFunc(mouse); 1772: glutKeyboardFunc(keyboard); 1773: glutMainLoop(); 1774: 1775: /* 1776: * When we reach here, we've left the event loop and are ready to 1777: * exit. 1778: * ----------------------------------------------------------------- */ 1779: return 0; 1780: } 1781: