用OPENCV视觉解数独
2010-06-29
看到增强视觉网站上介绍老外用视觉解SUDOKU(http://www.cvchina.info/2011/05/29/video-sudoku-solver/),觉得应该不难,于是用OPENCV和训练好的数字分类器,也试着做一个,纯属娱乐
基本思路如下:
一,定位网格,
1,寻找图像中的矩形,使用OPENCV中经典找矩形(因为速度问题使用C版本)代码就可以,将条件降低(CANNY的对比度和角度是否直角)以便找到更多的矩形。
2,由于不一定能找到所有的矩形,需要根据找到的矩形对网格进行推算,推算出的矩形被称为“虚拟矩形”
3,最后对网格中的虚拟矩形根据上下的矩形进行位置修正
二,数字分析
网格中存在两种可能,有数据和没有数据。我们通过对比度和轮廓大小过滤掉没有数据的网格。
对剩下的网格进行数字分析,使用SVM手写数字分类器,得到该网格的数据
三,求解
剩下的问题就简单了,对得到的SUDOKU题目进行求解就可以啦
效果吗,对网格和数字比较清晰的图像效果不错,从模糊的图片就差一些了,主要是数字和边框不清晰,导致矩形寻找和数字判断错误,可以根据图像的特征进行优化,但如果能做到各种图片通吃就困难多了。
手写数字的图片(蓝色为找到的网格,绿色为推算出的虚拟网格):
识别结果:
印刷数字的图片(蓝色为找到的网格,绿色为推算出的虚拟网格):
失败的例子,由于网格不清晰,导致无法找到足够的矩形网格:
主要代码如下(不包括SUDOKU求解部分),
// Sudoku detect
#include "stdafx.h"
#ifdef _CH_
#pragma package <opencv>
#endif
#ifndef _EiC
#include "opencv2/opencv.hpp"
#include <stdio.h>
#include <math.h>
#include <string.h>
#endif
#include <vector>
#include <list>
using namespace cv;
#define SHOW_IMG 0
const double MIN_CONTOUR_PROPORTION = 0.02;
const int CANNY_LOW_THRESH = 0;
const int CANNY_HIGH_THRESH = 30;
//int thresh = 50;
const double MIN_ANGLE = 0.1;
//double MIN_ANGLE = 0.05;
IplImage* img = 0;
IplImage* img0 = 0;
CvMemStorage* storage = 0;
CvPoint pt[4];
const char* wndname = "Square Detection Demo";
class FilterRect
{
public:
Rect rc;
bool useFlag;
FilterRect()
{
rc = Rect(0, 0, 0, 0);
useFlag = false;
}
};
vector<Rect> allRects;
vector<FilterRect> filterRects;
const int SUDOKU_GRID_NUM = 9;
const unsigned int DIFF_THRESHOLD = 16;
class RtTrainData
{
public:
float data[64];
int result;
};
class ValidRect
{
public:
Rect rc;
int row;
int col;
bool blankFlag;
bool virtualFlag;
Rect contourRc;
double contrast;
double areaProportion;
ValidRect()
{
rc = Rect(0, 0, 0, 0);
contourRc = Rect(0, 0, 0, 0);
row = 0;
col = 0;
blankFlag = true;
virtualFlag = true;
contrast = 0.0;
areaProportion = 0.0;
}
};
ValidRect validRects[81];
typedef struct rectWithArea
{
CvPoint pt[4];
double area;
}RectWithArea;
bool compareRects(const Rect& rc1, const Rect& rc2, bool& replaceFirst)
{
int dx = rc1.x - rc2.x;
int dy = rc1.y - rc2.y;
int dw = rc1.width - rc2.width;
int dh = rc1.height - rc2.height;
//if((dx*dx + dy*dy + dw*dw + dh*dh) < DIFF_THRESHOLD)
if((dx*dx + dy*dy + dw*dw + dh*dh) < DIFF_THRESHOLD)
{
if(rc1.area() > rc2.area())
replaceFirst = true;
else
replaceFirst = false;
return true;
}
else
return false;
}
double getBkInfo(Mat& src, float& highProportion, Scalar& contrast)
{
Mat temp, high, low;
Rect rc(src.cols / 10, src.rows / 10, src.cols * 0.9, src.rows * 0.9);
temp = src(rc);
double thresh = threshold(temp, high, 0, 0, CV_THRESH_OTSU); //求出分割阈值
threshold(temp, high, thresh, 0, CV_THRESH_TOZERO); //分割高亮部分
threshold(temp, low, thresh, 0, CV_THRESH_TOZERO_INV); //分割低暗部分
int highCount = cv::countNonZero(high);
highProportion = (float)highCount / src.total();
//highProportion = (float)highCount / (src.rows * src.cols);
Scalar lowValue = mean(low);
Scalar highValue = mean(high);
contrast = (highValue - lowValue) / lowValue;
double ret = abs(contrast.val[0]);
return ret;
}
// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 )
{
double dx1 = pt1->x - pt0->x;
double dy1 = pt1->y - pt0->y;
double dx2 = pt2->x - pt0->x;
double dy2 = pt2->y - pt0->y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage )
{
const int MAX_CONTOUR_SIZE = img->width * img->height / 80;
const int MIN_CONTOUR_SIZE = MAX_CONTOUR_SIZE / 16;
int contours_num = 0;
CvSeq* contours;
int i, c, l, N = 1; //11;
CvSize sz = cvSize( img->width & -2, img->height & -2 );
IplImage* timg = cvCloneImage( img ); // make a copy of input image
//IplImage* timg2 = cvCloneImage( img ); // make a copy of input image
IplImage* gray = cvCreateImage( sz, 8, 1 );
IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 );
IplImage* tgray;
CvSeq* result;
double s, t;
// create empty sequence that will contain points -
// 4 points per square (the square's vertices)
CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(RectWithArea), storage );
// select the maximum ROI in the image
// with the width and height divisible by 2
cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height ));
//Only care RED
//cvSetImageCOI( timg2, 3 );
//cvCopy(timg2, gray, NULL);
// down-scale and upscale the image to filter out the noise
cvPyrDown( timg, pyr, 7 );
cvPyrUp( pyr, timg, 7 );
tgray = cvCreateImage( sz, 8, 1 );
// find squares in every color plane of the image
for( c = 0; c < 3; c++ )
{
// extract the c-th color plane
cvSetImageCOI( timg, c+1 );
cvCopy( timg, tgray, 0 );
// try several threshold levels
for( l = 0; l < N; l++ )
{
// hack: use Canny instead of zero threshold level.
// Canny helps to catch squares with gradient shading
if( l == 0 )
{
// apply Canny. Take the upper threshold from slider
// and set the lower to 0 (which forces edges merging)
cvCanny( tgray, gray, CANNY_LOW_THRESH, CANNY_HIGH_THRESH, 5 );
// dilate canny output to remove potential
// holes between edge segments
cvDilate( gray, gray, 0, 1 );
}
else
{
// apply threshold if l!=0:
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY );
}
//imshow("gray", gray);
//waitKey(0);
// find contours and store them all as a list
cvFindContours( gray, storage, &contours, sizeof(CvContour),
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
// test each contour
while( contours )
{
contours_num++;
// approximate contour with accuracy proportional
// to the contour perimeter
result = cvApproxPoly( contours, sizeof(CvContour), storage,
CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
// square contours should have 4 vertices after approximation
// relatively large area (to filter out noisy contours)
// and be convex.
// Note: absolute value of an area is used because
// area may be positive or negative - in accordance with the
// contour orientation
if( result->total == 4 &&
fabs(cvContourArea(result,CV_WHOLE_SEQ)) > MIN_CONTOUR_SIZE && fabs(cvContourArea(result,CV_WHOLE_SEQ)) < MAX_CONTOUR_SIZE &&
cvCheckContourConvexity(result) )
{
s = 0;
for( i = 0; i < 5; i++ )
{
// find minimum angle between joint 检查3个角就足够了(对四边形来说如果三个角为直角,则第四个也为直角)
// edges (maximum of cosine)
if( i >= 2 )
{
t = fabs(angle(
(CvPoint*)cvGetSeqElem( result, i ),
(CvPoint*)cvGetSeqElem( result, i-2 ),
(CvPoint*)cvGetSeqElem( result, i-1 )));
s = s > t ? s : t;
}
}
// if cosines of all angles are small
// (all angles are ~90 degree) then write quandrange
// vertices to resultant sequence
if( s < MIN_ANGLE )
{
RectWithArea rwa;
for(int i=0; i<4; i++)
rwa.pt[i] = *(CvPoint*)cvGetSeqElem( result, i );
rwa.area = fabs(cvContourArea(result,CV_WHOLE_SEQ));
cvSeqPush( squares, &rwa);
//Add to all rects
Rect rect(rwa.pt[0], rwa.pt[2]);
allRects.push_back(rect);
}
}
// take the next contour
contours = contours->h_next;
}
}
}
// release all the temporary images
cvReleaseImage( &gray );
cvReleaseImage( &pyr );
cvReleaseImage( &tgray );
cvReleaseImage( &timg );
return squares;
}
// the function draws all the squares in the image
/* Sort 2d points in top-to-bottom left-to-right order */
static int cmp_func( const void* _a, const void* _b, void* userdata )
{
RectWithArea* a = (RectWithArea*)_a;
RectWithArea* b = (RectWithArea*)_b;
return a->area < b->area ? -1 : a->area > b->area ? 1 : 0;
}
void drawSquares( IplImage* img, CvSeq* squares )
{
cvSeqSort( squares, cmp_func, 0);
CvSeqReader reader;
IplImage* cpy = cvCloneImage( img );
int i;
// initialize reader of the sequence
cvStartReadSeq( squares, &reader, 0 );
// read 4 sequence elements at a time (all vertices of a square)
for( i = 0; i < squares->total; i ++ )
{
RectWithArea rwa;
int count = 4;
// draw the square as a closed polyline
memcpy(&rwa, reader.ptr, squares->elem_size);
CvPoint* rect = pt;
pt[0] = rwa.pt[0];
pt[1] = rwa.pt[1];
pt[2] = rwa.pt[2];
pt[3] = rwa.pt[3];
cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0 );
CV_NEXT_SEQ_ELEM( squares->elem_size, reader );
//if(i>10)
// break;
}
// show the resultant image
cvShowImage( wndname, cpy );
cvReleaseImage( &cpy );
}
void filterDupRect()
{
for(unsigned int i=0; i<allRects.size(); i++)
{
bool insertFlag = true;
for(unsigned int j=0; j<filterRects.size(); j++)
{
bool replaceFlag = false;
if(compareRects(filterRects[j].rc, allRects[i], replaceFlag))
{
if(replaceFlag)
filterRects[j].rc = allRects[i]; //Using smaller one
insertFlag = false; //Have similiar, not save
break;
}
}
if(insertFlag)
{
FilterRect fr;
fr.rc = allRects[i];
filterRects.push_back(fr);
}
}
}
int findTopLeftRect(Mat& src)
{
int min_dist = src.size().width + src.size().height;
int index = -1;
for(unsigned int j = 0; j < filterRects.size(); j ++ )
{
int dist = filterRects[j].rc.x + filterRects[j].rc.y;
if(dist < min_dist)
{
min_dist = dist;
index = j;
}
}
return index;
}
void updateValidRect(int col, int row, Rect rc, bool virtualFlag)
{
int index = col + row * 9;
validRects[index].col = col;
validRects[index].row = row;
validRects[index].rc = rc;
//validRects[index].processFlag = true;
validRects[index].virtualFlag = virtualFlag;
}
int findNearest(Rect& rc, int offsetX, int offsetY)
{
int min_dist = 1000;
int index = -1;
for(unsigned int j = 0; j < filterRects.size(); j ++ )
{
if(filterRects[j].useFlag == true)
continue;
int dx = abs(filterRects[j].rc.x -offsetX);
int dy = abs(filterRects[j].rc.y -offsetY);
int dw = abs(filterRects[j].rc.width - rc.width);
int dh = abs(filterRects[j].rc.height - rc.height);
int dist = dx+dy+dw+dh;
if( dist < min_dist)
{
index = j;
min_dist = dist;
}
}
int maxDiff = rc.width / 4 + rc.height / 4;
if(min_dist < maxDiff)
return index;
else
return -1;
}
bool findValidRects(Mat& src)
{
int maxW = src.size().width;
int maxH = src.size().height;
int dx = 10, dy = 10;
for(int row = 0; row < 9; row++)
{
for(int col = 0; col < 9; col++)
{
if((row == 0) && (col == 0))
{
int index = findTopLeftRect(src);
if(index>0)
{
updateValidRect(col, row, filterRects[index].rc, false);
filterRects[index].useFlag = true;
}
else
return false;
}
else if(row == 0)
{
Rect leftRc = validRects[col -1].rc; //Align to left
int offsetX = leftRc.x + leftRc.width + dx;
int offsetY = leftRc.y;
int index = findNearest(leftRc, offsetX, offsetY);
if(index>0) //Find rect
{
updateValidRect(col, row, filterRects[index].rc, false);
filterRects[index].useFlag = true;
}
else if( (offsetX + leftRc.width) > maxW) //over the scale
{
return false;
}
else
{
leftRc.x = offsetX;
leftRc.y = offsetY;
updateValidRect(col, row, leftRc, true);
}
}
else if(col == 0)
{
Rect upRc = validRects[9*(row-1) + col ].rc; //Align to top
int offsetX = upRc.x;
int offsetY = upRc.y + upRc.height + dy;
int index = findNearest(upRc, offsetX, offsetY);
if(index>0)
{
updateValidRect(col, row, filterRects[index].rc, false);
filterRects[index].useFlag = true;
}
else if( (offsetY + upRc.height) > maxH)
{
return false;
}
else
{
upRc.x = offsetX;
upRc.y = offsetY;
updateValidRect(col, row, upRc, true);
}
}
else
{
Rect leftRc = validRects[9*row + col -1].rc; //Align to left and top
Rect upRc = validRects[9*(row-1) + col ].rc;
int offsetX = upRc.x;
int offsetY = leftRc.y;
int index = findNearest(leftRc, offsetX, offsetY); //Widht and height can use left or top
if(index>0)
{
updateValidRect(col, row, filterRects[index].rc, false);
filterRects[index].useFlag = true;
}
else if( (offsetX + leftRc.width) > maxW)
{
return false;
}
else if( (offsetY + upRc.height) > maxH)
{
return false;
}
else
{
leftRc.x = offsetX;
leftRc.y = offsetY;
leftRc.width = upRc.width;
updateValidRect(col, row, leftRc, true);
}
}
}
}
}
int getVirtualRectsCount()
{
int count = 0;
for(int i = 0; i < 81; i++)
{
if(validRects[i].virtualFlag == true)
count++;
}
return count;
}
bool modifyValidRect()
{
for(int row = 0; row < 9; row++)
{
for(int col = 0; col < 9; col++)
{
if(validRects[9*row+col].virtualFlag == false)
continue;
ValidRect leftRc, upRc, rightRc, bottomRc;
bool left, up, right, bottom;
left = false;
up = false;
right = false;
bottom = false;
if(row != 0)
{
upRc = validRects[9*(row-1) + col ];
up = true;
}
if(row != 8)
{
bottomRc = validRects[9*(row + 1) + col];
bottom = true;
}
if(col != 0)
{
leftRc = validRects[9*row + col -1];
left = true;
}
if(col != 8)
{
rightRc = validRects[9*row + col + 1];
right = true;
}
if(left && (leftRc.virtualFlag == false))
{
validRects[9*row+col].rc.y = leftRc.rc.y;
validRects[9*row+col].rc.height = leftRc.rc.height;
}
else if(right && (rightRc.virtualFlag == false))
{
validRects[9*row+col].rc.y = rightRc.rc.y;
validRects[9*row+col].rc.height = rightRc.rc.height;
}
if(up && (upRc.virtualFlag == false))
{
validRects[9*row+col].rc.x = upRc.rc.x;
validRects[9*row+col].rc.width = upRc.rc.width;
}
else if(bottom && (bottomRc.virtualFlag == false))
{
validRects[9*row+col].rc.x = bottomRc.rc.x;
validRects[9*row+col].rc.width = bottomRc.rc.width;
}
}
}
return true;
}
//int findNearest(Mat& src, int offsetX, int offsetY)
//{
// int max_dist = src.size().width * src.size().width + src.size().height * src.size().height;
// int index = -1;
//
// for(unsigned int j = 0; j < stardRect.size(); j ++ )
// {
// int dx = stardRect[j].x -offsetX;
// int dy = stardRect[j].y -offsetY;
//
// int dist = dx*dx + dy*dy;
// if( dist < max_dist)
// {
// index = j;
// max_dist = dist;
// }
// }
//
// if(max_dist < 1800)
// return index;
// else
// return -1;
//}
//
void GetROI(Mat& src, Mat& dst)
{
int left, right, top, bottom;
left = src.cols;
right = 0;
top = src.rows;
bottom = 0;
for(int i=0; i<src.rows; i++)
{
for(int j=0; j<src.cols; j++)
{
if(src.at<uchar>(i, j) > 0)
{
if(j<left) left = j;
if(j>right) right = j;
if(i<top) top = i;
if(i>bottom) bottom = i;
}
}
}
cv::Point center;
center.x = (left + right) / 2;
center.y = (top + bottom) / 2;
int width = right - left;
int height = bottom - top;
int len = (width < height) ? height : width;
dst = Mat::zeros(len, len, CV_8UC1);
cv::Rect dstRect((len - width)/2, (len - height)/2, width, height);
cv::Rect srcRect(left, top, width, height);
Mat dstROI = dst(dstRect);
Mat srcROI = src(srcRect);
srcROI.copyTo(dstROI);
}
//bool findValidRects(Mat& src, double stardWidth, double stardHeight)
//{
// int minX, minY, maxX = 0, maxY = 0;
// minX = src.cols;
// minY = src.rows;
//
// for(unsigned int j = 0; j < stardRect.size(); j ++ )
// {
// if(stardRect[j].x < minX) minX = stardRect[j].x;
// if(stardRect[j].x > maxX) maxX = stardRect[j].x;
// if(stardRect[j].y < minY) minY = stardRect[j].y;
// if(stardRect[j].y > maxY) maxY = stardRect[j].y;
// }
//
// int cols = (maxX - minX) / stardWidth + 0.5;
// int rows = (maxY - minY) / stardHeight + 0.5;
//
//
// //Not enough valid rect
// if( (cols != 9) || (rows != 9) )
// return false;
//
// int realWidth = (maxX - minX) / 8;
// int realHight = (maxY - minY) / 8;
//
// for(int row = 0; row<rows; row++)
// {
// for(int col = 0; col<cols; col++)
// {
// int offsetX = col * realWidth + minX;
// int offsetY = row * realHight + minY;
//
// int index = findNearest(src, offsetX, offsetY);
// int vIndex = col + row *9;
//
// if(index >= 0)
// {
// validRects[vIndex].rc = stardRect[index];
// validRects[vIndex].flag = true;
// }
// else
// {
// validRects[vIndex].rc = Rect( offsetX, offsetY, stardWidth, stardHeight);
// validRects[vIndex].flag = false;
// }
// validRects[vIndex].col = col;
// validRects[vIndex].row = row;
//
// //Only care about 90% rect(ignore edge noise)
// //validRects[vIndex].rc.x = validRects[vIndex].rc.x + validRects[vIndex].rc.width / 10;
// //validRects[vIndex].rc.y = validRects[vIndex].rc.y + validRects[vIndex].rc.height / 10;
// //validRects[vIndex].rc.width = validRects[vIndex].rc.width / 10 * 9;
// //validRects[vIndex].rc.height = validRects[vIndex].rc.height / 10 * 9;
//
// }
// }
//
// return true;
//}
//
//bool findStardardRect(Mat& dst)
//{
// int allCount = rects.size();
//
// if(allCount < 81)
// return false;
//
// // read 4 sequence elements at a time (all vertices of a square)
// int step = 20;
// //int step = 8;
// int start1 = 0, start2 = step / 2;
//
// Mat widths = Mat::zeros(2, dst.cols / step + 1, CV_16UC1);
// Mat heights = Mat::zeros(2, dst.rows / step + 1, CV_16UC1);
//
// int col1, col2, row1, row2;
//
// for(unsigned int i = 0; i < rects.size(); i ++ )
// {
// col1 = rects[i].width / step;
// col2 = (rects[i].width - start2) / step;
//
// row1 = rects[i].height / step;
// row2 = (rects[i].height - start2) / step;
//
// widths.at<short>(0, col1) = widths.at<short>(0, col1) + 1;
// widths.at<short>(1, col2) = widths.at<short>(1, col2) + 1;
// heights.at<short>(0, row1) = heights.at<short>(0, row1) + 1;
// heights.at<short>(1, row2) = heights.at<short>(1, row2) + 1;
//
// //cv::rectangle(dst, rects[i], CV_RGB(0,255,0));
// }
//
// double maxCol, maxRow;
//
// Point pt1, pt2;
//
// cv::minMaxLoc(widths, 0, &maxCol, 0, &pt1, Mat());
// cv::minMaxLoc(heights, 0, &maxRow, 0, &pt2, Mat());
//
// double stardWidth = pt1.x * step + step / 2 + pt1.y * start2;
// double stardHeight = pt2.x * step + step / 2 + pt2.y * start2;
//
// for(unsigned int i = 0; i < rects.size(); i ++ )
// {
// if( (abs(rects[i].width - stardWidth) < step / 2) && (abs(rects[i].height - stardHeight) < step / 2 ) ) //Sharp check
// {
// bool flag = true;
// for(unsigned int j = 0; j < stardRect.size(); j ++ )
// {
// if( (abs(rects[i].x - stardRect[j].x) < (stardWidth - step / 2)) && (abs(rects[i].y - stardRect[j].y) < (stardHeight - step / 2)) )
// {
// flag = false;
// break;
// }
// }
//
// if(flag)
// stardRect.push_back(rects[i]);
// }
// }
//
// return findValidRects(dst, stardWidth, stardHeight);
//}
//
void drawAllRect(Mat& dst)
{
//for(unsigned int i = 0; i < stardRect.size(); i ++ )
// {
// cv::rectangle(dst, stardRect[i], CV_RGB(0,255,0));
//}
for(unsigned int i = 0; i < allRects.size(); i ++ )
{
cv::rectangle(dst, allRects[i], CV_RGB(0,255,128), 5);
}
}
void drawFilterRect(Mat& dst)
{
//for(unsigned int i = 0; i < stardRect.size(); i ++ )
// {
// cv::rectangle(dst, stardRect[i], CV_RGB(0,255,0));
//}
for(unsigned int i = 0; i < filterRects.size(); i ++ )
{
cv::rectangle(dst, filterRects[i].rc, CV_RGB(255,255,0), 5);
}
}
void drawValidRect(Mat& dst)
{
//for(unsigned int i = 0; i < stardRect.size(); i ++ )
// {
// cv::rectangle(dst, stardRect[i], CV_RGB(0,255,0));
//}
for(unsigned int i = 0; i < 81; i ++ )
{
if(validRects[i].virtualFlag)
cv::rectangle(dst, validRects[i].rc, CV_RGB(0,255,0), 5);
else
cv::rectangle(dst, validRects[i].rc, CV_RGB(0,0,255), 5);
}
}
void newFindSquares4(Mat& src)
{
storage = cvCreateMemStorage(0);
IplImage* img = &IplImage(src);
findSquares4(img, storage);
}
int getMaxContour(vector<vector<Point>> &contours, double& maxArea)
{
int index = -1;
maxArea = -1;
for(unsigned int i=0; i<contours.size(); i++)
{
double area = cv::contourArea(Mat(contours[i]));
if(area>maxArea)
{
index = i;
maxArea = area;
}
}
return index;
}
void removeBlank(Mat& src)
{
Mat gray, tempGray;
cvtColor(src, gray, CV_RGB2GRAY);
cv::pyrDown(gray, tempGray); //, 7 );
cv::pyrUp(tempGray, gray ); //, 7 );
//cv::GaussianBlur(gray, tempGray, cvSize(3,3), 3);
//cv::GaussianBlur(tempGray, gray, cvSize(3,3), 3);
//medianBlur(gray, tempGray, 1);
//medianBlur(tempGray, gray, 1);
//dilate(dst, temp, Mat(), Point(-1,-1), 3);
//dilate(temp, dst, Mat(), Point(-1,-1), 3);
//erode(gray, tempGray, Mat(), Point(-1,-1), 1);
//dilate(tempGray, gray, Mat(), Point(-1,-1), 1);
//cv::subtract(dst, back, dst, Mat());
//equalizeHist(dst, dst);
#if(0)
Mat gray2;
resize(gray, gray2, cvSize(gray.size().width / 2, gray.size().height / 2));
imshow("gray", gray2);
if(waitKey(0) == 27)
return;
#endif
for(unsigned int i = 0; i < 81; i ++ )
{
validRects[i].blankFlag = true;
//Mat dst, back, temp;
Mat dst = gray(validRects[i].rc);
float proportion;
Scalar contrast;
validRects[i].contrast = getBkInfo(dst, proportion, contrast);
if(validRects[i].contrast<2) // || proportion>0.95)
{
//stringstream ss;
cout << "No number at " << i << ", contrast " << validRects[i].contrast << endl;
continue;
}
//Make inner ellipse to reduce edge noise
Point2f centerPt;
centerPt.x = dst.size().width / 2;
centerPt.y = dst.size().height / 2;
cv::RotatedRect rrc(centerPt, cvSize(dst.size().width, dst.size().height), 0);
Mat mask = Mat::zeros(dst.size(), CV_8UC1);
cv::ellipse(mask, rrc, Scalar(255,255,255), -1);
Mat temp, temp2;
temp2 = Mat::zeros(dst.size(), CV_8UC1);
double thresh = threshold(dst, temp, 0, 0, CV_THRESH_OTSU); //求出分割阈值
threshold(dst, temp, thresh, 255, CV_THRESH_BINARY_INV); //分割
temp.copyTo(temp2, mask & 1);
temp2.copyTo(temp);
//if(i==60)
//{
// imshow("BIN", temp);
// if(waitKey(0) == 27)
// break;
//}
//cv::adaptiveThreshold(dst, temp, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, 3, 5);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
#if(0)
imshow("ellipse", temp);
if(waitKey(0) == 27)
break;
#endif
//Mat mColor = Mat::zeros(temp.size(), CV_8UC3);
findContours(temp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); //CV
//findContours(temp, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); //CV
Scalar color(0, 255, 255 );
double maxArea = -1;
int maxContourIndex = getMaxContour(contours, maxArea);
validRects[i].areaProportion = maxArea / validRects[i].rc.area();
if(maxContourIndex < 0)
{
cout << "No number at " << i << ", no contour find " << endl;
continue;
}
else if(validRects[i].areaProportion < MIN_CONTOUR_PROPORTION) //(validRects[i].rc.area() / 50) )
{
cout << "No number at " << i << ", contour area too small, proportion is " << validRects[i].areaProportion << endl;
continue;
}
else
{
validRects[i].blankFlag = false;
validRects[i].contourRc = boundingRect(Mat(contours[maxContourIndex]));
//if(validRects[i].contrast > 20)
//{
// imshow("ellipse", temp);
// if(waitKey(0) == 27)
// break;
//}
}
}
}
void Predict(Mat& src)
{
char result[81];
memset(result, '-', sizeof(result));
//CvRTrees forest;
//forest.load( "D:\\SourceCode\\2011\\CV\\CvChessMove\\SvmClassifierForNum\\new_rtrees_10000.xml" );
int featureLen = 64;
CvSVM svm;
svm.load( "D:\\SourceCode\\2011\\CV\\CvChessMove\\SvmClassifierForNum\\SVM_DATA_60000.xml" );
RtTrainData rtd;
memset(rtd.data, 0, sizeof(rtd.data));
Mat m = Mat::zeros(1, featureLen, CV_32FC1);
Mat gray, tempGray;
cvtColor(src, gray, CV_RGB2GRAY);
#if(0)
imshow("gray", gray);
if(waitKey(0) == 27)
return;
#endif
for(unsigned int i = 0; i < 81; i ++ )
{
Mat dst = gray(validRects[i].rc);
if(validRects[i].blankFlag == false)
{
Mat target, temp;
double thresh = threshold(dst, target, 0, 0, CV_THRESH_OTSU); //求出分割阈值
threshold(dst, target, thresh, 255, CV_THRESH_BINARY_INV); //分割
GetROI(target(validRects[i].contourRc), temp);
#if(0)
imshow("NUM", temp);
if(waitKey(0) == 27)
break;
#endif
Mat temp2 = Mat::zeros(8, 8, CV_8UC1);
resize(temp, temp2, temp2.size());
for(int i = 0; i<8; i++)
{
for(int j = 0; j<8; j++)
{
rtd.data[j + i*8] = temp2.at<uchar>(i, j);
//注意m.data为UCHAR*类型,因此m的值不能使用下面的表达式
//m.data[j + i*8] = temp3.at<uchar>(i, j);
}
}
memcpy(m.data, rtd.data, featureLen * sizeof(float));
////GetRectFeatures(rtd);
//memcpy(m.data, rtd.data, featureLen * sizeof(float));
//result[i] = forest.predict(m);
normalize(m, m);
float svm_ret = svm.predict(m);
char svm_char = (char)svm_ret;
cout << svm_char << " at " << i << ", contour proportion " << validRects[i].areaProportion << ", contrast " << validRects[i].contrast << endl;
result[i] = svm.predict(m);
}
}
cout << endl << "SUDOKU RESULT: "<< endl;
for(int i=0; i<9; i++)
{
for(int j=0; j<9; j++)
cout << result[9*i+j] << '\t';
cout << endl;
}
}
int main(int argc, char** argv)
{
string fileName = "../../res/sudoku/svg_sudoku7.jpg";
Mat src = imread(fileName, 1);
Mat dst; // = Mat::zeros(cvSize(src.size().width / 2, src.size().height / 2), CV_8UC3);
//Mat org = imread(fileName, 1);
//flip(src, src, -1);
//Mat src = Mat::zeros(768, 1024, CV_8UC3);
//resize(org, src, src.size());
newFindSquares4(src);
filterDupRect();
//drawAllRect(src);
//drawFilterRect(src);
//resize(src, dst, cvSize(src.size().width / 2, src.size().height / 2));
//imshow("all", src);
//waitKey(0);
if(findValidRects(src))
{
int vc = getVirtualRectsCount();
cout << "Virtual rects count is " << vc << endl;
if(vc < 30)
{
modifyValidRect();
removeBlank(src);
Predict(src);
}
else
cout << "Too much virtual rects to continue predict" << endl;
}
else
cout << "Find rects error" << endl;
drawValidRect(src);
resize(src, dst, cvSize(src.size().width / 2, src.size().height / 2));
imshow("IMG", dst);
waitKey(0);
return 0;
}