☰
基于OpenCV的实时棋盘与棋子识别:传统视觉方法实践
2026/10/21 5:19:38 网站建设 项目流程

最近在做一款桌面级的棋类对战小Demo,需求非常直白:用普通USB摄像头对着棋盘,实时识别当前局面——具体来说,就是感知3x3九宫格棋盘上哪些格子有棋子,以及棋子是哪一方的。这个任务拆开看就是两个经典方向:棋盘检测和棋子识别,而难点在于它们要在实时视频流里协同工作。我基于Python和OpenCV把这条链路完整跑通了,普通笔记本上不加任何优化也能稳定达到八九帧的处理速度,识别一张局面的耗时可以控制在数十毫秒。整个项目没有用任何深度学习模型,全靠传统计算机视觉手段,因为在这个场景下传统方法已经足够快、足够稳,而且每一步都可以可视化调试,特别适合作为摄像头交互类项目的起点。

如果你也在做类似的工作——不管是下棋机器人、课堂棋类教学辅助、或者只是想学一下OpenCV的“定位-矫正-分割-分类”这个标准套路,这篇记录应该能帮你省不少弯路。我会把从环境准备到九宫格切格、从棋子颜色分割到实时状态管理的过程完整讲一遍,重点放在那些文档里不会写的坑和数据阈值选择背后的逻辑。

1. 项目目标与整体方案设计:为什么传统视觉够用

开始写代码之前,先把需求想明白。这个项目的核心任务可以分解成四个子问题:如何在画面里找到棋盘、如何把倾斜的棋盘转成正面视角、如何把棋盘均匀切成九个格子、如何在每个格子里判断有没有棋子以及棋子属于哪一方。

听起来不复杂,但每一环都有陷阱。比如棋盘在画面里是倾斜的,直接按像素坐标切割格子一定会出错。再比如棋子和棋盘背景颜色接近时,单纯做颜色阈值会把格子线也当成棋子的一部分。所以我从一开始就把流程设计成了流水线:定位棋盘 -> 透视矫正 -> 切分网格 -> 格子级棋子识别。每一步的输出都可以单独可视化,调试时哪里不对就看哪一环。

技术选型上我也有过犹豫。要不要直接上深度学习目标检测?比如训练一个YOLO模型,让它同时框出棋盘和棋子?我认真评估过,最后放弃了。原因有三:第一,没有现成的适合自己棋盘样式和棋子配色的数据集,自己标注九宫格和棋子,工作量不小;第二,桌面摄像头场景相对固定,棋盘位置、棋子颜色、数量都是可控的,传统视觉方法的精度完全够用;第三,实时性要求不低,传统方法在CPU上就能跑,而轻量级模型推理虽然也能做,但部署链路明显更复杂。

所以最后的技术栈非常简单,OpenCV库加Python环境就够了。OpenCV作为开源计算机视觉库,自带了图像处理、形状检测、颜色空间转换和摄像头采集等今天需要用的全部能力。整个项目也再次印证了一个观点:工具选型不必追新,能解决当前场景的约束条件才是第一位。

2. 棋盘检测:从复杂背景里把九宫格抠出来

2.1 预处理:让棋盘轮廓从背景中浮出来

棋盘检测的第一步不是直接找格子,而是先找棋盘最外框。九宫格棋盘通常是一个矩形边框,内部横竖各两条线。如果你从摄像头画面里直接做边缘检测,结果会非常杂乱,因为背景里的桌子边缘、键盘轮廓、笔筒阴影都会一起出现。

所以预处理的核心目标是:把棋盘区域和背景的对比度拉大,同时消掉无关纹理。具体操作流程如下:

第一步,把BGR彩色图像转成灰度图。原因很简单,后续的形态学和轮廓检测都基于单通道图像,灰度化可以减少计算量,而且棋盘边框的亮度特征在灰度图上已经足够明显。

第二步,用高斯模糊抑制噪声。核大小我选的是5x5,这个参数不用太纠结,太小起不到降噪效果,太大又会让边缘变胖,影响角点精度。

第三步,是二值化。我实测发现固定阈值很容易受环境光照影响,上午的阳光和晚上的台灯会让同一个固定阈值失效。所以用了自适应阈值,它会把每个像素和周围小区域的亮度做比较,光照不均匀的地方也能得到一个相对干净的轮廓图。

第四步,做形态学膨胀和闭运算。膨胀让断裂的棋盘边缘线条连起来,闭运算填补边框内部的细小空洞。这一步属于经验操作,很多初学者会跳过,但实际效果非常显著。

