2017电赛B题板球系统全解析:OpenCV+PID控制从调参到实战
2026/9/2 2:14:17 网站建设 项目流程

简介:面向电子设计竞赛与嵌入式开发学习者的滚球控制系统(板球系统)完整开源工程,对应2017年全国大学生电子设计大赛B题。系统基于Keil MDK开发,源码以C语言为主,硬件驱动层覆盖PWM输出、超声波测距、编码器反馈、SPI通信、按键与激光模块等关键环节,并保留多份.bak备份、中间文件和编译列表,便于对比版本改动与调试复盘。资源共190个文件,以.c/.h源文件、.uvprojx/.uvoptx工程配置及.o/.lst/.crf等编译产物为主,附带少量图片、说明文档和Python脚本,压缩包仅5.96MB,目录结构清晰。目前已有1356人学习,适合正在备赛、研究二维云台/小球轨迹控制或希望借鉴完整代码框架的读者;可以结合源码梳理传感器采集、控制算法与执行机构输出之间的关系,并在此基础上二次开发与迭代。开源工程降低了复现门槛,可直接迁移到同类板球/平衡控制项目中。 2017年电赛B题“板球系统”是我带过那么多队伍里,觉得最“经典”的一道控制类题目。说它经典,是因为它把图像处理、运动控制和机械结构几个模块拧在一起,任何一个环节掉链子,小球就是滚不中你要的位置。这几天正好有学弟问我要当时的源码和笔记,我干脆整理一篇完整的复盘文章出来,把整个系统的拆解思路、关键代码、调参过程和踩坑记录都放这里,给后面参赛或者做相关项目的小伙伴一个参考。不管你是刚好要打电赛,还是单纯想用OpenCV加舵机做一个二维平衡的小玩意,这篇内容都值得耐心看完。

1. 项目概述与需求拆解

1.1 2017年B题到底要求什么

先带大家回顾一下题目本身的硬性指标。2017年电赛B题,要求的核心是这个场景:一块平板通过两个方向的倾斜来控制一个小球在板上滚动,让小球从初始位置自动滚到指定位置,并且在规定时间内稳定下来,精度要求相当高。具体来说有基础部分和发挥部分,基础部分要求定点停靠,发挥部分则涉及圆周运动、轨迹跟踪这类动态任务。

这道题的本质,就是一个典型的两轴平衡控制系统。你把它抽象出来,其实就是两个独立的控制环路:X轴方向上一个控制环,Y轴方向上一个控制环。板子绕X轴转动控制小球在Y方向上移动,绕Y轴转动控制小球在X方向上移动。搞清楚这个解耦关系,整个系统的设计框架就非常清晰了。

1.2 核心难点在哪里

这个题目说难不难,说简单也绝对不简单。我总结下来,几个核心难点值得每一个参赛者重视,因为它们决定了你最终是拿省奖还是国奖:

  • 图像识别的稳定性和实时性:小球定位必须既快又准,任何一帧的卡顿或者误检测都会直接反映成控制量的突变,小球就会抖动、冲出边界。
  • 坐标系的统一:摄像头看到的像素坐标,跟实际平板上的物理坐标不是一回事,必须做透视变换和比例换算,否则闭环控制算出来的误差是错的。
  • 控制的稳定性:PID参数调不好,小球要么超调冲出去,要么在目标点附近来回抖动,就是停不住。
  • 机械结构的响应速度:舵机或者电机的响应带宽必须够,否则控制律算得再好,执行机构跟不上也是白搭。

我们当时把题目读了三遍,最后拆解出来的结论是:这是一个串级或者并列的PID控制问题,外层是位置环,内层是速度环(或者至少需要位置环加阻尼项)。当然直接用单环位置PID也能跑,但动态性能会差很多,后面我会详细讲。

2. 系统整体架构与硬件选型

2.1 硬件组成一览

系统的硬件搭建,直接决定了你的稳定性和控制效率。我们把整个系统分成了几个模块:核心控制板、图像采集模块、执行机构(云台)、以及小球和平板本身。

