☰
OpenCV围棋识别实战:棋盘定位、棋子检测与参数调优全攻略
2026/10/7 6:23:35 网站建设 项目流程

简介:这是一份基于Python与OpenCV的围棋棋盘棋子识别项目,面向毕业设计、课程设计与项目开发场景,适合需要完成图像处理或计算机视觉课题的本科、高职学生,也适合OpenCV初学者学习棋盘定位、棋子检测与图像预处理的基本流程。项目包含可运行的识别源码与说明文档,源码经过严格测试,可在其基础上做功能延伸。压缩包共40个文件,包括2个Python脚本、1个Markdown说明文档,以及37张jpg、png图像素材;两个脚本分别对应不同测试流程,图片可用于观察不同棋盘状态和光照条件下的识别效果,整体大小约4.83MB,目录结构清晰,便于按图像素材与代码模块对照学习。目前已有283人浏览学习。读者拿到后可快速了解完整的识别思路,借助配套图片复现结果,并结合文档进行二次开发,节省从零搭建环境与调试算法的时间。

1. 基于 Python 和 OpenCV 的围棋棋盘棋子识别:这套源码到底怎么跑通

如果你拿到的是一份名为21_0629_opencv_python_weiqi-master的压缩包,里面躺着两个测试脚本、一堆img_temp.jpg和checkerboard目录,那大概率就是这套围棋识别项目了。它解决的是一件很具体的事:给定一张包含围棋棋盘的图片,用 Python 和 OpenCV 判断棋盘在哪、棋子落在哪些交叉点上,以及每颗子是黑还是白。这套源码的价值不在于算法多前沿,而在于它把图像处理里最常用的几个手段——灰度化、边缘检测、霍夫变换、轮廓查找——串成了一个完整可跑的流程,非常适合毕业设计、课程设计这类需要「能演示、能讲解、能交差」的场景。

我拆过不少同类项目,先说结论:这份源码能不能直接用,取决于你手里的照片是什么样的。如果是正对棋盘、光照均匀的图,跑通的概率很高;如果是斜着拍的、有反光的、背景杂乱的,你就得动参数、加预处理,甚至改检测逻辑。后面几章我会把两个测试脚本逐个拆开讲,把它做了什么、参数怎么调、坑在哪都说明白。适合谁看?打算做图像识别课设、需要快速搭一个棋盘检测 Demo、或者单纯想学 OpenCV 实战流程的开发者,这篇都值得你跟着跑一遍。

2. 先搞清楚识别流程:从读图到输出棋盘点位分几步

2.1 这个项目的整体架构:两个测试脚本分别干什么

压缩包里有21_0629_opencv_python_weiqi_test1.py和21_0629_opencv_python_weiqi_test2.py两个脚本,这是拆解整个项目最直接的入口。按照我看到的代码组织方式,第一个脚本通常承担「棋盘定位」的职责,也就是从一张任意图片里找到棋盘区域,并把棋盘网格的交叉点坐标算出来;第二个脚本则是在棋盘已经定位的基础上,进一步检测每个交叉点附近有没有棋子,以及棋子的颜色。

这两个脚本对应的处理思路不同,也体现了 OpenCV 做这类任务的两条典型路线:一条是几何驱动,靠边缘检测和直线拟合找棋盘网格;另一条是区域驱动,靠灰度分布和圆检测找棋子。实际做课设答辩的时候,你可以把这两段流程分别演示,一个讲「我怎么把棋盘从图片里抠出来的」,另一个讲「我怎么知道这个点上有黑子还是白子」,逻辑非常清晰。

两条脚本共享同一批测试图片,比如img_temp.jpg、img_temp1.jpg、src.jpg、3.png这些,说明作者在开发时是用多张不同光照、不同角度的图来验证的。你拿到手后,第一次运行建议先用img_temp.jpg,这张图大概率是作者调试得最顺的一张,作为基线跑通后再换其他图验证泛化性。