预处理做完之后,棋盘外框会变成一张二值图里非常显眼的白色闭合区域。

2.2 轮廓筛选与多边形逼近:找到棋盘的四条边

处理好图像后,就可以用OpenCV提供的findContours函数来提取轮廓了。但是找到的轮廓会有很多个,里面混杂着各种背景物体,怎么确定哪一个才是棋盘?

我采用的判断策略比较朴素,但实测相当可靠。棋盘在画面中长期保持较大面积,所以可以先找出轮廓中面积最大的一个候选者。但光看面积也有风险,比如桌面上摊开的书本或者笔记本屏幕也可能面积很大。所以还要再加一道形态约束:对每个候选轮廓做多边形逼近,得到多边形顶点之后判断它是否是一个四边形。

九宫格棋盘在透视视角下无论如何倾斜,最外框永远是一个凸四边形,四个顶点的关系是一个近似矩形。具体到OpenCV函数就是approxPolyDP,这个函数会根据设定的精度把轮廓折线化。我这里把epsilon设成轮廓周长的百分之二,经过几次简单测试,这个值在大多数摄像头距离下都能稳定输出四到六个顶点。如果得到的顶点数目等于四,并且这个四边形的面积同棋盘区域目标尺寸估算匹配,我就认为找到了棋盘区域的锚点。

这里还有几个我自己踩出来的补充条件。如果画面中除了棋盘还有其他矩形物体,建议增加一个"IsSquare"的判断,也就是说四边形的两组对边在长度上不应该相差太远。另外,多边形的四个顶点必须按照左上、右上、右下、左下的顺序排列,这一步极其关键,排错顺序后续的透视变换就会完全错乱。

2.3 透视矫正:把倾斜的棋盘拉成规整的方形

上一节讲到通过多边形逼近得到了棋盘外框的四个角点,但此时如果直接拷贝这个区域,得到的棋盘还是歪的。因为摄像头通常放在斜上方,棋盘的投影是一个不规则的四边形。如果我在这个歪斜的图像上直接按比例切格子,中间的格线和边界就无法对齐,后续对每个小格子的判断就会出大问题。

解决这个问题的方法是透视变换。透视变换做的事情,本质上就是把原图上的一个四边形投影到另一个四边形。在OpenCV里需要两套坐标:源坐标是刚才提取到的四个角点,目标坐标是预先定义好的正四边形,比如我定义成480x480像素的正方形。这样一台映射,摄像机视角造成的梯形形变就会被拉正。

对应实现就是两个函数:先用getPerspectiveTransform算出3x3变换矩阵,再把原始图像传入warpPerspective执行投影。这一步的输出,是一张正面视角下的棋盘图,横平竖直,每条格线都变成了容易定位的水平和垂直线。

在实际操作中,有一点特别值得注意:四个角点的顺序必须在计算透视矩阵前统一。OpenCV没有内置帮你排序的函数,需要自己写一个逻辑按横纵坐标判断,我通常的做法是先按x坐标分左右两组,再在每一组内按y坐标分上下。虽然现在有各种排序的奇技淫巧,但这种传统做法最稳。排序错了,整个棋盘会像被强行拧了一圈,识别结果自然全面崩溃。

2.4 切分九宫格:如何稳妥地拿到每个格子的坐标

棋盘变成480x480的方正图之后,切九宫格就变成了一件极其简单的事。每个格子的边长就是总边长除以3,也就是160像素。我可以直接按像素坐标把棋盘区域切成三行三列,每个格子对应一个感兴趣区域。

但我不会真的把九个格子图像都拷贝出来,那样会浪费内存和计算量。更优雅的做法是提前算好九个格子的中心点坐标,放到一个列表里。后续识别每个格子是否有棋子时,我只需要让程序在对应的中心点附近取一个小区域做颜色分析就够了。

这里有一个值得强调的细节。虽然透视矫正已经让棋盘变得规整,但摄像头画面和棋盘之间并不是绝对静止的。如果手肘不小心碰了一下摄像头或者桌面震动,棋盘在画面中的位置可能会产生几像素的偏移。在切格之前我通常会加一个可选的微调逻辑,也就是用边缘检测继续找一次棋盘内围的竖线和横线,把它们的位置取平均作为格线的最终坐标。这样做的好处是减少了对固定角点坐标的依赖,即使棋盘轻微滑动也不会让切格位置产生明显偏差。

这一套流程走完,棋盘就已经被数字化了。接下来要解决的问题是棋子识别。

