用机器学习的分类算法识别象棋棋子——兼论旋转不变性

1. 前言

文本识别早已经不是问题了,不过却不能直接应用于象棋棋子的识别,因为棋盘上的棋子是随机摆放上去的,不能保证棋子上的文字总是保持一个固定的角度。识别棋子的关键是找到具有“旋转不变性”的特征——无论棋子旋转多少度,其特征总是稳定的。

2. 图像的矩特征

矩是概率与统计中的一个概念,是随机变量的一种数字特征。如果把二维灰度图像视为有质量的平板,像素的灰度值pp代表平板的密度,那么灰度图像就可以用二维灰度密度函数 p=f(x,y)p=f(x,y) 来表示。通过计算图像的零阶矩、一阶矩、二阶矩、三阶矩,即可获得被称为不变矩的七个高度浓缩的图像特征。不变矩具有平移、灰度、尺度、旋转不变性。

以下代码给出了计算图像不变矩的函数,基于OpenCV提供的两个函数实现,返回包含七个高度浓缩的图像特征的数组。因为图像不变矩的动态范围过大,默认返回的是不变矩的对数。

import cv2
import numpy as np

def humoments(img_gray, log=True):
    """返回图像7个不变矩"""
    
    hu = cv2.HuMoments(cv2.moments(img_gray))[:,0]
    if log:
        hu = np.log(np.abs(hu))
    
    return hu

以下面四个角度的棋子“车”为例,不管角度如何,它们的不变矩具有很强的相关性。
在这里插入图片描述

-6.979, -21.904, -29.739, -28.555, -57.703, -39.707, -60.654
-6.945, -21.576, -29.658, -28.057, -57.050, -38.891, -57.633
-6.925, -21.083, -28.879, -27.764, -56.091, -38.464, -58.305
-6.978, -21.966, -29.773, -28.337, -57.442, -39.753, -58.575

3. 采集样本

选择不同角度、光照条件,对棋子拍照,不同照片上的棋子尽可能保持相同的尺寸。使用圆检测技术找到棋子,裁切、高斯模糊、二值化,通过旋转抖动生成多个样本,将其不变特征矩保存到样本集,其分类保存到标签集。最后,样本数据集保存成名为cchessman.npz的本地文件。

# -*- coding: utf-8 -*-

import os
import cv2
from PIL import Image
import numpy as np

def humoments(img_gray, log=True):
    """返回图像7个不变矩"""
    
    hu = cv2.HuMoments(cv2.moments(img_gray))[:,0]
    if log:
        hu = np.log(np.abs(hu))
    
    return hu

def jitter(im_cv, theta):
    """随机旋转和抖动,返回新的图像"""
    
    #theta = np.random.random()*360
    dx, dy = np.random.randint(0, 5, 2) - 2
    
    im_pil = Image.fromarray(im_cv)
    im_pil = im_pil.rotate(theta, translate=(dx, dy))
    im = np.array(im_pil)
    im[im==0] = 240
    im = np.where(im>128, 240, 15).astype(np.uint8)
    
    return im

if __name__ == '__main__':
    # 定义一个96x96像素的掩码,用以滤除棋子周边的无效像素
    _mask = np.empty((96,96), dtype=np.bool)
    _mask.fill(False)
    for i in range(96):
        for j in range(96):
            if np.hypot((i-48), (j-48)) > 42:
                _mask[i,j] = True

    target = list() # 分类结果集
    data = list() # 样本数据集
    
    chessman = ['车','马','炮','兵','卒','士','相','象','将','帅']
    files = [
        ('res/ju.jpg', 0, 12),
        ('res/ma.jpg', 1, 12),
        ('res/pao.jpg', 2, 12),
        ('res/bing.jpg', 3, 15),
        ('res/zu.jpg', 4, 15),
        ('res/shi.jpg', 5, 12),
        ('res/rxiang.jpg', 6, 6),
        ('res/bxiang.jpg', 7, 6),
        ('res/jiang.jpg', 8, 3),
        ('res/shuai.jpg', 9, 3)
    ]
        
    for fn, idx, count in files:
        print('------------------------')
        print(fn)
        
        img = cv2.imread(fn)
        img_gray = cv2.imread(fn, cv2.IMREAD_GRAYSCALE)
        
        # 圆检测
        circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, 1, 200, param1=100, param2=50, minRadius=90, maxRadius=140)
        circles = np.int_(np.around(circles))
        print(circles)
        for i, j, r in circles[0]:
            cv2.circle(img, (i, j), r, (0, 255, 0), 2)
            cv2.circle(img, (i, j), 2, (0, 0, 255), 3)
            
            piece = cv2.resize(img_gray[j-r:j+r, i-r:i+r], (96,96))
            piece[_mask] = 240
            piece = cv2.GaussianBlur(piece, (5,5), 0)
            piece = np.where(piece>128, 240, 15).astype(np.uint8)
            cv2.imwrite('res/chessman/%d_%d_%d.jpg'%(idx, i, j), piece)
            
             for theta in range(360):
                data.append(humoments(jitter(piece, theta)))
                target.append(idx)
            
        cv2.imshow('image', img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    target = np.array(target)
    data = np.stack(data, axis=0)
    
    np.savez('cchessman.npz', target=target, data=data)

代码运行中,每处理一张图片,显式一次圆检测结果。如下图所示。
在这里插入图片描述

4. 模型训练

使用随机森林分类器,简单设置一下参数,就可以取得不错的训练效果。使用交叉验证的代码如下。

# -*- coding: utf-8 -*-

import numpy as np
from sklearn.model_selection import cross_val_score, KFold
from sklearn.ensemble import RandomForestClassifier

ds = np.load('cchessman.npz')
X = ds['data']
y = ds['target']

cv = KFold(n_splits=10, shuffle=True, random_state=0)
rfc = RandomForestClassifier()
rfc_scroe = cross_val_score(rfc, X, y, cv=cv)
print(rfc_scroe)
print(rfc_scroe.mean())

交叉验证结果显式,模型精度居然高达99.9%。

[1.         0.99971065 0.99971065 0.99971065 0.99971065 0.9994213
 0.9994213  0.99971065 1.         0.9994213 ]
0.9996817129629629

5. 模型应用

新拍一张全家福,验证一下模型的泛化能力。
在这里插入图片描述
总共10个棋子,每个棋子随机旋转10次,生成100个测试样本。测试结果表明,准确率99%。

(34560, 7)
[[[ 534  756  119]
  [ 468  412  128]
  [ 208  640  128]
  [1140  620  128]
  [ 800  524  119]
  [ 270  998  132]
  [ 716  184  122]
  [ 622 1106  123]
  [1046  284  125]
  [ 888  866  134]]]
['象', '象', '象', '象', '象', '象', '象', '象', '象', '象']
['卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒']
['车', '车', '车', '车', '车', '车', '车', '士', '车', '车']
['马', '马', '马', '马', '炮', '马', '马', '马', '马', '马']
['相', '相', '相', '相', '相', '相', '相', '相', '相', '相']
['士', '士', '士', '士', '士', '士', '士', '士', '士', '士']
['将', '将', '将', '将', '将', '将', '将', '将', '将', '将']
['帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅']
['兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵']
['炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮']
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章