资讯动态

五子棋机器人实战:从坐标标定到AI博弈全解析

发布时间:2026/9/17 11:17:23 来源:尧图企业网站定制
简介智能人机对弈五子棋机器人设计相关学术论文PDF内容基于国家自然科学基金项目面向机器人、嵌入式及AI方向的学习者提供一套低成本、软硬件一体化的五子棋人机对战实现方案。资源仅含1个PDF文件压缩包大小2.55MB包含论文全文、图表与核心公式。已有193人学习适合作为毕业设计、机器人竞赛或嵌入式课程的参考资料。论文详述了采用普通摄像头采集棋盘图像通过灰度变换、Canny边缘检测、霍夫直线检测和模板匹配实现棋子识别利用决策树算法预测最优落子位置并借助STM32主控系统控制数字舵机完成落子动作。硬件上选用廉价数字舵机替代工业机械臂有效降低成本同时保证了落子准确度。文中还对比了山东海大机器人公司的同类产品突出了方案的经济性和普适性对智能游戏机器人开发具有直接借鉴意义。1. 智能人机对弈五子棋机器人先把坐标对齐再谈AI展厅里放一台五子棋机器人观众下白子机械臂从棋盒吸起黑子放到棋盘。多数人以为核心是五子棋AI实际上AI用一套评估函数就能打赢普通路人真正耗时间的是两件事摄像头像素坐标怎么换算成机械臂基座坐标以及吸盘怎么把棋子放进格子不碰倒旁边的子。整台设备涉及视觉识别、博弈搜索、机器人运动学三块硬骨头对做嵌入式、算法、控制方向的人来说都是很好的练手项目。本方案按常规桌面级实现来讲OpenCV感知alpha-beta决策四轴或六轴机械臂加吸盘执行。适合课程设计、毕业设计或想给自己配一台实机对弈设备的工程师。这条路线没有黑科技但调通每一层接口需要很细的耐心。2. 从相机像素到机器人基座五子棋机器人的坐标链路标定机器人要落子最少要过三套坐标系相机的像素坐标系、棋盘的格子坐标系、机械臂的基座坐标系。多数人一开始只盯着AI算法结果视觉识别做完了机械臂却把子放到格子外面问题全出在这条坐标链路上。2.1 感知、决策、执行三层的分工感知层由固定相机完成拍棋盘后检测棋盘角点和棋子位置决策层维护一个15×15的局面数组用搜索算法算出下一步应该落在哪个交叉点执行层负责把格子坐标换算成机械臂末端的目标位姿再驱动吸盘取子、落子。桌面级五子棋机器人一般把相机装在棋盘正上方拍全局图这样棋盘平面近似水平像素坐标到格子坐标就是一个平面透视变换标定最简单。如果非要装在机械臂末端就得做完整的手眼标定难度上了一个台阶对这种固定场景没必要。2.2 用四个点建立像素到格子坐标的单应矩阵有同学直接按像素比例换算坐标棋盘摆正时勉强能用但只要相机有一点点安装倾角棋盘边缘就会偏好几个像素。五子棋每格常见尺寸约15毫米机械臂末端偏3毫米棋子放下去肉眼看就是压线。常见的稳定做法是用单应矩阵做平面射影变换用棋盘四个角点的像素坐标和对应的格子坐标求一个3×3矩阵。OpenCV里一行就能算出来import cv2 import numpy as np # 四个角点在画面中的像素位置 pts_src np.array([ [112, 84], [608, 96], [596, 572], [104, 560] ], dtypenp.float32) # 对应的格子坐标按左上、右上、右下、左下排列 pts_dst np.array([ [0, 0], [14, 0], [14, 14], [0, 14] ], dtypenp.float32) H cv2.getPerspectiveTransform(pts_src, pts_dst) def pixel_to_grid(u, v): # 齐次坐标变换后除以 w 分量 out H np.array([u, v, 1.0]) return out[0] / out[2], out[1] / out[2]单应矩阵有8个自由度4对点就能解但pts_dst的顺序必须和pts_src一一对应顺序错一个角点结果就完全不对。标定完要立刻验证把已知格子的像素坐标代入看换算出来的格子坐标是否和真实值一致。如果镜头畸变比较大先用相机内参做一次undistort再算单应矩阵边缘位置的误差会明显降下来。2.3 机器人与棋盘的坐标对齐三点示教算仿射变换格子坐标到机器人基座坐标常见做法是假设机械臂X、Y轴与棋盘边缘平行只加一个平移量。这个假设在真机上容易翻车安装时偏一两度棋盘远端就会偏出好几毫米。更可靠的做法是在棋盘上示教三个点求一个6参数的平面仿射变换旋转、缩放、平移一次算齐import numpy as np def build_grid_to_robot(grid_pts, robot_pts): # grid_pts: [[0,0], [14,0], [0,14]] # robot_pts: 三个点对应的机械臂基座坐标 A np.zeros((6, 6)) b np.zeros(6) for i, (gx, gy) in enumerate(grid_pts): rx, ry robot_pts[i] A[2*i] [gx, gy, 1, 0, 0, 0] A[2*i1] [0, 0, 0, gx, gy, 1] b[2*i] rx b[2*i1] ry m np.linalg.solve(A, b) return m def grid_to_robot(gx, gy, m): # 仿射变换应用 return m[0]*gx m[1]*gy m[2], m[3]*gx m[4]*gy m[5]示教点选择格子0,0、14,0、0,14三点不能共线。操作方法是装好吸盘后用手动模式把吸嘴分别移到这三个交叉点正上方记录控制器的X、Y读数。最后再用角点14,14做验证对比计算坐标和实际示教坐标差值在0.5毫米内才算通过。这个套路和工业机器人做视觉引导抓取是同一套逻辑ABB、FANUC的控制器里帮你封装好了工具坐标和TCP校准桌面级舵机机械臂就自己写这几行矩阵运算。2.4 坐标链路自检流程坐标系代表变量标定手段常见误差像素坐标系u, v相机内参加角点检测±0.5像素格子坐标系gx, gy单应矩阵±0.05格机器人坐标系Rx, Ry三点示教仿射±0.3毫米链路全通的验证方法把一颗棋子放到任意交叉点视觉算出格子坐标机器人按算出的坐标去吸。如果发现吸嘴中心偏离棋子中心方向固定、距离固定先不要调视觉去检查仿射矩阵有没有算错。如果四个角都正确、中间区域偏优先怀疑角点检测本身歪了而不是机器人误差。3. 用OpenCV做棋盘、棋子和落点的实时识别视觉部分常见的坑是一上来就用霍夫圆检测整帧找棋子结果反光、手指入画、木头纹理全变成误检。稳定的做法是分两层走先粗定位棋盘再在棋盘区域内检测落子和棋色。3.1 为什么先做棋盘粗定位棋盘是场景里最稳定的结构横竖线条固定纹理几乎不变。如果相机安装位置固定可以在安装完成后手动标定一次棋盘四个角点的像素坐标之后每帧只裁剪棋盘区域做处理既减少计算量也排除大量背景干扰。用findChessboardCorners检测标准棋盘格标定板当然可以但五子棋棋盘不是标定板15路交叉点对应14×14格子内角点数量对不上反而容易出错。我更倾向于开机后手动点四个角点完成射影矫正一次标定永久使用稳且快。3.2 用帧间差分定位新落子玩家落子的一瞬间棋盘画面在局部区域发生明显变化。用上一帧减当前帧变化区域就是刚落下的子。这个方案对单步轮流落子非常合适比每次都全盘识别所有棋子快一个数量级import cv2 gray_prev None def detect_new_stone(gray_cur): global gray_prev if gray_prev is None: gray_prev gray_cur.copy() return None diff cv2.absdiff(gray_prev, gray_cur) diff cv2.GaussianBlur(diff, (5, 5), 0) _, thresh cv2.threshold(diff, 45, 255, cv2.THRESH_BINARY) contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: gray_prev gray_cur.copy() return None c max(contours, keycv2.contourArea) if cv2.contourArea(c) 300: # 小面积噪声直接忽略 gray_prev gray_cur.copy() return None x, y, w, h cv2.boundingRect(c) u, v x w // 2, y h // 2 gray_prev gray_cur.copy() return u, vthreshold取45是经验值环境光稳定时可以拍三张空棋盘算像素级标准差再取三倍标准差作为阈值。GaussianBlur的核大小影响相邻落子的区分度核太大会把两个离得近的新落子黏成一个区域太小则噪声点多。area阈值300像素在1080p画面里大约对应6×6像素的噪点过滤手指投影和灰尘足够。检测到落子后调用2.2节的pixel_to_grid换算格子坐标换算结果可能是(3.2, 7.8)这种小数不要直接四舍五入应该比较这个点到四个邻近交叉点的距离取最近的一个否则落在棋盘线中间时就容易选错格。3.3 用HSV区分黑白棋处理反光棋盘画面上最烦人的是反光白棋有高光点黑棋也会泛灰。RGB空间里黑色和白色在亮度通道上分布重叠不如直接用HSV分。取落子区域中心5×5像素的HSV均值按S和V阈值分类棋子类型HSV白棋不限 70 140黑棋不限 120 70黑白都是无彩色H通道本身没有区分意义所以H列写不限。强反光时白棋V值会过曝黑棋泛灰V值可能到90以上兜底逻辑是若S很低且V大于120优先判白棋。棋色分类错误会导致局面数组和真实棋盘不一致开局阶段不容易暴露中盘就会出现双方争同一个位置的诡异局面且这种问题很难从日志里一眼看出所以这一层宁可判错重拍一次也不要急于落子。4. 五子棋AI评估函数与alpha-beta剪枝为主深度学习不是必须机器人对弈体验的核心指标是响应速度。真人每步思考十几秒很正常机器如果也要算5秒就失去意义。15×15棋盘有225个可落点搜索树很深但五子棋本身规则简单用评估函数加alpha-beta剪枝在单核上跑4层深度轻松做到秒级响应。4.1 为什么评估函数加博弈搜索仍然是主流选择深度学习开局需要训练数据和推理硬件还要处理延迟和不确定性而五子棋在固定棋盘上的规则完全可枚举。评估函数加搜索是稳定、可解释的方案出问题时能定位到“是对手子的棋型统计错了”还是“搜索深度不够”。实际对局中评估函数对棋型的理解程度比搜索深度更影响棋力所以这一节把重点放在棋型评估上。4.2 棋型表驱动的评估函数评估函数最先要分清活三和冲三。活三两端都没被堵不应的话下一步就是活四冲三有一端被堵威胁小一个量级。实现时从每个已落棋子位置往四个方向延伸每个方向取前后各4格组成长度9的序列再用模式串匹配统计棋型。长度取9是因为一个棋型能影响的完整范围最多覆盖9格PATTERNS { AAAAA: 1000000, # 五连 .AAAA.: 100000, # 活四 AAAA.: 10000, # 冲四堵一侧 .AAAA: 10000, .AAA..: 6000, # 活三两空位 ..AAA.: 6000, .AAA.: 3000, # 活三空间窄 .AA..: 300, # 活二 ..AA.: 300, .A.A.: 300, # 隔空活二 } def evaluate_single(board, x, y, player): score 0 for dx, dy in ((1, 0), (0, 1), (1, 1), (1, -1)): line [] for step in range(-4, 5): nx, ny x dx * step, y dy * step if 0 nx 15 and 0 ny 15: line.append(board[nx][ny]) else: line.append(-1) # 越界视作墙 s .join( A if c player else (. if c 0 else W) for c in line ) for pattern, base in PATTERNS.items(): score s.count(pattern) * base return score def evaluate_player(board, player): total 0 for x in range(15): for y in range(15): if board[x][y] player: total evaluate_single(board, x, y, player) return total def evaluate(board, player): # 返回 player 视角的评分 return evaluate_player(board, player) - evaluate_player(board, 1 - player)这里把对手棋子和棋盘边界统一视为“W”因为它们在阻挡效果上语义一致。用s.count统计模式会有重叠计分但双方都重叠相对差值仍然可用。每个棋子会在多个滑动窗口里被重复计入分数绝对数值没有太多含义主要用于候选点排序和搜索树的局面比较够用就行。4.3 alpha-beta剪枝核心实现import math EMPTY 0 AI 1 HUMAN 2 def alphabeta(board, depth, alpha, beta, maximizing): score evaluate(board, AI) if depth 0 or score PATTERNS[AAAAA] or score -PATTERNS[AAAAA]: return score player AI if maximizing else HUMAN moves candidates(board, player) if maximizing: value -math.inf for x, y in moves: board[x][y] AI value max(value, alphabeta(board, depth - 1, alpha, beta, False)) board[x][y] EMPTY # 悔子必须和落子成对出现 alpha max(alpha, value) if alpha beta: break return value else: value math.inf for x, y in moves: board[x][y] HUMAN value min(value, alphabeta(board, depth - 1, alpha, beta, True)) board[x][y] EMPTY beta min(beta, value) if beta alpha: break return value递归里落子和悔子必须成对漏掉悔子棋盘上会积累大量幻影棋子之后所有评估全部失真。score直接使用AI视角的差值所以max节点和min节点的终止条件可以共用。maximizing节点更新alphaminimizing节点更新beta两者不能混写。单看这段代码只能跑4层想加深就得靠下一节的候选点压缩和排序。4.4 候选点范围与走子顺序剪枝效率的真正来源alpha-beta剪枝的效率高度依赖走子顺序。如果候选点从全盘225个位置里选搜索树还是太大。常见做法是只搜已有棋子周围两格以内的空位一手棋如果离所有棋子超过两格在五子棋战术中几乎不可能产生有效威胁。这样候选点从200多个压到20个上下。生成后还要对候选点做静态估值排序优先搜索看起来最强的着法剪枝率会明显提升def candidates(board, for_player): around set() for x in range(15): for y in range(15): if board[x][y] EMPTY: continue for dx in (-2, -1, 0, 1, 2): for dy in (-2, -1, 0, 1, 2): nx, ny x dx, y dy if 0 nx 15 and 0 ny 15 and board[nx][ny] EMPTY: around.add((nx, ny)) def quick_score(p): # 假设该点落子评估其棋型价值 x, y p board[x][y] for_player s evaluate_single(board, x, y, for_player) board[x][y] EMPTY return s return sorted(around, keyquick_score, reverseTrue)quick_score临时落子再悔子只统计以该点为中心的四个方向的棋型价值作为排序依据足够。深度6配合排序后单步响应可以控制在2秒内如果还超时就用第6章的迭代加深做时间预算。这个策略本质上就是启发式搜索五子棋工程里它比换一颗更快的CPU作用大得多。5. 机械臂运动控制吸盘末端与落子精度补偿执行层是机器人和纯软件方案的最大区别。桌面级五子棋机器人最常见的末端执行器是真空吸盘不是夹爪。吸盘从正上方接触棋子姿态容差大占用空间小也不会像夹爪那样两侧空间不够碰到邻子。5.1 末端执行器选型吸盘而不是夹爪五子棋棋子直径一般两厘米左右重约3克用直径8毫米的硅胶吸嘴、微型真空泵加电磁阀就能稳定吸取。选吸盘的理由有三个夹爪需要两侧避让相邻棋子末端空间经常不够磁吸方案只适用于铁棋子还会干扰视觉对棋色的判断吸盘对姿态偏差容忍度高歪一两度也能吸起来。真机上最容易出问题的是吸嘴内壁沾灰吸力会明显下降视觉看起来吸住了一移动就掉所以定期清洁吸嘴和棋子表面是日常维护项。5.2 取子到落子的动作序列一整轮动作拆成五段从棋盒上方移动到目标棋子上方垂直下降接触棋子等待真空建立抬升到安全高度再水平移动到目标格子上方垂直下降放子破真空抬升。安全高度至少要比棋子表面高2厘米保证水平移动时不刮到棋面。真空建立延时我一般给100毫秒太短电磁阀还没吸稳就抬升棋子会留在原处这类故障在实机上最常出现def pick_and_place(target_grid, source_grid): arm get_robot_arm() # 机械臂控制句柄 src grid_to_robot(*source_grid) arm.move_lin(src) # 直线移动到棋子正上方 arm.move_lin(src_z_pick) # 垂直下降接触棋子 vacuum_on() sleep(0.12) # 等真空建立 arm.move_lin(safe_height) # 抬升 dst grid_to_robot(*target_grid) arm.move_lin(dst) arm.move_lin(dst_z_drop) # 垂直下降 vacuum_off() sleep(0.05) # 放气后稍等再抬升 arm.move_lin(safe_height)move_lin是直线插补move_j是关节插补。落子阶段要用直线插补关节插补会让吸嘴划弧线经过相邻格子上方时如果高度不够就会碰倒已经落好的棋子。工业机械臂控制器里自带直线插补指令桌面级机械臂的SDK一般也提供相同接口。ROSL2里用MoveIt规划时默认是在关节空间搜索路径需要额外设置笛卡尔路径约束或者干脆手写分层直线插补。5.3 落子位置偏差补偿与Z轴下降细节即使坐标标定全部正确机械臂的机械间隙也会导致实际落点和目标点之间有固定偏移。如果发现偏差方向固定、大小固定可以做一个偏差表在棋盘四角和中心分别示教一次记录实际落点和目标点的偏差(dx, dy)再用双线性插值估算任意格点的补偿量def bias_compensate(gx, gy, bias_grid): # bias_grid: {(0,0): (dx,dy), (14,0): ..., (14,14): ..., (0,14): ..., (7,7): ...} x0, y0 0, 0 x1, y1 14, 14 dx00, dy00 bias_grid[(0, 0)] dx01, dy01 bias_grid[(0, 14)] dx10, dy10 bias_grid[(14, 0)] dx11, dy11 bias_grid[(14, 14)] tx (gx - x0) / (x1 - x0) ty (gy - y0) / (y1 - y0) dx (1 - tx) * (1 - ty) * dx00 tx * (1 - ty) * dx10 (1 - tx) * ty * dx01 tx * ty * dx11 dy (1 - tx) * (1 - ty) * dy00 tx * (1 - ty) * dy10 (1 - tx) * ty * dy01 tx * ty * dy11 return dx, dy参数上(7,7)是可选的第5个标定点加上它之后棋盘中心的偏差不会完全依赖四角线性外推。如果偏差超过1厘米不要无脑补偿优先检查机械臂基座螺丝和连杆是否有松动很多“标定不准”实际上是机械结构松动。最后是落子深度的细节吸嘴下降终点要穿过棋子上表面约0.5毫米靠吸盘硅胶的弹性把棋子压稳否则放气瞬间残余吸力会把棋子带起来。这个量根据硅胶硬度微调一般在0.3到1毫米之间。6. 从仿真到实机的验证方法与落子超时排错整台设备联调时最怕三个模块的问题混在一起。我的习惯是分阶段验证每一层独立跑通后再对接下一层。6.1 先验证AI命令行自对弈把视觉和机械臂全部断开用Python实现一个命令行五子棋程序让AI自己执黑执白对弈。这种基于Python的五子棋游戏实现最少只需要一个局面数组和一个终端渲染函数却能快速暴露评估函数和搜索逻辑的问题。python gomoku.py --self-play 200 --max-time 2.0观察两个指标全程无非法落子平均单步耗时稳定。再用几个残局测试用例验证棋型判断比开局摆一个活三看AI应手是否落在堵活三的位置上。6.2 离线录像验证视觉坐标视觉调试不要对着实时画面猜。录一段包含空棋盘到逐步落子的视频离线跑检测脚本把每步识别出的格子坐标输出成CSV再人工对照实际落子位置。改参数后重跑同一段视频才能对比出参数变化对识别结果的影响。如果视觉坐标稳定但偏差固定优先检查单应矩阵的角点是否标对如果连续几帧坐标抖动是差分阈值临界导致的把threshold调高一点再看。6.3 搜索超时的迭代加深保底alpha-beta搜索到第6层时单步耗时可能因为局面复杂度波动。给搜索加时间预算的常见做法是迭代加深从浅层开始逐层加深每层完成后检查剩余时间超时就沿用上一层的落子结果def alphabeta_root(board, depth): best_move (7, 7) best_val -math.inf for x, y in candidates(board, AI): board[x][y] AI val alphabeta(board, depth - 1, -math.inf, math.inf, False) board[x][y] EMPTY if val best_val: best_val, best_move val, (x, y) return best_move def iterative_deepening(board, max_seconds2.0): best (7, 7) start time.perf_counter() for depth in range(2, 10): if time.perf_counter() - start max_seconds * 0.7: break best alphabeta_root(board, depth) return best0.7是经验值保留三成时间给机械臂动作和通信开销。迭代加深不会明显削弱棋力因为浅层搜索给出的着法在大多数局面下已经够好深层搜索的价值主要在中盘攻防中体现。实机联调时把视觉、AI、机械臂三个模块的日志时间戳对齐出问题先确认是哪一层超时不要一上来就调机械臂速度。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价