3. 棋子识别:颜色分割与轮廓判定的完整链路

3.1 颜色空间选择:为什么非要用HSV

识别棋子的首选方案是看颜色,九宫格对弈一般就两种棋子颜色,黑白最常见,也有用红黄或者蓝红的。要在摄像头画面里判断一个像素是黑色、白色还是红色,很多人第一反应是在RGB空间设置阈值,比如判断R值大于多少、B值小于多少。但这样写出来的代码,遇到环境光偏暖或者偏冷时就容易失效,因为RGB的三个通道在光照变化下是一起变动的。

我把颜色判断全部转换到HSV空间来做。HSV把颜色拆成了三份独立的维度:H代表色相,就是颜色的本质;S代表饱和度,就是颜色的鲜艳程度;V代表亮度。切成这三个维度之后,光照对V影响最大,但色相H和饱和度S相对稳定。比如一张红色的棋子,无论室内光强还是光弱,它的H值都落在红色区间,这就可以把识别逻辑和亮度解耦。

具体到OpenCV的实现只需要一行cvtColor函数,把BGR图像转成HSV格式。之后我针对每种颜色建立一组阈值范围,用inRange函数生成一个掩码图,掩码中白色区域就是符合该颜色特征的像素。

3.2 逐格判断:面积占比与中心区域裁剪

有了HSV颜色掩码之后,很多人会想直接在整张棋盘图上做轮廓提取。但这样做会遇到一个比较尴尬的问题。如果棋盘上有两个相同颜色的棋子,它们在二值化图像里会变成独立的连通区域,这还好。但如果两颗棋子靠得太近,或者在某一视角下发生了部分遮挡,findContours可能把它们合并成一个轮廓,计数就会出错。

为了避开这种耦合问题,我把识别逻辑做了强制隔离:不在一整张图上找所有棋子,而是老老实实对九个小格子逐个分析。既然棋盘已经被切成九个格子,每个格子的中心坐标已经计算好,我只需要在每个中心点周围取一个半径整齐的区域,在这个局部小窗口内计算颜色掩码。

单个格子的判断逻辑要简单得多,我将局部区域内的掩码白色像素数量除以窗口总面积,得到一个“颜色覆盖率”的指标。如果覆盖率超过阈值,说明这个格子里存在某种颜色的物体,再结合该颜色对应的掩码类型确定棋子类别。如果覆盖率低于阈值,则判定为空。

单独设置一个中小面积阈值也很重要。棋盘上的格线、木纹、灰尘都会产生一些杂散颜色像素,如果阈值设太低,空格很容易误判成有棋子。我经过多轮实测,覆盖率阈值的合理区间大概在10%到25%之间,具体取决于棋子大小和摄像头距离。

3.3 轮廓校验:防止噪点和棋盘线干扰

单纯靠颜色覆盖率并不是一个足够稳健的方案,还需要再加一道形状层面的约束。做法很简单,我在局部窗口的掩码上调用findContours找出所有连通区域,然后从这些区域里选取面积最大的那个轮廓,检查它是否接近圆形。

为什么要检查圆形?因为棋子从正上方视角看就是圆形,而格线的交叉点、反光斑块等干扰物的轮廓往往是不规则的长条形或者块状。通过计算轮廓面积和外接圆的面积比值,能快速筛掉这些明显不符合棋子形状的噪声。这个比值越接近1,轮廓就越接近圆形。我通常要求比值不低于0.4,这已经能非常有效地排除掉格线交叉点这类细长噪声了。

经过颜色覆盖率和轮廓圆形度双重检查,识别结果会稳定很多。并且在进入下一轮判断之前,我还会检查一个容易被忽略的细节——棋子的轮廓中心位置是否偏离了格子的中心太远。如果棋子被放在格子边缘甚至压着格线,那么它可能不应该被记录为这个格子的有效棋子。这个约束结合上文提到的中心区域裁剪,可以让整个系统在棋盘上有散落杂物或者棋子摆放不规范时不至于横生枝节。

3.4 亮度反光与阴影的对抗处理

摄像头识别棋盘时最大的现实考验就是光线。白色棋子表面在灯光照射下会形成高光区域,而黑色棋子容易和深色桌面背景融合。应对高光,我用的方案是拆分判断:白色棋子不看H,因为它的H值在光照改变时不稳定,更可靠的是低饱和度加高亮度的组合,也就是S值很低且V值很高。配合中值滤波把高光点的孤立噪声揉平,白色棋子的识别在大多数场景下都很稳。

