/* This code is slightly modified in order to support programming the game of set. When the user hits the 'S' (capital S), nine windows holding set cards (from .\\setImages) appear. Images for the cards and separate images for drawing on top of the cards are available after this: they are in setimages and setimages_painted. The function processSetCard is a place-holder for your code that will determine the number, color, fill, and shape of the symbols on a particular set card. Also, once the code determines the symbols on all nine cards, it should somehow indicate which three cards (if any) comprise a set... . This will have to be done in a function outside processSetCard. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ How to get OpenCV and this working? 1. Download and install OpenCV for Windows 2. You'll need XCode 3.0 (the Mac OS X suite of developer tools) - it's free from Apple or Tim Buchheim if you don't have them... 3. Double-click the "project" file (SetMacOS.xcodeproj) 4. Compile (hopefully!) and run. 5. If you don't have a camera, it's not as much fun :-) Still images are supported, though. 6. Some key presses (with focus on the undistored image window) matter... see the code for what each one does. A couple of important ones: The 'r' key toggles on and off the selected color The 's' key starts/stops the image grabbing The ' ' (space bar) moves through saved images (in ./setImages) 7. It is listening on port PORT_NUMBER (default 2000) for a client (in any language) 8. The client.py file can be used as a starting point! Q. The program quits when you hit the escape key when the focus in on the OpenCV program or if you send it a 'q' from the client. */ /* * a small function to see if j is in the first * N of imageIndices's elements */ bool alreadyHaveIt(int j, int* imageIndices, int N) { for (int i=0 ; i #include #include #include #include #include #include // for memset #include #include #include #include #include #include #include using namespace std; //#define ROOT_FOLDER "/Users/bfsnet/Desktop/SetWithOpenCV/" //#define ROOT_FOLDER "/Users/admin/Desktop/SetWithOpenCV/" //#define ROOT_FOLDER "/Users/dodds/Desktop/SetMacOS30/" // This should point to the containing folder... #define ROOT_FOLDER "../../../SetMacOS30/" #endif /* * code for Set specifically */ #define NUM_WINDOWS 9 char *setwnd[] = {"set0","set1","set2","set3","set4","set5","set6","set7","set8","set9","set10","set11"}; static IplImage** setimages; //static IplImage** setimage_frames; static IplImage** setimages_painted; // a set-card-processing function void processSetCard(int i); bool firstTimeForSet = true; /* * global data for passing among * the main loop (in main) * the mouse-event handler (in mouseEventHandler) * the server (in runServerThread) */ IplImage *inputImage=0; // shared between mouse events and main loop int swapVerticalValues = 0; // shared between mouse events and main loop int drawingRectangle=0; // shared between mouse events and main loop CvPoint origin; // more rectangle/line-selection data CvRect selection; int runAlgorithm = 0; // shared between server and main loop int timeToQuitNow = 0; // shared between server and main loop int blobsAreOn = 1; // allows us to turn blob detection on and off int removePixels = 0; #define MAXCOLORS 5 #include "mainHelperBG.h" BlobData colorData[30]; // actually, not all 30 of these will be used... likely only MAXCOLORS #include "colorArrayBG.h" #include "serverStuffSabreen6408.h" ArrayColorDef currentColorArray; ArrayColorDef totalColorArray[MAXCOLORS]; char* colorNames[MAXCOLORS]; /* * Mouse-event handling routine */ void mouseEventHandler( int event, int x, int y, int flags, void* param ) { int r,g,b; double h,s,v; if ( !inputImage ) return; // no image, do nothing if (swapVerticalValues) y = inputImage->height - y; // platform-dependent! if ( event == CV_EVENT_MOUSEMOVE && drawingRectangle ) // are we pulling the corner of a rectangle? { // handle the geometry of the rectangle: selection.x = MIN(x,origin.x); selection.y = MIN(y,origin.y); selection.width = MIN( selection.x + CV_IABS(x - origin.x), inputImage->width ) - MAX( selection.x, 0 ); selection.height = MIN( selection.y + CV_IABS(y - origin.y), inputImage->height ) - MAX( selection.y, 0 ); } // end of rectangle-drawing if ( event == CV_EVENT_RBUTTONDOWN && !(removePixels)) // did we right-click (or control-click?) { getrgb( inputImage, x, y, r, g, b ); RGBtoHSV( r, g, b, h, s, v ); printf(" right-click at (%d,%d): rgb=(%d,%d,%d)\n", x, y, r, g, b ) ; printf(" hsv=(%6.3f,%6.3f,%6.3f)\n",h,s,v); int N = 7; // acceptable color width currentColorArray.expandToFit( r, g, b, N ); // box of side 2N: upper corner //currentColor.expandToFit( r-N, g-N, b-N ); // and lower corner //currentColor.print(); } if ( event == CV_EVENT_RBUTTONDOWN && removePixels) { getrgb( inputImage, x, y, r, g, b ); RGBtoHSV( r, g, b, h, s, v ); printf(" right-click at (%d,%d): rgb=(%d,%d,%d)\n", x, y, r, g, b ) ; printf(" hsv=(%6.3f,%6.3f,%6.3f)\n",h,s,v); int N = 7; // acceptable color width currentColorArray.subtractRange( r, g, b, N ); // box of side 2N: upper corner //currentColor.expandToFit( r-N, g-N, b-N ); // and lower corner //currentColor.print(); } if ( event == CV_EVENT_LBUTTONDOWN ) // did we left-click? { if (!drawingRectangle) // if we weren't drawing, now we start drawing a rectangle { origin = cvPoint(x,y); selection = cvRect(x,y,1,1); drawingRectangle = 1; printf("begin drawing rectangle. \n"); } else if(drawingRectangle && removePixels) // we'll remove the pixels in the enclosed rectangle { //currentColorArray.reset(); for ( int col=selection.x ; col<=selection.x+selection.width ; ++col) { for ( int row=selection.y ; row<=selection.y+selection.height ; ++row) { int N = 10; // acceptable color width getrgb( inputImage, col, row, r, g, b ); currentColorArray.subtractRange( r, g, b, N); } } drawingRectangle = 0; printf("end drawing subtraction rectangle. \n"); } else // drawing rectangle and adding pixels { for ( int col=selection.x ; col<=selection.x+selection.width ; ++col) { for ( int row=selection.y ; row<=selection.y+selection.height ; ++row) { int N = 10; // acceptable color width getrgb( inputImage, col, row, r, g, b ); currentColorArray.expandToFit( r, g, b, N); } } drawingRectangle = 0; printf("end drawing addition rectangle. \n"); } } //printf("end of mouse event handler \n"); } // end of mouse event handler void pixelsInArraybw(IplImage* INPUT, ArrayColorDef colorArray, IplImage* OUTPUT) { //printf(" beginning bw \n"); int imageWidth = INPUT-> width; int imageHeight = INPUT-> height; int r,g,b; for ( int col = 0; col < imageWidth; col++) { for ( int row = 0; row < imageHeight; row++) { getrgb(INPUT, col, row, r, g, b); if (colorArray.pixelMatch(r,g,b)) { setbw(OUTPUT, col, row, 255); //printf("pixel set"); } else { setbw(OUTPUT, col, row, 0); } } } } void drawBorder(IplImage* IMAGE) { int imageWidth = IMAGE-> width; int imageHeight = IMAGE-> height; for( int col = 0; col < 3; col++) { for( int row = 0; row < imageHeight; row++) { setbw(IMAGE, col, row, 0); } } for( int col = imageWidth-3; col < imageWidth; col++) { for( int row = 0; row < imageHeight; row++) { setbw(IMAGE, col, row, 0); } } for( int col = 0; col < imageWidth; col++) { for( int row = 0; row < 3; row++) { setbw(IMAGE, col, row, 0); } } for( int col = 0; col < imageWidth; col++) { for( int row = imageHeight-3; row < imageHeight; row++) { setbw(IMAGE, col, row, 0); } } } /** * totalColorArray handler -- allows us to load in an array with color definitions * for each color we look at. **/ void tCAHandler() { char filename[255]; colorNames[0] = "red"; colorNames[1] = "yellow"; colorNames[2] = "blue"; colorNames[3] = "green"; colorNames[4] = "pink"; for( int i = 0; i < MAXCOLORS; i++) { currentColorArray.reset(); sprintf(filename, "%s%sColorDef%d.txt", ROOT_FOLDER, colorNames[i], COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); totalColorArray[i].setAs(currentColorArray); } } /* * MAIN */ int main( int argc, char** argv ) { /* * set up vision server */ int runServer = true; #ifdef MAC_OS pthread_t serverThread; // the identifier for the thread if (runServer) // do we want to run the server? { printf("Creating serverThread.\n"); int rc = pthread_create(&serverThread, NULL, runServerThread, NULL); if (rc) printf("ERROR in pthread_create: %d.\n", rc); } #endif //printf("end setting up vision server\n"); /* * set up image input source */ enum { IMAGES_FROM_CAMERA, IMAGES_FROM_FOLDER, IMAGES_FROM_AVI_FILE }; int captureType = IMAGES_FROM_CAMERA; captureType = IMAGES_FROM_FOLDER; char* MOVIE_NAME = ROOT_FOLDER "path0.mpg"; char* FOLDER_NAME = ROOT_FOLDER "setImages"; printf("FOLDER_NAME: %s", FOLDER_NAME); int MAX_FILE_NUMBER =34; // the number of images in the folder int nextFileNumber = 0; // the next file number to read enum { LIVE, ONEFRAME, STILL }; // how are we now capturing int captureStatus = ONEFRAME; CvCapture* capture = 0; // the capture object to grab frames from a camera/avi file if ( captureType == IMAGES_FROM_CAMERA ) // initialize "capture," the image source capture = cvCaptureFromCAM( 0 ); // camera number zero, which is usually the one attached else if ( captureType == IMAGES_FROM_AVI_FILE ) capture = cvCaptureFromAVI( MOVIE_NAME ); else // captureType = IMAGES_FROM_FOLDER; capture = 0; //printf("end image input setup\n"); /* * create windows for displaying images */ cvNamedWindow( "InputImage", CV_WINDOW_AUTOSIZE ); int windowXCoord = 1275; int windowYCoordOrig = 100; int windowYCoordDelta = 300; cvMoveWindow( "InputImage", windowXCoord, windowYCoordOrig ); cvSetMouseCallback( "InputImage", mouseEventHandler, 0 ); // Crazy, but it just might work cvNamedWindow( "OutputImage", CV_WINDOW_AUTOSIZE ); cvMoveWindow( "OutputImage", windowXCoord, windowYCoordOrig + windowYCoordDelta ); cvSetMouseCallback( "OutputImage", mouseEventHandler, 0); cvNamedWindow( "BinaryImage", CV_WINDOW_AUTOSIZE ); cvMoveWindow( "BinaryImage", windowXCoord, windowYCoordOrig + 2*windowYCoordDelta ); //printf("created windows for displaying images\n"); /* * create an initial image, displayImage, and any other images... */ inputImage = cvCreateImage( cvSize(10,10), 8, 1 ); IplImage* outputImage = NULL; IplImage* frame = NULL; // image grabbed from camera IplImage* binaryImage = NULL; //printf("created inital images\n"); /* * blob handling */ CBlobResult blobs; CBlob blobToShow; /* * Load in the tCA */ if (blobsAreOn) tCAHandler(); /* * MAIN LOOP */ while (!timeToQuitNow) {//printf("beginning main loop\n"); /* * INPUT IMAGE SETUP * * the goal here is to populate the _image_ variable with pixels * depending on the source in captureType */ if (captureStatus != STILL) { if (captureType == IMAGES_FROM_FOLDER) { char filename[255]; if (nextFileNumber > MAX_FILE_NUMBER || nextFileNumber < 0) nextFileNumber = 0; sprintf(filename,"%s/image%05d.bmp", FOLDER_NAME, nextFileNumber++); printf("About to read the file %s.\n", filename); inputImage = cvLoadImage(filename, -1); // -1 means determine number of channels from format } else // captureType == IMAGES_FROM_AVI_FILE or IMAGES_FROM_CAMERA { frame = cvQueryFrame( capture ); if ( !frame ) { printf( "Frame not obtained when capturing!\n"); printf( "Setting captureStatus to STILL.\n"); captureStatus = STILL; } else { CvSize frameSize = cvGetSize(frame); /* REDUCTION_FACTOR */ /* old code if ( frameSize.width != inputImage->width || frameSize.height != inputImage->height ) { cvReleaseImage( &inputImage ); inputImage = cvCreateImage( frameSize, 8, 3 ); inputImage->origin = frame->origin; } // image is OK and the right size, now cvCopy( frame, inputImage, 0 ); // copy the frame data into the image end of OLD CODE */ int REDUCTION_FACTOR = 2; int desiredInputWidth = (frameSize.width/REDUCTION_FACTOR); int desiredInputHeight = (frameSize.height/REDUCTION_FACTOR); if ( desiredInputWidth != inputImage->width || desiredInputHeight != inputImage->height ) { cvReleaseImage( &inputImage ); CvSize newInputImageSize = cvSize(desiredInputWidth, desiredInputHeight); inputImage = cvCreateImage( newInputImageSize, 8, 3 ); inputImage->origin = frame->origin; // hmmm? } // image is OK and the right size, now /* copy it over ... */ int b,r,g; // loop over the input image for ( int col=0 ; colwidth ; ++col) { for ( int row=0 ; rowheight ; ++row) { // get pixel from across the full frame getrgb( frame, col*REDUCTION_FACTOR, row*REDUCTION_FACTOR, r, g, b ); // place into the inputImage setrgb( inputImage, col, row, r, g, b); } // end row } // end col } // end grab of camera image ("else") } // end of captureType == IMAGES_FROM_AVI_FILE or IMAGES_FROM_CAMERA block if (captureStatus == ONEFRAME) captureStatus = STILL; // we got our one frame... // now, we keep that image until an event asks to grab a new one //printf("finished input image setup\n InputImage : %s", inputImage); } // end of (captureStatus != STILL) block /* * OUTPUT IMAGE SETUP * * we need to make sure to set up the output images before processing! * (or other intermediate images) we check to be sure they're the right size... */ if ( outputImage == NULL || ( inputImage->width != outputImage->width || inputImage->height != outputImage->height ) ) { if (outputImage) cvReleaseImage( &outputImage ); outputImage = cvCreateImage( cvGetSize(inputImage), 8, 3 ); } cvCopy(inputImage, outputImage, NULL); // copy input to output image as a starting point for outputImage if ( binaryImage == NULL || ( inputImage->width != binaryImage->width || inputImage->height != binaryImage->height ) ) { if (binaryImage) cvReleaseImage( &binaryImage ); binaryImage = cvCreateImage( cvGetSize(inputImage), 8, 1); } if (!blobsAreOn) { pixelsInArraybw( inputImage, currentColorArray, binaryImage); cvErode(binaryImage, binaryImage, NULL, 2); } //printf("finished output image setup\n"); /* * OUR ALGORITHM/PROCESSING/VISUALIZATION */ if (runAlgorithm) { int b,r,g; // loop over the input image for ( int col=0 ; colwidth ; ++col) { for ( int row=0 ; rowheight ; ++row) { getrgb( inputImage, col, row, r, g, b ); if ( currentColorArray.pixelMatch(r,g,b)) {setrgb( outputImage, col, row, 0, 255, 255 ); // cyan for the color-defining color //setrgb( inputImage, col, row, 255, 255, 255); } //if (col > 315 && col < 325) //{ //setrgb( outputImage, col, row, 147, 147, 147); //} } // end row } //printf("end algorithm processing\n");// end col } //arrayified version... work please... if(blobsAreOn) { //CBlob allDestBlobs [30]; CBlob destinationBlobs [3]; for(int i=0; i < MAXCOLORS; ++i) { pixelsInArraybw(inputImage, totalColorArray[i], binaryImage); drawBorder(binaryImage); // erase pixels around the edges //cvErode(binaryImage, binaryImage, NULL, 1); //cvDilate(binaryImage, binaryImage, NULL, 1); blobs = CBlobResult( binaryImage, NULL, 100, true ); // get blobs with more than 50 pixels blobs.Filter( blobs, B_INCLUDE, CBlobGetArea(), B_GREATER, 50); // get blobs whose gray-scale value is greater than 200 // keep in mind that we are "blobbing" the binary image, so any value // between 1 and 254 would work! blobs.Filter( blobs, B_INCLUDE, CBlobGetMean(), B_GREATER, 200 ); // Here, we could filter out the long rectangular blobs. We'd only go to it if we had single // color blobs, as there won't be many long ones if we have 2 or 3 color blobs. // tCA = {red, yellow, blue, green, pink} int rr = (i==0 )? 255: (i==1)? 255: (i==2)? 0: (i == 3)? 0: 255; //gets the proper color for the different cases int gg = (i==0)? 0: (i==1)? 255: (i==2)? 0: (i == 3)? 255: 20; int bb = (i==0)? 0: (i==1)? 0: (i==2)? 255: (i == 3)? 0: 147; CBlobGetArea getArea = CBlobGetArea(); CBlobGetMinX getMinX = CBlobGetMinX(); CBlobGetMaxX getMaxX = CBlobGetMaxX(); CBlobGetMinY getMinY = CBlobGetMinY(); CBlobGetMaxY getMaxY = CBlobGetMaxY(); //printf("-------------------------\n"); for(int j=0; j<3;j++) { blobs.GetNthBlob( CBlobGetArea(), j, destinationBlobs[j] ); //double area = getArea(destinationBlobs[j]); //printf("area for blob #%d is = %f\n", j, area); int minxarea = (int) getMinX(destinationBlobs[j]); int maxxarea = (int) getMaxX(destinationBlobs[j]); int minyarea = (int) getMinY(destinationBlobs[j]); int maxyarea = (int) getMaxY(destinationBlobs[j]); colorData[i].xmin[j]=minxarea; colorData[i].xmax[j]=maxxarea; colorData[i].ymin[j]=minyarea; colorData[i].ymax[j]=maxyarea; colorData[i].colour= i; //printf("\nafter assignment\n"); //printf("minx = %d\n", colorData[i].xmin[j]); //printf("maxx = %d\n", colorData[i].xmax[j]); //printf("miny = %d\n", colorData[i].ymin[j]); //printf("maxy = %d\n", colorData[i].ymax[j]); //printf("colour number is: %d\n\n", colorData[i].colour); //1 is red, 2 is yellow, 3 is blue, 4 is green destinationBlobs[j].FillBlob( outputImage, CV_RGB( rr, gg , bb ) ); } // end of for(int j=0; j<3;j++) -- top three blobs //allDestBlobs[i] = destinationBlobs; //CBlobGetArea getArea = CBlobGetArea(); } // end of for(int i=0; i < MAXCOLORS; ++i) -- go through each color } // end of ifBlobsAreOn //////////////////////////////////////////////////////////////////////////////////////////////////////////// //printf("end blob processing\n"); /* * DRAW THE IMAGES TO SCREEN */ if (drawingRectangle) // shows a yellow rectangle when selecting a region cvRectangle(outputImage, cvPoint(selection.x,selection.y), // from here cvPoint(selection.x+selection.width, selection.y+selection.height), // to here CV_RGB(255,255,0),1,8,0); // yellow, 1 pixel thick, 8-connected cvShowImage( "InputImage", inputImage ); // render the input image cvShowImage( "OutputImage", outputImage ); // render the output image cvShowImage( "BinaryImage", binaryImage ); // render the binary image //printf("end drawing images\n"); /* * HANDLE KEY PRESSES to an OpenCV window */ char filename[255]; char cchar = (char) cvWaitKey(10); // waits for 10 ms to get a keypress // use cvWaitKey(0) in order to wait forever... if ( cchar == 27 ) break; // breaks on ESCape key int imageNumber; // Allows us to save files switch( cchar ) // handle other key presses { case ' ': // stop and get one more frame { captureStatus = ONEFRAME; //runAlgorithm = true; break; } case 'L': // restart live capturing { captureStatus = LIVE; printf("Getting one frame.\n"); break; } case 'C': // go to camera { captureType = IMAGES_FROM_CAMERA; printf("Switching to Camera"); break; } case 'F': // go to folder { captureType = IMAGES_FROM_FOLDER; printf("Switching to Folder\n"); break; } case 'R': // reset the current color definition { currentColorArray.reset(); break; } case 'A': // turn on or off algorithm running { if (runAlgorithm) runAlgorithm = false; else runAlgorithm = true; printf("Algorithm Status is %d\n", runAlgorithm); break; } case 'B': //run backwards through the pictures { nextFileNumber = nextFileNumber - 2; if (nextFileNumber < 0) nextFileNumber = MAX_FILE_NUMBER; captureStatus = ONEFRAME; //runAlgorithm = true; break; } case 'S': // save the current image { sprintf(filename, "%stmp/image%04d.bmp", ROOT_FOLDER, imageNumber++); printf("Saving File: %s \n", filename); cvSaveImage(filename, inputImage); } case '0': // turns blobs on or off { if (blobsAreOn) blobsAreOn = false; else blobsAreOn = true; break; } case 'P': // print current array { currentColorArray.print(); break; } case '!': // save alternate color array (Red) { sprintf(filename, "%sredColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.saveToFile( filename); break; } case '@': // save alternate color defintions (Yellow) { sprintf(filename, "%syellowColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.saveToFile( filename); break; } case '#': // save alternate color defintions (Blue) { char filename[255]; sprintf(filename, "%sblueColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.saveToFile( filename); break; } case '$': // save alternate color defintions (Green) { sprintf(filename, "%sgreenColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.saveToFile( filename); break; } case '%': // save alternate color defintions (Pink) { sprintf(filename, "%spinkColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.saveToFile( filename); break; } case '1': // load alternate color array (Red) { currentColorArray.reset(); sprintf(filename, "%sredColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); break; } case '2': // load alternate color array (Yellow) { currentColorArray.reset(); sprintf(filename, "%syellowColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); break; } case '3': // load alternate color array (Blue) { currentColorArray.reset(); sprintf(filename, "%sblueColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); break; } case '4': // load alternate color array (Green) { currentColorArray.reset(); sprintf(filename, "%sgreenColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); break; } case '5': // load alternate color array (Pink) { currentColorArray.reset(); sprintf(filename, "%spinkColorDef%d.txt", ROOT_FOLDER, COLOR_STEP_SIZE); currentColorArray.loadFromFile( filename); break; } case 'Z': // Reload color definitons { tCAHandler(); break; } case '-': // remove pixels from current definiton { if (removePixels) removePixels = false; else removePixels = true; printf("The next rectangle will %s pixels.\n", (removePixels ? "remove" : "add")); break; } /* * a lower-case 's' toggles playing set... */ case 's': { captureStatus = ONEFRAME; // we're no longer capturing... char filenameset[500]; // holds our set file names... if (firstTimeForSet) // need to set up the windows if it's { // the first time playing set... for (int i=0 ; i 80) continue; cout << " randImage is " << randImage << endl; if (!alreadyHaveIt(randImage,imageIndices,i)) imageIndices[i] = randImage; else { cout << "Have it "; for (int i=0 ; iheight - row; sourcepix = cvPtr2D(sourceImage,realrow,col); // get the pixel we care about r = (int)(sourcepix[2]); // the red component of the pixel we care about g = (int)(sourcepix[1]); // the green component of the pixel we care about b = (int)(sourcepix[0]); // the blue component of the pixel we care about destpix = cvPtr2D(destinationImage,realrow+row_offset,col+col_offset); // get the pixel we (might) change destpix[0] = 0; /*blue*/ destpix[1] = 0; /*green*/ destpix[2] = 255; /*red*/ } } cvShowImage(destinationWindow, destinationImage); }