PyOpenCV 实战:借助视觉识别技术实现围棋终局的胜负判定

原标题:PyOpenCV 实战:借助视觉识别技术实现围棋终局的胜负判定

作者 | 天元浪子 责编 | 张文

头图 | CSDN 下载自视觉中国

出品 | CSDN(ID:CSDNnews)

前言

准备工作

2.1 约定围棋局面的数据结构

标准的 19 路围棋盘上有 361 个交叉点可以落子,每个交叉点有三种状态:无子、黑子、白子,不难算出围棋共有 3 的 361 次方种不同的局面。

对于围棋局面,用什么样的数据结构来表示最合理呢?

我以前写过象棋和国际跳棋的代码,对于这两种棋,是用字符串来表示一个局面。考虑到围棋终局时需要统计黑白双方的棋子数量和围空数量,这里选择使用二维的 NumPy 数组来表示围棋局面,数组元素 0 表示无子,1 表示黑子,2 表示白子。下面是我手动输入的一个围棋局面。

2.2 显示一个围棋局面

怎样直观地显示一个围棋局面呢?下面的代码使用 Python 的内置函数 print就可以在控制台上打印出像照片一样的彩色棋盘。

os.system( )

defshow_phase(phase): “””显示局面”””

fori inrange( 19): forj inrange( 19): ifphase[i,j] == 1: chessman = chr( 0x25cf) elifphase[i,j] == 2: chessman = chr( 0x25cb) elifphase[i,j] == 9: chessman = chr( 0x2606) else: ifi == 0: ifj == 0: chessman = ‘%s ‘%chr( 0x250c) elifj == 18: chessman = ‘%s ‘%chr( 0x2510) else: chessman = ‘%s ‘%chr( 0x252c) elifi == 18: ifj == 0: chessman = ‘%s ‘%chr( 0x2514) elifj == 18: chessman = ‘%s ‘%chr( 0x2518) else: chessman = ‘%s ‘%chr( 0x2534) elifj == 0: chessman = ‘%s ‘%chr( 0x251c) elifj == 18: chessman = ‘%s ‘%chr( 0x2524) else: chessman = ‘%s ‘%chr( 0x253c) print( ’33[0;30;43m’+ chessman + ’33[0m’, end= ) print

phase = np.array([[ 0, 0, 2, 1, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 0, 2, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 2, 1, 1, 1, 0, 0], [ 0, 0, 2, 1, 1, 0, 0, 1, 2, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 2, 1, 0, 1, 1, 0, 1, 2, 0, 2, 2, 2, 0, 2, 1, 0, 1, 0], [ 0, 2, 1, 1, 0, 1, 1, 2, 2, 2, 2, 0, 0, 2, 2, 1, 0, 1, 0], [ 0, 0, 2, 1, 1, 1, 1, 2, 0, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 0, 0, 2, 2, 2, 2, 1, 2, 2, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 2, 2, 2, 0, 0, 0, 2, 1, 1, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 1, 1, 2, 0, 0, 0, 2, 2, 1, 2, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 1, 0, 1, 2, 0, 2, 1, 1, 1, 1, 2, 2, 2, 0, 2, 1, 1, 1, 1], [ 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 1, 2, 0, 2, 2, 1, 0, 0, 1], [ 1, 1, 2, 2, 2, 2, 2, 1, 0, 0, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 2, 2, 0, 2, 2, 0, 2, 1, 0, 0, 1, 2, 0, 2, 2, 2, 1, 0, 0], [ 0, 2, 0, 0, 0, 0, 2, 1, 0, 1, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 0, 2, 0, 0, 0, 2, 1, 0, 0, 1, 0, 1, 1, 2, 2, 1, 0, 0, 0], [ 0, 0, 2, 0, 2, 2, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0], [ 0, 2, 2, 0, 2, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], [ 0, 0, 2, 0, 2, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0], [ 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0] ], dtype=np.ubyte)

show_phase(phase)

这段代码的运行结果如下:

2.3 计算黑白双方的棋子和围空

判断一个围棋局面是否为终局,是有一定难度的。 为了简化代码,约定提交判决的围棋局面必须是双方死棋、残子均已提清,无任何异议。如此,代码就非常简单了。

deffind_blank(phase, cell): “””找出包含cell的成片的空格”””

def_find_blank(phase, result, cell): i, j = cellphase[i,j] = 9result[ ‘cross’].add(cell)

ifi -1> -1: ifphase[i -1,j] == 0: _find_blank(phase, result, (i -1,j)) elifphase[i -1,j] == 1: result[ ‘b_around’].add((i -1,j)) elifphase[i -1,j] == 2: result[ ‘w_around’].add((i -1,j)) ifi+ 1< 19: ifphase[i+ 1,j] == 0: _find_blank(phase, result, (i+ 1,j)) elifphase[i+ 1,j] == 1: result[ ‘b_around’].add((i+ 1,j)) elifphase[i+ 1,j] == 2: result[ ‘w_around’].add((i+ 1,j)) ifj -1> -1: ifphase[i,j -1] == 0: _find_blank(phase, result, (i,j -1)) elifphase[i,j -1] == 1: result[ ‘b_around’].add((i,j -1)) elifphase[i,j -1] == 2: result[ ‘w_around’].add((i,j -1)) ifj+ 1< 19: ifphase[i,j+ 1] == 0: _find_blank(phase, result, (i,j+ 1)) elifphase[i,j+ 1] == 1: result[ ‘b_around’].add((i,j+ 1)) elifphase[i,j+ 1] == 2: result[ ‘w_around’].add((i,j+ 1))

result = { ‘cross’:set, ‘b_around’:set, ‘w_around’:set} _find_blank(phase, result, cell)

returnresult

deffind_blanks(phase): “””找出所有成片的空格”””

blanks = listwhileTrue: cells = np.where(phase== 0) ifcells[ 0].size == 0: break

blanks.append(find_blank(phase, (cells[ 0][ 0], cells[ 1][ 0])))

returnblanks

defstats(phase): “””统计结果”””

temp = np.copy(phase)foritem infind_blanks(np.copy(phase)): iflen(item[ ‘w_around’]) == 0: v = 3# 黑空eliflen(item[ ‘b_around’]) == 0: v = 4# 白空else: v = 9# 单官或公气

fori, j initem[ ‘cross’]: temp[i, j] = v

black = temp[temp== 1].size + temp[temp== 3].size white = temp[temp== 2].size + temp[temp== 4].size common = temp[temp== 9].size

returnblack, white, common

if__name__ == ‘__main__’: phase = np.array([[ 0, 0, 2, 1, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 0, 2, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 2, 1, 1, 1, 0, 0], [ 0, 0, 2, 1, 1, 0, 0, 1, 2, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 2, 1, 0, 1, 1, 0, 1, 2, 0, 2, 2, 2, 0, 2, 1, 0, 1, 0], [ 0, 2, 1, 1, 0, 1, 1, 2, 2, 2, 2, 0, 0, 2, 2, 1, 0, 1, 0], [ 0, 0, 2, 1, 1, 1, 1, 2, 0, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 0, 0, 2, 2, 2, 2, 1, 2, 2, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 2, 2, 2, 0, 0, 0, 2, 1, 1, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 1, 1, 2, 0, 0, 0, 2, 2, 1, 2, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 1, 0, 1, 2, 0, 2, 1, 1, 1, 1, 2, 2, 2, 0, 2, 1, 1, 1, 1], [ 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 1, 2, 0, 2, 2, 1, 0, 0, 1], [ 1, 1, 2, 2, 2, 2, 2, 1, 0, 0, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 2, 2, 0, 2, 2, 0, 2, 1, 0, 0, 1, 2, 0, 2, 2, 2, 1, 0, 0], [ 0, 2, 0, 0, 0, 0, 2, 1, 0, 1, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 0, 2, 0, 0, 0, 2, 1, 0, 0, 1, 0, 1, 1, 2, 2, 1, 0, 0, 0], [ 0, 0, 2, 0, 2, 2, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0], [ 0, 2, 2, 0, 2, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], [ 0, 0, 2, 0, 2, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0], [ 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0] ], dtype=np.ubyte)

show_phase(phase)black, white, common = stats(phase)print( ‘————————————–‘) print( ‘黑方:%d,白方:%d,公气:%d’%(black, white, common))

代码运行结果如下图所示。

这段代码在查找成片的空地时使用了递归的方式,这是解决此类问题的有效手段。

处理流程

为了便于查看各种数据结构、对象的信息,整个处理流程我放在 IDLE 中运行。除了标准库,全部代码仅需另外导入 PyOpenCV 和 NumPy 模块。

3.1 图像预处理

下图是一幅围棋局面的照片,整个处理过程就用这张照片来演示。这个局面是随便摆出来的,并不是一个终局。

下面的代码使用 opencv 打开这张照片,转为灰度图像,对图像进行滤波降噪处理后做边缘检测。

下图是灰度图、滤波降噪后的灰度图和边缘检测的结果。

看起来杂乱无章,但整个棋盘的边缘还是非常清晰完整的。

如果边缘检测效果不佳,可以考虑在边缘检测之前做锐化处理,或者在边缘检测之后做膨胀处理。

3.2 识别并定位棋盘

  • 在检测结果中棋盘边缘是清晰完整的
  • 棋盘边缘围成的封闭区域是所有封闭区域中面积最大的

contours 中的每一个轮廓由该轮廓的各个点的坐标来描述,而 hierarchy 则表示每一个轮廓和其他轮廓的相对关系。仅使用轮廓数据列表 contours 就可以找到棋盘。

>>> if rect is None:print(‘在图像文件中找不到棋盘!’)else:print(‘棋盘坐标:’)print(‘t左上角:(%d,%d)’%(rect[ 0][ 0],rect[ 0][ 1])) print(‘t左下角:(%d,%d)’%(rect[ 1][ 0],rect[ 1][ 1])) print(‘t右上角:(%d,%d)’%(rect[ 2][ 0],rect[ 2][ 1])) print(‘t右下角:(%d,%d)’%(rect[ 3][ 0],rect[ 3][ 1]))

棋盘坐标:左上角:(111,216)左下角:(47,859)右上角:(753,204)右下角:(823,859)

下面的代码将这 4 个点用红色的十字标注在原始的彩色图像上。

>>> cv2.imshow( ‘go’, im)

用红色十字标注的棋盘四角如下图所示。

很显然,因为拍摄的角度问题,大多数情况下棋盘并不是一个矩形,因此需要对棋盘做透视矫正,使其看起来更像一块真正的棋盘。

3.3 透视矫正

假定透视变换后生成的棋盘大小为 640×640 像素,加上四边各有 10 个像素的空白,图像分辨率为 660×660 像素。

下图是经过透视矫正后的棋盘。

现在,我们找到了棋盘的四个角,还需要进一步确定棋盘上每一个交叉格点的坐标,也就是定位棋盘格子。

3.4 定位棋盘格子

透视矫正后的棋盘是边长为 660 像素的正方形、且棋盘格子一定位于棋盘中心,基于这个条件,我们可以简化定位棋盘格子的思路如下:

  • 借助于圆检测,找出棋盘上的每一颗棋子,但要尽量避免找到实际并不存在的棋子
  • 对全部棋子的 x 坐标排序,找出最左侧和最右侧的两列棋子,分别计算其x坐标排序的平均值 x_min 和 x_max
  • 对全部棋子的 y 坐标排序,找出最上方和最下方的两行棋子,分别计算其y坐标排序的平均值 y_min 和 y_max
  • 考察 x 和 y 坐标极值差,最接近 600 者,为棋盘格子的宽度和高度
  • 在分辨率为 660×660 像素的图像上计算出 19×19 的网格四角的坐标
  • 将棋盘映射到 620×620 像素的图像上,网格四角的坐标分别是(22, 22)、 (22, 598)、 (598, 22)和 (598, 598),网格的水平和垂直间距都是 32 像素

>>> x_min = int(round(xs[ :k].mean)) >>> k = 1>>> while ys[k]-ys[ :k].mean < 15: k += 1

>>> y_min = int(round(ys[ :k].mean)) >>> k = – 1>>> while xs[ k:].mean – xs[k- 1] < 15: k -= 1

>>> x_max = int(round(xs[ k:].mean)) >>> k = – 1>>> while ys[ k:].mean – ys[k- 1] < 15: k -= 1

>>> y_max = int(round(ys[ k:].mean)) >>> x_min, x_max, y_min, y_max ( 32, 629, 29, 622) >>> if abs( 600-(x_max-x_min)) < abs( 600-(y_max-y_min)): v_min, v_max = x_min, x_maxelse:v_min, v_max = y_min, y_max

>>> v_min, v_max ( 32, 629) >>> lt = (v_min, v_min) # 棋盘网格左上角>>> lb = (v_min, v_max) # 棋盘网格左下角>>> rt = (v_max, v_min) # 棋盘网格右上角>>> rb = (v_max, v_max) # 棋盘网格右下角>>> pts1 = np.float32([[ 22, 22], [ 22, 598], [ 598, 22], [ 598, 598]]) # 棋盘四个角点的最终位置>>> pts2 = np.float32([lt, lb, rt, rb]) >>> m = cv2.getPerspectiveTransform(pts2, pts1) >>> board_gray = cv2.warpPerspective(board_gray, m, ( 620, 620)) >>> board_bgr = cv2.warpPerspective(board_bgr, m, ( 620, 620)) >>> cv2.imshow( ‘go’, board_gray) >>> im = np.copy(board_bgr) >>> series = np.linspace( 22, 598, 19, dtype=np.int) >>> for i in series:im = cv2.line(im, ( 22, i), ( 598, i), ( 0, 255, 0), 1) im = cv2.line(im, (i, 22), (i, 598), ( 0, 255, 0), 1)

>>> cv2.imshow( ‘go’, im)

定位棋盘格子之后的效果如下图所示。

3.5 识别棋子及其颜色

>>> phasearray([[1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1],[0, 1, 1, 1, 2, 2, 2, 1, 2, 1, 1, 2, 0, 2, 1, 2, 1, 1, 1],[0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 0],[1, 1, 1, 2, 2, 0, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0],[1, 2, 2, 1, 1, 2, 2, 2, 0, 0, 2, 2, 1, 0, 1, 0, 0, 0, 0],[2, 2, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 0, 1, 0, 1, 1, 0, 0],[0, 2, 2, 1, 0, 1, 1, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0],[0, 0, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1],[0, 2, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 0, 1, 0, 2, 2, 0, 0],[0, 0, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 0, 0, 0],[0, 2, 2, 1, 1, 2, 2, 0, 2, 2, 2, 2, 1, 1, 2, 1, 0, 2, 1],[2, 2, 2, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 0, 1],[1, 2, 1, 1, 2, 2, 2, 1, 0, 1, 2, 2, 1, 2, 2, 2, 1, 1, 0],[1, 1, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 0],[0, 0, 1, 2, 1, 2, 2, 2, 1, 0, 1, 2, 1, 2, 1, 1, 1, 2, 1],[1, 1, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 1, 2, 2, 2, 1, 2, 1],[1, 1, 2, 0, 0, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 0],[1, 2, 2, 0, 0, 2, 2, 0, 2, 1, 1, 2, 1, 1, 1, 0, 0, 1, 1],[2, 2, 2, 0, 0, 0, 0, 2, 2, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0]],dtype=uint8)

源码文件

4.1 统计棋子和围空数量的脚本文件

stats.py

“””根据围棋局面,计算对局结果

使用NumPy二维ubyte数组存储局面,约定:0 – 空1 – 黑子2 – 白子3 – 黑空4 – 白空5 – 黑子统计标识6 – 白子统计标识7 – 黑残子8 – 白残子9 – 单官/公气/统计标识“””

importnumpy asnp importos

os.system( )

defshow_phase(phase): “””显示局面”””

fori inrange( 19): forj inrange( 19): ifphase[i,j] == 1: chessman = chr( 0x25cf) elifphase[i,j] == 2: chessman = chr( 0x25cb) elifphase[i,j] == 9: chessman = chr( 0x2606) else: ifi == 0: ifj == 0: chessman = ‘%s ‘%chr( 0x250c) elifj == 18: chessman = ‘%s ‘%chr( 0x2510) else: chessman = ‘%s ‘%chr( 0x252c) elifi == 18: ifj == 0: chessman = ‘%s ‘%chr( 0x2514) elifj == 18: chessman = ‘%s ‘%chr( 0x2518) else: chessman = ‘%s ‘%chr( 0x2534) elifj == 0: chessman = ‘%s ‘%chr( 0x251c) elifj == 18: chessman = ‘%s ‘%chr( 0x2524) else: chessman = ‘%s ‘%chr( 0x253c) print( ’33[0;30;43m’+ chessman + ’33[0m’, end= ) print

deffind_blank(phase, cell): “””找出包含cell的成片的空格”””

def_find_blank(phase, result, cell): i, j = cellphase[i,j] = 9result[ ‘cross’].add(cell)

ifi -1> -1: ifphase[i -1,j] == 0: _find_blank(phase, result, (i -1,j)) elifphase[i -1,j] == 1: result[ ‘b_around’].add((i -1,j)) elifphase[i -1,j] == 2: result[ ‘w_around’].add((i -1,j)) ifi+ 1< 19: ifphase[i+ 1,j] == 0: _find_blank(phase, result, (i+ 1,j)) elifphase[i+ 1,j] == 1: result[ ‘b_around’].add((i+ 1,j)) elifphase[i+ 1,j] == 2: result[ ‘w_around’].add((i+ 1,j)) ifj -1> -1: ifphase[i,j -1] == 0: _find_blank(phase, result, (i,j -1)) elifphase[i,j -1] == 1: result[ ‘b_around’].add((i,j -1)) elifphase[i,j -1] == 2: result[ ‘w_around’].add((i,j -1)) ifj+ 1< 19: ifphase[i,j+ 1] == 0: _find_blank(phase, result, (i,j+ 1)) elifphase[i,j+ 1] == 1: result[ ‘b_around’].add((i,j+ 1)) elifphase[i,j+ 1] == 2: result[ ‘w_around’].add((i,j+ 1))

result = { ‘cross’:set, ‘b_around’:set, ‘w_around’:set} _find_blank(phase, result, cell)

returnresult

deffind_blanks(phase): “””找出所有成片的空格”””

blanks = listwhileTrue: cells = np.where(phase== 0) ifcells[ 0].size == 0: break

blanks.append(find_blank(phase, (cells[ 0][ 0], cells[ 1][ 0])))

returnblanks

defstats(phase): “””统计结果”””

temp = np.copy(phase)foritem infind_blanks(np.copy(phase)): iflen(item[ ‘w_around’]) == 0: v = 3# 黑空eliflen(item[ ‘b_around’]) == 0: v = 4# 白空else: v = 9# 单官或公气

fori, j initem[ ‘cross’]: temp[i, j] = v

black = temp[temp== 1].size + temp[temp== 3].size white = temp[temp== 2].size + temp[temp== 4].size common = temp[temp== 9].size

returnblack, white, common

if__name__ == ‘__main__’: phase = np.array([[ 0, 0, 2, 1, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 0, 2, 1, 0, 1, 1, 1, 2, 0, 2, 0, 2, 2, 1, 1, 1, 0, 0], [ 0, 0, 2, 1, 1, 0, 0, 1, 2, 2, 0, 2, 0, 2, 1, 0, 1, 0, 0], [ 0, 2, 1, 0, 1, 1, 0, 1, 2, 0, 2, 2, 2, 0, 2, 1, 0, 1, 0], [ 0, 2, 1, 1, 0, 1, 1, 2, 2, 2, 2, 0, 0, 2, 2, 1, 0, 1, 0], [ 0, 0, 2, 1, 1, 1, 1, 2, 0, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 0, 0, 2, 2, 2, 2, 1, 2, 2, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 2, 2, 2, 0, 0, 0, 2, 1, 1, 2, 0, 2, 0, 0, 2, 1, 0, 0, 0], [ 1, 1, 2, 0, 0, 0, 2, 2, 1, 2, 0, 0, 0, 0, 2, 1, 0, 0, 0], [ 1, 0, 1, 2, 0, 2, 1, 1, 1, 1, 2, 2, 2, 0, 2, 1, 1, 1, 1], [ 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 1, 2, 0, 2, 2, 1, 0, 0, 1], [ 1, 1, 2, 2, 2, 2, 2, 1, 0, 0, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 2, 2, 0, 2, 2, 0, 2, 1, 0, 0, 1, 2, 0, 2, 2, 2, 1, 0, 0], [ 0, 2, 0, 0, 0, 0, 2, 1, 0, 1, 1, 2, 2, 0, 2, 1, 0, 0, 0], [ 0, 2, 0, 0, 0, 2, 1, 0, 0, 1, 0, 1, 1, 2, 2, 1, 0, 0, 0], [ 0, 0, 2, 0, 2, 2, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0], [ 0, 2, 2, 0, 2, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], [ 0, 0, 2, 0, 2, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0], [ 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0] ], dtype=np.ubyte)

show_phase(phase)black, white, common = stats(phase)print( ‘————————————–‘) print( ‘黑方:%d,白方:%d,公气:%d’%(black, white, common))

4.2 视觉识别的脚本文件

go.py

“””识别图像中的围棋局面“””

importcv2 importnumpy asnp

fromstats importshow_phase, stats

classGoPhase: “””从图片中识别围棋局面”””

def__init__(self, pic_file, offset= 3.75) : “””构造函数,读取图像文件,预处理”””

self.offset = offset # 黑方贴七目半self.im_bgr = cv2.imread(pic_file) # 原始的彩色图像文件,BGR模式self.im_gray = cv2.cvtColor(self.im_bgr, cv2.COLOR_BGR2GRAY) # 转灰度图像self.im_gray = cv2.GaussianBlur(self.im_gray, ( 3, 3), 0) # 灰度图像滤波降噪self.im_edge = cv2.Canny(self.im_gray, 30, 50) # 边缘检测获得边缘图像

self.board_gray = None# 棋盘灰度图self.board_bgr = None# 棋盘彩色图self.rect = None# 棋盘四个角的坐标,顺序为lt/lb/rt/rbself.phase = None# 用以表示围棋局面的二维数组self.result = None# 对弈结果

self._find_chessboard # 找到棋盘self._location_grid # 定位棋盘格子self._identify_chessman # 识别棋子self._stats # 统计黑白双方棋子和围空

def_find_chessboard(self): “””找到棋盘”””

contours, hierarchy = cv2.findContours(self.im_edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 提取轮廓area = 0# 找到的最大四边形及其面积foritem incontours: hull = cv2.convexHull(item) # 寻找凸包epsilon = 0.1* cv2.arcLength(hull, True) # 忽略弧长10%的点approx = cv2.approxPolyDP(hull, epsilon, True) # 将凸包拟合为多边形

iflen(approx) == 4andcv2.isContourConvex(approx): # 如果是凸四边形ps = np.reshape(approx, ( 4, 2)) # 四个角的坐标ps = ps[np.lexsort((ps[:, 0],))] # 排序区分左右lt, lb = ps[: 2][np.lexsort((ps[: 2, 1],))] # 排序区分上下rt, rb = ps[ 2:][np.lexsort((ps[ 2:, 1],))] # 排序区分上下

a = cv2.contourArea(approx)ifa > area: area = aself.rect = (lt, lb, rt, rb)

ifnotself.rect isNone: pts1 = np.float32([( 10, 10), ( 10, 650), ( 650, 10), ( 650, 650)]) # 预期的棋盘四个角的坐标pts2 = np.float32(self.rect) # 当前找到的棋盘四个角的坐标m = cv2.getPerspectiveTransform(pts2, pts1) # 生成透视矩阵self.board_gray = cv2.warpPerspective(self.im_gray, m, ( 660, 660)) # 执行透视变换self.board_bgr = cv2.warpPerspective(self.im_bgr, m, ( 660, 660)) # 执行透视变换

def_location_grid(self): “””定位棋盘格子”””

ifself.board_gray isNone: return

circles = cv2.HoughCircles(self.board_gray, cv2.HOUGH_GRADIENT, 1, 20, param1= 90, param2= 16, minRadius= 10, maxRadius= 20) # 圆检测xs, ys = circles[ 0,:, 0], circles[ 0,:, 1] # 所有棋子的x坐标和y坐标xs.sortys.sort

k = 1whilexs[k]-xs[:k].mean < 15: k += 1x_min = int(round(xs[:k].mean))

k = 1whileys[k]-ys[:k].mean < 15: k += 1

y_min = int(round(ys[:k].mean))

k = -1whilexs[k:].mean – xs[k -1] < 15: k -= 1x_max = int(round(xs[k:].mean))

k = -1whileys[k:].mean – ys[k -1] < 15: k -= 1y_max = int(round(ys[k:].mean))

ifabs( 600-(x_max-x_min)) < abs( 600-(y_max-y_min)): v_min, v_max = x_min, x_maxelse: v_min, v_max = y_min, y_max

pts1 = np.float32([[ 22, 22], [ 22, 598], [ 598, 22], [ 598, 598]]) # 棋盘四个角点的最终位置pts2 = np.float32([(v_min, v_min), (v_min, v_max), (v_max, v_min), (v_max, v_max)])m = cv2.getPerspectiveTransform(pts2, pts1)self.board_gray = cv2.warpPerspective(self.board_gray, m, ( 620, 620)) self.board_bgr = cv2.warpPerspective(self.board_bgr, m, ( 620, 620))

def_identify_chessman(self): “””识别棋子”””

ifself.board_gray isNone: return

mesh = np.linspace( 22, 598, 19, dtype=np.int) rows, cols = np.meshgrid(mesh, mesh)

circles = cv2.HoughCircles(self.board_gray, cv2.HOUGH_GRADIENT, 1, 20, param1= 40, param2= 10, minRadius= 12, maxRadius= 18) circles = np.uint32(np.around(circles[ 0]))

self.phase = np.zeros_like(rows, dtype=np.uint8)im_hsv = cv2.cvtColor(self.board_bgr, cv2.COLOR_BGR2HSV_FULL)

forcircle incircles: row = int(round((circle[ 1] -22)/ 32)) col = int(round((circle[ 0] -22)/ 32))

hsv_ = im_hsv[cols[row,col] -5:cols[row,col]+ 5, rows[row,col] -5:rows[row,col]+ 5] s = np.mean(hsv_[:,:, 1]) v = np.mean(hsv_[:,:, 2])

if0< v < 115: self.phase[row,col] = 1# 黑棋elif0< s < 50and114< v < 256: self.phase[row,col] = 2# 白棋

def_stats(self): “””统计黑白双方棋子和围空”””

self.result = stats(self.phase)

defshow_image(self, name= ‘gray’, win= “GoPhase”) : “””显示图像”””

ifname == ‘bgr’: im = self.board_bgrelifname == ‘gray’: im = self.board_grayelse: im = self.im_bgr

ifim isNone: print( ‘识别失败,无图像可供显示’) else: cv2.imshow(win, im)cv2.waitKey( 0) cv2.destroyAllWindows

defshow_phase(self): “””显示局面”””

ifself.phase isNone: print( ‘识别失败,无围棋局面可供显示’) else: show_phase(self.phase)

defshow_result(self): “””显示结果”””

ifself.result isNone: print( ‘识别失败,无对弈结果可供显示’) else: black, white, common = self.resultB = black+common/ 2-self.offset W = white+common/ 2+self.offset result = ‘黑胜’ifB > W else‘白胜’

print( ‘黑方:%0.1f,白方:%0.1f,%s’%(B, W, result))

if__name__ == ‘__main__’: go = GoPhase( ‘res/pic_0.jpg’) go.show_image( ‘origin’) go.show_image( ‘gray’) go.show_phasego.show_result

Linux 能否拿下苹果 M1 阵地?

Firefox 终于对退格键“下手”了!

25 款软件上榜,2020“最佳开源奖” 出炉!

量 子计算还没搞懂,光子计算又要来统治世界?

☞ 程序员为教师妻子开发专属应用;2020 最佳开源项目出炉;中国构建全星地量子通信网|开发者周刊

☞ “干掉”程序员饭碗后,OpenAI 又对艺术家下手了!

开考!狮子,老虎,企鹅,技术圈的这些飞禽走兽你认识多少?

点分享

点收藏

点点赞

点在看

免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如该页面侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。