1. 概述
本文将介绍如何使用Python作为工具,定位棋子的位置,并且识别棋子的颜色。首先我们需要知道什么是OpenCV。OpenCV是一个开源计算机视觉库,可以处理图像和视频,它广泛应用于机器视觉、动作识别、对象识别、人脸识别和车牌识别等领域。我们将主要介绍在计算机视觉中解决问题的一些方法和技术。本文将分3部分介绍本次任务的实现:
实现棋子定位和截图。
使用KMeans算法对截图的颜色进行聚类,并提取出主色调。
通过分类模型识别棋子颜色。
2. 实现棋子定位和截图
2.1 准备工作
在进行棋子识别之前,我们需要先安装OpenCV库和Python环境,并确保电脑上已经连接好了手机。接下来,在Python中导入所需的库。
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import time
2.2 连接手机
在代码中,我们使用Android Debug Bridge(ADB)连接手机。ADB是一个面向Android设备的命令行工具,可以通过ADB在计算机和手机之间进行相互传输操作。在终端运行以下代码:
os.system('adb connect 127.0.0.1:62001')
os.system('adb shell screencap -p /sdcard/chess.png')
os.system('adb pull /sdcard/chess.png .')
这将会把手机上当前屏幕截图保存到电脑本地。需要注意的一点是,如果不经过任何处理直接读取截图,可能会将棋盘边框误认为是棋盘上的线条,从而造成识别错误。
2.3 定位棋子
为了避免扫描整张棋盘图像,我们只扫描棋盘网格中心的点以定位棋子。
def chess_position(img):
"""
找到棋子位置.
"""
# 获取棋盘网格大小
grid_size = img.shape[0] / 10
# 网格中心的点的坐标
start_x, end_x = int(grid_size * 2), int(grid_size * 8)
start_y, end_y = int(grid_size * 2), int(grid_size * 8)
x_range = np.arange(start_x, end_x, grid_size)
y_range = np.arange(start_y, end_y, grid_size)
xv, yv = np.meshgrid(x_range, y_range)
position_list = np.column_stack((xv.ravel(), yv.ravel()))
position_list = position_list.astype(int)
chess_position_list = []
for position in position_list:
# 获得感兴趣区域
roi_location = [position[0]-30, position[1]-30, position[0]+30, position[1]+30]
roi = img[roi_location[1]:roi_location[3], roi_location[0]:roi_location[2]]
# 将感兴趣区域转为HSV颜色空间
hsv_img = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
# 根据棋子颜色特征,用cv2.inRange函数找到棋子区域
lower_color1 = np.array([0, 43, 46])
upper_color1 = np.array([10, 255, 255])
lower_color2 = np.array([156, 43, 46])
upper_color2 = np.array([180, 255, 255])
mask1 = cv2.inRange(hsv_img, lower_color1, upper_color1)
mask2 = cv2.inRange(hsv_img, lower_color2, upper_color2)
mask = cv2.bitwise_or(mask1, mask2)
if mask.sum() > 50:
chess_position_list.append(position)
return chess_position_list
其中,我们使用掩模mask
查找感兴趣区域中的棋子。构造掩模时,使用cv2.inRange函数,找到HSV空间中颜色在特定范围内的像素点,并且将这些像素点赋值为1,其他像素赋值为0。
2.4 切割图片
使用OpenCV中的cv2.split函数,分离出BGR三个通道信息。bwchess_position函数用于进行图像切割,函数中传入的变量img是numpy数组,shape为(480, 270, 3),表示一张高度为480,宽度为270,通道数为3的彩色图片。
def bwchess_position(img, position_list):
"""
切割感兴趣区域
"""
grid_size = img.shape[0] / 10
x_offset = grid_size / 2
y_offset = grid_size / 2
piece_height = grid_size / 1.4
piece_width = grid_size / 1.4
pieces = np.empty((0, int(piece_height), int(piece_width), 3))
for position in position_list:
roi_location = [position[0]-x_offset, position[1]-y_offset, position[0]+x_offset, position[1]+y_offset]
roi = img[roi_location[1]:roi_location[3], roi_location[0]:roi_location[2], :]
pieces = np.append(pieces, [roi], axis=0)
return pieces
在得到所有棋子的位置后,使用bwchess_position函数将所有棋子的感兴趣区域切割出来。
3. KMeans算法对截图的颜色进行聚类,并提取出主色调
3.1 KMeans算法介绍
KMeans算法是一种无监督学习算法,用于将数据分成K个类别。算法的目标是将所有点分为K个集群,使得集群内的点越相似,集群之间的点越不相似。
3.2 对棋子图片进行颜色扫描和聚类
使用Ipython显示棋子长成这样:
pieces = bwchess_position(img, chess_position(img))
# 在IPython Notebook中显示棋子图片
%matplotlib inline
f,axarr=plt.subplots(2,5)
for k in range(10):
i=k//5
j=k%5
axarr[i,j].imshow(pieces[k])
结果长成这样:
![piece.png](https://img-blog.csdn.net/20180511155731691?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5eC05OTk5OTk5OTk5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/70)
使用numpy中的reshape函数将所有的棋子统一改变为(n_samples, n_features)
的形状。即每张图片的颜色分量信息与坐标位置无关,只统计各颜色通道出现的频率,用[R,G,B]
的方式表示。
def img2vector(img):
return cv2.resize(img, (16, 16)).flatten()
vector_pieces = np.array([img2vector(piece) for piece in pieces])
print(vector_pieces.shape)
接下来使用KMeans算法对棋子图片进行颜色扫描和聚类。
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=4, random_state=42)
kmeans.fit(vector_pieces)
centers = kmeans.cluster_centers_
labels = kmeans.labels_
3.3 查看聚类结果
让我们首先看一看棋子的中心颜色。
plt.figure(figsize=[8,4])
for i, center in enumerate(centers):
plt.subplot(1, 4, i+1)
plt.imshow(np.array([[center/255]]))
plt.axis('off')
聚类中心颜色如下所示。
![result.png](https://img-blog.csdn.net/20180511160230103?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p5eC05OTk5OTk5OTk5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/70)
3.4 提取棋子主色调
为了提取出棋子主要颜色,我们需要将图像分成几个部分,然后取每部分最常出现的颜色作为该部分的代表颜色。最后通过张量积将不同部分的代表颜色组合成整张棋盘的颜色矩阵。
def board_colors(centers, labels):
"""
提取棋子颜色.
"""
background_label = np.bincount(labels).argmax()
background = centers[background_label]
colors = np.delete(centers, [background_label], axis=0)
labels[labels == background_label] = 4
reshaped_labels = labels.reshape((10, 10))
color_map = np.zeros([8, 8])
for x in range(8):
for y in range(8):
cell = reshaped_labels[x * 2:(x + 1) * 2, y * 2:(y + 1) * 2]
color_count = np.bincount(cell.ravel(), minlength=4)
main_color = colors[color_count.argmax()]
color_map[x, y] = main_color
return color_map
其中变量background_label
代表了棋盘背景,将背景颜色设置成最常出现的颜色,并从颜色列表中删除。
4. 通过分类模型识别棋子颜色
4.1 准备工作
使用OpenCV中的cv2.split函数,分离出BGR三个通道信息。bwchess_position函数用于进行图像切割,函数中传入的变量img是numpy数组,shape为(480, 270, 3),表示一张高度为480,宽度为270,通道数为3的彩色图片。
4.2 特征提取
在特征提取阶段,筛选了三种特征:hist-hue表示棋子颜色分布信息,hist-saturation表示饱和度分布信息和麻点密度better-representation。
思路从分子角度切入,往往可以更加简单有效地解决问题。
4.3 构建分类模型
过去我们可能需要手动分裂出红色和黑色的棋子,将一个问题分裂成多个小问题,然后分开解决。但现在我们可以构建一个神经网络模型,来自动作出这个决策,只需训练一个神经网络来做二元分类任务便可。因为我们只需要区分红色的棋子和黑色的棋子,所以这个任务可以看做是一个二元分类器。
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.optimizers import SGD
X_train = np.array(features)
Y_train = np_utils.to_categorical(np.array(labels))
model = Sequential()
model.add(Dense(64, input_dim=X_train.shape[1]))
model.add(Activation('relu'))
model.add(Dense(32))
model.add(Activation('relu'))
model.add(Dense(2))
model.add(Activation('softmax'))
sgd = SGD(lr=0.1)
model.compile(loss='categorical_crossentropy',
optimizer=sgd, metrics=['accuracy'])
history = model.fit(X_train, Y_train, epochs=300, batch_size=32, verbose=0)
这里所使用的深度神经网络结构包括输入层、两个使用ReLU激活的全联通层和一个输出层,激励函数使用softmax。模型的损失函数使用交叉熵(categorical_crossentropy),用随机梯度下降算法(SGD)优化目标函数。分别使用了hist-hue、hist-saturation和better-representation三个特征训练,训练结果显示出现了较好的识别效果。
5. 总结
本文介绍了如何使用Python编写代码以解决棋子识别的问题。我们展示了如何使用OpenCV和KMeans算法来分析棋盘,并通过分类模型识别红色和黑色的棋子。通过对训练模型的优化并针对棋子点的提取,识别效果显著提升。