模块选型方案说明
主控芯片STM32F407或树莓派3BSTM32做闭环控制响应更实时,树莓派做图像处理更方便,也可以两者结合
图像采集USB摄像头(30万像素以上,免驱)关键是帧率,最好能达到60fps,至少也要30fps
执行机构双轴云台(配MG996R舵机或步进电机)舵机便宜但控制精度一般,步进电机(如42步进)精度高但需要驱动器
平板+小球亚克力板+高亮橙色乒乓球橙色识别度高,不易受环境光干扰
电源方案5V/3A给舵机,5V/2A给树莓派或STM32分开供电,避免舵机抖动造成主控掉电

2.2 处理器选型教训

我们当时最初方案是纯STM32F407,图像处理用OV7670摄像头加上FIFO,后来发现做色彩识别阈值调整太费劲,帧率也上不去。后来直接换成了树莓派做视觉识别,结合STM32或者直接用树莓派输出PWM控制舵机,效果马上就好多了。

这里我建议后来人:如果你不是对嵌入式图像处理特别熟,主控板加树莓派组合是最稳妥的。树莓派跑OpenCV做图像识别,通过串口把坐标发到STM32,STM32做闭环控制输出PWM给舵机,各有分工,实时性和稳定性都能兼顾。当然,如果你们队伍里有人特别擅长C++优化加纯ARM方案,那也完全可以,只是调试周期会拉长。

2.3 机械结构那些事

机械结构是整个系统的地基,千万别忽视。我们第一次试跑的时候,板子是硬固定在一个便宜的云台上的,结果两个舵机的转动会互相耦合,导致小球运动轨迹根本不是直线。后来换了带轴承的双轴云台支架,才解决这个问题。

平板的尺寸也要注意,一般用50cm×50cm的亚克力板比较合适。太小,小球很快就滚出去了,留给控制的反应时间太少;太大,力矩需求更大,舵机响应不过来。边框最好贴上防撞条,防止小球高速冲出去砸坏东西。

3. 图像识别与小球定位

3.1 图像处理流程设计

在小球定位这个环节,我们的目标是从摄像头图像里拿到小球在板上的像素坐标。整个处理流程可以分成五步:图像采集、预处理、二值化、轮廓提取、坐标计算。

  • 图像采集:使用OpenCV的VideoCapture读取摄像头数据,读取时不要逐帧直接处理,而是放到一个缓冲区,避免处理器等待摄像头I/O。
  • 预处理:先高斯模糊去除噪声,然后再转到HSV颜色空间。直接转HSV是因为RGB空间下颜色受光照影响太大,同样的橙色在阳光下和阴影里BGR值差异巨大,HSV把色调H分离出来,对光照的鲁棒性就好很多。
  • 二值化:根据HSV阈值,将图像中符合小球颜色的区域变成白色,其余变黑。这一步的关键是阈值上下界的选取,后面我会专门给出注意事项。
  • 轮廓提取:OpenCV里用findContours函数,找到所有白色区域的外轮廓,用minEnclosingCircle或者boundingRect把小球框出来。
  • 坐标计算:用轮廓的矩或者外接圆圆心,得到小球的像素坐标(u,v)。

3.2 HSV阈值设置与标定

HSV阈值设置是最容易踩坑的环节。需要特别强调的是,不同光照环境下HSV阈值差异非常大。室内开灯、窗户自然光、晚上灯光下,识别效果完全是三个样子。

这里给出一个参考区间,如果你是橙色乒乓球的话:

通道最小值最大值
H0(或5)25
S80255
V100255

实际标定时,我建议你写一个小工具,把摄像头的实时画面显示出来,鼠标点击到小球上,程序自动读取该像素的HSV值,这样采样很多点再去设置阈值,远比凭经验猜要准确。

3.3 像素坐标到物理坐标的变换

这一步非常关键,也是很多队伍忽略的。摄像头不是垂直对准平板的,总是有一定的倾斜角度,这时候直接拿像素坐标算误差,会导致越靠近边缘误差越大。

解决方法是使用OpenCV的透视变换。你先在板上固定四个已知物理坐标的角点(比如板的四个角),然后找到这四个角点在图像中的像素坐标,用getPerspectiveTransform计算出变换矩阵,再通过warpPerspective或者直接对坐标点做矩阵乘法,就能把任意像素坐标换算到平板的物理坐标系上。

