前言
在近几年的全国大学生电子设计竞赛中,"无视觉,不电赛"已经成为一句名言。无论是无人机的精准降落、小车的循迹避障,还是云台的激光打靶,机器视觉(视觉传感器)就是整个控制系统的"眼睛" 。
但这双"眼睛"往往也是比赛中最容易翻车的一环:实验室里百发百中,一到赛场见光死;画面卡成PPT,单片机疯狂丢包...
本文将从视觉平台选型、核心算法、光线抗干扰以及最核心的"视觉-STM32通信协议"四个维度,带你彻底打通电赛视觉的任督二脉!
@TOC
一、 军火库大赏:电赛视觉硬件平台怎么选?
很多队伍一开始就陷入了"必须用最高端设备"的误区,实际上够用、稳定、开发快才是电赛的王道。
1. 萌新与快攻首选:OpenMV
-
定位:轻量级、开箱即用、单片机工程师的福音。
-
适用场景:色块追踪(红绿激光、小球)、寻找色环、基础巡线、AprilTag 识别。
-
优势:MicroPython 开发,IDE 自带无数官方例程,5 分钟就能跑通一个色块追踪。
-
痛点:算力极弱,跑不了复杂的神经网络,处理高分辨率画面时帧率会掉到个位数。
2. 性价比与轻量AI之王:K210 (如 Maix Bit / Maix Dock)
-
定位:带硬件 KPU 的边缘计算芯片,能跑深度学习的 OpenMV。
-
适用场景:数字识别(手写体/印刷体)、特定目标分类检测(YOLO 目标检测)、高帧率巡线。
-
优势:几十块钱的价格,内置神经网络加速器,跑轻量级 YOLO 帧率极高,同样支持 MicroPython (MaixPy)。
3. 性能怪兽:树莓派4B/5 & 香橙派 (Orange Pi) & Jetson Nano
-
定位:真正的微型计算机,Linux 平台 + OpenCV + Python/C++。
-
适用场景:复杂图像处理(轮廓提取、形态学处理)、高精度位姿解算、大型深度学习模型(YOLOv5/v8)。
-
优势:算力碾压,生态极其丰富。
-
痛点:需要系统配置时间,开机慢(比赛要求一键上电运行,系统启动要几十秒是个大坑),需要掌握 Linux 基础。
💡 黄金建议 :如果题目只需要找颜色、找黑线、扫码,无脑选 OpenMV ;如果需要识别数字和复杂物体,选 K210 或 香橙派。不要在比赛的四天三夜里现场编译 OpenCV!
二、 电赛视觉核心四大算法模块
比赛中常用的视觉算法其实就那几种,提前准备好代码块,比赛时直接拼装。
-
LAB 颜色空间寻色块(千万别用 RGB!)
- RGB 受光照影响极大,而 LAB 颜色空间将**亮度(L)与色彩(A,B)**分离。在寻找目标颜色(如红色激光点)时,使用 LAB 阈值可以极大增加对环境光的鲁棒性。
-
大视野下的 ROI(感兴趣区域)巡线
- 不要对整个画面进行运算。把画面切分成上、中、下三个横向的 ROI 区域,分别计算黑线的形心,再进行线性回归拟合,这样既能提高帧率,又能预防十字路口的干扰。
-
AprilTag(电赛官方指定的"物理外挂")
- AprilTag 是一系列类似二维码的标签。由于它具有极其强大的抗遮挡、抗模糊能力,还能直接解算出摄像头的 X, Y, Z 坐标和旋转姿态,在无人机定位、小车绝对坐标导航中简直是降维打击。
-
轻量级神经网络(MobileNet / YOLO-Fastest)
- 遇到"识别数字 1-8"或"分类不同形状标靶"时,传统的模板匹配容易死,直接上云端平台(如 Edge Impulse 或 百度飞桨)训练一个极小模型部署到 K210 或 香橙派上。
三、 视觉与主控的桥梁:高鲁棒 UART 通信协议(重点附码)
视觉识别得再准,STM32 接收不到或者接错位,全是零分!
绝大多数初学者使用简单的 printf 发送字符串,STM32 用 scanf 接收,这在高速控制中极其容易丢包和错位。
必须使用**"帧头 + 数据 + 校验和 + 帧尾"**的十六进制状态机解析法!
1. 通信数据包定义 (视觉端 Python 发送)
假设我们需要发送目标中心的 X 坐标、Y 坐标和目标标志位:
格式:0x55 0xAA X_H X_L Y_H Y_L Flag Checksum 0xBB
2. STM32 状态机解析框架 (C语言,直接抄作业)
在 STM32 的串口接收中断(或者 DMA + 空闲中断)中,使用以下状态机机制解析数据,这套代码能免疫 99% 的乱码和丢包错位问题!
codeC
#include "stdint.h"
// 定义接收状态
typedef enum {
WAIT_HEADER1, // 等待帧头1
WAIT_HEADER2, // 等待帧头2
RECEIVE_DATA, // 接收数据体
WAIT_CHECKSUM,// 等待校验和
WAIT_TAIL // 等待帧尾
} ReceiveState;
uint8_t Rx_Buffer[10]; // 缓存数组
uint8_t Rx_Index = 0; // 索引
ReceiveState State = WAIT_HEADER1;
uint8_t Checksum_Cal = 0; // 计算出的校验和
// 视觉数据结构体
typedef struct {
int16_t X;
int16_t Y;
uint8_t Flag;
} VisionData_t;
VisionData_t VisionTarget;
/**
* @brief 视觉串口单字节接收状态机(放在串口RX中断中调用)
* @param data: 串口接收到的1字节数据
*/
void Vision_Parse_Byte(uint8_t data)
{
switch (State) {
case WAIT_HEADER1:
if (data == 0x55) {
State = WAIT_HEADER2;
Checksum_Cal = 0; // 重置校验和
}
break;
case WAIT_HEADER2:
if (data == 0xAA) {
State = RECEIVE_DATA;
Rx_Index = 0;
} else {
State = WAIT_HEADER1; // 接收错误,复位状态机
}
break;
case RECEIVE_DATA:
Rx_Buffer[Rx_Index++] = data;
Checksum_Cal += data; // 累加校验和
if (Rx_Index == 5) { // 假设数据体有5个字节(X高低,Y高低,Flag)
State = WAIT_CHECKSUM;
}
break;
case WAIT_CHECKSUM:
if (data == (Checksum_Cal & 0xFF)) { // 验证校验和
State = WAIT_TAIL;
} else {
State = WAIT_HEADER1; // 校验失败,丢弃这一帧
}
break;
case WAIT_TAIL:
if (data == 0xBB) {
// !!!成功接收一帧完整数据,开始解包!!!
VisionTarget.X = (Rx_Buffer[0] << 8) | Rx_Buffer[1];
VisionTarget.Y = (Rx_Buffer[2] << 8) | Rx_Buffer[3];
VisionTarget.Flag = Rx_Buffer[4];
// 可以在这里设置一个标志位通知主循环数据已更新
}
State = WAIT_HEADER1; // 准备接收下一帧
break;
default:
State = WAIT_HEADER1;
break;
}
}
四、 赛场求生:视觉防翻车玄学经验(血泪总结)
1. "见光死"的罪魁祸首:曝光与白平衡
-
现象:晚上在实验室调好的颜色阈值,第二天早上去测评现场,摄像头识别一团糟。
-
解决 :必须关闭摄像头的自动曝光(Auto Exposure)和自动白平衡(Auto White Balance)! 手动将曝光值固定,同时自带补光灯(LED阵列)。让你的补光灯亮度盖过环境光,视觉就能稳定如狗。
2. 帧率(FPS)就是控制的生命线
-
现象:小车在追踪目标时"画龙"(疯狂左右摇摆),PID 怎么调都没用。
-
解决:这不是 PID 的问题,是你的视觉帧率太低了(比如只有 10 帧/秒),导致控制系统出现了严重的滞后。
-
提帧率秘籍 :降低分辨率(能用 QQVGA 160x120 绝不用 VGA 640x480)、缩小感兴趣区域(ROI)、关闭图像实时显示。保证视觉帧率在 30 FPS 以上,最高做到 50+ FPS。
3. 镜头畸变
- 广角镜头(无畸变镜头)视野虽然大,但在边缘处会把直线看成弯曲的。如果是做精密打靶或计算坐标,必须在赛前跑一遍相机标定(Camera Calibration),获取内参矩阵并进行图像去畸变。
结语
视觉技术在电赛中是上限的代名词。一个优秀的电赛视觉方案,不在于你用了多么复杂的算法模型,而在于它能抵抗恶劣的光线变化、拥有极高的实时帧率,以及与 STM32 主控之间无缝且坚如磐石的数据交互。
现在就把这套状态机通信代码写进你的工程库里吧!预祝大家在赛场上摄像头永不罢工,稳稳斩获国一!🏆
看完觉得干货满满?
👍 点赞 + ⭐ 收藏 ,你的支持是我更新的动力!
如果你在配环境、调 OpenMV/K210 或者串口通信时遇到玄学 Bug,欢迎在评论区贴出你的问题,有问必答!👇