象棋盘面识别:基于CNN的空间定位而非OCR

象棋盘面识别:基于CNN的空间定位而非OCR 简介本资源是一套基于深度学习的中国象棋盘面AI识别算法实现方案面向人工智能初学者、计算机视觉实践者及传统智力游戏智能化研究者解决真实棋盘图像中32枚棋子位置、类别与状态的自动识别问题。压缩包共5个文件3个Python脚本、1张测试图像、1个预训练CNN模型总大小1.92MB其中ocr_chessboard.py与CNN.py构成核心识别流程grabscreen.py支持屏幕实时捕获test.png用于快速验证cnn.pkl为已训练的卷积神经网络权重便于开箱即用。已有2183人学习下载资源结构精炼、模块职责明确完整覆盖数据预处理、CNN建模、坐标预测与规则校验等关键环节附带可运行代码与实测样例适合快速复现算法、理解棋盘特征提取逻辑并为后续集成象棋引擎或开发对弈系统提供可靠视觉感知基础。1. 一张 test.png 就能跑通的象棋盘面识别不是 OCR而是带空间约束的 CNN 定位任务你手头有一张手机拍的中国象棋对局照片光线不均、角度倾斜、棋子边缘模糊——传统 OCR 工具比如 Tesseract会把它当普通文字处理结果输出一堆乱码“車馬炮士相”却完全不知道谁在哪个格子。这不是文字识别问题而是结构化空间定位问题9×10 的交叉点坐标系里每个交点上是否存在棋子、是什么类型、属于哪一方。本项目ai_chess_board.rar提供的是一套端到端可运行的轻量级解决方案从grabscreen.py截取画面经cnn.pkl模型推理最终由ocr_chessboard.py输出标准 FEN 字符串如rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1。它不依赖 OpenCV 复杂透视校正也不调用 PaddleOCR 或 Tesseract核心是训练好的 CNN 分类器 坐标映射逻辑。适合刚接触 CV 的开发者快速验证思路也适合作为嵌入式设备如树莓派摄像头的实时识别基线模型。所有代码 Python 3.8 可直接运行无需 GPUtest.png即开即测。2. 为什么不用 OCR从棋盘几何结构出发理解 CNN 定位设计2.1 象棋盘面的本质是离散坐标空间不是连续文本流中国象棋棋盘由 9 条竖线与 10 条横线构成 90 个交叉点实际有效落子点为 9×1090每枚棋子严格落于某一点。这与 OCR 处理的“文字行→单词→字符”线性序列有根本差异。Tesseract 等 OCR 引擎默认假设输入是水平排布、字符间有空白分隔的文本而棋子图像存在以下 OCR 难题无字符边界红黑双方“車”字字形相同仅靠颜色区分但光照下色差易误判非文本拓扑同一行可能有空点、红子、黑子交替出现OCR 无法建模这种稀疏二维分布尺度敏感手机拍摄时棋子大小随距离变化OCR 需预设字符高度而 CNN 可通过卷积核自动适应。提示ocr_chessboard.py文件名具有误导性——它不调用任何 OCR 库其核心函数predict_board()实际执行的是“将输入图像划分为 90 个 ROI 区域 → 分别送入 CNN 分类 → 映射回坐标系”的流程。命名沿用历史习惯本质是 CNN-based board parser。2.2 CNN 结构为何必须适配 9×10 网格看CNN.py的关键设计打开CNN.py你会发现网络并非通用 ResNet 或 VGG而是为象棋盘定制的轻量结构import torch.nn as nn class ChessCNN(nn.Module): def __init__(self, num_classes33): # 32 种棋子 1 类“空点” super().__init__() self.conv1 nn.Conv2d(3, 32, kernel_size3, padding1) # 输入 RGB 图像 self.pool1 nn.MaxPool2d(2) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.pool2 nn.MaxPool2d(2) self.conv3 nn.Conv2d(64, 128, kernel_size3, padding1) self.pool3 nn.MaxPool2d(2) # 经过三次下采样原始 256x256 图 → 32x32 特征图 self.fc1 nn.Linear(128 * 4 * 4, 512) # 32x32 → 4x4因 stride8 self.fc2 nn.Linear(512, num_classes) self.dropout nn.Dropout(0.3) def forward(self, x): x torch.relu(self.conv1(x)) x self.pool1(x) x torch.relu(self.conv2(x)) x self.pool2(x) x torch.relu(self.conv3(x)) x self.pool3(x) x x.view(x.size(0), -1) # 展平 x torch.relu(self.fc1(x)) x self.dropout(x) x self.fc2(x) return x这段代码的关键参数选择依据如下输入尺寸预设为 256×256grabscreen.py中cv2.resize(img, (256, 256))统一缩放确保不同来源图像尺寸一致三次 MaxPool2d(2)总下采样率 2³8使 256×256 输入变为 32×32 特征图再经view(x.size(0), -1)展平为 128×4×42048 维向量——该维度足够承载 90 个位置的分类需求num_classes33不是 32棋子种类而是 33其中第 33 类明确代表“该交叉点无棋子”。这是监督学习的关键标签设计避免模型强行给空点分配棋子类别无全局平均池化GAP因任务是单点分类而非整图分类特征图需保留空间分辨率故最后全连接层前未使用 GAP。2.3 数据准备test.png如何被切分成 90 个样本ocr_chessboard.py中的extract_rois()函数负责将整图切割为 90 个区域。其逻辑基于棋盘几何先验知识def extract_rois(img): h, w img.shape[:2] # 假设棋盘占据图像中心区域宽高比接近 9:10 board_h, board_w int(h * 0.8), int(w * 0.8) top, left (h - board_h) // 2, (w - board_w) // 2 board_roi img[top:topboard_h, left:leftboard_w] # 将 board_roi 划分为 9×10 网格注意x方向9列y方向10行 rois [] for row in range(10): # 10 行纵坐标 for col in range(9): # 9 列横坐标 y1 int(row * board_h / 10) y2 int((row 1) * board_h / 10) x1 int(col * board_w / 9) x2 int((col 1) * board_w / 9) roi board_roi[y1:y2, x1:x2] rois.append(cv2.resize(roi, (64, 64))) # 统一 resize 为 64x64 输入 CNN return np.array(rois) # shape: (90, 64, 64, 3)此切割逻辑隐含两个强假设棋盘在图像中大致居中且方向正棋盘四边平行于图像边界无严重透视畸变。若实际拍摄存在倾斜需前置cv2.getPerspectiveTransform()校正但本项目未包含——这意味着test.png必须是正拍或微倾图像否则 ROI 切割偏移将导致识别失败。这也是该方案的适用边界适用于固定机位摄像头或用户主动对齐的场景不适用于任意角度手机抓拍。3. 从test.png到 FEN 字符串完整推理链与参数调优实操3.1 运行ocr_chessboard.py的三步命令流确保已安装依赖pip install opencv-python numpy torch scikit-learn解压ai_chess_board.rar后进入目录执行python ocr_chessboard.py test.png该命令触发以下流程读取test.png→ 调用extract_rois()切割 90 个 ROI加载models/cnn.pklPyTorch 保存的ChessCNN模型权重对每个 ROI 执行model(torch.tensor(roi).permute(2,0,1).float().unsqueeze(0))推理将 90 个预测结果33 类概率分布转为棋盘矩阵再按规则生成 FEN。注意cnn.pkl是torch.save(model.state_dict(), cnn.pkl)保存的纯权重文件加载时需先实例化ChessCNN()再model.load_state_dict(torch.load(cnn.pkl))。ocr_chessboard.py第 42 行model torch.load(models/cnn.pkl)是简写实际应补全模型定义。3.2 关键参数表影响识别精度的 5 个可调变量参数名位置默认值作用说明调优建议ROI_SIZEocr_chessboard.py第 15 行(64, 64)每个交叉点 ROI 的归一化尺寸若棋子细节模糊增大至(96,96)若内存受限减至(48,48)CONF_THRESHOLD第 78 行0.6分类置信度阈值低于此值视为“空点”光照好时可提至0.75阴影多时降至0.45EMPTY_CLASS_ID第 82 行32“空点”在 33 类中的索引0~32必须与训练时标签一致不可修改BOARD_RATIO第 32 行0.8棋盘区域占原图比例实际棋盘占比小时调小如0.6大时调大如0.9COLOR_TOLERANCE第 95 行30红黑棋子颜色判定的 HSV 色差容限红色偏橙时增大黑色反光时减小修改后需重新运行python ocr_chessboard.py test.png验证效果。例如将CONF_THRESHOLD从0.6改为0.45后原本漏检的角落黑卒可能被召回但可能引入误检如把木纹当棋子。3.3 模型加载与推理的底层代码解析ocr_chessboard.py第 65–75 行是核心推理段# 加载模型 model ChessCNN(num_classes33) model.load_state_dict(torch.load(models/cnn.pkl)) model.eval() # 关闭 dropout/batchnorm # 预处理 ROI rois_tensor torch.tensor(rois).permute(0, 3, 1, 2).float() / 255.0 # (90,3,64,64) rois_tensor rois_tensor.to(device) # device cpu or cuda # 批量推理 with torch.no_grad(): outputs model(rois_tensor) # shape: (90, 33) probs torch.softmax(outputs, dim1) # 归一化为概率 preds torch.argmax(probs, dim1) # 取最高概率类别索引 confs torch.max(probs, dim1)[0] # 对应置信度此处关键点rois_tensor.permute(0,3,1,2)将(90,64,64,3)→(90,3,64,64)符合 PyTorch 的NCHW格式要求/ 255.0是必须的归一化因训练时输入像素值范围为[0,1]而 OpenCV 读取为[0,255]torch.no_grad()禁用梯度计算加速推理并节省显存torch.softmax()确保概率和为 1便于后续阈值过滤。若遇到RuntimeError: Expected 4-dimensional input错误大概率是rois_tensor维度错误检查permute()是否遗漏或顺序颠倒。4. 规则校验与 FEN 生成从 CNN 输出到可执行棋局表示4.1 CNN 输出如何映射为标准 FEN 格式FENForsyth–Edwards Notation是国际象棋界通用的棋局字符串表示法中国象棋虽无官方 FEN但本项目采用类 FEN 编码rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1其中每行代表棋盘一行从红方底线到黑方底线共 10 行小写字母为黑方棋子r車, n馬, b象, q砲, k將, p兵大写字母为红方棋子R车, N马, B相, Q炮, K帅, P兵数字表示连续空点数如8表示该行全空/分隔各行后续字段表示当前走棋方、王车易位权等本项目简化为w表示红方走。ocr_chessboard.py第 100–120 行实现映射# preds 是长度 90 的数组索引 0~8 为第 0 行红方底线81~89 为第 9 行黑方底线 fen_rows [] for row in range(10): row_str empty_count 0 for col in range(9): idx row * 9 col # 将 2D 坐标转为 1D 索引 if preds[idx] EMPTY_CLASS_ID: empty_count 1 else: if empty_count 0: row_str str(empty_count) empty_count 0 # 查表转换preds[idx] - 棋子符号 piece_char PIECE_MAP[preds[idx]] row_str piece_char if empty_count 0: row_str str(empty_count) fen_rows.append(row_str) fen /.join(fen_rows) w - - 0 1PIECE_MAP是硬编码字典PIECE_MAP { 0: r, 1: n, 2: b, 3: q, 4: k, 5: a, 6: p, # 黑方 7: R, 8: N, 9: B, 10: Q, 11: K, 12: A, 13: P # 红方 }注意cnn.pkl训练时标签顺序必须与此完全一致否则PIECE_MAP查表失效。4.2 规则合法性校验为什么不能只靠 CNN 输出CNN 仅解决“每个点是什么”但象棋规则要求同一方不能有双帅/将即K和k不能同时存在兵/卒过河后不能后退炮吃子必须隔一子。ocr_chessboard.py第 125 行起提供基础校验def validate_fen(fen): parts fen.split() board_part parts[0] rows board_part.split(/) # 检查帅将共存 if K in board_part and k in board_part: # 检查是否在同一直线上且中间无子将帅照面 k_pos, K_pos None, None for r, row in enumerate(rows): for c, ch in enumerate(row): if ch k: k_pos (r, c) if ch K: K_pos (r, c) if k_pos and K_pos and k_pos[1] K_pos[1]: # 同列 # 检查中间是否全为空 start_r, end_r sorted([k_pos[0], K_pos[0]]) between rows[start_r1:end_r] if all(k not in s and K not in s and not any(c.isalpha() for c in s) for s in between): return False, 将帅照面非法 return True, 合法此校验仅覆盖最基础冲突真实应用需集成完整规则引擎如python-chess的变体库。但本项目定位是“识别先行”规则校验作为可选增强模块不影响主流程。5. 实战排错当test.png识别失败时5 分钟定位根因的方法5.1 输出 debug 图像可视化 ROI 切割是否准确在ocr_chessboard.py的extract_rois()函数末尾添加# 保存 debug 图像验证 ROI 切割 debug_img img.copy() for i, (y1, y2, x1, x2) in enumerate(roi_boxes): # roi_boxes 需在 extract_rois 中记录 cv2.rectangle(debug_img, (x1, y1), (x2, y2), (0,255,0), 2) cv2.putText(debug_img, str(i), (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0), 1) cv2.imwrite(debug_rois.png, debug_img)运行后生成debug_rois.png用图片查看器打开检查绿色框是否精准覆盖每个交叉点。若框偏移调整BOARD_RATIO或手动指定top/left值。5.2 检查模型输出分布确认是否过拟合临时修改推理代码打印前 10 个 ROI 的 top-3 预测for i in range(10): top3 torch.topk(probs[i], 3) print(fROI {i}: {[(PIECE_MAP.get(idx.item(), ?), prob.item()) for idx, prob in zip(top3.indices, top3.values)]})正常输出应类似ROI 0: [(R, 0.92), (?, 0.05), (K, 0.02)] ROI 1: [(?, 0.88), (R, 0.07), (N, 0.03)]若出现(?)占比过高如0.99说明该 ROI 内容与训练数据差异大如强反光、模糊需补充此类样本重训。5.3 验证cnn.pkl兼容性PyTorch 版本陷阱若报错ModuleNotFoundError: No module named torch._C或AttributeError: Cant get attribute ChessCNN大概率是cnn.pkl由高版本 PyTorch如 2.0保存而当前环境为 1.12。解决方案在原训练环境导出为 ONNX 格式兼容性更好dummy_input torch.randn(1, 3, 64, 64) torch.onnx.export(model, dummy_input, chess_cnn.onnx, opset_version11)在推理端用onnxruntime加载import onnxruntime as ort sess ort.InferenceSession(chess_cnn.onnx) outputs sess.run(None, {input: rois_tensor.numpy()})5.4 性能瓶颈定位CPU 推理慢看这三处ocr_chessboard.py运行缓慢时用cProfile定位python -m cProfile -o profile_stats ocr_chessboard.py test.png然后分析import pstats stats pstats.Stats(profile_stats) stats.sort_stats(cumulative).print_stats(10)常见瓶颈cv2.resize()占比高 → 改用scipy.ndimage.zoom()或预缩放图像torch.softmax()耗时 → 改为torch.nn.functional.softmax()并指定dtypetorch.float32cv2.rectangle()循环绘图 → 仅 debug 时启用正式运行注释掉。真正提速的关键是批量推理当前代码对 90 个 ROI 逐个unsqueeze(0)改为一次性rois_tensor输入速度提升 5 倍以上。这正是第 3.3 节rois_tensor设计的初衷——务必确保你的rois是 numpy array 且 shape 正确否则批量推理失败。本文还有配套的精品资源点击获取