数独的识别与求解
前言
🤐 疫情在家宅太久了,闷得慌,2020 年 2 月 26 日,微软数独正式上架苹果 App Store,就下载玩了起来,游戏本身制作非常精良,只是内置了广告,付费去广告的价格有点高,15 元/月或 73 元/年,买不起,还是将就一下吧。玩得久了后,又觉得有点无聊和费劲了,索性来写个程序解数独吧。
Tip:数独求解用的是DFS,图像识别用的是OpenCvSharp,数字识别用的是Tesseract OCR。
😅 话说好久不练算法了,还得去百度查一下才敢说自己写的是 DFS。
数独求解
从最简单的部分讲起吧,是的,求解才是最简单的部分。首先用 WPF 写个简单的数独游戏界面出来,数独的格子就用 Button 吧,点击再弹出一个数字选择的窗体,当然也支持点击后直接用按键输入。
随便输入一些数字,会以黑色展示,然后点击 Solve
按钮,就可以求解,当然,你可以连续点击 Solve
,获取随机答案。
求解的算法倒是很好写,很久没碰算法了,也是一遍写过,不需要调试。
求解方法Solve
的代码如下所示:
1 | private static readonly Random random = new Random(); |
核心求解方法 Solve
,数独的数据通过二维数组int[,] data
保存,0 代表没有填充数字。算法简单的应该不需要讲解了,CheckValue
方法就是判断填入的数字是否满足数独的规则。
数独识别
最麻烦的部分来了,求解半小时,识别一整天,一开始没意识到这么麻烦,计算机视觉(Computer Vision)之前也只是简单了解,从没有实际应用过,这次也算是实战了一回。
为什么要做识别呢?理由很简单,数独游戏可能是来自手机 APP、网页或程序等等,获取这些数据,最通用的方式,还是先截图,然后再从图片中识别。
既然是截图,索性就直接全屏截图,然后识别截图中的数独,识别部分的思路是按照 OpenCV 的惯用思路去处理,首先获取 MAT
,然后依次调用CvtColor
、GaussianBlur
、Canny
方法进行灰化、降噪、边缘检测处理。
之后的思路便是自由发挥了,我先假定屏幕上只有一个数独:
- 通过
FindContours
方法提取图像的所有轮廓; - 然后遍历轮廓,记录这些轮廓作为其他轮廓的
Parent
的出现次数; - 找到出现次数不小于 9 次(图示里用红色矩形标记)且高度(宽度或面积也行)最大的一个轮廓,应该就是数独的轮廓;
- 截取数独轮廓,从全屏截图中剔除数独以外的图像内容;
- 再次通过
FindContours
方法提取数独图像的所有轮廓; - 遍历轮廓,通过
BoundingRect
方法定位矩形边界,这是为了找到数独图像中的数字; - 判断矩形边界的高度(毕竟数字的字符宽度不确定,但高度至少是一致的),高度在整个数独轮廓的 1/18 和 1/12 之间的,基本可以判断是数字;
- 计算矩形的所在坐标,并调用
Tesseract
进行数字识别。
setNumber
的三个参数,分别是行、列、值,用于将识别出来的数据进行输出,识别部分的代码如下所示:
1 | public static async Task OCR(Action<int, int, int> setNumber, string fileName = tempFilename) => await Task.Run(() => |
数独识别部分的完整代码在SudokuCV.cs
文件里。
识别效果
对于主色调是黑白灰之类的,识别效果尚可,像 iOS 版的微软数独,但是对于颜色丰富点的图像,识别效果就相当差了,调这个有点麻烦,还是把这个坑留着吧。
- 效果还行的位置:iOS 微软数独、http://www.sudoku.name/index-cn.php
- 效果很差的位置:UWP 微软数独、https://www.sudoku-cn.com/
后记
本项目的完整源码已存到 Github 上,HT.Sudoku
至于数独答案怎么自动输入,留坑,看以后啥时候又闲得慌就来填坑。
另外,😏 对数独求解有兴趣的 gamer,去瞧瞧下面这篇文章吧。