2.2 棋盘定位的核心链路:灰度化、边缘检测、轮廓查找和直线拟合

先看棋盘这一步。常见做法是先用cv2.cvtColor把彩色图转成灰度图,因为后续的边缘检测和霍夫变换都只处理单通道数据,转灰度既能减少计算量,又能避免颜色干扰。接着用cv2.GaussianBlur做高斯模糊,这一步非常必要,拍照产生的噪点会在边缘检测阶段被放大成大量碎片边缘,不提前平滑一下,后面找直线会找到几十条杂乱线段。

模糊之后是cv2.Canny边缘检测。Canny 的两个阈值(低阈值和高阈值)是这个环节的核心参数:高阈值决定一条边缘必须强到一定程度才被保留,低阈值用于连接断裂的边缘。对棋盘这类黑白格子交替、边缘锐利的物体,我一般会把高阈值设在 150~200 之间,低阈值设在 50 左右,具体数值要看图片的分辨率和光照。

边缘图出来后,就要找棋盘的外边界了。这里有两种实现思路:一种是直接cv2.findContours找轮廓,然后用cv2.approxPolyDP做多边形逼近,去找四边形轮廓作为棋盘候选;另一种是用cv2.HoughLinesP检测直线,再把直线聚类成交点,从而推导网格结构。从源码的文件命名习惯看,大概率是轮廓优先,因为后续还要配合角点检测来矫正透视,而轮廓逼近正好能给出四个角点。

从工程角度说,这两种方案都有坑:轮廓方案怕的是棋盘和背景颜色相近,导致轮廓断裂;直线方案怕的是棋子本身也产生大量直线干扰。对于课程设计来说,先跑通轮廓方案是性价比最高的选择,因为它代码量小、调试直观。

2.3 棋盘网格的定位策略:交叉点坐标是怎么算出来的

检测到棋盘区域只是第一步,真正的难点在于把 19×19 的网格交叉点坐标算出来。如果图片是正对棋盘拍的,网格线是水平和垂直的,那可以用霍夫变换找直线,按角度聚类分成横竖两组,再求交点。但如果图片带透视,棋盘是倾斜的,这个方案就不灵了。

更稳妥的做法是拿到棋盘四个角点后,用cv2.getPerspectiveTransform做透视矫正,把棋盘区域拉正,再去算网格点。具体来说,矫正后棋盘变成标准的矩形,你可以把四条边等间距切分来估算交叉点:假设棋盘边长为 W 像素(矫正后的),那么第 i 行第 j 列的交叉点坐标大约是(W * j / 18, W * i / 18)。这个做法在矫正准确的情况下误差很小,而且是纯几何计算,不需要额外调参。

源码里是否做了透视矫正,可以从 ReadMe.md 里的说明判断。但我建议你拿到手后,第一步就是确认这一点:如果脚本里没有矫正环节,那你用斜着拍的图测试时,检测结果会严重偏移。这时候你需要补上角点检测和透视变换的代码,这部分我会在最后一章给出一个可以直接用的扩展思路。

3. 把源码跑起来:环境配置、两个脚本的执行过程与输出解读

3.1 环境准备:Python 版本和 OpenCV 安装的注意事项

这个项目基于 Python 和 OpenCV,本身依赖不算复杂,但环境搭不对会浪费大量时间。先说 Python 版本,我用 Python 3.8 跑这类兼容 OpenCV 的老项目最稳,3.10 以上版本偶尔会遇到 NumPy 接口兼容的坑,如果源码里用了np.float这类旧接口,在新版本里会直接报AttributeError。

OpenCV 的安装是另一个高频翻车点。很多人执行pip install opencv-python之后,import cv2报ModuleNotFoundError,或者报cv2.error: OpenCV(4.4.0)这类 C++ 层错误。前者多半是装到了不同的 Python 环境里,后者多半是包版本冲突。我的习惯是新建一个虚拟环境再装:

python -m venv weiqi_env source weiqi_env/bin/activate # Windows 下用 weiqi_env\Scripts\activate pip install opencv-python opencv-contrib-python numpy matplotlib

opencv-contrib-python包含了cv2.xfeatures2d这类扩展模块,虽然本项目不一定用到,但毕设答辩时如果你要现场加一个特征匹配的演示,有它就不需要重新装包。装完后用下面这段代码验证环境是否正常:

import cv2 import numpy as np print(cv2.__version__) print(np.__version__)

能正常打印版本号,说明环境没问题。如果打印cv2.__version__报错,先执行pip list看opencv-python是否在列表里,不在就重装;在的话就检查当前 shell 使用的是不是虚拟环境的 Python。

3.2 运行 test1 脚本:棋盘检测的代码逻辑与输出文件

第一个脚本21_0629_opencv_python_weiqi_test1.py是棋盘检测的主脚本。我拆解后的典型流程是这样的:读取图片 → 灰度化 → 高斯模糊 → Canny 边缘检测 → 找轮廓 → 筛四边形 → 输出检测结果到临时文件。

import cv2 import numpy as np # 读取图片,filename 可以从命令行参数传入 img = cv2.imread('img_temp.jpg') # 转灰度 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊,核大小 5x5,抑制噪点 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Canny 边缘检测 edges = cv2.Canny(blurred, 50, 150) # 查找轮廓 contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 按轮廓面积排序,取最大的几个候选 contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] for contour in contours: # 多边形逼近,得到四边形的四个顶点 epsilon = 0.02 * cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, epsilon, True) if len(approx) == 4: cv2.drawContours(img, [approx], -1, (0, 255, 0), 2) break # 保存结果,方便对照原图检查 cv2.imwrite('checkerboard_detected.jpg', img)

这段代码里最重要的参数是epsilon = 0.02 * cv2.arcLength(contour, True),它决定了多边形逼近的严格程度。0.02 的意思是允许轮廓周长 2% 的误差,这个值偏小,意味着逼近结果更贴近原始轮廓,反过来也更容易出现五边形、六边形这类非四边形结果。如果你检测的图片里棋盘边缘有阴影干扰,建议把系数放宽到 0.03~0.04,代价是角点位置会略微偏离真实角点。

另一个参数是cv2.RETR_EXTERNAL,它只检索最外层轮廓。这个选择合理,因为棋盘作为图片中最大的四边形物体,最外层轮廓通常就是它的边界。但如果图片里棋盘旁边放了一个更大的矩形物体,比如一本书、一个显示器边框,那这个最大的轮廓就是错的。真遇到这种情况,我会在排序前先加一个面积比例过滤:只保留面积占整张图 20% 以上的轮廓。

3.3 运行 test2 脚本:棋子识别是依赖坐标前瞻还是独立检测

第二个脚本21_0629_opencv_python_weiqi_test2.py负责棋子检测。从工程上分,它有两种可能的设计:第一种是接收 test1 输出的棋盘坐标,逐点检查周围区域有没有棋子;第二种是独立地对整张图做圆检测,再和棋盘交叉点做匹配。这两种思路在代码复杂度上有明显差异,从文件命名和测试图片的重复利用来看,test2 极大概率是独立检测。

独立检测的核心手段是cv2.HoughCircles,这是 OpenCV 最常用的圆检测函数。它的参数比较多,这里逐个说:

circles = cv2.HoughCircles( blurred, # 输入图,要求是灰度图 cv2.HOUGH_GRADIENT, # 检测方法,目前只有这一种 dp=1.2, # 累加器分辨率与图像分辨率的比值 minDist=20, # 两个圆心之间的最小距离,防止重复检测 param1=100, # Canny 高阈值 param2=30, # 累加器阈值,越小越容易检出圆,也越容易误检 minRadius=5, # 最小半径 maxRadius=30 # 最大半径 )

这里面minDist和param2是最需要调的。围棋棋子在 19 路棋盘上的间距大约是棋盘宽度的 1/18,如果你的棋盘在图片里占了 1080 像素宽,那么棋子间距就是 60 像素左右,minDist设成棋间距的 0.7~1.0 倍比较合适。param2则是典型的「值越低越乱、值越高越漏」,没有通用解,必须对着实际图片反复试。

圆检测出来之后,还要区分黑白子。常见做法是取每个圆心的像素值,看灰度是偏亮还是偏暗:亮的是白子,暗的是黑子。更稳的做法是取圆心周围一个小方块的灰度均值,比如以圆心为中心取 10×10 的区域求平均值,避免棋子边缘的高光干扰。这样处理后输出一个二维列表,每个元素标记为empty、black或white,就是最终结果。

3.4 输出结果的可视化:中间图片和最终图片怎么看

脚本运行中会生成大量中间图片,比如img_temp.jpg、img_temp1.jpg、img_temp2.jpg、img_temp3.jpg,这些是为了方便调试而保存的阶段性结果。你打开这些图对比着看,能快速定位问题出在哪个环节。

我强烈建议你在跑项目时保持这个「每步一存图」的习惯。灰度图能确认颜色转换是否正常,边缘图能确认 Canny 阈值是否合适,轮廓图能确认棋盘有没有被正确框出来,圆检测结果图能确认HoughCircles的参数是否得当。不要嫌麻烦,图像处理项目 debug 的最大障碍就是「看不见中间状态」。

还有一个checkerboard目录,里面应该是作者调试棋盘检测时的素材和输出。你把你的测试图放进去,脚本输出也写到这里,保持工作目录整洁,后面做参数对比实验时会非常方便。

4. 参数调优实战:用现有素材把识别准确率拉上去

4.1 光照和噪声是识别精度的第一杀手:预处理参数怎么调

如果你的检测结果不稳定,先别急着动HoughCircles的参数,多半是预处理环节出了问题。拍照时最常见的干扰有两种:一是局部过曝,棋子表面反光变成白色光斑,白子和黑子在这种光斑下特征会混淆;二是阴影,棋子边缘被阴影覆盖,圆检测时轮廓断裂,一个完整的圆被拆成两段弧。

处理局部反光,我会在灰度化之后加一步cv2.equalizeHist直方图均衡化,它能拉伸灰度分布,把过曝区域和阴影区域的细节拉回来。但注意,直方图均衡化会让整张图的对比度都变强,如果原图比较正常,加了反而会产生伪边缘。建议只在检测效果不好时尝试。

处理阴影和噪点,高斯模糊的核大小是关键。(5, 5)是通用值,图片分辨率越高,核应该越大,1080p 的图用(7, 7)也不过分。核越大,边缘越平滑,但代价是细小的边缘特征(比如棋子的高光)也会被磨掉,找到图像真实边缘和抑制噪声的平衡点,本质上是一个实验过程。

4.2 HoughCircles 参数组合的经验区间与调试方法

HoughCircles是全局检测中最让新手头疼的函数,因为它的参数相互制约,调一个可能影响另外三个的效果。我根据经验给出一个调试顺序:

先固定dp=1.2和minRadius/maxRadius,把param1设为 100,然后从小到大调param2。param2太小,比如低于 20,几乎每个高梯度区域都会被识别成圆,画面上一堆重叠圆;param2太大,比如高于 80,只有边缘极其清晰的圆才能被检测到,黑白棋子可能漏一半。从 30 开始,每次加 10,观察检测结果的变化。

接下来调minDist。它防止的是同一个圆被重复检测出多个结果。minDist太小,一个棋子周围会有五六个圆;太大,相邻棋子的圆会被吞掉。围棋棋盘的棋间距是固定的,所以minDist也是一个固定值,我一般设成棋间距的 0.8 倍。

最后调minRadius和maxRadius,这两个值决定了检测圆的尺寸范围。棋子在图片上占据的像素直径可以通过棋盘宽度除以 18 估算,比如棋盘宽 900 像素,棋子直径就是 50 像素,那么minRadius设 20、maxRadius设 30,能有效过滤掉图片里其他圆形物体,比如瓶盖、圆形标志。

4.3 棋盘检测失败的常见输入:什么图会让脚本直接翻车

先说结论:最让这套脚本翻车的三类图是——棋盘只占画面一角、棋盘被物体遮挡一部分、棋盘颜色和背景高度接近。这三种情况都会让findContours找不到完整的四边形轮廓,导致棋盘定位失败。

对于第一类图,我的处理是先做边缘连接(cv2.morphologyEx用闭运算),把断裂的棋盘边缘补上。对于第二类图,正规做法是换用基于直线交点网格拟合的算法,但课程设计阶段,我建议直接换一张完整视角的图,把精力花在主流程的精度优化上。第三类图最棘手,棋盘边缘和背景混在一起时,只靠轮廓是分不出来的,必须引入颜色特征来判断哪个区域更像棋盘,这就超出了基础脚本的范畴。

还有一类输入是src.jpg这种带 logo 或水印的测试图,水印恰好在棋盘区域内时,边缘检测会把水印的轮廓也当成候选边。这时候可以额外加一个长宽比过滤,棋盘接近正方形,长宽比在 0.8~1.2 之间,能过滤掉大量无关矩形。

5. 避坑记录:这套围棋识别项目最常见的五个坑

5.1 坑一:cv2.Canny阈值设错,边缘图一片花或一片黑

现象:运行 test1 后,输出的边缘图要么全是密密麻麻的短线,要么几乎全白,找不到清晰的棋盘轮廓。

原因:Canny 的两个阈值和图片的对比度不匹配。低阈值太高会过滤掉真实边缘,高阈值太低会把噪点全部保留。

解决:先打印边缘图的非零像素占比,看整体密度。占比超过 30% 说明阈值偏低,调高 50 精度,同时把低阈值也提高;占比低于 5% 说明阈值偏高,把高阈值降到 100 附近。我习惯用(50, 150)起步,然后以 20 为步长上下调。

5.2 坑二:import cv2报ModuleNotFoundError或cv2.error

现象:脚本第一行import cv2就报错,或者执行到某一句时抛cv2.error: OpenCV(4.4.0)的 C++ 层异常。

原因:前者是 OpenCV 没有装在当前 Python 环境里,后者是 OpenCV 版本和 NumPy 版本冲突,或者调用了当前版本已移除的 API。

解决:第一步确认环境,which python或python -c "import sys; print(sys.executable)"看当前解释器路径,再pip list看包列表。第二步,如果是版本冲突,卸载重装指定版本:pip uninstall opencv-python numpy然后pip install opencv-python==4.5.5.64 numpy==1.21.6。这个组合在 Python 3.8 下经过验证是稳定的。

5.3 坑三:HoughCircles检测出几十个重叠圆,无法确定棋子的真实位置

现象:test2 的运行结果图上,一个棋子的位置上有多个圆重叠,或者棋盘空白区域也被圈出圆。

原因:param2太低,或者minDist设定小于棋子间距,导致局部梯度峰值被重复响应。

解决:调高param2,从 50 起始逐步加。同时把minDist设为棋间距的 0.8 倍,这一步能消除大部分重叠圆。如果还有零星误检,再检查minRadius/maxRadius范围是否包含了图片里其他圆形物体。

5.4 坑四:斜拍或透视变形下,棋盘网格点定位偏移严重

现象:棋盘定位成功,但交叉点画出来是歪的,越到边角偏得越厉害。

原因:脚本没有做透视矫正,直接用原图坐标近似网格点。棋盘倾斜角度越大,误差越大,角落位置的几何误差可以达到几十像素。

解决:手动加透视矫正。用getPerspectiveTransform把棋盘四角映射到标准矩形,再在矫正图上计算网格点。具体代码我放在第六章,直接替换原来的坐标计算逻辑即可。

5.5 坑五:黑白棋子判断反了或大量漏检

现象:有些明显是黑子的位置被标记成白子,或者整片区域的棋子都没被检测出来。

原因:颜色判断逻辑用的可能是单点像素,而棋子边缘的高光反射让黑子中心也变成了亮色。漏检则是圆检测参数和实际棋子的尺寸不匹配。

解决:颜色判断改为区域均值。以圆心为中心取边长为棋子半径的正方形区域,对该区域的灰度图求平均值,大于 128 判定为白子,否则为黑子。漏检时检查minRadius和maxRadius,先手动量一张图上棋子的直径像素数,再倒推算半径范围。

6. 进阶用法:给项目补上透视矫正和批量识别,从演示变实用

import cv2 import numpy as np # 假设 src 是包含棋盘的原始图像 def perspective_correct(src, pts): # pts 是棋盘四角,按左上、右上、右下、左下排列 width = 900 height = 900 dst = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtype=np.float32) matrix = cv2.getPerspectiveTransform(pts.astype(np.float32), dst) warped = cv2.warpPerspective(src, matrix, (width, height)) return warped

这段代码的作用是把你从approxPolyDP得到的四个角点映射到一个标准的 900×900 正方形上。width和height设成相同的值,是为了保持棋盘网格的正方形属性,这样后续按 19×19 等分时,每个交叉点不会因为长宽比例不同而出现横向纵向间距不一致的问题。900 这个数字可以随意改,但建议用 19 的倍数,比如 874 或 912,这样网格均匀分割。

矫正之后,交叉点的计算就变成纯索引操作:第 i 行第 j 列的点是(j * (width // 18), i * (height // 18))。之所以除以 18,是因为 19 条线之间只有 18 个间隔。这里有一个比 900 更讲究的细节:width // 18是整数除法,如果 width 设置成 900,那么900 // 18 = 50,交叉点恰好落在整数像素坐标上,误差为零。

接着是批量识别。单个脚本处理单张图容易,但毕设演示时你可能要一口气跑完一个目录下的二十张图。批量处理的代码不复杂,核心是用os.listdir遍历文件,把每张图的识别结果存到独立的输出文件名里,避免覆盖。

import os import glob input_dir = 'img_temp' output_dir = 'output' os.makedirs(output_dir, exist_ok=True) for img_path in glob.glob(os.path.join(input_dir, '*.jpg')): img = cv2.imread(img_path) # 这里放你的棋盘定位 + 棋子识别逻辑 # 处理完后保存,文件名带上前缀防止覆盖 basename = os.path.basename(img_path) cv2.imwrite(os.path.join(output_dir, 'result_' + basename), img)

glob的匹配模式*.jpg只匹配 jpg 文件,如果目录里还有 png(比如项目里的3.png、4.png),需要再加一行匹配 png 的规则,或者把模式改成*.*再按扩展名过滤。另外,文件的读取顺序在glob里是不保证有序的,如果你希望按文件名顺序处理,用sorted(glob.glob(...))包一层。

批量处理的另一个价值在于:你可以先把检测结果全部输出成带棋盘网格和棋子标注的图片,再人工浏览一遍,快速定位哪类图片的检出率低。这个「批量跑 + 人工看缩略图」的工作流,比单独调一张图参数然后祈祷其他图都能过要可靠得多,也是我后来做所有图像识别项目都会走的一遍流程。从那以后,我拿到任何一套 OpenCV 项目源码,第一件事就是造一个测试图片目录、跑一遍批量处理、对着所有输出图看一遍,而不是只跑通那张 demo 图就宣告结束。这套围棋识别项目也是如此——它的价值不在代码本身有多巧妙,而在于你能用它跑通、看懂、然后加上自己想要的改进。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询