ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

OpenCV级联分类器实战:中国象棋棋子检测与识别

OpenCV级联分类器实战:中国象棋棋子检测与识别 简介这是一套面向计算机相关专业学生与初学者的OpenCV实战项目围绕中国象棋棋子的自动识别展开采用级联分类器完成红黑双方共14类棋子的检测与分类适合用作课程设计、毕业设计或Python与计算机视觉入门练手。资源包共16个文件包含3个Python源码文件、3个压缩数据包、8张识别结果示例图以及说明文档与授权声明整体约20.78MB。其中数据集按红黑两色分开保存每类棋子经灰度化与中值滤波后保留约200张多角度样本并附有训练好的红黑分类模型文件可直接调用或重新训练。代码注释详尽配套项目说明与运行结果截图便于理解级联分类器的训练流程与识别逻辑。目前已有266人学习适合在此基础上修改扩展完成其他棋子或目标识别类课题。1. 从一张棋盘照片到棋子坐标这套级联分类器资源到底能跑出什么很多人做中国象棋识别第一反应是上 YOLO 或者直接调大模型结果卡在标注成本和显存上。这套资源走的是另一条路用 OpenCV 自带的级联分类器Cascade Classifier把红黑双方各 7 类棋子拆成两个模型分别训练输入是灰度化加中值滤波后的棋子小图输出是棋子在画面里的检测框。它解决的不是识别整盘棋局这种大问题而是给定一张棋盘照片把每个棋子的位置和颜色类别框出来这个具体环节。适合计算机相关专业的课程设计、毕设起步也适合想搞懂传统目标检测流程、不想一上来就被深度学习框架绑住的人。资源里带了采集好的数据集、训练脚本、识别脚本和已经训好的 yml 模型拿到手就能先跑通再改。2. 级联分类器识别棋子的原理与这套代码的选型逻辑2.1 为什么用 Haar 级联而不是深度学习级联分类器的核心是 Haar-like 特征加 AdaBoost 筛选加级联结构。简单说它先用一堆黑白矩形模板在图像上滑动算出明暗差异作为特征再用 AdaBoost 从成千上万个特征里挑出对区分棋子最有用的几百个最后把强分类器串成级联——前几级快速排除明显不是棋子的区域后面的级才做精细判断。这套机制在棋子这种纹理固定、形状规整、背景相对可控的场景里够用而且训练不需要 GPU一张普通显卡甚至纯 CPU 就能跑完。选它而不是 YOLO理由很实际数据集只要 200 张每类就能起步标注只需要画框不用做像素级分割模型文件是 yml 文本格式能直接打开看参数调参过程不是黑匣子推理时 CPU 单帧几十毫秒嵌入式设备也能部署。代价是它对旋转和光照变化比深度学习敏感所以资源里才专门做了灰度化和中值滤波预处理把颜色和噪声这两个变量先摁住。2.2 数据集的组织方式与预处理链路资源里的 Dataset_Red_Black 文件夹按颜色分了两大块每块下面再按棋子类别分 7 个子文件夹每类保留 200 张各种角度的图。这个结构不是随便定的——级联分类器训练时正样本需要统一尺寸负样本需要足够多的背景图所以 getDataSet.py 干的事就是把原始棋子图做灰度化、中值滤波、尺寸归一化然后生成正样本描述文件。import cv2 import os import numpy as np # 读取原始棋子图转灰度并做中值滤波 def preprocess_chess_image(img_path, target_size(24, 24)): img cv2.imread(img_path) if img is None: return None # 灰度化去掉红黑颜色信息只保留明暗纹理 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 中值滤波核大小 3去掉椒盐噪声同时保留边缘 filtered cv2.medianBlur(gray, 3) # 统一缩放到 24x24这是级联分类器常用的正样本尺寸 resized cv2.resize(filtered, target_size, interpolationcv2.INTER_AREA) return resized # 遍历每个类别文件夹生成正样本 def build_positive_samples(root_dir, output_txt): lines [] for label in os.listdir(root_dir): label_dir os.path.join(root_dir, label) if not os.path.isdir(label_dir): continue for fname in os.listdir(label_dir): if not fname.lower().endswith((.jpg, .png, .bmp)): continue fpath os.path.join(label_dir, fname) processed preprocess_chess_image(fpath) if processed is None: continue # 保存处理后的图路径写入描述文件格式路径 1 0 0 24 24 save_path os.path.join(root_dir, processed, label, fname) os.makedirs(os.path.dirname(save_path), exist_okTrue) cv2.imwrite(save_path, processed) lines.append(f{save_path} 1 0 0 24 24) with open(output_txt, w) as f: f.write(\n.join(lines)) print(f生成 {len(lines)} 条正样本记录)这段代码里三个参数值得盯住中值滤波的核大小 3 是经验值棋子表面如果有明显反光可以试 5但再大就会把棋子上的字糊掉目标尺寸 24x24 是级联分类器的经典正样本尺寸改大能保留更多细节但训练时间成倍涨描述文件里每行末尾的1 0 0 24 24分别表示样本数量、左上角坐标和宽高写错一个数字训练就会报错。2.3 训练脚本的关键参数与执行流程train.py 调用的是 OpenCV 的opencv_traincascade命令行工具参数配置直接决定模型能不能收敛。资源里红黑两个模型分开训是因为红色棋子和黑色棋子在灰度图上的明暗分布差异明显混在一起训会让分类器在颜色边界上反复横跳。# 生成负样本描述文件负样本用棋盘背景图 find ./negative_images -name *.jpg negatives.txt # 训练红色棋子模型 opencv_traincascade \ -data ./model_save/red_model \ -vec ./positive_red.vec \ -bg ./negatives.txt \ -numPos 1400 \ -numNeg 700 \ -numStages 15 \ -featureType HAAR \ -w 24 -h 24 \ -minHitRate 0.995 \ -maxFalseAlarmRate 0.5 \ -mode ALL-numPos是每级实际用的正样本数一般设成总正样本数的 80% 左右留一部分做验证-numNeg是负样本数经验值是正样本的一半到一倍-numStages是级联层数15 层在棋子场景下够用层数越多误检越少但训练越慢-minHitRate 0.995要求每级至少保留 99.5% 的正样本这个值设低了后面级联会漏检-maxFalseAlarmRate 0.5控制每级的误报率两级乘起来就是 0.25十五级下来误报率会压到很低。训练过程中如果某一级卡住不收敛通常是负样本不够多样往 negatives.txt 里加棋盘格、桌面、手部这些干扰图。3. 从零跑通识别流程环境、脚本与参数调优3.1 环境搭建与依赖安装这套代码依赖 OpenCV 的 Python 绑定和训练工具。常见做法是用 conda 建一个干净环境避免和系统里的 OpenCV 版本打架。conda create -n chess_cascade python3.8 conda activate chess_cascade pip install opencv-python opencv-contrib-python numpy装完之后验证一下cv2.__version__能正常打印并且cv2.CascadeClassifier和cv2.data.haarcascades都能访问。如果报ModuleNotFoundError: No module named cv2八成是装到了别的 Python 环境里用which python确认当前解释器路径。训练工具opencv_traincascade在 opencv-contrib-python 里不一定带Linux 下可以用包管理器装opencv-data和libopencv-devWindows 下需要单独下载 OpenCV 的预编译包把opencv_traincascade.exe所在目录加进 PATH。3.2 recognize.py 的检测逻辑与参数含义识别脚本的核心是加载 yml 模型对输入图做同样的灰度化和中值滤波然后调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(img_path): img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.medianBlur(gray, 3) # 红色棋子检测 red_boxes red_cascade.detectMultiScale( gray, scaleFactor1.05, # 每次缩放 5%越小越慢但越不容易漏 minNeighbors6, # 一个框至少被检测到 6 次才保留压误检 minSize(20, 20), # 最小棋子尺寸小于这个不检 maxSize(80, 80) # 最大棋子尺寸大于这个不检 ) # 黑色棋子检测参数略调 black_boxes black_cascade.detectMultiScale( gray, scaleFactor1.08, minNeighbors5, minSize(20, 20), maxSize(80, 80) ) # 画框并标注颜色 for (x, y, w, h) in red_boxes: cv2.rectangle(img, (x, y), (xw, yh), (0, 0, 255), 2) cv2.putText(img, Red, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) for (x, y, w, h) in black_boxes: cv2.rectangle(img, (x, y), (xw, yh), (255, 0, 0), 2) cv2.putText(img, Black, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1) return img result detect_chess(./TestPicture/Test1.jpg) cv2.imwrite(./output/Test1_result.jpg, result)scaleFactor控制图像金字塔的缩放步长1.05 意味着每次缩小 5%检测窗口能覆盖更多尺寸但计算量上升minNeighbors是同一个目标被重复检测多少次才输出设太低会出一堆重叠框设太高会漏掉边缘棋子minSize和maxSize要根据实际拍摄距离调拍得远棋子小就把 minSize 降到 15 左右拍得近就往上提。红黑两个模型的参数可以不一样因为黑色棋子在灰度图上对比度通常比红色低需要更宽松的邻居阈值。3.3 用测试图验证与结果判读资源里带了 TestPicture 文件夹和 8 张分类结果图覆盖红黑双方各 4 个测试场景。跑完 recognize.py 后把输出图和自带的 Test1 到 Test4 结果图对比重点看三个地方棋子密集排列时有没有漏检、棋子边缘有没有重复框、红黑模型有没有互相误检。如果红色模型把黑色棋子也框了说明负样本里黑色棋子放少了需要往红色模型的负样本集里补黑色棋子图重新训。如果某个角上的棋子总是漏检查那张图里棋子的实际像素尺寸是不是低于 minSize。4. 避坑与排查训练和识别阶段最容易翻车的五个点4.1 训练到某一级报 Required leaf false alarm rate achieved 后卡死现象是终端停在某一级不动CPU 占用掉到接近零。原因是负样本被前几级筛完了后面级没有足够的负样本继续训练。解决方法是往 negatives.txt 里补图数量至少是 numNeg 的两倍并且负样本要包含各种背景——棋盘格、木桌面、纯色布、手部局部不要只用一种背景。4.2 识别时同一个棋子出现三四个重叠框现象是画面上一个棋子被框了好几次框之间大量重叠。原因是 minNeighbors 设得太低分类器在相邻尺度上都给出了响应。解决方法是把 minNeighbors 从 3 提到 6 到 8同时把 scaleFactor 从 1.1 降到 1.05让尺度搜索更细但合并更严格。如果还不行在 detectMultiScale 之后加一步非极大值抑制手动合并 IoU 超过 0.3 的框。4.3 红色模型把黑色棋子也框进去现象是红方模型在黑色棋子上也输出检测框颜色分类完全乱掉。原因是训练红色模型时负样本里没有黑色棋子分类器只学到了圆形有字这个特征没学到颜色差异。解决方法是把黑色棋子的灰度图批量加入红色模型的负样本集反之亦然让两个模型在颜色维度上真正分开。4.4 换了拍摄环境后检测率断崖下跌现象是训练集里检测很好拿到新拍的棋盘照片上漏检一大半。原因是级联分类器对光照和背景变化敏感训练集的灰度分布和新环境不一致。解决方法是识别前先做直方图均衡化把灰度分布拉平或者用 CLAHE 做局部对比度增强。如果新环境背景特别花考虑在识别前用颜色阈值先把棋盘区域抠出来只在棋盘范围内做检测。4.5 opencv_traincascade 命令找不到或报缺少 DLL现象是 Windows 下运行 train.py 提示opencv_traincascade不是内部或外部命令或者双击 exe 报缺少 opencv_worldxxx.dll。原因是训练工具不在 Python 包的 PATH 里且依赖的 DLL 没一起放。解决方法是去 OpenCV 官方 release 页面下载对应版本的预编译包把build\x64\vc15\bin目录加进系统 PATH并把该目录下的所有 DLL 复制到 exe 同级目录。Linux 下用apt install libopencv-dev后工具通常在/usr/bin下直接能调。5. 进阶玩法把两个模型串成完整棋局识别流水线单靠红黑两个级联模型只能给出这是红棋子或这是黑棋子但中国象棋有 7 种棋子帅仕相马车炮兵各有各的走法。要把识别做到能用的程度得在检测框后面接一个分类环节。我一般会这么做先用级联模型把棋盘上所有棋子框出来并按颜色分组然后对每个框内的图做一次模板匹配或者小型的 CNN 分类把具体棋子类别定下来。# 在检测框基础上做棋子类别细分 def classify_piece(gray_crop): # 方案一模板匹配适合字体固定的印刷体棋子 templates { shuai: cv2.imread(./templates/shuai.png, 0), jiang: cv2.imread(./templates/jiang.png, 0), # ... 其余 12 类 } best_label, best_score None, 0 for label, tmpl in templates.items(): if tmpl is None: continue tmpl cv2.resize(tmpl, (gray_crop.shape[1], gray_crop.shape[0])) res cv2.matchTemplate(gray_crop, tmpl, cv2.TM_CCOEFF_NORMED) score res.max() if score best_score: best_score, best_label score, label return best_label if best_score 0.6 else unknown模板匹配的阈值 0.6 是经验值低于这个值说明棋子角度偏得太多或者光照差异太大宁可标 unknown 也不要硬分。如果模板匹配效果不稳可以拿级联模型输出的框做数据增强训练一个 7 分类的小 CNN输入 32x32 灰度图三层卷积就能到 95% 以上。另一个进阶方向是把识别结果映射到棋盘坐标检测到所有棋子后按框的中心点做聚类拟合出 9 列 10 行的网格再把每个棋子填进对应格子这样就能输出 FEN 格式的棋局字符串后面接引擎或者走法判断都方便。验证流水线是否靠谱我习惯用一套固定流程先拿训练集里的图跑一遍确认检测率和分类准确率再拿没参与训练的测试图跑看泛化最后拿手机现拍一张看真实场景下的表现。三步都过了才算能用。从那以后我每次改完模型参数都强制走一遍这三步验证不再凭单张图的效果下结论。希望帮到你。本文还有配套的精品资源点击获取
返回列表