import cv2 import numpy as np # 四个角点的像素坐标(手动标定或自动检测) src_points = np.float32([[150, 120], [490, 150], [120, 470], [460, 500]]) # 对应的物理坐标(单位cm,假设板子50x50) dst_points = np.float32([[0, 0], [50, 0], [0, 50], [50, 50]]) matrix = cv2.getPerspectiveTransform(src_points, dst_points) # 将小球像素坐标(u, v)转换为物理坐标(x, y) pixel_point = np.array([[[u, v]]], dtype=np.float32) physical_point = cv2.perspectiveTransform(pixel_point, matrix) x, y = physical_point[0][0]

第一次跑这个变换的时候,我们看到小球在板子边缘时,输出的物理坐标跟实际测量的差距越来越大,还以为是代码写错了,后来才发现是因为收集角点的时候不够精确。所以标定角点时一定要用棋盘格或直角靠尺辅助,误差在几个像素内,后续物理坐标误差才能控制在毫米级。

4. 运动控制算法与PID实现

4.1 PID控制的基础设计

控制部分是整个系统的灵魂。最基础的做法是对X轴和Y轴分别做位置PID控制。以X轴方向为例,当前小球物理坐标为x,目标位置为x_target,误差e = x_target - x,控制器输出控制量u,这个u映射到Y方向舵机的PWM值。

float pid_update(float target, float current, float *integral, float last_error, float kp, float ki, float kd) { float error = target - current; *integral += error; float derivative = error - *last_error; *last_error = error; float output = kp * error + ki * (*integral) + kd * derivative; return output; }

这一段看起来简单,但实际上容易犯两个错误:一个是积分饱和,如果小球长时间无法到达目标点,积分一直累积,输出会暴涨,小球会直接冲出去。另一个是微分噪声放大,如果通过图像识别到的坐标一旦有噪声,微分会把噪声放大,导致舵机抖动。所以实际使用中,一定要给积分量加限幅,微分项加低通滤波

4.2 单环PID不够,试试串级PID

单环位置PID在小球离目标点比较近的时候还能用,但一旦距离较远,你会发现小球会疯狂超调,甚至画着S形轨迹冲向边界。原因很简单,位置环只考虑了位置误差,没有考虑小球当前的速度,相当于一个人开车只看“离目的地还有多远”,却不看“当前车速有多快”,那肯定要冲过头。

解决思路是加入速度环,形成位置外环、速度内环的串级PID结构。外环输出的是期望速度,内环根据当前速递去调整舵机角度。速度可以通过对位置差分得到:

vel_x = (x_current - x_last) / dt

但这里有个问题,就是你用摄像头帧率去差分算速度,噪声极大,算出来的速度跳来跳去。所以一定要对原始位置数据做平滑处理,比如用滑动窗口平均,或者一阶低通滤波:

alpha = 0.4 x_filtered = alpha * x_raw + (1 - alpha) * x_last_filtered

我自己的实际使用感受是:加了串级PID之后,小球在长距离移动时明显“有刹车感”,到目标附近会平滑减速,而不是之前那种冲过头再倒回来的观感。串级PID代码也不复杂,外环计算出的期望速度作为内环的目标值,内环再计算一次PID得到舵机PWM即可。

4.3 舵机PWM映射与执行

舵机的控制需要20ms周期脉冲,高电平时间在0.5ms到2.5ms之间对应0到180度。我们通过对每个轴独立控制舵机角度来让板子倾斜。这里要注意:

  • 舵机中位对应的PWM值要先在机械上校准,确保平板在舵机处于中位时是完全水平的。
  • 控制量u到PWM值的映射要限幅,防止舵机打到底导致机械卡死。
// 假设控制量u的范围是[-30, 30],映射到PWM范围[1000, 2000] float pwm_x = 1500 + u_x * (1000.0f / 30.0f); if (pwm_x > 2000) pwm_x = 2000; if (pwm_x < 1000) pwm_x = 1000;

这里有一个工程上的细节:两个舵机的安装方向可能相反。比如一个舵机顺时针转是让板子左倾,另一个顺时针转却是让板子前倾。如果没仔细标定,控制方向反了,闭环直接变成正反馈,小球会不降反升,瞬间冲出板子。每换一次机械结构,第一步就是确认控制量正负与实际倾斜方向的关系。

5. 常见问题与排查技巧实录

5.1 小球识别不稳定,坐标跳动严重

