OpenCV级联分类器实战:中国象棋棋子识别与训练全流程

📅 发布时间:2026/10/5 7:18:56
OpenCV级联分类器实战:中国象棋棋子识别与训练全流程
简介这份资源面向计算机相关专业学生与OpenCV初学者提供一套基于级联分类器识别中国象棋棋子的完整Python项目可用于课程设计、毕业设计或图像识别入门练习。压缩包共16个文件约20.78MB包含3个Python源码文件、3个zip数据与模型包、1份README说明及多张识别效果图源码注释详尽便于理解训练与识别流程。数据集按红黑棋子分开保存每色7类、每类约200张经灰度化与中值滤波的多角度图片并附训练好的Red_Chess.yml与Black_Chess.yml模型可直接复现识别效果。项目涵盖数据采集、模型训练与棋子分类完整环节代码均经测试运行成功适合在此基础上修改扩展或作为项目初期演示。目前已有266人学习配套说明与效果图能帮助读者快速上手并排查常见问题。1. 从一张棋盘照片到棋子坐标这套级联分类器资源到底能干什么拍一张中国象棋残局照片想让程序自动说出“红方左侧有车、黑方九宫格里有将”这件事听起来像是个深度学习项目但这份资源给出的答案很朴素——用 OpenCV 自带的级联分类器Cascade Classifier配合一份手工筛选的棋子数据集把识别拆成“红黑两套模型 七类棋子”的工程问题。它不依赖 GPU不需要 PyTorch 或 TensorFlow装好 opencv-python 和 numpy 就能跑对课程设计、毕业设计、Python 入门阶段的图像处理练手来说落地门槛比训练一个 YOLO 小得多。资源包里包含recognize.py、train.py、getDataSet.py三个核心脚本一份按红黑分开、每类 200 张的灰度棋子数据集以及已经训练好的Red_Chess.yml和Black_Chess.yml两个模型文件。适合谁适合正在做 Python 课程设计、需要一套能跑通、能改、能写进论文的完整流程的人也适合想理解级联分类器从数据采集到模型落地的全过程、但不想被深度学习环境折腾的开发者。下面按“数据怎么来 → 模型怎么训 → 识别怎么跑 → 坑在哪”的顺序拆开讲。2. 数据集与训练脚本从棋子图片到 .yml 模型2.1 为什么选级联分类器而不是深度学习级联分类器本质上是多个弱分类器Haar-like 特征 AdaBoost串联成的强分类器OpenCV 从 2.x 时代就内置了训练和检测接口。它的优势在于训练数据量要求低、CPU 推理速度快、模型文件小.yml 通常几百 KB 到几 MB而且整个训练流程可以用命令行工具opencv_traincascade完成不需要写反向传播。对于中国象棋棋子这种“形状固定、纹理简单、背景可控”的目标级联分类器在特定角度和光照下能达到可用的识别率。但它的边界也很明显对旋转、遮挡、光照剧烈变化敏感泛化能力远不如 CNN。这份资源把红棋和黑棋分开训练两个模型本质上是在用“颜色先验”降低单模型的分类压力——红色棋子用红色模型检测黑色棋子用黑色模型检测避免一个模型同时学两种颜色分布导致特征冲突。常见做法是先用 HSV 阈值把红黑区域分开再分别送入对应模型这样误检率会明显下降。2.2 数据采集脚本 getDataSet.py 的逻辑getDataSet.py的作用是从原始棋盘图片中截取棋子区域做灰度化和中值滤波然后按颜色和类别保存到Dataset_Red_Black文件夹。资源说明里提到“每类棋子经筛选后保留 200 张各种角度的图片”这意味着采集阶段需要人工筛选去掉模糊、遮挡、光照过曝的样本。下面是一个典型的采集与预处理代码结构import cv2 import os import numpy as np # 原始图片目录和输出目录 RAW_DIR raw_images OUTPUT_DIR Dataset_Red_Black CATEGORIES [red_ju, red_ma, red_xiang, red_shi, red_jiang, red_pao, red_bing, black_ju, black_ma, black_xiang, black_shi, black_jiang, black_pao, black_zu] def preprocess_chess_piece(img): 灰度化 中值滤波去噪同时保留边缘 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 中值滤波核大小 5对椒盐噪声效果好且不会像高斯滤波那样模糊边缘 filtered cv2.medianBlur(gray, 5) return filtered def save_samples(img, category, count): 保存预处理后的棋子图片统一缩放到 40x40 processed preprocess_chess_piece(img) resized cv2.resize(processed, (40, 40)) save_path os.path.join(OUTPUT_DIR, category, f{count:04d}.png) cv2.imwrite(save_path, resized) # 主循环遍历原始图片手动框选或按固定坐标截取棋子区域 # 实际使用时通常配合 cv2.selectROI 交互式截取这段代码的关键参数是cv2.medianBlur的核大小 5 和最终缩放尺寸 40×40。中值滤波核太大会把棋子上的文字笔画抹掉太小则去噪不干净40×40 是级联分类器训练的常见正样本尺寸太小丢失纹理太大训练时间成倍增加。采集时建议每类至少 150 张有效样本资源里保留 200 张是留了筛选余量。2.3 训练脚本 train.py 与 opencv_traincascade 参数train.py通常是对 OpenCV 命令行工具opencv_traincascade的封装或者用cv2.CascadeClassifier配合正负样本列表文件进行训练。级联分类器训练需要准备正样本描述文件每行图片路径 目标数量 目标区域坐标和负样本列表每行一个负样本图片路径。下面是训练红棋模型的核心命令# 生成正样本描述文件 positives_red.txt # 格式图片路径 目标数量 x y width height find Dataset_Red_Black/red_* -name *.png | while read f; do echo $f 1 0 0 40 40 done positives_red.txt # 生成负样本列表 negatives.txt用非棋子区域图片 find raw_images/background -name *.jpg negatives.txt # 训练级联分类器 opencv_traincascade \ -data model_save/red_model \ -vec positives_red.vec \ -bg negatives.txt \ -numPos 140 \ -numNeg 300 \ -numStages 12 \ -featureType HAAR \ -w 40 -h 40 \ -minHitRate 0.995 \ -maxFalseAlarmRate 0.5 \ -mode ALL参数说明-numPos是每级实际使用的正样本数一般设为总正样本数的 70%80%因为训练过程中会逐级淘汰-numNeg是负样本数建议是正样本的 23 倍-numStages是级联层数层数越多误检越低但训练越慢12 层是棋子识别的常用值-minHitRate 0.995表示每级至少保留 99.5% 的正样本-maxFalseAlarmRate 0.5表示每级允许 50% 的误检通过这两个参数共同决定模型的召回率和精度平衡。训练完成后会在model_save目录生成cascade.xml资源里重命名为Red_Chess.yml和Black_Chess.yml。注意训练前必须用opencv_createsamples把正样本图片转成.vec文件直接拿图片训练会报格式错误。.vec文件生成命令是opencv_createsamples -info positives_red.txt -vec positives_red.vec -w 40 -h 40。3. 识别脚本 recognize.py加载模型与棋子定位3.1 红黑双模型加载与检测流程recognize.py的核心逻辑是读取一张棋盘图片 → 分别用红色模型和黑色模型做detectMultiScale→ 对检测到的矩形框做非极大值抑制 → 输出棋子位置和类别。由于资源里每个颜色只有一个模型文件而不是七类各一个模型实际识别时模型只负责判断“这是不是红色棋子”具体是车马炮还是将士象需要额外做分类。常见做法是先用级联分类器定位所有棋子区域再对每个区域做模板匹配或简单特征分类来区分具体棋子类型。import cv2 import numpy as np # 加载训练好的红黑模型 red_cascade cv2.CascadeClassifier(model_save/Red_Chess.yml) black_cascade cv2.CascadeClassifier(model_save/Black_Chess.yml) def detect_chess_pieces(image_path): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.medianBlur(gray, 5) # 红色棋子检测scaleFactor 控制金字塔缩放步长minNeighbors 控制误检抑制 red_pieces red_cascade.detectMultiScale( gray, scaleFactor1.05, minNeighbors6, minSize(30, 30), maxSize(60, 60) ) # 黑色棋子检测 black_pieces black_cascade.detectMultiScale( gray, scaleFactor1.05, minNeighbors6, minSize(30, 30), maxSize(60, 60) ) results [] for (x, y, w, h) in red_pieces: results.append((red, x, y, w, h)) for (x, y, w, h) in black_pieces: results.append((black, x, y, w, h)) # 在图上画框并保存 for color, x, y, w, h in results: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(result.jpg, img) return resultsscaleFactor1.05表示每次金字塔缩放 5%值越小检测越细但速度越慢minNeighbors6表示一个候选框周围至少要有 6 个重叠框才判定为有效检测调大可以减少误检但可能漏掉小目标minSize和maxSize根据棋子在实际图片中的像素尺寸设定资源里的测试图片棋子大约在 40×40 到 60×60 之间。3.2 检测结果的后处理与棋子分类级联分类器输出的只是“疑似棋子”的矩形框要区分具体是哪个棋子资源里的做法通常是结合颜色和位置做规则判断。比如红色棋子检测框内如果文字区域偏上且笔画密集可能是“车”如果笔画简单且居中可能是“将”。更稳妥的方式是训练七个独立的级联分类器每个对应一种棋子但资源里只提供了红黑两个模型说明具体分类可能依赖模板匹配或人工规则。def classify_piece(gray_piece, color): 基于简单模板匹配的棋子分类示例 templates { ju: cv2.imread(ftemplates/{color}_ju.png, 0), ma: cv2.imread(ftemplates/{color}_ma.png, 0), # ... 其他棋子模板 } best_match None best_score -1 for name, tmpl in templates.items(): if tmpl is None: continue tmpl cv2.resize(tmpl, (gray_piece.shape[1], gray_piece.shape[0])) score cv2.matchTemplate(gray_piece, tmpl, cv2.TM_CCOEFF_NORMED).max() if score best_score: best_score score best_match name return best_match, best_score模板匹配的阈值一般设在 0.60.8 之间低于 0.6 说明模板和实际棋子差异太大需要重新采集模板或调整预处理参数。如果要做更鲁棒的分类可以把检测到的棋子区域送入一个简单的 SVM 或 KNN 分类器用 HOG 特征做输入这样比纯模板匹配抗光照变化能力强。3.3 测试图片与结果验证资源里附带了TestPicture.zip和 8 张测试结果图红黑各 4 张覆盖了不同角度和光照条件。验证时建议按以下步骤走解压TestPicture.zip确认图片格式为 JPG 或 PNG且分辨率不低于 640×480运行recognize.py指定测试图片路径观察输出结果图对比资源里提供的Test1红色棋子分类结果.jpg等参考图看检测框位置和数量是否一致如果检测框明显偏多或偏少优先调整minNeighbors和scaleFactor而不是重新训练模型。提示测试时如果发现黑色棋子被红色模型误检说明两个模型的负样本有重叠需要在训练时把对方的正样本加入自己的负样本列表。4. 避坑与排查级联分类器训练和识别的五个血泪经验4.1 现象训练时报 “Train dataset for temp stage can not be filled”原因负样本数量不足或负样本图片尺寸小于训练窗口尺寸。级联分类器每级训练都需要从负样本中随机裁剪出与正样本同尺寸的窗口如果负样本图片本身比 40×40 还小就无法裁剪。解决确保每张负样本图片的宽高都大于训练窗口尺寸40×40且负样本总数不少于numNeg的 1.5 倍。常见做法是收集 500 张以上的背景图片分辨率统一缩放到 200×200 以上。4.2 现象模型训练完成但检测时一个棋子都框不出来原因正样本描述文件里的目标区域坐标写错了或者.vec文件生成时没有正确读取正样本。另一个常见原因是训练时-numPos设得太大导致每级实际使用的正样本数超过文件里声明的数量训练提前终止。解决用opencv_createsamples -vec positives_red.vec -show 10可视化检查.vec文件内容确认每张正样本都被正确加载。-numPos设为实际正样本数的 70% 左右比如 200 张正样本就设 140。4.3 现象识别时同一枚棋子被框出多个重叠矩形原因minNeighbors设得太小级联分类器在目标附近产生多个候选框非极大值抑制没有生效。解决把minNeighbors从默认的 3 调到 68同时适当增大scaleFactor比如从 1.05 调到 1.1减少金字塔层数。如果仍然重叠可以在检测后手动做一次 NMS保留得分最高的框。4.4 现象红色棋子识别正常黑色棋子大量漏检原因黑色棋子的灰度值和棋盘背景接近中值滤波后边缘对比度低级联分类器学到的 Haar 特征区分度不够。资源里把红黑分开训练就是为了缓解这个问题但如果黑色样本采集时光照偏暗模型仍然会欠拟合。解决在getDataSet.py里对黑色棋子做直方图均衡化cv2.equalizeHist增强文字和背景的对比度。另外黑色模型的minHitRate可以适当降低到 0.99允许更多正样本通过减少漏检。4.5 现象换一张新棋盘照片识别率断崖式下降原因级联分类器对训练时未出现的棋盘材质、光照角度、棋子字体非常敏感。资源里的数据集是在特定棋盘上采集的换一套棋子或换一个拍摄角度Haar 特征分布就变了。解决如果必须跨棋盘使用最直接的办法是重新采集 50100 张新棋盘上的棋子图片对模型做增量训练在已有.vec文件基础上追加新样本。另一个思路是放弃级联分类器改用 HOG SVM 或轻量 CNN泛化能力会好很多但那就超出这份资源的范围了。5. 进阶技巧用滑动窗口 多尺度检测提升小棋子召回率级联分类器的detectMultiScale本身已经做了多尺度检测但默认的金字塔缩放策略在棋子尺寸变化较大时容易漏检。一个实用的改进是手动构建图像金字塔对每一层分别检测再合并结果。这样做的好处是可以针对不同尺度单独调参比如大尺度用minNeighbors4小尺度用minNeighbors8。def multi_scale_detect(cascade, gray, scales[0.5, 0.75, 1.0, 1.25, 1.5]): 手动多尺度检测合并所有尺度的结果 all_boxes [] for scale in scales: # 按比例缩放图像 resized cv2.resize(gray, None, fxscale, fyscale) boxes cascade.detectMultiScale( resized, scaleFactor1.05, minNeighbors6, minSize(30, 30), maxSize(80, 80) ) # 把缩放后的坐标还原到原图 for (x, y, w, h) in boxes: all_boxes.append((int(x / scale), int(y / scale), int(w / scale), int(h / scale))) # 合并重叠框IoU 大于 0.3 的框取平均 merged [] used [False] * len(all_boxes) for i in range(len(all_boxes)): if used[i]: continue group [all_boxes[i]] used[i] True for j in range(i 1, len(all_boxes)): if used[j]: continue iou compute_iou(all_boxes[i], all_boxes[j]) if iou 0.3: group.append(all_boxes[j]) used[j] True # 取组内平均框 avg_x int(np.mean([b[0] for b in group])) avg_y int(np.mean([b[1] for b in group])) avg_w int(np.mean([b[2] for b in group])) avg_h int(np.mean([b[3] for b in group])) merged.append((avg_x, avg_y, avg_w, avg_h)) return merged def compute_iou(box1, box2): 计算两个矩形框的交并比 x1 max(box1[0], box2[0]) y1 max(box1[1], box2[1]) x2 min(box1[0] box1[2], box2[0] box2[2]) y2 min(box1[1] box1[3], box2[1] box2[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area1 box1[2] * box1[3] area2 box2[2] * box2[3] return inter / (area1 area2 - inter 1e-6)这段代码的关键在于scales列表的选择和 IoU 阈值 0.3。scales覆盖 0.5 到 1.5 倍基本能覆盖棋子从远到近的尺寸变化IoU 阈值 0.3 比常规的 0.5 更宽松因为级联分类器输出的框本身就不够精确太严格会导致同一棋子被拆成多个结果。合并时取平均而不是取最大是为了让最终框的位置更稳定。另一个进阶方向是把识别结果映射到棋盘坐标。中国象棋棋盘是 9 列 10 行检测到棋子中心点后按棋盘格子的像素间距做取整就能得到“第几行第几列”的坐标。这一步需要先检测棋盘边界或手动标定四个角点然后用透视变换把棋盘拉正。常见做法是用cv2.findContours找棋盘外框再用cv2.getPerspectiveTransform做矫正。矫正后的棋盘上棋子坐标就是简单的除法取整误差能控制在半个格子以内。注意透视变换的四个角点顺序必须统一比如左上、右上、右下、左下顺序错了会导致棋盘翻转棋子坐标全乱。从那以后我每次做级联分类器项目都会先把正样本用-show参数过一遍确认.vec文件没问题再开始训练这个习惯帮我省了至少三次通宵重训。希望帮到你。本文还有配套的精品资源点击获取