ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于OpenCV的象棋识别与棋谱定位:传统图像处理实战

2026/8/31 20:56:57 拓冰建站 浏览量
基于OpenCV的象棋识别与棋谱定位:传统图像处理实战 简介本资源是一套基于OpenCV的象棋图像识别与棋谱定位完整实现方案面向人工智能课程设计、本科毕设及CV方向初学者解决传统棋类图像中棋子分类识别与坐标精确定位两大核心问题。压缩包共394个文件含385张标注清晰的棋子PNG样本如b_ma_9.png、b_xiang_2.png等、4张实景棋盘JPG测试图、3个核心Python脚本含详细中文注释、1个PyInstaller打包配置spec文件及1份README说明文档整体仅2.96MB轻量易部署。已有1163人学习下载适合作为图像预处理、模板匹配、轮廓分析与坐标映射等OpenCV典型任务的实战参考。读者可直接运行源码完成从棋盘图像输入、棋子检测、类别判别到棋谱坐标输出的全流程代码结构模块化、关键步骤均有注释便于理解算法逻辑并在此基础上拓展深度学习识别模块。 前几天整理硬盘翻出一个课程设计压缩包标题是“基于opencv实现象棋识别及棋谱定位python源码数据集”这是我人工智能课程设计的一部分。当时用OpenCV加Python把一整盘象棋从图像里识别出来、再把走棋步骤整理成棋谱前前后后折腾了一个多星期。跑通的那一刻确实挺有成就感因为这个项目几乎是传统图像处理技术栈的“全家桶”图像预处理、透视变换、颜色分割、轮廓提取、模板匹配、坐标映射一条链路全部走了一遍。这篇我打算把里面的核心思路、关键代码、避坑经验全部摊开讲一遍想用OpenCV做图像识别课程设计的朋友可以直接参考已经写完作业想优化的人也能从里面找到一些思路。1. 项目整体设计与思路拆解1.1 为什么用OpenCV而不是直接上深度学习我自己当初在立项的时候也纠结过现在很多人工智能课程设计动不动就是目标检测、YOLO、卷积神经网络用OpenCV做模板匹配是不是有点“传统”但实际做下来我反而觉得这个项目选传统图像处理路线是更合理的。先算一笔账。如果用YOLO这类目标检测算法来做棋子识别最麻烦的问题就是数据集。象棋棋子有14类红方帅仕相车马炮兵、黑方将士象车马炮卒每类如果要训练出能用的模型少说上百张标注图而且拍摄角度、光照、棋盘样式一变模型往往就废了。我当时搜遍了网络也没有找到现成的象棋目标检测数据集自己标数据的话几百张图加上调参课程设计的周期根本不够用。而OpenCV的模板匹配方案只需要给每类棋子拍几张清晰的照片就能跑工程上完全可行。再一个原因是原理展示度。课程设计答辩的时候老师更关心你对算法原理的理解。传统图像处理每一步都有明确的数学含义边缘检测是梯度计算透视变换是矩阵映射颜色分割是HSV空间阈值判断模板匹配是归一化相关计算。这些在答辩时可以讲得很清楚也能现场调试。如果你只是调一个训练好的模型老师一问“损失函数怎么设计的”“特征提取在哪里”场面就很尴尬了。当然也要说清楚这个方案的边界。如果使用场景变成了自然场景下的象棋识别比如棋盘放在复杂背景里、摄像头角度随意、棋子互相遮挡那传统方案确实会崩这个时候就该上深度学习目标检测了。但课程设计的场景通常是可控的棋盘摆正、光线稳定OpenCV方案完全够用。1.2 整体流程与模块划分拿到项目之后我没有立刻写代码而是先把整个流程拆了一遍。这个项目本质上可以分成五个模块每个模块的输入输出都非常清晰。模块输入输出核心方法图像预处理原始图像透视校正后的棋盘正视图高斯模糊、Canny边缘检测、透视变换棋盘网格定位棋盘正视图90个交叉点的像素坐标投影法检测线条、坐标网格生成棋子检测棋盘正视图每个棋子的位置和颜色HSV颜色分割、轮廓检测棋子识别棋子ROI图像棋子类别车、马、炮、兵等模板匹配棋谱生成棋子类别坐标中文棋谱文本坐标映射、棋盘状态矩阵维护这个模块划分在后面帮我省了很多事。每个模块可以单独测试出问题的时候能快速定位是哪个环节出了问题而不是在几百行代码里瞎找。比如透视变换如果结果不对那就只查角点排序和矩阵计算不用管后面的识别逻辑。1.3 数据集是怎么组织和处理的标题里带了“数据集”其实这个项目的数据集并不是用于深度学习的训练集而是两部分一部分是模板库另一部分是验证用的测试图像。模板库我按类别组织成文件夹结构是这样的template/ ├── red_shuai/ ├── red_shi/ ├── red_xiang/ ├── red_che/ ├── red_ma/ ├── red_pao/ ├── red_bing/ ├── black_jiang/ ├── black_shi/ ├── black_xiang/ ├── black_che/ ├── black_ma/ ├── black_pao/ └── black_zu/每个文件夹里放3到5张该棋子在不同光照下的照片用OpenCV读取后转为灰度图统一resize到50×50像素作为模板数组保存成一个Python字典。这里有个关键点模板必须和你要识别的棋子是同一副至少是同一种款式。不同款式的象棋字体差异很大用楷体棋盘去匹配宋体棋子分数会非常难看。测试图像则是用手机在几个不同角度拍的整盘棋包含盘前、盘后、侧面三个角度方便验证透视校正的鲁棒性。我建议做类似项目时测试集至少要准备10张以上不同场景的照片不然代码在某一两张图上跑通了你自己都不知道是真正有效还是过拟合了一张图。2. 核心模块实现与实操要点2.1 图像预处理透视校正的原理与实现摄像头不可能是完全垂直正对着棋盘往下拍的照片里棋盘一定是变形的梯形或平行四边形。如果不做透视校正后续网格划分和坐标映射全部都会出错所以这个环节是整个项目的地基。校正的核心是找到棋盘最外边框的四个角点然后通过cv2.getPerspectiveTransform计算变换矩阵用cv2.warpPerspective把图像映射成矩形。找角点我用的方法是先Canny边缘检测再对二值图做膨胀让边缘连续然后找最大轮廓最后用approxPolyDP把轮廓逼近成一个四边形。import cv2 import numpy as np def find_board_corners(img_gray): # Canny边缘检测 edges cv2.Canny(img_gray, 50, 150) # 膨胀让棋盘外框的线条连接更完整 kernel np.ones((5, 5), np.uint8) edges cv2.dilate(edges, kernel, iterations2) # 找最大轮廓 contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None board_contour max(contours, keycv2.contourArea) # 多边形逼近得到4个顶点 epsilon 0.02 * cv2.arcLength(board_contour, True) approx cv2.approxPolyDP(board_contour, epsilon, True) if len(approx) ! 4: return None return approx.reshape(4, 2).astype(np.float32)拿到四个角点之后一个非常容易踩坑的地方是角点的顺序不固定。approxPolyDP返回的点可能是顺时针、逆时针也可能从任意一个点开始。如果不排序就直接传给getPerspectiveTransform出来的校正图很有可能是翻转或者错位的。我踩过这个坑之后写了一个排序函数先计算四个点的中心坐标然后把每个点相对中心的极角求出来按角度排序这样就能保证顺序固定为左上、右上、右下、左下。def order_corners(pts): # 计算中心点 center pts.mean(axis0) # 计算极角然后排序 angles np.arctan2(pts[:, 1] - center[1], pts[:, 0] - center[0]) ordered pts[np.argsort(angles)] # 调整顺序为 左上、右上、右下、左下 return np.array([ordered[0], ordered[1], ordered[2], ordered[3]], dtypenp.float32)透视变换本身是一行代码的事但变换后的目标尺寸需要自己定。我设成了640×640这个尺寸对大部分模板匹配来说已经足够清晰而且计算速度也快。注意目标尺寸不要太大超过1000×1000后后面每个像素的处理时间都会成倍增加实时性会变差。def perspective_correct(img, corners, output_size(640, 640)): dst_pts np.array([[0, 0], [output_size[0], 0], [output_size[0], output_size[1]], [0, output_size[1]]], dtypenp.float32) M cv2.getPerspectiveTransform(corners, dst_pts) warped cv2.warpPerspective(img, M, output_size) return warped2.2 棋盘网格定位投影法比霍夫变换更稳棋盘校正成正方形之后下一步要找到棋盘的10条竖线和9条横线也就是要得到90个交叉点的坐标。一开始我想当然用了霍夫变换检测直线结果效果很差——棋盘线被棋子挡住之后会断成好几段霍夫变换对这种不连续的场景非常敏感经常把同一个逻辑上的线条检测成很多线段还要做聚类合并非常麻烦。后来我换成了投影法效果一下子稳了很多。投影法的思路很朴素对二值图沿着水平方向累加每一行的白色像素数量有横线的行会得到一个明显的峰值沿着垂直方向累加每一列的白色像素数量有竖线的列同理。这样就把二维的直线检测问题降成了一维的峰值查找问题运行速度快而且对断线不敏感。def find_projection_peaks(binary_img, axis0): # axis0 表示垂直投影检测竖线方向axis1 表示水平投影检测横线方向 sums np.sum(binary_img, axisaxis) peaks [] threshold np.max(sums) * 0.3 for i in range(1, len(sums) - 1): if sums[i] sums[i-1] and sums[i] sums[i1] and sums[i] threshold: peaks.append(i) return peaks为了减少噪声干扰在投影之前我会先把图像转成灰度图做一次高斯模糊然后用Otsu阈值法二值化。这里的一个关键经验是二值化之后不要急着投影先做一次形态学操作比如开运算把棋盘线条上的毛刺去掉这样找出来的峰值会更干净。找到横线和竖线的位置之后用笛卡尔积生成90个交叉点。需要说明的是棋盘的网格并不是均匀的因为象棋棋盘上下两侧各有一个“九宫”区域中间的河界会让两边的线条距离看起来与边路略有差异但这里我们是通过实际检测线条位置来生成交叉点所以不会存在均匀划分带来的误差。2.3 棋子定位颜色分割加轮廓检测棋子的定位我试过两种方案霍夫圆检测和颜色分割。霍夫圆检测cv2.HoughCircles对参数极其敏感minDist、param1、param2三个参数稍微不对就会漏检一堆棋子或者把棋盘上的字误检成圆。在光照不均匀的情况下几乎没法用。最终我采用的是HSV颜色分割方案。象棋只有红黑两种颜色的棋子用颜色阈值分别把红色和黑色像素抠出来然后找轮廓用minEnclosingCircle求出圆心和半径这比霍夫圆检测稳定太多了。代码也很直观def detect_pieces(warped_bgr): hsv cv2.cvtColor(warped_bgr, cv2.COLOR_BGR2HSV) # 红色范围HSV中红色在0度和180度附近都有分布需要两个区间 red_mask1 cv2.inRange(hsv, (0, 60, 60), (10, 255, 255)) red_mask2 cv2.inRange(hsv, (156, 60, 60), (180, 255, 255)) red_mask cv2.bitwise_or(red_mask1, red_mask2) # 黑色范围V通道足够低S通道不能太低避免接近黑色的背景也被误检 black_mask cv2.inRange(hsv, (0, 0, 0), (180, 255, 120)) pieces [] for mask, color in [(red_mask, red), (black_mask, black)]: contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area cv2.contourArea(cnt) if area 400: # 过滤掉小噪点 continue (x, y), radius cv2.minEnclosingCircle(cnt) # 过滤掉不符合棋子尺寸比例的区域 if 10 radius 50: pieces.append((int(x), int(y), int(radius), color)) return pieces这里说一个很容易忽略的细节黑棋的遮盖范围不能只靠V通道低来判断黑色棋子的边框其实偏灰偏亮直接把V阈值设得很低会把棋子丢掉。我实测下来把V上限设到120同时要求S通道大于某个下限防止把阴影误判成黑棋效果最好。这个值可以根据实际光照微调建议做一个人工交互的调参脚本拖动滑块看掩膜效果而不是每次都用固定的魔法数字。确定了棋子位置之后下一步就是把棋子区域裁剪成ROI送入识别模块。裁剪时要注意在圆心周围多留一点边距把棋子的边框一起包含进去不要只裁中间的文字部分。模板匹配对整体轮廓的相似度同样敏感如果模板是整个棋子而ROI只裁了文字匹配分数会非常低。2.4 棋子识别模板匹配的参数与坑棋子识别用的是模板匹配核心代码是cv2.matchTemplate加cv2.TM_CCOEFF_NORMED。这个方法的输出是-1到1之间的相关系数越接近1表示越相似。def recognize_piece(roi_gray, template_dict): roi_resized cv2.resize(roi_gray, (50, 50)) roi_norm cv2.equalizeHist(roi_resized) # 直方图均衡化减弱光照影响 best_label None best_score -1 for label, template in template_dict.items(): template_norm cv2.equalizeHist(template) result cv2.matchTemplate(roi_norm, template_norm, cv2.TM_CCOEFF_NORMED) _, max_val, _, _ cv2.minMaxLoc(result) if max_val best_score: best_score max_val best_label label return best_label, best_score模板匹配虽然写起来简单但有几个坑必须注意。第一个坑是模板与输入ROI的尺寸不一致。如果模板是50×50ROI是80×80直接匹配的分数会非常低。解决方法是全部统一resize到相同尺寸。但是resize会拉伸图像如果棋子本来就拍歪了拉伸之后字体会变形匹配分数也会下降。更稳妥的做法是检测到棋子半径后按半径把ROI缩放成统一尺寸模板库也在同样尺度下构建。第二个坑是模板匹配对旋转敏感。现实里摄像头拍到的棋子方向与模板方向不一定一致我就遇到过把“车”识别成“炮”的情况。最简单的解决办法是在模板库里同时加入旋转180度后的版本。因为象棋棋子基本都是上下对称朝向的旋转90度的情况在正常拍摄时很少出现加180度就够了。如果场景里出现任意旋转角度的棋盘模板匹配就不太合适了可以换HOG特征加SVM甚至直接用CNN。第三个坑是阈值设定。TM_CCOEFF_NORMED的分数在0.5到0.9之间都很常见不能把所有低于0.9的识别结果都当作错误。我自己的经验是阈值设在0.65到0.75之间比较平衡。低于0.6的时候误判率明显上升高于0.8时漏检率上升。但是在某些光照不均匀的图上匹配分数整体会下降需要针对测试集微调。2.5 棋谱生成坐标映射与状态矩阵这一步是把识别出来的棋子坐标转换成棋谱比如“炮二平五”“马八进七”。棋谱不是简单输出棋子在哪而是要记录每一步的移动。核心是两个东西一个坐标映射关系一个棋盘状态矩阵。坐标映射很简单前面已经算出了90个交叉点现在拿到了某个棋子的圆心坐标找到它最近的交叉点就得到了这个棋子在棋盘上的逻辑位置行、列。然后需要把“像素行列”转换成“棋谱坐标”——象棋棋谱中红方和黑方分别从各自底线数起列数则是从右往左数1到9。这里有个关键细节红方视角和黑方视角的列方向是相反的。从红方这边看最右边是1路但从黑方那边看同一列是最左边。所以生成棋谱的时候必须知道当前是哪一方在走棋然后选择对应的坐标映射方式。状态矩阵是一个9×10的二维数组每个位置保存当前这个交叉点上是什么棋子。识别完当前帧的所有棋子后和上一帧的状态矩阵比较找出哪些位置发生了变化就能推导出哪一方的哪个棋子从哪走到了哪。举个简单的例子上一帧矩阵中(row8, col0)是“炮”红方从底线数第2行最右列这一帧这个位置变成空而(row7, col2)处出现了“炮”那就可以生成“炮二平五”。def board_to_move(prev_state, current_state, color): changed [] for row in range(9): for col in range(9): if prev_state[row][col] ! current_state[row][col]: changed.append((row, col, current_state[row][col])) # 找到从旧位置消失的棋子和新位置出现的棋子 # 根据颜色决定红方视角还是黑方视角生成炮二平五这类描述 # 具体实现时需要结合象棋走子规则判断是平、进还是退 return move_str生成棋谱这一步最花时间的其实是判断“进”“退”“平”。象棋规则里“平”表示横向移动“进”和“退”是纵向移动但不同棋子的移动规则不一样车炮可以走任意格数的直线马走日象走田兵卒只能前进不能后退。为课程设计做简化可以假设每次移动后根据目标位置和起始位置的横向、纵向位移来判断。如果横向位移不为0且纵向位移为0就是“平”如果纵向位移不为0再结合棋种判断是“进”还是“退”。考虑到课程设计的时间限制我并没有实现完整的走子合法性检测只实现了坐标差分生成描述。这在演示场景下够用了因为演示时会手动摆好每一步需要拍摄的局面程序负责“拍摄-识别-输出棋谱”这条链路。如果要接上完整的走子规则需要额外实现一套象棋规则引擎那就是另一个大项目了。3. 完整实操过程与关键代码解读3.1 可复现的环境准备我用的是最常规的组合不需要GPU不需要跑模型训练一台普通笔记本电脑就够。Python 3.8opencv-python 4.5numpy 1.21安装命令只有一行装不上就走国内镜像源pip install opencv-python numpy需要说明的是OpenCV的cv2.inRange、cv2.matchTemplate这些函数都是CPU计算的在这个项目里性能完全够用。我做了一个粗略的性能统计640×640的图像所有处理步骤加起来大约耗时300毫秒对静态图识别毫无压力。3.2 模板库构建从拍摄到归一化模板库是整个识别准确率的地基拍摄模板时尽量在均匀光照下拍背景干净棋子立正放好。import os import cv2 import numpy as np def load_templates(template_root, size(50, 50)): template_dict {} for label in os.listdir(template_root): label_dir os.path.join(template_root, label) if not os.path.isdir(label_dir): continue templates [] for fname in os.listdir(label_dir): img cv2.imread(os.path.join(label_dir, fname)) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, size) templates.append(gray) template_dict[label] templates return template_dict这里有个细节模板库里每类最好放多张不同时间拍的。比如红方的“车”可以拍3张不同的摆位和光照条件识别时把每个模板的匹配分数都算出来取所有模板中的最高分作为该类别的最终分数。这样即使个别模板因为拍摄角度问题分数偏低其他模板也能补上。另一个细节是直方图均衡化。模板和ROI在匹配前都做一次cv2.equalizeHist可以明显降低光线变化带来的影响。但有个副作用如果棋子本身颜色特别深或特别浅均衡化之后可能会让匹配分数整体下降所以这个操作是否启用建议做成开关根据实际效果调整。3.3 预处理流程的完整串联我建议把预处理写成一个独立的流程函数便于测试和调试中间结果。整个流程如下读图转灰度。高斯模糊核大小设为5×5。Canny边缘检测低阈值50高阈值150。膨胀让棋盘外框连续迭代次数2。找最大轮廓多边形逼近得到4个角点。角点排序计算透视变换矩阵输出640×640校正图。对校正图转灰度、二值化投影法检测所有横线和竖线坐标。根据线条坐标生成90个交叉点存入网格数组中。def preprocess_pipeline(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.GaussianBlur(gray, (5, 5), 0) corners find_board_corners(gray) if corners is None: return None corners order_corners(corners) warped perspective_correct(img, corners) warped_gray cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) # Otsu阈值分割 _, binary cv2.threshold(warped_gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) kernel np.ones((3, 3), np.uint8) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) row_peaks find_projection_peaks(binary, axis1) col_peaks find_projection_peaks(binary, axis0) return warped, row_peaks, col_peaks这段代码里值得提醒的是阈值选择用了THRESH_BINARY_INV而不是THRESH_BINARY。因为棋盘的线条和格子通常是深色的直接二值化会让线条变白、背景变黑而THRESH_BINARY_INV会把线条变成白色背景变成黑色这样投影法的峰值才会对应棋盘线。3.4 棋谱生成的输出示例程序最终输出的棋谱格式类似于第1步 红方炮二平五 黑方马8进7 第2步 红方马二进三 黑方车9平8实际实现时我建议在终端输出棋谱的同时也把可视化结果保存成一张标注了棋子和位置的图片这样演示的时候可以直接展示识别效果。用cv2.putText在棋子上方标注识别结果用cv2.circle画出检测到的棋子边界看起来很直观也方便排查识别错误。保存可视化输出的代码def visualize_result(warped, pieces, recognized, output_path): vis warped.copy() for (x, y, r, color), label in zip(pieces, recognized): cv2.circle(vis, (x, y), r, (0, 255, 0), 2) cv2.putText(vis, f{label}, (x - 20, y - r - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(output_path, vis)4. 常见问题与排查技巧实录4.1 高频问题速查表这个项目在跑的过程中我遇到的问题五花八门但绝大多数都可以归结到下面几类。整理成一张速查表方便你直接对照排查。现象可能原因排查方法 / 解决方案棋盘角点检测不到外框边缘断裂严重或者背景复杂干扰太多增大膨胀迭代次数、调低Canny低阈值如果还不行手动设置角点做备用方案透视校正后棋盘翻转或错位角点顺序不对用极角排序函数统一角点顺序部分棋子漏检光线反光导致红色区域被分割成多个小块对掩膜做膨胀合并邻近轮廓或改用多张图去重黑棋识别成红棋或反之HSV阈值范围不合适用调参脚本查看掩膜特别注意红黑交界处模板匹配把“车”识别成“炮”匹配分数阈值过低或者模板数量不够提高阈值到0.7以上往模板库补图生成的棋谱坐标明显偏移坐标映射用的交叉点索引不对在可视化图上打印交叉点索引号逐一核对处理速度很慢图像分辨率过高模板库遍历太多先降采样到640×640再优先做颜色筛选只对目标ROI做匹配4.2 我从项目中总结出来的几条实战经验第一光照是最大的敌人。项目跑通之后我做了一次对比实验在均匀白光下识别准确率可以到95%以上但在午后窗边只靠自然光准确率掉到了70%左右。原因是强光在棋子表面产生高光红色棋子的颜色特征被冲淡黑棋子和棋盘阴影混在一起。我的对策是拍摄时避免光线直射棋盘如果不行可以在预处理环节加一个自适应光照校正比如cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8))来限制对比度放大的幅度它比整图的equalizeHist更温和不容易把暗部噪声放大到离谱。第二棋谱定位的坐标系设计要提前规划不要等到最后一步才想。我一开始把坐标映射和状态矩阵分开写结果联调的时候发现行和列的对应关系总是反的返工浪费了一晚上。建议在写代码之前先画一张棋盘示意图把像素行列和棋谱坐标的关系标清楚然后照图实现。第三写一个独立的调参脚本。这个项目里涉及到大量阈值参数——Canny阈值、颜色HSV范围、匹配阈值、面积过滤阈值用手动改代码的方式非常痛苦。我写了一个带滑动条的小工具可以实时查看掩膜和检测结果调参效率提高了不止一倍。核心代码只有几行但带来的便利是指数级的cv2.createTrackbar(H_min, mask, 0, 180, lambda x: None) cv2.createTrackbar(H_max, mask, 180, 180, lambda x: None) # 在循环里读取滑块值实时更新掩膜显示第四模板里保留完整的棋子形状比只保留文字效果更好。我之前想走捷径只把棋子中心的汉字截下来作为模板但实际匹配效果很差因为同一款棋子的字体样式在不同字体渲染下略有不同而且裁剪边框稍微收一点字体的笔画比例就完全变了。反而是包含整个棋子的圆形轮廓作为模板更稳定因为匹配的时候不仅看文字还看外圈的圆环和色块分布特征更丰富。第五针对“兵”和“卒”这类长得很像的棋子如果匹配分数卡在临界值上可以从颜色上再加一个判断。红方的“兵”和黑方的“卒”在颜色上其实差异明显红色通道的均值差异足够把它们区分开。同样的思路也可以用在“帅”和“将”上颜色辅助判断能显著降低模板匹配的误识别率。最后再分享一个小优化如果你想让这个项目的识别率再上一个台阶最直接的办法不是换算法而是把ROI的归一化做得更好。我后期在ROI送入模板匹配之前先按照棋子的圆心和半径做了一个圆形掩膜把ROI四个角上的棋盘背景像素全部置零这样模板匹配时只比较圆形区域内的特征背景干扰瞬间少了很多。这个改动让整体准确率提高了大约3到5个百分点代价只是多几行cv2.bitwise_and的操作性价比非常高。整个项目做下来我的体会是图像识别并不一定非要上深度学习大模型很多传统方法在受控场景下反而更简洁、更可靠。这个项目的核心价值在于把OpenCV里最常用的几个图像处理模块串成了一个完整的产品逻辑从图像到数据从数据到信息每一步都有清晰的物理意义。如果你想继续扩展可以考虑加入走子合法性校验把棋谱输出接到在线棋局数据库甚至用摄像头实时捕捉整局对弈这些方向都很有意思。本文还有配套的精品资源点击获取