这个问题出现的频率最高。如果你发现小球坐标在静止时也会上下跳好几个像素,先检查二值化后的图像中是否有多余的干扰区域,比如反光的光斑、背景里的橙色物体。解决办法是:

  • 加强预处理,不只是高斯模糊,还可以用形态学操作(腐蚀+膨胀)把噪声区域去掉。
  • 检查HSV阈值是否过宽,过宽就会把相近颜色都当成目标。
  • 给坐标加低通滤波,从软件层面减小跳动。

我们当时还发现一个有趣的现象:如果平板表面太光滑,LED灯会在板面上形成反光,形成一块白色或者高亮的区域,被误判成小球。后来在摄像头前面加了一个偏振片,这个问题就大幅缓解了。

5.2 小球总往一个方向偏,回不到中心

如果你发现静态时小球总是慢慢往一个固定方向滚过去,首先要怀疑的不是PID参数,而是平板机械水平没有调好。舵机中位时板子不一定完全水平,用手或者水平仪确认一下,如果确实不水平,需要通过调整舵机中位值的软件偏移来补偿。

另外,摄像头安装如果不平行于平板平面,透视变换后物理坐标也会有固定的系统误差。这两个问题都属于需要先做标定补偿的,不是控制参数能完全兜住的。

5.3 PID参数整定口诀与步骤

调PID是我觉得整个项目中最磨人的环节,但也是最有成就感的一刻。分享一下我自己的整定步骤,按照这个顺序来,比盲目试要快很多:

  1. 先把积分和微分设为0,只保留比例系数Kp,从小到大慢慢加,直到小球开始出现等幅震荡(在目标点附近来回摆动)。
  2. 记录下这个临界振荡增益Kp_crit,此时振荡周期大约为T_crit。
  3. 按照齐格勒-尼克尔斯(Ziegler-Nichols)经验公式给定初始参数:P = 0.6 * Kp_crit,I = 1.2 * Kp_crit / T_crit,D = 0.075 * Kp_crit * T_crit。
  4. 在这个基础上微调,主要是看动态跟踪时的超调量和静态时的稳定时间。如果超调大,先减Kp或者加Kd;如果静态有静差,再适当加Ki。
  5. 建议串级PID先调内环速度环,让速度环稳定之后再调外环位置环。

注意:在整定过程中,一定要先把小球放在板的中心附近,并且把积分限幅调小,否则小球一旦偏移过大,积分饱和会让系统失控,板子翻转过猛。

5.4 帧率与控制频率的匹配问题

树莓派做图像识别,帧率一般在30到60fps之间,而普通的PID控制频率理论上可以跑到几百赫兹。这里我的建议是让控制频率跟随视觉帧率,而不是用固定高频。原因是最简单的,如果控制频率远高于视觉更新频率,那两次视觉数据之间的控制指令都是基于旧数据的,没有意义,还会造成舵机频繁抖动。

我们在实际实现中是设置了一个定时器,每收到一帧新的坐标就触发一次PID计算和PWM更新,这样既自然又有实时性。如果后续有更高的控制需求,可以在两帧视觉之间加入惯性测量单元(IMU)数据做预测,但我们当时没有用到这么复杂,效果也已经足够。

5.5 参赛时的调试心态与流程

最后说点题外话。电赛时间是有限的,调试流程一定要有主次。我们当时的策略是:

  • 第1天:搭建机械结构,完成摄像头标定,确保小球能被稳定识别;
  • 第2天:跑通开环控制,人为输入不同PWM值观察小球运动方向,标定正反向;
  • 第3天:实现单环PID,调稳定到定点停靠;
  • 第4天:升级为串级PID,优化动态响应;
  • 第5天:集中精力跑轨迹跟踪和发挥部分,储备各种异常处理方案。

因为B题的核心就是控制精度和稳定速度,其他功能都是在这个基础上叠加的,所以前期一定要先把“小球能稳定停住”这个闭环跑通,后面拓展轨迹、跟踪都会顺利很多。

这一个多月折腾下来,我对“板球系统”最大的感受是:它把理论课上学的图像处理、控制理论、机械设计、嵌入式编程全部融合进了一个看起来小小的项目里,每一个环节都会给你“上一课”。做这个系统,收获最大的不是最后拿了什么奖,而是你真正理解了“反馈”这两个字的重量——无论是代码里的PID,还是调试过程中的反复试错,本质上都是对误差的修正。如果你现在也正在做这个项目,别怕踩坑,每一步调试的经验都会变成你最终的底气。

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

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

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

立即咨询