news 2026/9/6 13:02:28

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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, iterations=2) # 找最大轮廓 contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None board_contour = max(contours, key=cv2.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(axis=0) # 计算极角,然后排序 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]], dtype=np.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]]], dtype=np.float32) M = cv2.getPerspectiveTransform(corners, dst_pts) warped = cv2.warpPerspective(img, M, output_size) return warped

2.2 棋盘网格定位:投影法比霍夫变换更稳

棋盘校正成正方形之后,下一步要找到棋盘的10条竖线和9条横线,也就是要得到90个交叉点的坐标。一开始我想当然用了霍夫变换检测直线,结果效果很差——棋盘线被棋子挡住之后会断成好几段,霍夫变换对这种不连续的场景非常敏感,经常把同一个逻辑上的线条检测成很多线段,还要做聚类合并,非常麻烦。

后来我换成了投影法,效果一下子稳了很多。投影法的思路很朴素:对二值图沿着水平方向累加每一行的白色像素数量,有横线的行会得到一个明显的峰值;沿着垂直方向累加每一列的白色像素数量,有竖线的列同理。这样就把二维的直线检测问题降成了一维的峰值查找问题,运行速度快,而且对断线不敏感。

def find_projection_peaks(binary_img, axis=0): # axis=0 表示垂直投影(检测竖线方向),axis=1 表示水平投影(检测横线方向) sums = np.sum(binary_img, axis=axis) 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[i+1] and sums[i] > threshold: peaks.append(i) return peaks

为了减少噪声干扰,在投影之前我会先把图像转成灰度图,做一次高斯模糊,然后用Otsu阈值法二值化。这里的一个关键经验是:二值化之后不要急着投影,先做一次形态学操作(比如开运算)把棋盘线条上的毛刺去掉,这样找出来的峰值会更干净。

找到横线和竖线的位置之后,用笛卡尔积生成90个交叉点。需要说明的是,棋盘的网格并不是均匀的,因为象棋棋盘上下两侧各有一个“九宫”区域,中间的河界会让两边的线条距离看起来与边路略有差异,但这里我们是通过实际检测线条位置来生成交叉点,所以不会存在均匀划分带来的误差。

2.3 棋子定位:颜色分割加轮廓检测

棋子的定位我试过两种方案:霍夫圆检测和颜色分割。霍夫圆检测cv2.HoughCircles对参数极其敏感,minDistparam1param2三个参数稍微不对,就会漏检一堆棋子,或者把棋盘上的字误检成圆。在光照不均匀的情况下几乎没法用。

最终我采用的是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.matchTemplatecv2.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×50,ROI是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的二维数组,每个位置保存当前这个交叉点上是什么棋子。识别完当前帧的所有棋子后,和上一帧的状态矩阵比较,找出哪些位置发生了变化,就能推导出哪一方的哪个棋子从哪走到了哪。举个简单的例子:上一帧矩阵中(row=8, col=0)是“炮”(红方从底线数第2行最右列),这一帧这个位置变成空,而(row=7, col=2)处出现了“炮”,那就可以生成“炮二平五”。

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.8+
  • opencv-python 4.5+
  • numpy 1.21+

安装命令只有一行,装不上就走国内镜像源:

pip install opencv-python numpy

需要说明的是,OpenCV的cv2.inRangecv2.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 预处理流程的完整串联

我建议把预处理写成一个独立的流程函数,便于测试和调试中间结果。整个流程如下:

  1. 读图,转灰度。
  2. 高斯模糊,核大小设为5×5。
  3. Canny边缘检测,低阈值50,高阈值150。
  4. 膨胀让棋盘外框连续,迭代次数2。
  5. 找最大轮廓,多边形逼近,得到4个角点。
  6. 角点排序,计算透视变换矩阵,输出640×640校正图。
  7. 对校正图转灰度、二值化,投影法检测所有横线和竖线坐标。
  8. 根据线条坐标生成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, axis=1) col_peaks = find_projection_peaks(binary, axis=0) 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(clipLimit=2.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里最常用的几个图像处理模块串成了一个完整的产品逻辑,从图像到数据,从数据到信息,每一步都有清晰的物理意义。如果你想继续扩展,可以考虑加入走子合法性校验,把棋谱输出接到在线棋局数据库,甚至用摄像头实时捕捉整局对弈,这些方向都很有意思。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 15:10:06

MATLAB实现EKF电池SOC估计:从建模到仿真完整流程

简介&#xff1a;本资源是一套面向电池管理系统&#xff08;BMS&#xff09;算法工程师、新能源方向研究生及MATLAB仿真学习者的SOC估计算法实践材料&#xff0c;聚焦锂电池非线性建模与状态估计核心问题&#xff0c;提供基于扩展卡尔曼滤波&#xff08;EKF&#xff09;的完整S…

作者头像 李华
网站建设 2026/9/5 16:39:11

基于MATLAB/Simulink的电梯控制系统仿真建模全解析

简介&#xff1a;本资源是一套面向控制工程与自动化专业初学者的电梯控制系统仿真实践材料&#xff0c;聚焦MATLAB Simulink平台建模与PID控制算法实现&#xff0c;解决动态系统建模、闭环控制设计与仿真结果分析等核心学习难点。压缩包共3个文件&#xff08;4KB&#xff09;&a…

作者头像 李华
网站建设 2026/9/4 15:58:30

智能模型路由实战:优化多LLM应用的质量、速度与成本

模型多了之后&#xff0c;不少人的真实感受是&#xff1a;模型能力越强&#xff0c;账单越贵&#xff1b;模型切得越多&#xff0c;维护越乱。每次对话都往最强模型上送&#xff0c;质量是稳了&#xff0c;但延迟和成本一起涨。这个问题其实就是“模型路由”要解的题&#xff1…

作者头像 李华
网站建设 2026/9/2 14:06:12

基于Python与迁移学习的菌类图像识别系统实战

简介&#xff1a;本资源是一套基于Python的菌类&#xff08;蘑菇&#xff09;图像识别系统源码&#xff0c;面向人工智能初学者、计算机视觉实践者及生物信息学爱好者&#xff0c;旨在解决野外蘑菇快速分类与辅助鉴别的实际问题。系统采用深度学习技术构建图像识别模型&#xf…

作者头像 李华
网站建设 2026/9/4 17:04:14

基于Python的招聘网站数据爬取与分析系统实战

简介&#xff1a;本资源是一份面向高校计算机专业本科生及数据分析初学者的课程报告型实践项目&#xff0c;聚焦招聘市场数据的自动化采集与智能分析&#xff0c;解决求职者信息获取低效、企业岗位需求洞察不足等现实问题。压缩包共3个文件&#xff08;1.42MB&#xff09;&…

作者头像 李华
网站建设 2026/9/6 8:45:59

踩坑无数总结:USB转485总是识别不到、无故掉线?九大维度彻底根治稳定性问题

做工控调试、物联网设备接入、门禁安防系统的工程师,几乎没人能绕开USB转485这个看似不起眼的小东西。它成本低、使用灵活,是现场调试485总线设备的标配,但也恰恰是最容易出问题的环节: 电脑一重启就识别不到设备,设备管理器里全是黄色感叹号 传输数据中途莫名掉线,重插一…

作者头像 李华