从棋盘识别到智能决策:OpenMV在电赛中的视觉与AI实战

张开发
2026/4/10 10:40:44 15 分钟阅读

分享文章

从棋盘识别到智能决策:OpenMV在电赛中的视觉与AI实战
1. OpenMV视觉系统在电赛中的核心价值第一次接触OpenMV是在大三的电赛备赛期间当时我们需要在有限的计算资源下实现实时视觉识别。这个火柴盒大小的开发板让我意识到嵌入式视觉系统完全可以在不依赖高性能计算机的情况下完成复杂任务。OpenMV最大的优势在于它将图像传感器、处理器和MicroPython环境集成在一个紧凑的硬件中特别适合电赛这类对实时性要求高、又受限于功耗和体积的场景。在井字棋机器人项目中OpenMV承担着眼睛和初级大脑的双重角色。通过OV7725图像传感器采集棋盘画面后系统需要完成三个关键任务定位棋盘位置、识别棋子分布、计算棋盘旋转角度。这里有个容易踩坑的地方——很多人会直接使用颜色阈值来识别棋子但实际比赛中光照条件变化很大。我们的解决方案是改用区域灰度平均值判断将每个ROI感兴趣区域内的像素灰度值取平均低于阈值视为黑棋高于阈值视为白棋。这种方法在实验室窗边做过测试即使有阳光直射也能保持90%以上的识别准确率。2. 棋盘定位与角度计算的工程实践2.1 矩形检测的优化技巧OpenMV的find_rects()函数是棋盘定位的核心但默认参数在复杂背景下容易误检。经过多次实测我们发现这几个参数最可靠threshold10000过滤掉面积过小的矩形长宽比约束30w70且30h70根据实际棋盘尺寸调整边缘阈值适当提高可以避免棋盘格纹造成的干扰检测到矩形后通过corners()方法获取四个角点坐标。这里有个细节处理——我们最初直接取前两个角点计算角度结果发现当棋盘旋转超过90度时会出现跳变。后来改用向量叉积判断角点顺序确保始终使用同一条边进行角度计算。具体实现时先计算corner[0]到corner[1]的向量v1以及corner[0]到corner[3]的向量v2通过v1×v2的符号确定角点排列方向。2.2 角度计算的数学原理角度计算的核心代码如下x1, y1 corner[0] x2, y2 corner[1] dx x2 - x1 dy y2 - y1 angle_rad atan2(dy, dx) angle_deg degrees(angle_rad)这里用到的atan2函数比普通arctan更可靠它能自动处理分母为零的情况并返回完整的[-π, π]范围角度。在实际部署时我们增加了角度滤波算法连续取5帧的角度值去掉最大最小值后取平均这样可以将抖动控制在±0.5度以内。特别提醒OpenMV的坐标系Y轴是向下的这与常规数学坐标系相反计算时要注意符号处理。3. 棋子识别与棋盘状态重建3.1 抗光照干扰的识别方案传统方法依赖颜色阈值但在比赛现场的光照条件下极易失效。我们的改进方案包含三个关键步骤ROI灰度转换将每个格子区域转为灰度图像圆形检测使用find_circles()定位棋子位置动态阈值判断计算圆形区域的平均灰度值具体实现时发现圆形检测的min_radius和max_radius参数需要根据摄像头距离精确设定。我们开发了一个校准模式在系统启动时自动检测不同位置的棋子动态调整半径范围。对于灰度阈值采用双窗口滑动平均法——记录最近10次的有效灰度值动态更新判断阈值这样可以适应缓慢的光照变化。3.2 棋盘状态的数据结构用3×3的二维数组表示棋盘状态board [[0 for _ in range(3)] for _ in range(3)] # 0空 1白棋 2黑棋每个ROI的索引与棋盘位置的映射关系是row i // 3 # ROI编号整除3得到行号 col i % 3 # ROI编号取模3得到列号在实际调试中发现机械臂放置棋子时可能会轻微遮挡相邻格子。解决方案是引入状态确认机制当检测到新棋子时连续3帧确认状态一致才更新board数组。同时通过串口将棋盘状态实时发送给主控我们用的数据格式是a12b21这样的键值对便于上位机解析。4. 井字棋决策算法的实现与优化4.1 博弈树搜索的简化策略完整的博弈树搜索对OpenMV来说计算量太大我们采用分层决策策略胜利检查检查己方能否立即获胜防御检查阻止对手即将获胜战略位置抢占按中心角落边缘的优先级选择核心算法用到了递归模拟def best_move(board, player): # 立即获胜检查 for r,c in get_empty_positions(board): board[r][c] player if is_winner(board, player): board[r][c] 0 return r,c board[r][c] 0 # 防御对手 opponent 2 if player 1 else 1 for r,c in get_empty_positions(board): board[r][c] opponent if is_winner(board, opponent): board[r][c] 0 return r,c board[r][c] 0 # 战略位置选择 empty get_empty_positions(board) if (1,1) in empty: return (1,1) corners [(0,0),(0,2),(2,0),(2,2)] for corner in corners: if corner in empty: return corner return empty[0] if empty else None4.2 先手优势的平衡设计在早期版本中先手方执黑胜率过高。我们通过三种方式平衡游戏性开局随机化电脑先手时有50%概率主动选择边缘位置开局防守权重调整对手有两子连线时防御优先级提高20%陷阱识别检测双杀局面时优先封锁关键点实测发现这些调整能将胜率从75%降到接近50%。一个有趣的发现是当加入500ms的思考延迟后玩家体验会明显提升虽然实际上算法运行只需要不到50ms。5. 系统集成与实时性优化5.1 串口通信协议设计OpenMV与主控STM32的通信采用紧凑的二进制协议帧头(0xAA) | 命令字(1字节) | 数据长度(1字节) | 数据(N字节) | 校验和常用命令包括0x01棋盘角度数据2字节单位0.1度0x02棋子位置通知1字节行列1字节类型0x03游戏状态1字节0进行中 1胜利 2平局在波特率115200下每帧传输时间控制在3ms以内。遇到数据丢帧时我们发现增加10μs的字节间延迟能显著改善稳定性。5.2 帧率与功耗的平衡默认模式下OpenMV功耗约300mA通过以下优化降至180mA将分辨率从QVGA(320x240)降至QQVGA(160x120)关闭JPEG压缩直接使用RGB565原始数据动态调整帧率检测阶段30fps决策阶段降至10fps一个意外的收获是降低分辨率反而提高了识别率因为棋盘区域在图像中的占比更大ROI内的有效像素更多。最终系统在4.2V电源下可连续工作3小时以上完全满足比赛要求。

更多文章