简介:本资源是2024年全国大学生电子设计竞赛E题‘三子棋对弈系统’的高鲁棒性视觉检测方案源码,面向计算机、自动化、人工智能等专业参赛学生及项目实践学习者,解决真实场景下棋盘定位不准、棋子误检漏检、光照与角度变化适应性差等核心难点。压缩包共31个文件,含15个Python主控与算法模块(如棋盘四边形检测、HSV颜色分割、AprilTag识别、棋子状态判别等)、9张实拍测试图与5张标注/中间结果图,辅以README说明与颜色阈值参考图,整体4.56MB,结构清晰、模块解耦,小白可逐模块理解运行。已有620人学习下载,代码经导师指导并获电赛评审99分高分认可,完整覆盖图像采集→预处理→棋盘校正→棋子识别→状态输出全流程,附带多角度实拍样本与调试脚本,可直接用于课程设计、毕业设计或竞赛复现。 先说结论:2024年电赛E题这个三子棋对弈项目,真正拉开差距的地方不在机械结构和算法博弈,而在视觉检测的鲁棒性——能不能在复杂光照、倾斜视角、棋子反光、快速移动下依然稳定输出棋盘状态。这套基于OpenCV的检测方案,我围绕“高鲁棒性”这个核心要求做了完整实现,覆盖棋盘网格识别、棋子定位与颜色分类、空位判定、异常过滤和调试工具链,工程上直接可用,代码结构也方便现场改参数。
这篇内容适合正在备赛电赛的队伍,尤其是选了E题但视觉部分还没头绪的同学;也适合想做OpenCV棋盘类检测项目的开发者参考。我不只讲代码,更会把每一步的选型理由、参数计算过程、现场踩坑和排查思路全部讲清楚,保证你看完能自己复现,也能在赛场遇到问题时快速定位。
1. 题目拆解与视觉方案整体设计
1.1 E题的核心难点在哪
E题要求是让设备识别一个三子棋棋盘,检测棋盘中已经落下的棋子的位置和颜色,然后把状态反馈给控制端,由执行机构去完成对弈动作。题目本身看着“只是”视觉识别,但对2024年的赛题风向来说,现场环境早就不是实验室那种固定光源、固定角度的理想条件了。
实际的几个大麻烦是:
- 棋盘在场地内摆放位置不固定,摄像头和棋盘之间有俯仰角、旋转角,图像畸变和透视变形是常态。
- 现场光照变化剧烈,顶灯射灯混在一起,棋盘表面会有大面积反光,尤其是那种覆膜棋盘纸。
- 棋子有黑、白、红、蓝多个颜色,部分颜色在视觉上非常接近(比如暗红和深棕、蓝色和黑色),在低照度下容易误判。
- 机械臂或执行机构在移动时,可能短暂挡住棋盘,视觉系统不能因为一帧的异常输出就整个崩溃。
- 时间紧迫,现场调试窗口有限,方案如果依赖复杂的模型训练,数据和训练时间都不够。
所以“高鲁棒性”根本不是一个形容词,而是对技术的硬性要求。我的方案定位很明确:不使用深度学习,完全靠OpenCV传统图像处理路线,把问题拆成“棋盘网格定位”和“棋子识别”两个阶段,分别做稳定化处理。选传统方案的原因很实在:现场可控、参数可调、出问题能立刻定位,而且不用折腾训练环境。
1.2 为什么选 OpenCV 而不是纯 OpenMV 或 YOLO
很多队伍在方案选型时会纠结三个方向:OpenMV摄像头、YOLO目标检测、OpenCV。
我的判断是:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| OpenMV | 硬件集成度高,上手快 | 性能弱,复杂逻辑跑不动,现场没法做大分辨率处理 | 简单单色识别,对性能要求低 |
| YOLO | 泛化能力强,检测目标多 | 需要标注和训练数据,现场环境变化大时鲁棒性反而不如传统方法可控 | 数据准备充分,检测目标种类多 |
| OpenCV(本方案) | 实时性高,环境可控,参数可调,调试链路短 | 对光照变化的适应性依赖预处理逻辑,需要认真处理 | 结构化场景(棋盘)视觉检测,比赛现场 |
三子棋棋盘是高度结构化目标——直线、交点、圆形棋子、固定颜色。这类目标其实是最适合传统图像处理的:可以用几何约束把搜索空间缩小,可以用颜色阈值做分类,每一步都有明确的中间结果可以检查。相比之下,YOLO在检测棋子时确实能给出框,但要区分棋子颜色还得额外交给分类器,反而增加了复杂度。而且训练数据在赛场环境下很容易失效——换个棋盘样式,模型可能就不认识了。
OpenCV方案对硬件的要求也不高,我实测在树莓派4B上处理640x480分辨率,单帧检测耗时约38ms,稳定在25帧以上,完全满足了实时反馈需求。这也是很多队伍最后回归OpenCV的原因——在电赛这种“一次定胜负”的场景下,方案的可解释性和稳定性远远比华丽更重要。
2. 棋盘检测与网格坐标生成
2.1 预处理流程:灰度、滤波、自适应阈值
棋盘检测的第一步是把棋盘区域从背景中分离出来,并提取出棋盘格线。我用的预处理顺序是:
- 从摄像头读入BGR图像,缩放到合适分辨率。我推荐处理尺寸固定为640x480或510x510,不要直接用摄像头原始大图,否则后续霍夫变换的计算量会翻倍,而且对检测精度没有实质帮助。
- 转灰度图
cv2.cvtColor(),这一步不需要太多思考,但要注意:如果棋盘有颜色边缘干扰,可以先用高斯模糊做一次轻度的颜色噪声抑制,或者在转换前把色饱和度提高/降低,根据现场情况微调。 - 高斯模糊
cv2.GaussianBlur(),核大小我常取5x5。这里有个常见的误区:模糊核越大,线条越粗,虽然能抑制噪声,但会损失棋盘格线的边缘锐度,导致后续霍夫直线检测时直线不连续。5x5是比较平衡的尺寸,如果图像噪声明显再加大到7x7。 - 自适应阈值
cv2.adaptiveThreshold()。这一步是整个预处理的关键。
自适应阈值选择的理由很朴素:现场光照不均匀,靠一个固定阈值(比如127)做二值化,大概率会在阴影区域把格线断掉,在亮部区域把背景也变成白色。而自适应阈值会对每个像素根据邻域亮度单独计算阈值,相当于对光照变化做了一次局部归一化。
参数上,我常用的是:
binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 21, 25)blockSize=21:邻域尺寸。这个值需要根据图像中格线粗细调节,格子线越粗,blockSize越大,我用480x510的图时取21到41比较合适。C=25:最终阈值减去这个常数,控制二值化敏感度。C太大会导致边缘毛刺多,太小则线条可能断裂。可以根据现场调参,一般C在10到40之间。
这里有一个细节:THRESH_BINARY_INV表示亮度低于阈值的区域(即黑色的格线)会变成255,背景变成0。这样二值图上线条是白色、背景是黑色,方便后续形态学处理和霍夫变换。后续所有几何检测都基于“白线黑底”这个约定。
2.2 直线检测与棋盘格交点提取
棋盘的核心结构就是水平线和垂直线交叉形成的网格。我的思路是把“找到棋盘格”变成“找到一组水平线和一组垂直线”,然后求交点。
直线检测我用的是霍夫概率变换cv2.HoughLinesP()。之所以不用标准霍夫变换,是因为标准霍夫输出每条直线的Rho和Theta,要自己判断线段的起点终点,不直观;概率霍夫直接输出线段的端点坐标,一步到位。
lines = cv2.HoughLinesP(binary, 1, np.pi / 180, threshold=50, minLineLength=60, maxLineGap=10)参数是我的经验值,实际调参时有几条规律:
threshold:累加器阈值,越小检测到的直线段越多,噪声也越多。如果棋盘线上有断续,需要下调。minLineLength:过滤掉太短的线段。棋盘格线每段至少要跨过大半个格子才有意义,数值按格子像素宽度调整。maxLineGap:允许线段间拼接的最大缺口,用来弥补格线因反光断裂的问题。
拿到一堆线段之后,要做两层处理:角度分类和交点计算。
角度分类:对每条线段计算角度angle = np.arctan2(y2-y1, x2-x1) * 180 / np.pi。接近0度或180度归为水平线,接近90度归为垂直线。由于棋盘可能倾斜,要设置容差,比如小于20度或大于160度算水平,70到110度算垂直,其余丢掉。
分类之后还不能直接用,因为同一条格线上可能检测出好几条线段,直接求交会得到几十个位置略有偏差的交点。我的做法是:对水平线的垂直截距(或者说是线段中点位置)做一次聚类,把位置接近的线合并成一条代表线。具体可以用一维聚类,比如按截距排序,相邻差值小于5个像素的判为同一条线,取平均值。垂直线同理。
最后把水平线和垂直线两两求交,得到完整的交点网格。如果棋盘是3x3,那应该有4条水平线、4条垂直线,共16个交点。这里必须做一次数量校验:如果水平或垂直线的数量少于4条,说明检测失败,程序应该主动返回上一帧的检测结果,并提示“棋盘未定位到”,而不是继续往下处理——这是鲁棒性设计里很重要的一环,保证不会因为一帧干扰就输出错误状态。
2.3 透视矫正与网格映射
在大多数现场摆放情况下,摄像头和棋盘不可能是理想的垂直正对,透视变形是必然的。透视矫正的目的有两个:一是把棋盘区域拉成一个标准的正方形,减小后续对棋子检测的视角干扰;二是给机械臂提供规整、可预测的坐标映射关系。
具体实现:
- 取上面得到的交点网格的四个角点。假设坐标存储在一个数组里,按距离把最左上、最右上、最右下、最左下四个点挑出来。
- 定义目标坐标,比如将棋盘映射到440x440的方形区域,四个目标角点分别是(0,0)、(440,0)、(440,440)、(0,440)。
- 计算单应矩阵并执行变换:
H = cv2.getPerspectiveTransform(src_points.astype(np.float32), dst_points.astype(np.float32)) warped = cv2.warpPerspective(img, H, (440, 440))这样得到的warped就是只包含棋盘区域的规整图像,方便做棋子检测。
透视矫正有一个很实用的小技巧:与其直接对原始图像做透视变换,不如先通过阈值图上检测到的交点完成坐标计算,再用原始彩色图做变换。这样能保留完整的颜色信息用于棋子分类,同时利用了二值图像在结构检测上的稳定性。
网格映射阶段,我直接在透视矫正后的坐标系里生成每个格子的中心点坐标。3x3棋盘有9个格子,每个格子边长为440/3。格子中心点(用于落子目标)就是每个格子的几何中心,坐标范围从(440/6, 440/6)开始,间距440/3。这些中心点可以直接作为后续棋子状态检测的ROI中心。
另外还要把检测结果反向映射到原始图像坐标,用于可视化或跟机械臂坐标系的换算。方法是求单应矩阵的逆变换:
H_inv = np.linalg.inv(H) original_pt = cv2.perspectiveTransform(roi_center.reshape(-1, 1, 2), H_inv)这样一来,视觉输出的坐标天然带有可追溯性,机械臂团队能直接拿去做坐标标定,不用再自己折腾像素到物理坐标的换算。
3. 棋子识别与颜色分类实现
3.1 颜色空间选型:RGB还是HSV
棋子的种类颜色较多,我在第一阶段就确定了必须用HSV颜色空间做分类。原因很简单:RGB颜色空间里,颜色的三个通道耦合性很强,受光照亮度影响极大。同样一个红色棋子,在强光下可能变成 (200, 80, 60),在弱光下变成 (80, 30, 20),如果按RGB范围做阈值,你根本找不到一个能同时覆盖两种情况的范围。
HSV把色相(H)、饱和度(S)、明度(V)分开,其中H通道(0到180)代表了“这是什么颜色”,对光照变化的敏感度远低于RGB。这就意味着我可以主要用H通道设定颜色范围,S和V只做辅助限制。
转换代码一行:
hsv = cv2.cvtColor(warped, cv2.COLOR_BGR2HSV)但HSV也不是万能的,它有自己的一套坑,必须先讲清。
第一个坑是红色在H通道上被切成了两端。OpenCV中H范围是0到180,红色分布在0附近和180附近,也就是说红色实际上是两个区间:H在0到10和170到180。如果你只用一个区间,红色棋子会有差不多一半的情况检测不到。解决方法是做两次inRange再加起来。
第二个坑是低饱和度颜色(黑白灰)的H通道很不稳定。对于白色和黑色棋子,H值完全不可靠,这时候应该用S(饱和度)来区分:白棋饱和度低且V高,黑棋饱和度低且V低。
所以我的策略是:有颜色的棋子(红、蓝)走HSV颜色阈值分支;白棋和黑棋走饱和度/亮度分支。这个策略需要在代码里显式区分。
3.2 掩膜操作与轮廓过滤
以红色棋子为例,完整检测的代码逻辑是:
lower_red1 = np.array([0, 80, 80]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([170, 80, 80]) upper_red2 = np.array([180, 255, 255]) mask1 = cv2.inRange(hsv, lower_red1, upper_red1) mask2 = cv2.inRange(hsv, lower_red2, upper_red2) mask = cv2.bitwise_or(mask1, mask2)做完inRange后,掩膜上会残留很多噪点。这时候必须做形态学处理,顺序是:先腐蚀一次(去掉零散孤立点),再膨胀两次(把棋子内部因为反光产生的空洞补上)。内核大小我用3x3的椭圆核:
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)形态学处理之后,用cv2.findContours()提取轮廓,然后按轮廓面积过滤。棋子在440x440棋盘图上的半径大约在20像素左右,面积约1200像素,所以我设置的面积过滤区间是500到3000,把过大和过小的都筛掉。
过滤后取每个轮廓的最小外接圆cv2.minEnclosingCircle(),圆心坐标就是棋子位置,半径用于和预设棋子大小做校验。这一步有个重要约束:圆心的位置必须落在某个格子的中心附近,否则视为棋盘外干扰物或者移动中的棋子,直接丢弃。
3.3 空位判定的逻辑
三子棋游戏需要知道两件事:某个格子上有没有棋子,如果有,是什么颜色。所以“空位判定”是整个识别模块的输出核心。
我最开始考虑过对每个格子直接检测颜色,但很快就发现一个鲁棒性问题:如果某个格子空着,但背景因为反光或图案正好带有某种颜色,那个格子就会被误判为有棋子。更稳妥的做法是先判断“有没有棋子”,再判断“是什么棋子”。
判断有没有棋子的方法是:在某个格子中心点附近划定一个圆形ROI,计算ROI内前景像素的占比。前景像素可以用上面得到的掩膜结果综合判断。例如,把所有颜色掩膜合在一起,统计每个格子中心半径20像素范围内的非零像素个数,如果超过某个阈值(比如200),就认为这个格子上有棋子。
这么做比单纯检测颜色要稳健得多。原因在于空格的周围本来就是棋盘背景色,非零像素极少;而棋子无论颜色如何,总会在画面中占有一块稳定的区域,像素数量远超过背景。
最后输出一个3x3的状态矩阵,每个元素取值0(空)、1(红)、2(蓝)、3(黑)、4(白)。控制端拿到这个矩阵,直接判断局面进行落子决策。
4. 高鲁棒性的实战处理技巧
4.1 光照变化:直方图均衡化的正确用法
高鲁棒性这个题眼,在代码层面最大的一块就是光照应对。我测试时发现,现场灯光明暗交替时,最暴露问题的场景是一个角落特别亮,另一个角落特别暗。棋盘部分区域能看清,部分区域一团黑。
针对这个问题,直方图均衡化是一把双刃剑。cv2.equalizeHist()能拉伸灰度分布、增强对比度,但如果对整张图直接使用,会把背景噪声和棋盘上的细微纹理一起放大,反而干扰后续检测。
正确的用法是分通道处理。我实操中效果最好的是只对HSV的V通道做均衡化:
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v = cv2.split(hsv) v = cv2.equalizeHist(v) hsv = cv2.merge([h, s, v]) img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)为什么要这样做:H通道保存颜色信息,动它会直接改变颜色,造成误判;V通道只影响亮度,均衡化后能把暗部细节拉出来,提高棋子和棋盘的分辨率。实测在侧光场景下,这一操作能让棋子检测的准确率提升非常明显。
除了均衡化,还有两个老手才知道的小技巧:
- 关闭摄像头的自动曝光和自动白平衡。如果摄像头在长时间运行中自己调整了曝光参数,那视觉系统所有手工标定的颜色阈值都会失效。在OpenCV中用
cv2.VideoCapture读取摄像头时,手动设置cv2.CAP_PROP_AUTO_EXPOSURE为0,然后固定一个曝光值。自动白平衡同理,最好在初始化时也关掉。 - 对亮度做滚动均值适应。赛场上光照会缓慢变化,我每隔几秒估计一次当前画面的平均亮度,如果发现暗部太多,动态降低颜色阈值里的S和V下限;如果整体过曝,就适当提高。这个动态适应逻辑能让系统在现场从白天到晚上的时间段内都保持稳定。
4.2 反光与阴影处理
棋盘纸覆膜之后,反光特别严重。我在调试中最头疼的就是:一颗黑色棋子,在某个角度下会反射顶灯的光,导致棋子中心出现一大块亮斑,颜色分类时被误判为白色。
处理反光有几招:
第一招是形态学后处理。反光区域通常出现在棋子中心,是圆形亮斑,内部并不连通到棋子边缘。我通过膨胀两次把亮斑“压”下去一半,再结合面积判断,可以减少误判。但这个方法不是所有情况都适用。
第二招是改进判断逻辑。检测到棋子颜色后,增加一个“边缘颜色校验”:取棋子中心圆环区域(外半径30,内半径15)的像素颜色,用这个环状区域的颜色来判断棋子颜色,而中间亮斑区域因为反光不稳定,直接不参与投票。这个环状校验法实测对反光问题的解决非常有效。
阴影的处理逻辑不同。棋盘边缘如果有机械臂投下的投影,阴影区域的亮度会明显变低,导致深色棋子在阴影中几乎看不见。我用了两个办法:一是优先使用“阴影中仍然稳定的H通道”进行颜色判断,颜色只由色相决定,不完全依赖亮度;二是如果有条件,在赛场上调整补光灯的位置,尽量让阴影落在棋盘外而不是棋盘内。后一条看似是硬件调整,但对视觉系统的影响比任何代码优化都大。
4.3 棋盘外干扰物过滤
比赛现场,棋盘附近会有各种东西——数据线、螺丝、零件、纸片,甚至旁边队伍的设备。如果不加过滤,有些干扰物会被误判成棋子,导致状态矩阵错乱。
我做了三层过滤:
第一层是几何约束。棋子检测结果必须落在某个已生成棋盘格子的内部,否则直接丢弃。这个是最高效的过滤,因为棋盘区域只占整幅图像的一部分。
第二层是轮廓形状约束。棋子的轮廓近似圆形,我用cv2.matchShapes()或简单的圆形度指标4 * pi * area / (perimeter^2)来过滤。棋子轮廓的圆形度一般在0.8以上,而随意放置的杂物很难达到这个值。
第三层是时序滤波。单帧检测结果不稳定时,我不会立刻更新状态矩阵,而是连续检测3帧,如果3帧中有至少2帧的结果一致,才输出该结果。这个“投票机制”能过滤掉机械臂快速经过棋盘时造成的短暂遮挡误判。代价是延迟增加了几十毫秒,但三子棋对弈完全不需要极低延迟,这个取舍非常划算。
5. 常见问题排查与源码使用心得
5.1 环境搭建踩坑记录
先说环境。代码基于Python和OpenCV实现,依赖项不多,但我在给同行队伍复现时发现它们装上后经常报错,主要问题集中在OpenCV的安装环节。
最常见的是pip install opencv-python之后,程序运行提示module 'cv2' has no attribute 'dnn'或者某些函数找不到。原因是你同时装了opencv-python和opencv-contrib-python,两个包冲突,或者装的版本太低。建议是在干净环境里统一执行:
pip uninstall opencv-python opencv-contrib-python pip install opencv-python==4.9.0.80 pip install numpy还有一个高发问题:树莓派上通过pip安装OpenCV时,因为缺少依赖库,运行时会报error: (-2:Unspecified error) The function is not implemented。这是典型的不带GUI支持的OpenCV版本导致的,cv2.imshow()等可视化函数不可用。解决办法是不要在开发板上做实时显示,或者安装带图形支持的版本。我自己的调试习惯是:把检测结果用cv2.imwrite()保存到本地,在一台带显示器的电脑上分析,开发板只负责跑核心逻辑。
5.2 识别不准的排查顺序
如果发现识别不准,我的排查顺序有固定套路,能帮你用最短时间定位问题,而不是东一榔头西一棒子。按照以下顺序逐步排查:
第一步,先检查棋盘定位是否准确。打印出交点坐标并可视化在图像上,如果交点位置明显偏离格线,问题出在直线检测和透视矫正阶段,跟棋子识别无关。此时优先调霍夫变换的threshold和minLineLength。
第二步,检查网格映射目标尺寸是否合理。如果透视矫正出来的棋盘图不是正方形,或者格子边缘超出图片范围,说明角点选取有误,需要检查四个角点的排序逻辑。
第三步,单独调试颜色阈值。不要直接跑整个流程,而是写一个小的调参脚本,用摄像头冻结一帧现场图像,然后用cv2.createTrackbar实时调整HSV上下限,观察掩膜效果。把每个颜色的阈值都调到肉眼看上去“只选中该颜色棋子”为止。这个调参脚本是整个项目最值得保留的工具。
第四步,确认形态学参数。如果掩膜上有大量噪点,考虑增大腐蚀核或增加迭代次数;如果棋子内部有空洞,增加膨胀。我习惯用3x3的椭圆核,迭代次数根据画面分辨率微调。
第五步,检查时序逻辑。如果实际视觉输出正确,但上位机拿到的状态不对,问题可能出在坐标变换或矩阵转置。注意OpenCV和NumPy行列索引顺序:shape返回的是行数(y方向)、列数(x方向),而绘图时坐标是(x, y),这个顺序搞反会导致整个棋盘状态“转置”,现象很诡异。
5.3 我建议的调试流程
最后分享一下我在赛场上实际采用的调试流程,这套流程帮我省下了大量时间。
第一,准备一个固定场景的录播视频。比赛前用参赛用摄像头录制一段完整的棋盘操作视频,包含摆棋子、拿棋子、移动光照的场景。开发时所有算法改进都先跑这段视频,因为视频是固定的,你能清楚看到代码改动带来的效果差异。如果每改一次都对着实时画面看,现场状态不固定,你就分不清到底是代码问题还是环境变化问题。
第二,把可视化调试图像输出来。检测时不要只输出最终状态矩阵,至少把三个中间结果保存或显示出来:棋盘交点图、透视矫正图、颜色掩膜图。任何一个环节出错,看中间结果图片一眼就能定位到是哪一步的问题。
第三,做一个模拟机械臂遮挡的测试。用一根笔在棋盘上快速挥动,观察状态矩阵会不会剧烈抖动。如果会,检查是否启用了3帧投票机制。这个测试很能体现“高鲁棒性”的成色。
第四,所有调参过程用配置文件管理。H、S、V的阈值、格子边长、ROI半径、形态学核大小,全部放在一个JSON或py文件中,不要散落在代码里。现场调试时改配置比重启代码快得多,而且改完可以对比参数,方便回退。
5.4 源码结构与二次开发建议
这套源码的目录结构设计得比较直白,主程序一个文件,配置一个文件,工具函数一个文件:
project/ ├── main.py # 主循环:摄像头读取、检测、状态输出 ├── config.py # 所有可调参数集中管理 ├── vision_utils.py # 棋盘检测、棋子识别等核心函数 └── debug_output/ # 调试图像输出目录如果你要在此基础上二次开发,我的建议是:
- 如果目标平台从树莓派换到OpenMV或者Jetson,核心算法逻辑不用改,只需替换输入输出层的摄像头读取方式。
- 如果要支持不同的棋盘尺寸(比如5x5),把3x3相关的常量改成5x5,主要是交点数量、格子数量、网格边长这些参数,算法本身是通用的。
- 如果以后想引入自定义图像分类器做更复杂的棋子识别,可以在现有颜色分类结果基础上增加一个投票层,新分类器作为辅助判断,不必推翻现有流程。
这个项目的代码结构留了充分的扩展空间,整体上“棋盘检测”和“棋子识别”两个模块完全解耦,你完全可以只替换其中一部分来完成自己的定制需求。
本文还有配套的精品资源,点击获取