OpenCV玩九宫格数独(一):九宫格图片中提取数字

前言

首先要明确我们的任务。要想解数独,需要进行计算,图片格式的数字肯定是不行的,所以必须把图片上的数字转换为实实在在的数字才能进行计算。要得到实实在在的数字,我们需要做的是对图片上的数字进行提取和识别。本文先说第一步,图片中数字的提取。

在一年之前,我曾用 C++ 尝试过 opencv 解数独,但由于当时水平有限,未能完成。当时的成果就是透视变换的应用和方格数字的提取。现在稍微简化一下工作,不再从倾斜的数独图片中提取数独,而是直接用正拍且已经提取好的数独开始处理。这里用到的数独图片如下图所示:

方法

1.以前的方法

从上图这样的九宫格图片中提取数字,我以前用的方法是,先利用轮廓提取,通过轮廓的面积进行筛选,得到所有的81个小方格;然后对检测小方格中是否有黑色像素以及像素的多少(排除噪音)来判定哪个小方格中有数字;最后对有数字的小方格再次进行轮廓提取得到数字的轮廓和轮廓外包矩形。

此方法实现起来相对来说比较麻烦,思路仅供参考。

2.本次所用方法

在仔细研究了 opencv 轮廓提取函数findContours()之后,发现利用轮廓的层级结构会更加简单。作为本节最主要的函数,有必要稍微多说几句。

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset] ] ]) → contours, hierarchy

在Python中,findContours()接受如下参数并返回contours和hierarchy。

1.image 源图像,一般为8为单通道图像,更具体来说,二值图像。其他情况暂且不论。

2.mode 轮廓检索模式,简要介绍几种:

  • cv2.RETR_EXTERNAL 只检测外轮廓。对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1
  • cv2.RETR_LIST 提取所有轮廓,并放置在list中,检测到的轮廓不建立等级关系。
  • cv2.RETR_TREE 提取所有轮廓,建立网状的轮廓结构。

3.method 轮廓的近似办法,是提取轮廓上所有像素点,还是只提取关键的一些点。比如一条线段是提取所有点还是只提取两个端点。

4.contours 检测到的轮廓,为组成轮廓的点集。

5.hierarchy 下面详述。

hierarchy

什么是层级结构呢?我们检测轮廓的时候,有时候可能会出现其中一个轮廓包含了另外一个轮廓,比如同心圆。这里我们认为外侧轮廓为父轮廓,内侧被包含的为子轮廓。同一级别的又有前一个轮廓后一个轮廓。总的来说,hierarchy表达的是不同轮廓之间的 关系和联系。

这样,每一个轮廓都会有[Next, Previous, First_Child, Parent]

上面说到,cv2.RETR_EXTERNAL 只检测外轮廓。对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1。由于只检测最外围轮廓,所有检测到的轮廓肯定没有父轮廓和子轮廓,所有层级结构的第三个和第四个元素都设置为-1。

看下图:

如果只检测最外围轮廓,那么只会检测到轮廓012

如果建立层级关系,以轮廓3为例,那么它的父轮廓是2a,子轮廓是3a,没有前一轮廓和后一轮廓,设为-1。所以它的hierarchy应该是[-1,-1,3a,2a]

如果是轮廓2,那么它的前一轮廓就是1,子轮廓是2a,没有后一轮廓和父轮廓。所以它的hierarchy应该是[-1,1,2a,-1]

有兴趣的可以仔细看看,没兴趣的可以略过。兴趣更浓的可以去看opencv文档,那里的讲解更加详细。

这里就说这么多,对于我们本节的内容来说,已经够了。

上面说了啥

我觉得大部分人这个时候还会问,上面说了这么一堆到底是要干什么???因为这里确实不是那么清晰明了。

别忘了我们本节的目的是要提取数字,什么样的轮廓包含数字?

一般来说经过前面的阈值分割得到二值图像,然后从二值图像中提取的轮廓是这样的。这是处理的比较好的情况下:

显然最最外面的那个包围所有的就是0号轮廓,里面的九九八十一个小方格就是0号轮廓的子轮廓。而每一个已知数字的轮廓都是对应方格的子轮廓。

提取数字

所有我们的办法就是先提取方格,然后提取数字。

八十一个小方格有什么特点?父轮廓都是0号轮廓!所以:

boxes = []
for i in range(len(hierarchy[0])):
    if hierarchy[0][i][3] == 0:
        boxes.append(hierarchy[0][i])

