免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

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

OpenCV级联分类器实战:中国象棋棋子识别全流程 简介这份资源围绕OpenCV级联分类器识别中国象棋棋子展开面向计算机、人工智能、通信、自动化等专业的在校学生与教师也适合作为课程设计、毕业设计或项目立项的参考案例。包内共16个文件以py源码、jpg识别效果图、zip数据集与模型压缩包、md说明文档和txt声明为主整体约20.78MB。其中数据集按红黑两色分开保存每色7类棋子、每类约200张经灰度化与中值滤波的多角度图片并附有训练好的Red_Chess.yml与Black_Chess.yml模型文件。源码包含数据采集、模型训练与识别脚本注释较为完整可直接运行验证。已有266人学习适合希望快速理解级联分类器训练流程、复用数据集与模型或在此基础上修改扩展功能的读者参考。1. 从一张棋盘照片到 32 个棋子标签级联分类器识别中国象棋到底在做什么手里有一张俯拍的中国象棋棋盘照片红黑双方 32 枚棋子散落或摆好你想让程序自动说出「这是红车」「那是黑炮」。这件事听起来像深度学习的目标检测但用 OpenCV 自带的级联分类器Cascade Classifier也能做而且不需要 GPU、不需要标注几千张图、不需要训练几小时。级联分类器识别中国象棋棋子本质是把「找棋子」和「认棋子」拆成两步先用 Haar 或 LBP 特征在整张图里滑窗框出所有圆形棋子候选区域再对每个候选区域做一次分类判断它属于红帅、黑将、红车、黑车等 14 种棋子中的哪一种。这套方案适合谁适合刚学 OpenCV、想找一个完整可跑项目练手的人也适合嵌入式或低算力场景下需要快速验证棋子识别可行性的工程师。它不追求 SOTA 精度但胜在依赖少、流程透明、每一行代码都能看懂。下面按「数据怎么准备、分类器怎么训、代码怎么写、坑怎么避」的顺序把这条路走通。2. 象棋分类数据集怎么造从拍照到正负样本的完整流水线2.1 为什么级联分类器需要「正样本 负样本」两套图级联分类器不是端到端目标检测它不直接输出「棋子类别 坐标」。OpenCV 的CascadeClassifier训练流程要求你提供两类图像正样本positive samples是只包含目标物体的裁剪图负样本negative samples是不包含任何目标的背景图。对于中国象棋棋子识别正样本就是单个棋子的圆形裁剪图负样本就是棋盘格、桌面、手部、阴影等干扰区域。训练时算法从正样本中提取 Haar-like 特征或 LBP 特征从负样本中随机采样同样大小的窗口作为反例通过 AdaBoost 筛选出若干弱分类器组成强分类器再级联成多级结构。关键点在于级联分类器一次只能检测「一类物体」。如果你要识别 14 种棋子常见做法是训练 14 个独立的级联分类器每个负责一种棋子或者先训练一个「通用棋子检测器」框出所有棋子再用一个简单的模板匹配或颜色直方图分类器区分红黑和字形。我一般会选后者因为 14 个级联分类器训练时间长、误检叠加严重而「通用检测 二次分类」在象棋场景下更稳。2.2 用 OpenCV 自带工具生成正样本描述文件假设你已经拍好了若干张棋盘照片并且手动裁剪出了单个棋子的图片放在pos/目录下每张图命名如red_che_01.jpg、black_pao_03.jpg。接下来需要生成正样本描述文件pos.txt每行格式为图片路径 目标数量 x y width height。对于单棋子裁剪图目标数量为 1坐标从 0 0 开始宽高等于图片尺寸。可以用下面这段 Python 脚本批量生成import os import cv2 pos_dir pos lines [] for fname in os.listdir(pos_dir): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue path os.path.join(pos_dir, fname) img cv2.imread(path) if img is None: continue h, w img.shape[:2] # 格式路径 目标数 1 x y w h lines.append(f{path} 1 0 0 {w} {h}) with open(pos.txt, w, encodingutf-8) as f: f.write(\n.join(lines)) print(f生成 {len(lines)} 条正样本记录)逻辑说明遍历pos/下所有图片读取宽高按 OpenCV 要求的格式写入。参数说明目标数固定为 1因为每张裁剪图只有一个棋子x y为 0 0表示目标从左上角开始w h是图片实际宽高。注意图片尺寸不要差异过大建议统一缩放到 40x40 到 80x80 之间否则训练时归一化会损失细节。2.3 负样本从哪来棋盘背景比你想的更重要负样本不需要标注只需要一批不含棋子的图片。常见来源空棋盘照片、棋盘局部特写、桌面纹理、手部遮挡图、光照不均的棋盘角落。数量上正样本和负样本比例建议 1:2 到 1:3。如果正样本有 500 张负样本至少准备 1000 张。把所有负样本路径写入neg.txt每行一个路径即可find neg/ -name *.jpg neg.txt这里有个血泪经验负样本里如果混入了棋子边缘或棋子阴影训练出的检测器会把棋子误判为背景导致漏检。所以负样本裁剪时要确保视野内没有任何棋子哪怕只露出一个角也不行。2.4 数据集划分与增强别把同一张棋盘的照片同时放进训练和测试如果你拍了 10 张棋盘照片每张裁剪出若干棋子那么同一张棋盘产生的棋子应该整体归入训练集或测试集不能随机打散。否则测试集里的棋子背景和训练集高度相似准确率虚高。我一般按棋盘编号划分7 张棋盘用于训练3 张用于测试。数据增强方面级联分类器对旋转和缩放有一定鲁棒性但你可以额外生成一些亮度变化、轻微旋转的副本加入正样本提升泛化。注意不要做水平翻转因为象棋棋子上的字翻转后没有意义反而引入噪声。3. 训练通用棋子检测器opencv_traincascade 参数怎么设才不翻车3.1 准备训练目录与命令格式OpenCV 提供了opencv_traincascade命令行工具训练前需要创建data/目录存放中间文件和最终模型。基本命令如下mkdir -p data opencv_traincascade -data data -vec pos.vec -bg neg.txt \ -numPos 400 -numNeg 1000 -numStages 15 \ -featureType LBP -w 48 -h 48 -minHitRate 0.995 \ -maxFalseAlarmRate 0.5 -mode ALL逻辑说明-data指定输出目录-vec是正样本向量文件需要先用opencv_createsamples生成-bg是负样本描述文件-numPos和-numNeg是每级训练使用的正负样本数-numStages是级联层数-featureType选 LBP 或 HAAR-w -h是训练窗口大小-minHitRate和-maxFalseAlarmRate控制每级阈值。参数说明numPos要小于实际正样本数因为每级训练会消耗样本建议设为实际正样本的 80% 左右numNeg同理。numStages不是越大越好15 到 20 级通常够用再多容易过拟合。3.2 生成 vec 文件opencv_createsamples 的隐藏参数正样本需要先转成.vec文件opencv_createsamples -info pos.txt -vec pos.vec -num 500 -w 48 -h 48这里-num是生成的正样本数量可以大于实际图片数工具会通过旋转、缩放、加噪等方式增强。但注意如果-num设得远大于实际图片数生成的样本会高度重复训练时容易过拟合。我一般设成实际正样本数的 1.5 到 2 倍。-w -h必须和 traincascade 的窗口大小一致否则训练报错。3.3 LBP 还是 HAAR象棋棋子场景下的选型对比特征类型训练速度检测速度对光照变化适合场景HAAR慢中等敏感纹理简单、对比度高LBP快快较鲁棒纹理丰富、光照不均象棋棋子表面有汉字纹理比人脸复杂且棋盘照片常有反光和阴影。我实测 LBP 在象棋场景下训练时间缩短约 60%检测速度也更快误检率略高但可以通过提高minHitRate补偿。如果你追求极致精度且不怕等可以试 HAAR但多数情况下 LBP 是更务实的选择。3.4 训练过程监控什么时候该停什么时候该调参训练时终端会输出每一级的N | HR | FA分别表示级数、命中率、误报率。正常情况HR 应接近 1.0FA 逐级下降。如果某一级 FA 突然升高说明负样本不够或太简单需要补充难例。如果训练到 10 级后 FA 不再下降继续训练意义不大可以提前停止。训练完成后data/下会生成cascade.xml这就是最终模型文件。4. 用 Python 加载 cascade.xml 识别棋子从滑窗到分类的完整代码4.1 最小可运行检测脚本import cv2 import numpy as np # 加载训练好的通用棋子检测器 cascade cv2.CascadeClassifier(data/cascade.xml) if cascade.empty(): raise RuntimeError(cascade.xml 加载失败检查路径) img cv2.imread(test_board.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.equalizeHist(gray) # 直方图均衡缓解光照不均 # detectMultiScale 参数缩放步长、最小邻居数、最小尺寸、最大尺寸 pieces cascade.detectMultiScale( gray, scaleFactor1.05, minNeighbors6, minSize(40, 40), maxSize(80, 80) ) for i, (x, y, w, h) in enumerate(pieces): cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) roi gray[y:y h, x:x w] roi cv2.resize(roi, (48, 48)) # 此处可接二次分类器判断红黑与棋子类型 cv2.putText(img, fP{i}, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(result.jpg, img) print(f检测到 {len(pieces)} 个候选棋子)逻辑说明先转灰度并做直方图均衡减少光照影响detectMultiScale返回所有候选框对每个框画矩形并裁剪出 ROI后续可送入二次分类器。参数说明scaleFactor1.05表示每次缩放 5%值越小检测越细但越慢minNeighbors6控制误检值越大框越少但可能漏检minSize和maxSize根据棋子实际像素尺寸设定设错会导致全部漏检。4.2 二次分类用颜色直方图区分红黑用模板匹配区分字形通用检测器只告诉你「这是棋子」不告诉你「这是红车还是黑炮」。二次分类我一般用两个特征颜色和字形。颜色方面把 ROI 转到 HSV 空间统计红色和黑色像素比例红色占优判为红方黑色占优判为黑方。字形方面把 ROI 缩放到固定大小与预先准备好的 14 种棋子模板做归一化互相关匹配取最高分作为类别。模板可以从训练集里每类挑一张清晰图手动裁剪并二值化。def classify_piece(roi_gray, roi_bgr, templates): hsv cv2.cvtColor(roi_bgr, cv2.COLOR_BGR2HSV) # 红色范围 mask_red cv2.inRange(hsv, (0, 70, 50), (10, 255, 255)) | \ cv2.inRange(hsv, (170, 70, 50), (180, 255, 255)) red_ratio np.count_nonzero(mask_red) / mask_red.size side red if red_ratio 0.15 else black roi_resized cv2.resize(roi_gray, (48, 48)) best_score, best_label -1, unknown for label, tmpl in templates.items(): if not label.startswith(side): continue score cv2.matchTemplate(roi_resized, tmpl, cv2.TM_CCOEFF_NORMED).max() if score best_score: best_score, best_label score, label return best_label, best_score逻辑说明先根据红色像素比例判断红黑再在对应颜色的模板中做匹配减少计算量。参数说明红色阈值(0,70,50)-(10,255,255)和(170,70,50)-(180,255,255)覆盖了常见红色范围如果棋盘偏暗可适当降低 S 和 V 下限red_ratio 0.15是经验阈值低于此值判为黑方。4.3 检测结果可视化与坐标输出把检测框、类别标签和置信度画回原图同时输出每个棋子的中心坐标方便后续做棋盘映射。坐标输出格式建议用 JSON包含label、x、y、w、h、score。如果要做棋盘格定位可以进一步用cv2.solvePnP把像素坐标映射到棋盘坐标系但这需要先标定相机或已知棋盘角点属于进阶内容。5. 避坑与排查级联分类器识别象棋时最容易翻车的 5 个点5.1 现象检测框密密麻麻几乎每个棋盘格都被框住原因minNeighbors设得太小或者负样本中棋盘格纹理太少分类器没学会区分棋子与棋盘。解决把minNeighbors从默认 3 提高到 6 到 8补充棋盘格负样本尤其是棋子周围的棋盘区域如果仍无效增加训练级数到 20 级。5.2 现象训练时报错Train dataset for temp stage can not be filled原因numPos或numNeg设得大于实际可用样本数或者pos.vec中样本数量不足。解决检查pos.txt行数和neg.txt行数把numPos降到实际正样本数的 80%numNeg降到负样本数的 80%。如果用了opencv_createsamples增强确认-num不超过实际图片数的 2 倍。5.3 现象模型在训练集上检测很好换一张棋盘照片就全漏原因正样本背景太单一模型学到了「特定棋盘背景 棋子」的组合而不是棋子本身。解决正样本裁剪时尽量只保留棋子圆形区域去掉周围棋盘格增加不同光照、不同角度、不同棋盘材质的照片训练时开启-mode ALL使用所有特征。5.4 现象红方棋子识别正常黑方棋子大量漏检原因黑方棋子颜色暗与棋盘深色区域对比度低LBP 特征区分度不足。解决改用 HAAR 特征试一次或者在预处理时对灰度图做自适应直方图均衡CLAHE增强暗部细节也可以单独为黑方棋子训练一个检测器与红方检测器并联。5.5 现象cv2.CascadeClassifier加载模型后detectMultiScale返回空元组原因模型路径错误、模型文件损坏、或者输入图像不是灰度图。解决先用cascade.empty()检查加载是否成功确认cascade.xml文件大小不为 0检查gray的dtype是否为uint8如果是float需要先转换。另外minSize设得比棋子实际尺寸大也会导致返回空。6. 把识别结果落到棋盘坐标一个容易被忽略的验证技巧训练完模型、跑通检测脚本之后很多人会停在「框出棋子」这一步。但真正验证识别是否可靠有一个很实用的技巧把检测到的棋子中心点映射到标准棋盘坐标然后检查每个交叉点上是否有且只有一个棋子以及红黑分布是否符合开局规则。具体做法是先用cv2.findContours找到棋盘外边框做透视变换把棋盘拉成正视图再根据棋盘行列数等分网格得到每个交叉点的像素坐标最后把检测框中心点匹配到最近的交叉点。如果某个交叉点匹配到两个棋子或者红方棋子出现在黑方底线说明检测或分类有误。这个验证方法不需要额外标注只用象棋规则就能筛出大部分错误。我自己的习惯是每次调整detectMultiScale参数后先跑一遍坐标映射看错误集中在哪些位置再针对性补负样本或调阈值。级联分类器不是黑匣子它的每一级、每个参数都能对应到具体行为只要你愿意花时间看训练日志和检测结果它就能稳定工作。希望帮到你。本文还有配套的精品资源点击获取
返回列表