ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

电赛E题视觉追踪方案:OpenMV与云台PID控制实战

电赛E题视觉追踪方案:OpenMV与云台PID控制实战

简介:这份资料面向备战全国大学生电子设计竞赛的参赛团队与指导教师,聚焦2023年E题“基于STM32F1的自动追光云台”的完整实现方案。资源包共199个文件,约6.74MB,以C语言源文件、头文件、编译中间文件与工程配置为主,涵盖STM32F10x标准外设库、Keil工程文件及可烧录固件,便于直接编译验证与二次开发。已有7797人学习下载,热度较高。内容围绕光敏传感器采集、电机驱动控制、PID算法调节与电源管理等关键环节展开,读者可据此理解云台从姿态检测到精确追踪的完整控制链路,并参考工程目录组织方式快速搭建自己的开发环境,适合具备嵌入式基础、希望提升实战能力的参赛者对照学习与排错优化。

1. 从一道电赛E题说起:这份参考方案到底能帮你省下多少试错时间

如果你正在准备电子设计竞赛,或者带学生做运动控制类题目,大概率绕不开2023年那道经典的E题——它把运动目标控制与自动追踪这两个看似独立的功能塞进了一套系统里,要求用摄像头识别目标、用二维云台驱动激光笔完成指定轨迹和追踪。很多人第一次拿到题目的反应是:硬件搭得起来,代码写不出来;或者代码跑通了,精度死活上不去。这份参考方案的价值就在这儿——它不是一份“标准答案”,而是一套经过验证的工程实现路径,把视觉识别、云台控制、激光点定位、闭环调参这几个环节的坑都踩过一遍了。适合谁?适合已经搭好基础硬件、卡在算法整合阶段的参赛者,也适合想理解“视觉+运动控制”完整链路的嵌入式开发者。接下来我会按实际复现顺序,把这份参考里最核心的模块拆开讲。

2. 视觉识别与坐标映射:从像素到云台角度的完整链路

2.1 为什么选OpenMV而不是树莓派方案

这份参考方案用的是OpenMV作为视觉前端,而不是树莓派+OpenCV。原因很直接:电赛现场没有时间让你调Linux驱动、配Python环境、处理USB摄像头掉线。OpenMV把图像采集、色块识别、串口通信封装成几条API,上电就能跑。它的算力确实有限,但对于识别红色激光点、黑色矩形边框、A4纸上的目标区域这类任务,帧率能稳在25fps以上,完全够用。

常见做法是:OpenMV负责找目标质心和激光点质心,把两组坐标通过串口发给主控;主控跑PID算云台角度。这样分工的好处是视觉端不占用主控资源,主控可以专心做控制周期和舵机驱动。如果你非要用树莓派,那得做好心理准备——系统启动时间、摄像头曝光自动调整、进程调度延迟,每一个都可能让你在赛场上翻车。

2.2 色块识别的阈值怎么调才不玄学

OpenMV的find_blobs函数依赖LAB阈值,很多人调阈值全靠试,结果换个场地光线一变就废。参考方案里给了一个可复用的调参流程:

# OpenMV 色块识别核心代码片段 import sensor, image, time from pyb import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240,兼顾速度和精度 sensor.skip_frames(time=2000) # 等摄像头稳定,别省这一步 sensor.set_auto_gain(False) # 锁定增益,防止光线变化导致阈值漂移 sensor.set_auto_whitebal(False) # 锁定白平衡,同上 # 红色激光点的LAB阈值,L通道范围放宽,A通道偏红 red_threshold = (30, 100, 40, 80, 10, 70) uart = UART(3, 115200) while True: img = sensor.snapshot() blobs = img.find_blobs([red_threshold], pixels_threshold=10, area_threshold=10) if blobs: # 取最大色块,避免噪点干扰 b = max(blobs, key=lambda x: x.pixels()) img.draw_rectangle(b.rect(), color=(0, 255, 0)) # 发送质心坐标,格式:x,y\n uart.write("{%d,%d}\n" % (b.cx(), b.cy())) time.sleep_ms(10)

逻辑说明:set_auto_gain(False)和set_auto_whitebal(False)是关键,不锁的话摄像头会自动调整曝光,导致同一颜色在不同时间被识别成不同阈值。pixels_threshold和area_threshold设成10是过滤小噪点,如果激光点较暗可以降到5。串口发送用{%d,%d}\n格式,主控端解析时以{和}为边界,比纯逗号分隔更抗干扰。

参数怎么改:如果识别不到激光点,先把red_threshold的L通道下限降到20,A通道下限降到30;如果识别到太多红色物体,把pixels_threshold提到30以上。注意,阈值没有万能值,每换一个场地都要重新校准,建议赛前写一个阈值调试模式,按按键切换不同预设。