不记得的可以上翻看一下hierarchy是不是第四个元素表示父轮廓。

然后从小方格中提取数字轮廓。数字轮廓的有什么特点?其父轮廓有子轮廓,也即是说包含子轮廓的小方格里面就有数字。所以:

for j in range(len(boxes)):
    if boxes[j][2] != -1:
        x,y,w,h = cv2.boundingRect(contours[boxes[j][2]])
        number_boxes.append([x,y,w,h])

不记得的可以上翻看一下hierarchy是不是第三个元素表示子轮廓。不等于-1表示存在。

最后把检测到的数字画出来就可以得到下面的这幅图了。

###代码

# -*- coding: UTF-8 -*-
import cv2

img = cv2.imread('001.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
## 阈值分割
ret,thresh = cv2.threshold(gray,200,255,1)

## 对二值图像执行膨胀操作
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5))     
dilated = cv2.dilate(thresh,kernel)

## 轮廓提取,cv2.RETR_TREE表示建立层级结构
image, contours, hierarchy = cv2.findContours(dilated,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

## 提取小方格,其父轮廓都为0号轮廓
boxes = []
for i in range(len(hierarchy[0])):
    if hierarchy[0][i][3] == 0:
        boxes.append(hierarchy[0][i])

## 提取数字,其父轮廓都存在子轮廓        
number_boxes = []
for j in range(len(boxes)):
    if boxes[j][2] != -1:
        #number_boxes.append(boxes[j])
        x,y,w,h = cv2.boundingRect(contours[boxes[j][2]])
        number_boxes.append([x,y,w,h])
        img = cv2.rectangle(img,(x-1,y-1),(x+w+1,y+h+1),(0,0,255),2)

cv2.namedWindow("img", cv2.WINDOW_NORMAL); 
cv2.imshow("img", img)
cv2.waitKey(0)

下一步

数字已经提取出来,下一步就该是数字的识别了......

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

2 条评论
登录 后参与评论

相关文章

来自专栏Python中文社区

用 Python 分析《红楼梦》(1)

專 欄 ❈楼宇,Python中文社区专栏作者。一位正在海外苦苦求学的本科生。初中时自学编程,后来又在几位良师的帮助下走上了计算机科学的道路。曾经的 OIer,...

2248
来自专栏函数式编程语言及工具

泛函编程(17)-泛函状态-State In Action

    对OOP编程人员来说,泛函状态State是一种全新的数据类型。我们在上节做了些介绍,在这节我们讨论一下State类型的应用:用一个具体的例子来示范如何使...

1888
来自专栏数据结构与算法

BZOJ4518: [Sdoi2016]征途(dp+斜率优化)

Description Pine开始了从S地到T地的征途。 从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。 Pine计划用m天到达T地。除第m...

3528
来自专栏落影的专栏

程序员进阶之算法练习(二十一)

前言 转眼已经到5月,可是我还没订17年的计划,真是悲伤的故事。 今年还是想花点时间,玩玩题目。 题目不会太简单,也不会太难,简单的如1、2题,难的如3、...

3184
来自专栏tkokof 的技术,小趣及杂念

小聊聊NGUI中Panel的Clip功能(之二)

  上篇简单聊了一下NGUI中Panel裁剪的实现原理,总结来看其实比较简单,就是通过Shader计算fragment关于Panel裁剪区域的相对位置,然后通过...

912
来自专栏小樱的经验随笔

高斯消元法(Gauss Elimination)【超详解&模板】

高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵。 高斯消元法的原理是: 若用初等行变换将增广矩阵 化为 ...

5038
来自专栏人工智能头条

MXNet设计笔记之:深度学习的编程模式比较

1663
来自专栏点滴积累

geotrellis使用(十五)使用Bokeh进行栅格数据可视化统计

Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html 目录 前言 实现方案 ...

3127
来自专栏数说工作室

统计师的Python日记【第八天:数据清洗(2)文本处理】

本文是【统计师的Python日记】第8天的日记 回顾一下: 第1天学习了Python的基本页面、操作,以及几种主要的容器类型。 第2天学习了python的函数、...

4546
来自专栏企鹅号快讯

机器视觉:用图像驱动智能小车

本篇大致探索下图像的识别。实现了颜色识别,以及利用直方图来计算图像相似度。先来个视频。 摄像头实时捕捉画面,从画面中解析出蓝色的区域,并与预设图像对比,相似度小...

2210

扫码关注云+社区