1. 前言
文本识别早已经不是问题了,不过却不能直接应用于象棋棋子的识别,因为棋盘上的棋子是随机摆放上去的,不能保证棋子上的文字总是保持一个固定的角度。识别棋子的关键是找到具有“旋转不变性”的特征——无论棋子旋转多少度,其特征总是稳定的。
2. 图像的矩特征
矩是概率与统计中的一个概念,是随机变量的一种数字特征。如果把二维灰度图像视为有质量的平板,像素的灰度值 代表平板的密度,那么灰度图像就可以用二维灰度密度函数 来表示。通过计算图像的零阶矩、一阶矩、二阶矩、三阶矩,即可获得被称为不变矩的七个高度浓缩的图像特征。不变矩具有平移、灰度、尺度、旋转不变性。
以下代码给出了计算图像不变矩的函数,基于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)
#data.append(humoments(piece))
#target.append(idx)
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 train_test_split as tsplit
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import 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]]]
['象', '象', '象', '象', '象', '象', '象', '象', '象', '象']
['卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒', '卒']
['车', '车', '车', '车', '车', '车', '车', '士', '车', '车']
['马', '马', '马', '马', '炮', '马', '马', '马', '马', '马']
['相', '相', '相', '相', '相', '相', '相', '相', '相', '相']
['士', '士', '士', '士', '士', '士', '士', '士', '士', '士']
['将', '将', '将', '将', '将', '将', '将', '将', '将', '将']
['帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅', '帅']
['兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵', '兵']
['炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮', '炮']
转载:https://blog.csdn.net/xufive/article/details/106367107