应对阴影,则是借助局部窗口而不是全图基准。当一个格子里有棋子,它在自然光下确实会产生阴影,但这个阴影通常只在棋子轮廓的一侧,不会覆盖整个格子。用面积覆盖率而不是绝对阈值去判断,在一定程度上天然免疫了阴影带来的影响。如果阴影实在太重,还可以考虑把HSV判断改成在ROI局部做差分法,用当前帧和背景帧做减法。不过我的项目里没有走到那一步,颜色加轮廓方案已经够用了。

4. 实时视频流处理:帧管理、状态机与性能调优

4.1 控制采集参数,别让每一帧都跑满全套

摄像头采集视频流的默认配置往往是30帧每秒,分辨率可能是1280x720甚至更高。如果每一帧都执行完整的棋盘检测和九个格子识别,耗时会非常惊人。更合理的方案是调整采集参数,把分辨率降到640x480,这个精度对棋盘识别来说已经足够。因为后续还要做透视矫正,更高的原始分辨率并不会显著提升识别准确率,反而白白拖慢处理速度。

同时我在创建VideoCapture对象之后,建议用set方法关闭摄像头的自动白平衡和自动曝光。自动曝光会导致画面亮度不断跳动,颜色分割阈值很难在一个固定区间内工作。锁死这些参数之后,整个识别流程面对的输入图像会稳定很多。这个细节如果配置得当,会让棋子的HSV阈值调一次就足够。

4.2 降帧策略:不是每秒30帧都要处理

即使分辨率降到640x480,每个完整周期还是要经历灰度化、二值化、轮廓提取、透视变换、九格颜色分析等等步骤。在我那台普通笔记本上实测,完整跑一遍大约需要70到100毫秒,这意味着即使用最好的参数,也只能达到每秒十帧左右的处理速度。对于棋盘识别这类低频交互任务,每秒十帧的刷新率完全够用,根本不需要每帧都分析。

我的做法是建立了一个简单的抽帧机制。在视频循环里只对每一帧画面做棋盘定位,比如隔三帧跑一次完整的识别链路,其他帧只读取摄像头数据丢弃或者绘制上一次的识别结果。这样用户看到的是流畅的视频预览,而系统内部保持着稳定且不过载的计算压力。这个方法对任何实时视觉项目都适用,值得养成习惯。

4.3 状态机与连续帧稳定:防止棋子识别结果抖动

实时识别最大的痛点在于,单帧结果经常出现抖动。比如某一帧黑色棋子的高光特别强,导致面积覆盖率掉到阈值以下,该格子被误判成空;下一帧光线恢复正常,又识别到黑棋。如果直接把每一帧结果都输出,下游逻辑会看到一个不断闪烁的棋盘状态,非常难用。

我的解决方案是维护一个棋盘状态对象,里面记录上一次稳定识别的3x3矩阵。每次完成新的识别后,不急着覆盖状态,而是先和上一次的结果做比较。只有连续超过三帧的结果保持一致,才确认状态变更。如果只是偶发的一帧波动,系统会继续沿用上一次的稳定结果。这个过程其实就是软件工程里非常经典的稳定降噪机制,放在视觉识别场景里也一样有效。

这里还可以再补充一个触发事件的细节。当确认某个格子的状态发生变化之后,系统就可以生成一条“落子事件”,比如记录当前格子坐标、棋子颜色和时间戳。这个事件可以继续派发给下游的规则引擎,用来判断胜负或者记录步数。

4.4 可视化调试:把中间结果都显示出来

调试这类视觉项目,最忌讳闭着眼睛盲调参数。我的做法是从预处理灰度图开始,到二值化图、透视矫正图、每个格子的掩码图、最终的识别结果绘制,全部都要能在调试窗口里逐级查看。

具体实现就是在主循环里把几张小图用np.hstack横向拼接在一起,再通过OpenCV的imshow函数显示。这样棋盘定位失败、透视矫正变形、棋子颜色漏检以及轮廓误判这几个典型问题,都能一眼定位到具体环节。我在实际开发中至少有一半的时间花在这些调试视图上,它们比任何日志都好使。

5. 实测中的高频坑与对应解法

5.1 棋盘角点检测失败怎么办

如果摄像头画面里棋盘只占了一小块区域,或者背景里有大量高纹理物体,轮廓筛选很容易把棋盘和背景粘连成一大块连通区域,导致多边形逼近后得到的顶点数目根本不是四。这种情况下,我的第一反应是先检查棋盘是否完整入镜,第二个检查点是棋盘外框线条是否清晰。

