基于OpenCV及Python的数独问题识别与求解(三)数字提取

时隔10个月的时间,我终于决定继续更新这个系列博客……
所有代码已经放在GitHub,包括用mnist数据集做手写识别的尝试,文档和注释等正在完善,程序的结构也在优化。

在上一篇文章中得到了清晰、标准的数独问题图像,为下一步提取并识别数字做好了准备。

处理过程

数字提取的主要步骤是:将含有数字的图像,分为 9*9 即 81 个大小相同的方格,遍历这个81个位置,判断每个方格中是否有数字,记录数字所在位置(0~80),保存在 indexes_numbers 中,数字储存在数组sudoku中,主要的代码如下:

# 识别并记录序号 (SUDOKU_SIZE = 9)
indexes_numbers = []
for i in range(SUDOKU_SIZE):for j in range(SUDOKU_SIZE):img_number = img_puzzle[i * GRID_HEIGHT:(i + 1) * GRID_HEIGHT][:, j * GRID_WIDTH:(j + 1) * GRID_WIDTH]hasNumber, sudoku[i * 9 + j, :] = extractNumber.recognize_number(img_number, i, j)if hasNumber:indexes_numbers.append(i * 9 + j)

Recognize_number() 函数

数字提取部分的核心函数就是 Recognize_number(),用来判断方格中是否有数字,并存储数字,代码如下:

def recognize_number(im_number, x, y):"""判断当前方格是否存在数字并存储该数字:param im_number: 方格图像:param x: 方格横坐标 (0~9):param y: 方格纵坐标 (0~9):return: 是否有数字,数字的一维数组"""# 提取并处理方格图像[im_number_thresh, n_active_pixels] = extract_grid(im_number)# 条件1:非零像素大于设定的最小值if n_active_pixels > N_MIN_ACTIVE_PIXELS:# 找出外接矩形[x_b, y_b, w, h] = find_biggest_bounding_box(im_number_thresh)# 计算矩形中心与方格中心距离cX = x_b + w // 2cY = y_b + h // 2d = np.sqrt(np.square(cX - GRID_WIDTH // 2) + np.square(cY - GRID_HEIGHT // 2))# 条件2: 外接矩形中心与方格中心距离足够小if d < GRID_WIDTH // 4:# 取出方格中数字number_roi = im_number[y_b:y_b + h, x_b:x_b + w]# 扩充数字图像为正方形,边长取长宽较大者h1, w1 = np.shape(number_roi)if h1 > w1:number = np.zeros(shape=(h1, h1))number[:, (h1 - w1) // 2:(h1 - w1) // 2 + w1] = number_roielse:number = np.zeros(shape=(w1, w1))number[(w1 - h1) // 2:(w1 - h1) // 2 + h1, :] = number_roi# 将数字缩放为标准大小number = cv2.resize(number, (NUM_WIDTH, NUM_HEIGHT), interpolation=cv2.INTER_LINEAR)# 二值化retVal, number = cv2.threshold(number, 50, 255, cv2.THRESH_BINARY)# 转换为1维数组并返回return True, number.reshape(1, NUM_WIDTH * NUM_HEIGHT)# 没有数字,则返回全零1维数组return False, np.zeros(shape=(1, NUM_WIDTH * NUM_HEIGHT))
  1. 提取当前小方格的图像,并对方格图像进行预处理操作,这里用一个函数 extract_grid() 实现,具体代码见下文。函数返回方格图像、方格二值图像、方格二值图像非零像素数

  2. 条件1,判断方格二值图像的非零像素数是否大于阈值,若大于阈值,认为可能存在数字。

  3. 使用 find_biggest_bounding_box() 函数找出数字的外接矩形,具体代码见下文。函数返回外接矩形的左上坐标、宽度、高度

  4. 计算矩形中心,计算矩形中心与方格中心距离。

  5. 条件2,判断矩形中心是否离方格中心足够近,若小于阈值(方格宽度的四分之一),认为存在数字。

  6. 使用 Python 的切片取出方格中数字,之后扩充数字图像为正方形,正方形边长取长宽较大者。

  7. 将数字缩放为统一的标准大小。注意! cv2.resize() 函数在缩放二值化的图像时,默认插值方法输出不再是二值图像,若令参数 interpolation=cv2.INTER_NEAREST 输出还是二值图像,但因为像素较低效果并不好。因此涉及缩放的地方均使用原图而不是使用二值化后的图像。

  8. 最后,转换为一维数组返回。

