#include "Recognizer.h" Recognizer::Recognizer() { } // Read the 52 1-bit deep card images from a file. void Recognizer::readCards(string file) { FILE* f; f = fopen(file.c_str(), "rb"); if (!f) { printf("Error reading cards.bin file: %s\n", file.c_str()); exit(-1); } for (int i = 0; i < 52; i++) { vBits[i] = CBitmap(f); // read card value part of image sBits[i] = CBitmap(f); // suit part of image // imshow("Display window", vBits[i].b2Mat()); // waitKey(300); } fclose(f); printf("Read cards.bin.\n"); } // Write the card images (1-bit deep) to a file. void Recognizer::writeCards(string file) { FILE* f; f = fopen(file.c_str(), "wb"); if (!f) { printf("Error writing cards.bin file: %s\n", file.c_str()); exit(-1); } for (int i = 0; i < 52; i++) { vBits[i].write(f); sBits[i].write(f); // imshow("Display window", vBits[i].b2Mat()); // waitKey(300); } fclose(f); printf("Wrote %s.\n", file.c_str()); } /* Return index of card value with best correlation to v. */ int Recognizer::maxCorr(CBitmap* stored, CBitmap v, bool fromTop, float& rmax) { rmax = 0; int imax = 0; for (int i = 0; i < 52; i++) { float rv = v.correlate(stored[i], fromTop); if (rv > rmax) { rmax = rv; imax = i; } } return imax; } void printArr(int* arr, int n) // debugging { for (int i = 0; i < n; i += 10) { printf("%3d ", i); int k = MIN(n - i, 10); for (int j = 0; j < k; j++) printf(" %3d", arr[i + j]); printf("\n"); } } // given the cropped card image, determine if it's a red or black card. // the minimum euclidian color distance is used. void Recognizer::getColors(Mat rgb, Mat rslt, const Vec3b red, const Vec3b black) { CV_Assert(rslt.isContinuous()); MatIterator_ it, end; uchar* p = rslt.ptr(0); for (it = rgb.begin(), end = rgb.end(); it != end; it++) { // square of color red differences float fr = SQRD((float)(*it)[0], red[0]) + SQRD((float)(*it)[1], red[1]) + SQRD((float)(*it)[2], red[2]); // square of black differences float fb = SQRD((float)(*it)[0], black[0]) + SQRD((float)(*it)[1], black[1]) + SQRD((float)(*it)[2], black[2]); if (fr < SQR(THRESHOLD)) *p++ = Recognizer::RED; else if (fb < SQR(THRESHOLD)) *p++ = Recognizer::BLACK; else *p++ = Recognizer::NEITHER; } } // count the number of elements in an image equal to the given value. int Recognizer::countImage(Mat im, uchar value) { CV_Assert(im.depth() == CV_8U); // CV_Assert(im.isContinuous()); int t = 0; for (int iy = 0; iy < im.rows; iy++) { uchar* p = im.ptr(iy); for (int ix = 0; ix < im.cols; ix++) if (*p++ == value) t++; } return t; } /* Return the # of elements that match the given value.iStart = first element to sum in each * row or column. iEnd = last+1 element to sum. Default = all. * Every row or column is summed between the limits. * Input matrix must be 1 byte deep, CV8_U. * If the pointer to the result is NULL, the result is malloced. * */ int* Recognizer::profile(Mat im, bool rowSum, uchar match, int* result, int iStart = 0, int iEnd = 0) { int n, nsum; CV_Assert(im.depth() == CV_8U); CV_Assert(im.isContinuous()); if (rowSum) { // sum across rows if (iEnd == 0) iEnd = im.cols; n = im.rows; nsum = im.cols; } else { if (iEnd == 0) iEnd = im.rows; n = im.cols; nsum = im.rows; } if (iStart < 0 || iEnd > nsum || iStart > iEnd) { printf("profile() bad params, start %d, end %d\n", iStart, iEnd); exit(-1); } if (!result) { result = (int*)malloc(n * sizeof(int)); assert(result != 0); } uchar* p = im.data; for (int i = 0; i < n; i++) { // each row or column int t = 0; if (rowSum) { #if 1 for (int j = iStart; j < iEnd; j++) if (p[j] == match) t++; p += im.cols; #else for (int j = iStart; j < iEnd; j++) if (im.at(i, j) == match) t++; #endif } else { uchar* p = im.data + i + iStart * im.cols; for (int j = iStart; j < iEnd; j++, p += im.cols) if (*p == match) t++; } result[i] = t; } return result; } /* *** Extract the suit and value subimages from a cropped card image *** More hueristics than a pin-the-tail on a donkey game. The image is a one bit deep image with 1's representing either red or black, and 0's every other color. * first the colum sums are scanned from the middle to the right looking for a column with no pixels with the color of interest. This is the left edge of our subimage. * row sums are made, starting at the left edge found in the previous step. * from near the bottom, we scan row sums going up */ void Recognizer::getSubImages(Mat im, bool isRed, Mat& value, Mat& suit) { uchar color = isRed ? Recognizer::RED : Recognizer::BLACK; int tops; // top of suite part int topv; // top of value part int botv; // bottom of value part. int left, right; // left and right columns of suit part int* columns = profile(im, false, color, NULL); // column sums // Find left edge of markings for (left = im.cols / 2; left > 0; left--) { if (columns[left] < 1) break; } // get row sums starting at left. int* rows = profile(im, true, color, NULL, left, im.cols); // row sums across the meat of the image #ifdef DEBUG printArr(rows, im.rows); imwrite("/users/dave/desktop/junk.jpg", im); cvGraph(rows, im.rows, im.rows, 300, "Rows"); imshow("Display window", im); waitKey(0); #endif // Top of suit part (part 1): from bottom, go up till we find the color for (tops = im.rows - 10; tops > 0; tops--) { if (rows[tops] > 3) break; // find first mark } // go up from the middle to top of value part for (topv = 150; topv > 0; topv--) { // go up till we find blanks if (rows[topv] < 2) break; } #if 0 // doesn't seem to help int bkmin = 1000; // find background for (int i = topv; i < tops; i++) { if (rows[i] < bkmin) bkmin = rows[i]; } if (bkmin != 0) printf("Bkmin = %d\n", bkmin); bkmin++; // for good measure for (int i = 0; i < im.rows; i++) { // subtract backround rows[i] -= bkmin; if (rows[i] < 0) rows[i] = 0; } #endif // go up to find the blank space above the suit, below the value. for (tops = tops - 30; tops > 0; tops--) { // up to blank at top of suit part if (rows[tops] < 5) break; } // **** left - right for suit part **** columns = profile(im, false, color, columns, tops, im.rows - 1); #ifdef DEBUG cvGraph(columns, im.cols, im.cols, 300, "Columns"); waitKey(10); #endif // from middle, go left till no markings for (left = 130; left > 0; left--) { if (columns[left] == 0) break; } // from left, go right till no markings for (right = left + 30; right < im.cols; right++) { if (columns[right] < 3) break; } rows = profile(im, true, color, rows, left, right); // from gap between suit & value, go up till we have something for (botv = tops - 10; botv > 0; botv--) { if (rows[botv] > 3) break; } // find top of value part again, only using columns that matter for (topv = botv - 60; topv > 0; topv--) { if (rows[topv] < 3) break; } // profile of only the value part, columns columns = profile(im, false, color, columns, topv, botv); // cvGraph(columns, botv-topv, botv-topv, 300, "Columns"); int rightv; #if 0 if (columns[right] > 5) { // wide value, probably a 10. for (rightv = right; rightv < im.cols; rightv++) { // go right to blank if (columns[rightv] < 2) break; } printf("Wide card %d.\n", rightv - right); } else { // value is narrower for (rightv = right; rightv > left; rightv--) { // go left to mark if (columns[rightv] > 2) break; } } #else // find right edge of value part. for (rightv = (left + right) / 2; rightv < im.cols; rightv++) { if (columns[rightv] < 2) break; } #endif #if 0 line(im, Point(0, tops), Point(im.cols - 1, tops), 255); line(im, Point(left, 0), Point(left, im.rows - 1), 255); line(im, Point(right, 0), Point(right, im.rows - 1), 255); line(im, Point(0, botv), Point(im.cols - 1, botv), 255); line(im, Point(0, topv), Point(im.cols - 1, topv), 255); #endif #ifdef DEBUG printf("Rows %d, Cols %d, tops %d, botv %d, topv %d, left %d, right %d, rightv %d\n", im.rows, im.cols, tops, botv, topv, left, right, rightv); #endif // imshow("Display window", im); // Show image // int rightv = MIN(im.cols, right + 50); value = Mat(im, Range(topv, botv), Range(left, rightv)); // value field is wider // imshow("Value", value); suit = Mat(im, Range(tops, im.rows), Range(left, right)); // imshow("Suit", suit); free(rows); free(columns); } /* Separate the matrix into two bitmaps, one for the value part, and one for the suit part. */ void Recognizer::getBitmaps(Mat m, CBitmap& vb, CBitmap& sb, bool& isRed) { // only take what we need. Will have to be redone if camera is moved. // Mat im1 = Mat(m, Range(m.rows - 414, m.rows), Range(0, 280)); Mat im1 = Mat(m, Range(0, m.rows), Range(0, 290)); Mat im8 = Mat(im1.rows, im1.cols, CV_8U); // single depth getColors(im1, im8, red, black); // printf("Red %d, Black %d\n", countImage(image1, Recognizer::RED), countImage(image1, BLACK)); isRed = countImage(im8, RED) > countImage(im8, Recognizer::BLACK); // SHOW(im8, 200); // imshow("BW", im8); Mat value, suit; getSubImages(im8, isRed, value, suit); // separate suit & value areas vb = CBitmap(value, isRed ? Recognizer::RED : Recognizer::BLACK); sb = CBitmap(suit, isRed ? Recognizer::RED : Recognizer::BLACK); } int Recognizer::recognize(Mat& m) { CBitmap vb, sb; bool isRed; float rmax = 0.; int imax = 0; getBitmaps(m, vb, sb, isRed); for (int i = 0; i < 52; i++) { // find best suit match.... if (isRed == (bool)isBlack[SUIT(i)]) continue; float sv = sb.correlate(sBits[i], true); if (sv > rmax) { imax = i; rmax = sv; } } int iSuit = imax / 13; float vmax = 0; int iValue = maxCorr(vBits, vb, true, vmax) % 13; // find best value field match... imax = iValue + iSuit * 13; printf("Rs=%4.2f, Rv=%4.2f ", rmax, vmax); return imax; #if 0 printf("%sFile %s. Found card %s, r=%f\n", iCard == imax ? "" : "* ERROR * ", cardName(iCard, false).c_str(), cardName(imax, false).c_str(), rmax); if (iCard != imax) { Mat sb = vBits[iCard].b2Mat(); Mat is = vb.b2Mat(); if (sb.rows < is.rows) { line(is, Point(0, sb.rows - 1), Point(is.cols - 1, sb.rows - 1), 255); } else { line(sb, Point(0, is.rows - 1), Point(sb.cols - 1, is.rows - 1), 255); } imshow("Is", is); imshow("Should be", sb); waitKey(0); } #endif }