2.3 像素坐标到云台角度的映射公式

OpenMV给出的是图像坐标系下的像素坐标,主控需要把它转成云台的两个舵机角度。参考方案用的是线性映射加死区补偿:

// 主控端(以STM32为例)坐标映射与PID计算 #define IMG_WIDTH 320 #define IMG_HEIGHT 240 #define PAN_CENTER 90.0f // 水平舵机中值角度 #define TILT_CENTER 90.0f // 垂直舵机中值角度 #define PAN_RANGE 30.0f // 水平方向最大偏转角度 #define TILT_RANGE 20.0f // 垂直方向最大偏转角度 float pixel_to_pan_angle(int cx) { // 将像素x坐标映射到舵机角度,中心对齐 float offset = (cx - IMG_WIDTH / 2.0f) / (IMG_WIDTH / 2.0f); return PAN_CENTER + offset * PAN_RANGE; } float pixel_to_tilt_angle(int cy) { float offset = (cy - IMG_HEIGHT / 2.0f) / (IMG_HEIGHT / 2.0f); return TILT_CENTER - offset * TILT_RANGE; // 注意方向,图像y向下增大 }

逻辑说明:offset归一化到[-1, 1],乘以最大偏转角度得到实际角度。PAN_RANGE和TILT_RANGE需要根据你的云台机械结构实测——比如舵机转30度对应图像边缘,那PAN_RANGE就设30。垂直方向取负号是因为图像坐标系y轴向下,而云台抬头对应角度增大。

参数怎么改:如果云台运动方向反了,把PAN_RANGE或TILT_RANGE的符号取反。如果中心点对不准,微调PAN_CENTER和TILT_CENTER。死区补偿是在PID输出后加一个最小占空比,防止舵机在误差很小时抖动,具体值看舵机型号,一般3%到5%。

3. 云台控制与激光追踪:PID调参和抗抖动策略

3.1 双环PID还是单环PID

参考方案用的是位置式单环PID,输入是像素误差,输出是舵机PWM增量。为什么不用双环?因为电赛E题对动态性能要求没那么高,目标基本是静态或慢速移动,单环够用且调参快。双环(位置环+速度环)需要测角速度,得加陀螺仪,硬件复杂度和调试时间都上去了。

单环PID的代码结构:

typedef struct { float Kp, Ki, Kd; float integral; float prev_error; float output_limit; } PID; float pid_calc(PID *pid, float error, float dt) { pid->integral += error * dt; // 积分限幅,防止饱和 if (pid->integral > pid->output_limit) pid->integral = pid->output_limit; if (pid->integral < -pid->output_limit) pid->integral = -pid->output_limit; float derivative = (error - pid->prev_error) / dt; pid->prev_error = error; float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; // 输出限幅 if (output > pid->output_limit) output = pid->output_limit; if (output < -pid->output_limit) output = -pid->output_limit; return output; }

逻辑说明:dt是控制周期,建议5ms到10ms。output_limit对应舵机最大角度变化量,设太大容易过冲,设太小响应慢。积分限幅是必须的,否则误差持续存在时积分项会累积到爆炸,表现为云台慢慢偏向一边然后突然猛转。

参数怎么改:先调Kp,从小到大加,直到云台开始等幅振荡,然后取振荡值的60%。再加Kd抑制振荡,Kd太大会引入高频抖动。Ki最后加,一般给Kp的1/100到1/50,用来消除静态误差。如果云台一直抖,先检查Kd是不是太大了,再检查电源是不是功率不够。

3.2 激光点追踪的丢帧处理

实际跑的时候,OpenMV偶尔会丢帧——激光点被遮挡、反光、或者运动模糊导致识别不到。参考方案的处理策略是:连续丢帧超过3帧才认为目标丢失,丢失后云台保持最后角度不动,而不是归零。归零会导致激光点突然跳回中心,在追踪任务里直接判失败。

// 丢帧计数与保持逻辑 static int lost_count = 0; static float last_pan = PAN_CENTER; static float last_tilt = TILT_CENTER; void update_gimbal(int cx, int cy, int valid) { if (valid) { lost_count = 0; float pan = pixel_to_pan_angle(cx); float tilt = pixel_to_tilt_angle(cy); last_pan = pan; last_tilt = tilt; set_servo_angle(pan, tilt); } else { lost_count++; if (lost_count < 3) { // 保持最后角度,不动作 set_servo_angle(last_pan, last_tilt); } else { // 超过3帧,可以在这里做搜索策略,比如缓慢扫描 // 参考方案里简单保持不动,等目标重新出现 } } }

逻辑说明:valid由主控解析串口数据时置位,收到完整坐标帧为1,超时未收到为0。lost_count阈值3是根据25fps帧率算的,对应120ms,再长就影响追踪连续性了。

参数怎么改:如果场地光线稳定、识别率高,阈值可以降到2;如果激光点经常被手挡住,可以升到5。搜索策略参考方案没做,但你可以加一个:丢失超过10帧后,云台按螺旋轨迹缓慢扫描,直到重新捕获。

3.3 舵机供电和机械回差的坑

这一条是血泪经验:很多人代码调通了,一上电云台就抖,或者追踪时总是慢半拍。八成是供电问题。舵机堵转电流能到1A以上,如果和主控共用一路LDO,电压会被拉低,主控复位、摄像头掉线全来了。正确做法是舵机单独供电,共地但不共电源。

机械回差是另一个坑。塑料齿轮舵机用久了会有间隙,PID输出变化了但云台没动,等间隙消除后突然跳一下。解决方法是选金属齿轮舵机,或者在PID输出后加一个死区,小于死区的变化不输出。死区大小实测:给舵机发一个微小角度变化,看云台实际开始动的时候PWM变化了多少,那个值就是死区。

4. 避坑与排查:调试现场最常见的五个翻车点

4.1 现象:OpenMV串口数据主控收不到

原因:串口波特率不匹配,或者TX/RX接反了。OpenMV的UART3默认波特率是115200,但有些主控初始化时设成了9600。另外OpenMV的TX要接主控的RX,RX接主控的TX,很多人按颜色接反了。

解决:先用USB转TTL单独测OpenMV,确认能收到数据。再检查主控串口初始化代码,波特率、数据位、停止位都要对上。如果用的是软串口,注意引脚是否支持。

4.2 现象:激光点识别不稳定,时有时无

原因:摄像头自动曝光没锁,或者环境光变化导致LAB阈值漂移。另外激光点在白色纸面上反光强,在黑色背景上反而弱,阈值不能一套用到底。

解决:锁定auto_gain和auto_whitebal。如果还不行,加一个补光灯,用恒定光源消除环境光影响。阈值分两套,根据背景颜色切换。

4.3 现象:云台追踪时振荡,激光点画圈

原因:PID的Kp太大或者Kd太小,系统相位裕度不够。也有可能是控制周期不稳定,dt波动大导致微分项计算错误。

解决:先降Kp到原来的70%,看振荡是否减弱。如果减弱了就是增益问题,重新按3.1的步骤调。如果没变化,检查控制周期是不是被其他任务打断了,把PID计算放到定时器中断里,保证dt恒定。

4.4 现象:追踪慢速目标时跟得上,快速目标直接丢

原因:单环PID的响应带宽不够,或者OpenMV帧率太低。25fps意味着每40ms才更新一次目标位置,目标移动快了,两次更新之间位置变化太大,PID以为误差很大,输出饱和。

解决:提高帧率,把framesize降到QQVGA(160x120),帧率能到50fps以上。如果还不行,加前馈——根据目标历史位置预测下一帧位置,提前给云台指令。参考方案里没做前馈,但这是进阶方向。

4.5 现象:比赛现场跑得好好的,换了个场地就废了

原因:场地光照条件变了,背景颜色变了,甚至地面反光都会影响识别。这是视觉方案的通病,没有万能参数。

解决:赛前准备一套快速校准流程:放一个标准红色目标在场地中心,按按键让OpenMV自动扫描当前阈值,保存到Flash。上场后先跑校准,再跑任务。这个流程参考方案里没写,但强烈建议加上。

5. 进阶技巧:用示波器看PID输出和串口数据对齐

调试到后期,光看代码和现象已经不够了,得用示波器。具体做法:把PID输出PWM的一路接到示波器通道1,把串口TX接到通道2,触发方式设成串口起始位下降沿。这样你能看到每次收到新坐标后,PWM是怎么变化的。

如果PWM变化滞后串口数据超过一个控制周期,说明主控解析串口太慢,得优化解析代码,比如用DMA接收加空闲中断。如果PWM变化方向反了,说明坐标映射的符号错了,回去改PAN_RANGE或TILT_RANGE的符号。

还有一个技巧:把PID的误差值通过DAC输出(如果有)或者PWM滤波后输出,接示波器看误差收敛曲线。正常应该是指数衰减,如果等幅振荡说明增益临界,如果发散说明系统不稳定。这个习惯我每次调闭环都强制走一遍,比盯着云台看靠谱得多。

最后说一个验证方法:在A4纸上画一个边长10cm的正方形,让激光点沿边框走一圈,用手机慢动作拍下来,回放看激光点有没有偏离边框超过2mm。如果超了,先查机械安装是不是歪了,再查坐标映射的线性度——像素到角度的映射在边缘可能非线性,需要分段标定。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进