extract_grid() 函数

该函数用来提取小方格的图像,然后对其进行预处理。

def extract_grid(im_number):"""将校正后图像划分为9x9的棋盘,取出小方格;二值化;去除离中心较远的像素(排除边框干扰);统计非零像素数(判断方格中是否有数字):param im_number: 方格图像:param x: 方格横坐标 (0~9):param y: 方格纵坐标 (0~9):return: im_number_thresh: 二值化及处理后图像n_active_pixels: 非零像素数"""# 二值化retVal, im_number_thresh = cv2.threshold(im_number, 150, 255, cv2.THRESH_BINARY)# 去除离中心较远的像素点(排除边框干扰)for i in range(im_number.shape[0]):for j in range(im_number.shape[1]):dist_center = np.sqrt(np.square(GRID_WIDTH // 2 - i) + np.square(GRID_HEIGHT // 2 - j))if dist_center > GRID_WIDTH // 2 - 2:im_number_thresh[i, j] = 0# 统计非零像素数,以判断方格中是否有数字n_active_pixels = cv2.countNonZero(im_number_thresh)return [im_number_thresh, n_active_pixels]
  1. 取出坐标为 (x,y) 的方格图像 im_number。使用Python中的二维数组切片功能,类似OpenCV中感兴趣区域(ROI)功能,可以方便地提取图像的某一部分,对图像进行分割。
  2. 方格图像自适应二值化。
  3. 由于校正后图像中有白色边框,在分割后会在每一个方格四周存在白色像素,需要去除离中心较远的像素点(排除边框干扰)。这里遍历小方格每一个像素,判断该像素与中心的距离,远于阈值的将其值设为0,简单来说就是以方格中心为圆心画一个圆,圆内的保留,圆外的去除。
  4. 使用 cv2.countNonZero() 统计现在方格中的非零像素数。
  5. 将当前坐标方格图像和非零像素数返回。

find_biggest_bounding_box() 函数

该函数用来找出数字的外接矩形,输入参数为二值图像,输出为外接矩形的左上坐标和宽度、高度。

def find_biggest_bounding_box(im_number_thresh):"""找出小方格中外接矩形面积最大的轮廓,返回其外接矩形参数:param im_number_thresh: 当前方格的二值化及处理后图像:return: 外接矩形参数(左上坐标及长和宽)"""# 轮廓检测b, contour, hierarchy1 = cv2.findContours(im_number_thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 找出外接矩形面积最大的轮廓biggest_bound_rect = []bound_rect_max_size = 0for i in range(len(contour)):bound_rect = cv2.boundingRect(contour[i])size_bound_rect = bound_rect[2] * bound_rect[3]if size_bound_rect > bound_rect_max_size:bound_rect_max_size = size_bound_rectbiggest_bound_rect = bound_rect# 将外接矩形扩大一个像素x_b, y_b, w, h = biggest_bound_rectx_b = x_b - 1y_b = y_b - 1w = w + 2h = h + 2return [x_b, y_b, w, h]
  1. 对方格二值图像使用轮廓检测。
  2. 遍历找到的轮廓,使用 cv2.boundingRect() 去轮廓的外接矩形, 求外接矩形面积,找出最大的面积。
  3. 将外接矩形扩大一个像素,有利于识别,返回外接矩形坐标、长、宽。

效果展示

通过以上步骤,可找出哪些方格有数字,并将数字存储,如下图所示。

提取数字过程

1->2 去除了四周的边框干扰;

2->3 找出外接矩形面积最大的轮廓并提取;

3->4 扩充为正方形,再进行缩放二值化等;

最终数字提取结果如图所示。
数字提取效果

提取了数字,并统一成标准大小,下一步就可以进行数字识别了。事实上数字提取的过程还有一些需要改进的地方:比如从原图上提取出的数字,未缩放前为29x29,而标准大小定义为20x20,这样一缩放就会损失信息,有可能影响识别精度,等等。

上文提到的方法在很多细节上都可以改进,欢迎交流。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部