OpenCV求解数独

背景

  • 在一次旅行中,在车上看到旁边坐着的两个同学用手机玩数独。但是当我凑过去的时候,被嘲笑了。。。

求解思路

  • 找出图片中哪个格子是有数字的,并记录数字处于格子的什么位置
  • 有数字的格子中,数字是多少
  • 根据识别出来的数字和记录的位置初始化一个数独数组,并求解。

找出哪个格子有数字并记录位置

这里写图片描述

首先确定数独在哪个位置,即ROI 区域的寻找

  • 这个比较简单,使用windows的画图软件,找出数独的左上角和右下角的坐标就可以了。

在ROI区域中寻找带有数字的格子

  • 在opencv中提供一个强大的寻找轮廓的函数:findContours()。具体代码及注释
    //contours是由一系列点构成的矩形,每个图形中又包括许多轮廓
    //contours[i]:表示第 i 个轮廓,
    //contours[i][i]:表示第 i 个轮廓的第 i 个点
    vector<vector<Point> > contours;
    //hierarchy[i][0] :后一个轮廓序号, 没有则-1
    //hierarchy[i][1] :前一个轮廓序号, 没有则-1
    //hierarchy[i][2] :子轮廓的序号, 没有则-1
    //hierarchy[i][3] :父轮廓的序号,没有则 -1
    vector<Vec4i> hierarchy;
    //建立层级关系
    findContours(roibin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

    for (size_t i = 0; i < contours.size(); i++)
    {   //当前轮廓有子轮廓,并且当前轮廓的面积大于10000
        if (hierarchy[i][2] != -1 && contourArea(contours[i])>10000)
        {//当前轮廓的子轮廓点的序号,即是数字外围的轮廓
            int childIndex = hierarchy[i][2];
            Rect recttemp = boundingRect(contours[i]);//第 i 个轮廓的轮廓
            Rect numberbox = boundingRect(contours[childIndex]);//第 i 个轮廓的子轮廓,即包围数字
            rect.push_back(numberbox);
            pos.push_back(Point(recttemp.br().x / recttemp.width -1 , recttemp.br().y / recttemp.height - 1));
        }
    }
  • 效果
    这里写图片描述

识别数字是多少

制作样本数据

  • 这里使用knn算法识别数字,但是是印刷体的数字。首先应找一些图片作为样本,为了提高效率和准确率,直接使用上面提取到的数字图片即可,但是要注意将所有照片的规格要统一一下。
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;

    findContours(roibin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
    for (size_t i = 0; i < contours.size(); i++)
    {//当前轮廓有子轮廓,并且当前轮廓的面积大于10000
        if (hierarchy[i][2] != -1 && contourArea(contours[i])>10000)
        {//当前轮廓的子轮廓点的序号,即是数字外围的轮廓
            int childIndex = hierarchy[i][2];
            Rect recttemp = boundingRect(contours[childIndex]);//第 i 个轮廓的字轮廓
            string filename = to_string(i) + ".jpg";
            Mat temp = roi(recttemp);
            resize(temp, temp, Size(60, 60));
            cvtColor(temp, temp, COLOR_BGR2GRAY);
            imwrite(filename, temp);
        }
    }
  • 将上述提取的数字分别放到9个文件夹中,使用这些作为样本。
    这里写图片描述

训练数据

    for (size_t i = 0; i < 10; i++)
    {
        string filename = samplefloderpath + "\\" + to_string(i);
        string imgtype = ".jpg";
        vector<string> imgvector;
        getFilesName(filename, imgtype, imgvector);
        for (size_t j = 0; j < imgvector.size(); j++)
        {
            Mat tmp = imread(imgvector[j], IMREAD_GRAYSCALE);
            data.push_back(tmp.reshape(0,1));/*将图像转换成行向量,每张图像就是一个行向量,有多少张图片该矩阵中就有多少行*/
            labels.push_back((int)i);//对应的标签
            trainNum++;
        }
    }
    data.convertTo(data, CV_32F); //uchar型转换为cv_32f
    int samplesNum = data.rows;

    Mat trainData, trainLabels;
    trainData = data(Range(0, trainNum), Range::all());  
    trainLabels = labels(Range(0, trainNum), Range::all());

    //使用KNN算法
    int K = 5;
    Ptr<TrainData> tData = TrainData::create(trainData, ROW_SAMPLE, trainLabels);
    Ptr<KNearest> model = KNearest::create();
    model->setDefaultK(K);
    model->setIsClassifier(true);
    model->train(tData);

识别数字

    Mat sample = imread("xx.jpg",IMREAD_GRAYSCALE);//灰度图
    sample = sample.reshape(0, 1);//还是要将数据转换成行向量
    sample.convertTo(sample, CV_32F);//这里一定要做类型转换,否则会报错
    int res = int(model->predict(sample));

递归求解数独

按照数独的规则检查每个格子可以填写的数字

bool CheckCell(int(*sudoku)[9], int index, int value)
{
    int row = index / 9;
    int col = index % 9;

    //检查行
    for (int i = 0; i < 9; i++)
    {
        if (sudoku[row][i] == value) return false;
    }
    //检查列
    for (int i = 0; i < 9; i++)
    {
        if (sudoku[i][col] == value) return false;
    }

    //检查小格子
    int box_row = row / 3;
    int box_col = col / 3;

    int box_row_start = box_row * 3;
    int box_row_to = box_row * 3 + 3;

    int box_col_start = box_col * 3;
    int box_col_to = box_col * 3 + 3;

    for (int i = box_row_start; i < box_row_to; i++)
    {
        for (int j = box_col_start; j < box_col_to; j++)
        {
            if (sudoku[i][j] == value)
                return false;
        }
    }
    //都正确返回true
    return true;
}

递归求解

//求解数独,从第  0 个位置开始填写就可以了
void DFS_Slove(int(*sudoku)[9], int index)
{
    static bool flag = false;
    if (index > 80)
    {
        flag = true;
        return;
    }
    else
    {
        int row = index / 9;
        int col = index % 9;

        if (sudoku[row][col] != 0)
        {
            DFS_Slove(sudoku, index + 1);
        }
        else
        {
            for (int i = 1; i <= 9; i++)
            {
                if (CheckCell(sudoku, index, i) == true)
                {
                    sudoku[row][col] = i;
                    DFS_Slove(sudoku, index + 1);
                    if (flag == true) return;
                    sudoku[row][col] = 0;
                }
            }
        }
    }
    return;
}

最终效果

这里写图片描述

样本数据和代码下载地址

直接点击
https://download.csdn.net/download/mengxiangpeng123/10606576

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值