如果线条本身比较淡,可以在预处理阶段考虑把自适应阈值的邻域范围调大,或者在校正阶段改用Canny边缘检测配合Hough直线检测来增强棋盘边框的存在感。还有一个更实际的经验,就是给棋盘加一个深色粗边框,或者把棋盘放在素色桌面上和背景拉开亮度差距。在开发阶段这种物理手段往往比调参更高效。

5.2 棋子和格子线粘连,轮廓合并

某些材质棋子表面比较光滑,在画面上会反射出棋盘格线的投影。此时对某个格子做颜色掩码,可能得到一个大轮廓,它既包含棋子又连上了格线。这种轮廓的圆形度会很差,就可能被误杀成非棋子。我做过一个调节方案:对掩码图像做几次开运算,也就是先腐蚀再膨胀,目的是把细长的格线连接断开,只保留相对独立的棋子圆形块。

如果开运算效果还不够,我会把局部窗口缩小,让分析区域尽量只覆盖棋子的中心部位,而不包含边缘格线。这两种手段搭配使用,基本能解决九成以上的粘连问题。

5.3 性能没有达到预期时优化从哪里下手

如果你把整个处理流程写完之后发现帧率很低,我的建议是先做分步计时。在代码里用time模块包住每个阶段,看耗时到底落在哪一块。绝大多数情况下,耗时大户不是颜色判断,而是对整张图做的高分辨率轮廓提取和透视变换。

可以优化的方向有两个。一是把灰度化和轮廓提取分辨率降低,比如先把原始帧缩小一半再做棋盘定位,得到角点后再用原始坐标映射回全分辨率棋盘。二是只在九个格子的小窗口内做颜色分析,避免在整张矫正图上反复扫描。经过这两步优化,我的处理帧率提升了两倍以上。

5.4 多摄像头和不同的棋盘尺寸适配

调试过程中我换过一次摄像头,还试过不同尺寸的九宫格棋盘。旧的摄像头视角窄,棋盘需要放得很远才能完整入镜,棋盘在画面里面积小,识别的稳定性就差。换了广角稍大一点的摄像头之后,棋盘可以做得很近,格子变大,棋子覆盖率提高,整体识别率明显上升。

棋盘尺寸的变化则影响切格逻辑。如果一个棋盘的外框比例本来就接近正方形,那么透视矫正后按三等分切割没有问题。但如果棋盘不是正方形,就应该在切分时把宽度和高度分别除以3,不要假设长宽相等。这些参数我把它们全部做成了配置文件常量,换棋盘时只需要改几个数字。

6. 从棋盘识别到完整对弈系统的扩展思路

九宫格棋盘检测和棋子识别本身可以看作一个视觉感知模块,这块稳定之后,上游和下游都有很多值得扩展的方向。

上游可以做摄像头标定和自动棋盘对齐。如果摄像头安装位置是固定的,可以把棋盘定位结果缓存起来,每次启动时只做一次透视变换,之后就可以直接跳到棋子识别,减少大量计算。如果摄像头可以移动,则可以在画面中持续追踪棋盘角点,并动态更新透视矩阵,让系统在移动场景下也能正常运转。

下游最自然的扩展是搭建一个对弈规则引擎。识别到3x3矩阵状态之后,可以很容易地判断出棋盘上是否出现三子连线,并据此判定胜负。也可以结合一个简单的启发式AI来计算电脑的下一步落子位置,再用视觉反馈验证AI落子后的局面变化。这样一套系统,就能形成从“眼睛”到“大脑”再到“手”的完整闭环,这也是我后续准备继续深挖的方向。

另外,如果项目场景从九宫格升级到五子棋或者围棋,只需要把切格逻辑改成对应的行数和列数,同时把棋子颜色改成黑白两种经典配置,整套架构几乎可以平移复用。对于更复杂的棋子类型识别,比如多套花色或图案不同的棋子,传统颜色分割会到瓶颈,届时再考虑引入轻量级图像分类模型也不迟。

最后分享一个我个人的开发体会:这类视觉项目最容易犯的错误,是想一口吃成一个胖子,上来就想让系统在复杂背景下完美工作。实际上,把识别链路拆成定位、矫正、切格、分类这几个环节,每一步都用可视化工具确认输出质量,再把它们串联起来,才是效率最高的路径。摄像头实时识别并不神秘,它本质上就是一个稳扎稳打的图像处理流水线加上合理的状态管理。希望这篇分享能帮你在自己的棋类识别项目上少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询