现在的位置: 首页 > 综合 > 正文

用OPENCV视觉解数独

2013年09月04日 ⁄ 综合 ⁄ 共 21044字 ⁄ 字号 评论关闭

用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;
}

 

抱歉!评论已关闭.