最近在准备2026年电赛,发现视觉识别类题目(比如钢珠/金属球识别、视觉巡线)的热度越来越高。这类题目往往要求快速、稳定地从复杂背景中提取目标并计算位置,对嵌入式平台的算力和易用性提出了双重挑战。传统的方案如OpenMV或树莓派+摄像头,在实时性和集成度上总有取舍。而MaixCAM2这类专为边缘AI设计的一体化开发板,正逐渐成为电赛视觉赛题的“新宠”。
本文将围绕使用MaixCAM2实现钢珠/金属球识别与视觉巡线这一核心任务,拆解从环境搭建、模型训练、算法部署到与主控通信的完整闭环流程。无论你是初次接触嵌入式视觉的“电赛萌新”,还是想寻找更优方案的“老司机”,都能从中获得一套可直接复用的代码、清晰的配置步骤以及避坑指南。我们将重点解决几个关键问题:如何在MaixCAM2上高效运行目标检测模型?如何针对反光强烈的金属球设计图像预处理?如何将识别结果通过串口稳定地发送给STM32等主控?文章最后还会分享一套经过实战检验的模型训练参数和工程优化建议。
1. 背景与核心概念:为什么是MaixCAM2?
在电赛的视觉类题目中,我们通常需要完成“感知-决策-控制”的闭环。感知部分的核心任务可以抽象为两类:
- 目标检测与定位:例如,从散落的物料中识别出特定颜色或形状的钢珠、金属球,并输出其中心坐标、半径或边界框。
- 路径识别与提取:例如,识别赛道上的黑色引导线,并提取其中心线或边缘,用于计算小车的偏移量和转角。
传统的实现方案各有优劣:
- STM32/单片机 + 摄像头模块(如OV7670):成本低,但需要编写底层图像处理算法(二值化、边缘检测等),对复杂环境和光线变化鲁棒性差,开发周期长。
- 树莓派 + USB摄像头 + OpenCV:灵活性高,算法库丰富,但系统较为臃肿,实时性受操作系统调度影响,功耗和体积也相对较大。
- OpenMV:专为机器视觉设计,集成MicroPython,易上手,但其算力有限,运行稍复杂的神经网络模型比较吃力。
MaixCAM2的出现提供了一个折中且强大的选择。它是一款基于算能科技Sophgo SG230X系列芯片的AIoT开发板,其核心优势在于:
- 强大的端侧AI算力:内置NPU(神经网络处理单元),可高效运行YOLO、Mobilenet等轻量化模型,实现真正的实时目标检测与分类,远超传统单片机方案。
- 高度集成与易用性:板载摄像头、LCD屏幕、麦克风、扬声器,并支持Wi-Fi/BT。其开发框架(MaixPy)基于MicroPython,语法简单,且有丰富的视觉和AI相关库,大大降低了开发门槛。
- 丰富的硬件接口:提供UART、I2C、SPI、GPIO等接口,方便与STM32、Arduino等主控芯片通信,构成“MaixCAM2(感知)+ STM32(控制)”的经典电赛架构。
对于电赛中的钢珠识别,难点在于金属表面的高反光特性,容易导致过曝或特征丢失。对于视觉巡线,难点在于光照不均、赛道交叉、虚线识别等。利用MaixCAM2的AI能力,我们可以训练一个模型来直接学习这些特征,而非手动设计复杂的图像预处理流程,从而获得更好的鲁棒性。
2. 环境准备与版本说明
在开始编码前,需要搭建好软硬件开发环境。以下是经过验证的稳定版本组合,建议优先使用。
2.1 硬件清单
- MaixCAM2开发板:主控平台。
- USB Type-C数据线:用于供电、程序烧录和串口通信。
- 电脑:Windows 10/11 或 macOS,用于模型训练和代码编写。
- STM32或其他主控板(可选):用于接收MaixCAM2的识别结果并执行控制。本文将以STM32为例,演示串口通信协议。
2.2 软件环境
- MaixHub模型训练平台:在线训练YOLO模型,无需本地配置复杂的训练环境。访问 MaixHub官网 注册账号。
- MaixPy IDE:用于在电脑上编写、调试和烧录MaixCAM2的MicroPython代码。从 Sipeed官网 下载对应系统版本。
- 串口调试助手:如
SecureCRT、MobaXterm或Arduino IDE自带的串口监视器,用于查看MaixCAM2的打印信息和通信数据。 - 数据集标注工具:推荐使用
LabelImg或在线平台Roboflow,用于标注收集到的钢珠或巡线图片。
版本关键点:
- MaixPy固件:确保MaixCAM2刷写了最新版本的MaixPy固件。固件版本会直接影响AI模型加载和摄像头驱动的兼容性。本文示例基于
MaixPy v0.6.5及以上版本。 - 模型格式:MaixCAM2的NPU支持
KMODEL V4格式的模型。在MaixHub上训练导出时,务必选择正确的目标设备(MaixCAM)和模型格式。
3. 核心流程与原理拆解
整个项目可以分为离线训练和在线部署两大阶段。
3.1 模型训练流程(离线)
这是项目的“大脑”训练环节,决定了识别的准确率。
- 数据采集:使用MaixCAM2的摄像头,在不同光线、角度、背景下拍摄数百张包含钢珠或赛道的图片。对于钢珠,要特别注意包含反光强烈、部分遮挡、多个目标的情况。对于巡线,要包含直线、弯道、十字路口、光线明暗变化等情况。
- 数据标注:使用标注工具,在图片中框出目标。
- 钢珠识别:通常标注为单个类别,如“steel_ball”。标注框应尽可能贴近球体边缘。
- 视觉巡线:这里的标注有技巧。一种方法是不直接识别线,而是识别“可行驶区域”。将赛道两条边线之间的区域标注为一个类别(如“track”)。模型学习后,可以输出这个区域的位置,进而计算出中心线。这种方法比识别细线更稳定。
- 模型训练:将标注好的数据集上传到MaixHub,选择YOLOv5s或YOLOv8n等轻量模型进行训练。平台会自动完成数据增强、训练和验证。
- 模型导出:训练完成后,在MaixHub上选择部署到“MaixCAM”,平台会生成并下载一个
.kmodel文件,这就是可以在MaixCAM2上运行的AI模型。
3.2 嵌入式部署流程(在线)
这是项目的“执行”环节,在MaixCAM2上运行。
- 模型加载:将
.kmodel文件放入MaixCAM2的SD卡或文件系统中。在MaixPy代码中,使用KPU模块加载该模型。 - 图像采集与预处理:通过
sensor模块设置摄像头参数(分辨率、色彩格式等),并捕获图像。通常需要将图像缩放到模型要求的输入尺寸(如224x224)。 - 推理与后处理:将预处理后的图像送入KPU进行推理。获取推理结果(一组包含类别、置信度、坐标的列表)后,需要进行后处理,如非极大值抑制(NMS)来过滤重叠的框。
- 结果分析与通信:解析后处理的结果。对于钢珠,可以计算中心坐标(
cx, cy)和半径近似值。对于巡线,根据识别出的“可行驶区域”框,计算其底部中心点作为路径点。最后,将这些数据封装成简单的协议(如“B,x,y,r\n”),通过UART串口发送给STM32。
4. 完整实战案例:钢珠识别与串口通信
我们以一个具体的钢珠识别项目为例,展示从模型部署到数据输出的全流程代码。
4.1 项目文件结构
将以下文件放在MaixCAM2的SD卡根目录或Flash文件系统中。
SD_CARD/ ├── steel_ball.kmodel # 训练好的YOLO模型文件 └── main.py # 主程序4.2 主程序代码 (main.py)
# main.py - 钢珠目标检测与串口输出 import sensor, image, time, lcd from maix import KPU, GPIO, UART import gc # 1. 初始化硬件 lcd.init() # 初始化显示屏 sensor.reset() # 复位摄像头 sensor.set_pixformat(sensor.RGB565) # 设置彩色格式 sensor.set_framesize(sensor.QVGA) # 设置分辨率 320x240 sensor.set_vflip(1) # 根据摄像头安装方式调整 sensor.set_hmirror(1) sensor.run(1) # 启动摄像头 # 2. 初始化UART串口 (与STM32通信) # 查看MaixCAM2原理图,假设使用UART2 (TX=PIN10, RX=PIN11) uart = UART(UART.UART2, 115200, 8, 0, 1, timeout=1000) # 3. 加载KPU模型 kpu = KPU() # 加载模型,注意模型输入尺寸需与训练时一致,例如224x224 kpu.load_kmodel("/sd/steel_ball.kmodel") # 初始化YOLO参数,需要根据模型anchor和类别数调整 # anchor参数通常在模型训练后由MaixHub提供 anchors = [1.19, 1.98, 2.79, 4.59, 4.53, 8.92, 8.06, 5.29, 10.32, 10.65] kpu.init_yolo2(anchors=anchors, num_classes=1, img_w=224, img_h=224) clock = time.clock() target_class_id = 0 # 假设钢珠类别ID为0 while True: clock.tick() img = sensor.snapshot() # 捕获一帧图像 # 4. 图像预处理 (缩放至模型输入尺寸) img_224 = img.resize(224, 224) img_224.pix_to_ai() # 5. KPU推理 kpu.run_with_output(img_224) dect = kpu.regionlayer_yolo2() # 6. 结果后处理与显示 fps = clock.fps() max_ball = None max_area = 0 for obj in dect: # 过滤低置信度目标 if obj['classid'] == target_class_id and obj['prob'] > 0.6: # 置信度阈值 x, y, w, h = obj['x'], obj['y'], obj['w'], obj['h'] # 将坐标映射回原始图像(320x240)的坐标系 x_orig = int(x * 320 / 224) y_orig = int(y * 240 / 224) w_orig = int(w * 320 / 224) h_orig = int(h * 240 / 224) # 计算中心点和近似半径 cx = x_orig + w_orig // 2 cy = y_orig + h_orig // 2 radius = (w_orig + h_orig) // 4 # 在屏幕上绘制识别框和中心点 img.draw_rectangle(x_orig, y_orig, w_orig, h_orig, color=(0, 255, 0), thickness=2) img.draw_cross(cx, cy, color=(255, 0, 0), size=5) img.draw_string(x_orig, y_orig - 20, "Ball:%.2f" % obj['prob'], color=(0,255,0)) # 找出画面中最大的钢珠 (适用于需要追踪单个目标的场景) area = w_orig * h_orig if area > max_area: max_area = area max_ball = (cx, cy, radius, obj['prob']) # 7. 通过串口发送数据 if max_ball: cx, cy, r, prob = max_ball # 协议定义:以'B'开头,依次发送x, y, 半径,以换行符结束。例如 "B,160,120,25\n" # 坐标原点在图像左上角 data_str = "B,%d,%d,%d\n" % (cx, cy, r) uart.write(data_str) # 也可以在屏幕上打印发送的数据 img.draw_string(10, 10, "UART: " + data_str.strip(), color=(255, 255, 255)) else: # 未识别到目标,可发送特定指令,如 "B,-1,-1,-1\n" uart.write("B,-1,-1,-1\n") # 显示帧率 img.draw_string(10, 220, "FPS:%.2f" % fps, color=(255, 0, 0)) lcd.display(img) # 内存管理 (防止长时间运行内存泄漏) if gc.mem_free() < 10240: # 少于10KB时触发垃圾回收 gc.collect() # 8. 释放资源 (通常不会执行到这里) kpu.deinit() uart.deinit()代码关键点解释:
- 坐标映射:模型在224x224尺寸下推理,但摄像头捕获和显示是320x240。需要将检测框坐标按比例映射回去,否则位置会错乱。
- 置信度阈值:
obj['prob'] > 0.6用于过滤不可信的检测结果,这个值需要根据模型实际表现调整。 - 串口协议:定义了一个简单的ASCII协议
“B,x,y,r\n”。STM32端只需解析以‘B’开头,逗号分隔的数据即可。协议设计应尽量简单,减少解析负担和出错概率。 - 找最大目标:在多个钢珠中只发送最大的一个,这是许多电赛题目的要求(如抓取最大的球)。如果需要所有目标,可以修改为发送列表。
4.3 STM32端接收示例(伪代码/思路)
STM32通过串口中断接收数据,解析出坐标。
// STM32 HAL库 串口接收中断回调示例 (伪代码) char uart_rx_buffer[32]; uint8_t rx_index = 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2_INSTANCE) { // 假设使用USART2 char rx_char = uart_rx_buffer[rx_index]; if(rx_char == '\n') { // 接收到换行符,一帧数据结束 uart_rx_buffer[rx_index] = '\0'; // 添加字符串结束符 // 解析数据,例如使用sscanf char header; int ball_x, ball_y, radius; if(sscanf(uart_rx_buffer, "%c,%d,%d,%d", &header, &ball_x, &ball_y, &radius) == 4) { if(header == 'B') { // 获取到了有效的钢珠数据 // ball_x, ball_y, radius 可用于后续控制逻辑 if(ball_x >= 0) { // 计算舵机PWM或电机速度... } else { // 未识别到目标,执行搜索动作... } } } rx_index = 0; // 重置索引,准备接收下一帧 } else { rx_index++; if(rx_index >= sizeof(uart_rx_buffer)) rx_index = 0; // 防止溢出 } // 重新启动接收中断 HAL_UART_Receive_IT(huart, (uint8_t*)&uart_rx_buffer[rx_index], 1); } }4.4 运行与验证
- 将
steel_ball.kmodel和main.py上传到MaixCAM2。 - 用USB线连接MaixCAM2和电脑,在MaixPy IDE中运行
main.py或将其设为开机自启。 - 将MaixCAM2的UART引脚(TX, RX, GND)与STM32的对应串口引脚相连。
- 打开串口调试助手,连接到MaixCAM2的USB串口(用于调试),观察打印信息。
- 在摄像头前放置钢珠,屏幕上应出现绿色识别框和红色中心十字。同时,串口调试助手中应看到
“B,160,120,25\n”格式的数据输出。 - STM32程序烧录后,应能正确解析该数据并做出响应。
5. 视觉巡线的实现差异与技巧
视觉巡线的代码框架与钢珠识别类似,主要区别在于数据标注策略和结果解析逻辑。
5.1 模型训练策略
- 标注方法:如前所述,推荐对“赛道区域”进行标注,而不是对“线”进行标注。这样模型更稳定,抗干扰能力更强。
- 类别定义:可以只定义一个类别
“track”。
5.2 代码调整要点
在main.py的基础上,修改结果处理部分:
# ... 前面的初始化、模型加载、推理部分与钢珠识别相同 ... while True: # ... 图像捕获、预处理、推理 ... dect = kpu.regionlayer_yolo2() track_roi = None for obj in dect: if obj['classid'] == 0 and obj['prob'] > 0.7: # track类别 x, y, w, h = obj['x'], obj['y'], obj['w'], obj['h'] # 映射坐标到原图 x_orig = int(x * 320 / 224) y_orig = int(y * 240 / 224) w_orig = int(w * 320 / 224) h_orig = int(h * 240 / 224) track_roi = (x_orig, y_orig, w_orig, h_orig) break # 假设只找一个最大的赛道区域 if track_roi: x, y, w, h = track_roi img.draw_rectangle(x, y, w, h, color=(0, 255, 0), thickness=2) # 关键计算:取赛道区域底边中心点作为导航点 # 对于巡线小车,通常关心图像下方区域的路径 bottom_center_x = x + w // 2 bottom_center_y = y + h # 底部Y坐标 img.draw_circle(bottom_center_x, bottom_center_y, 5, color=(255, 0, 0), thickness=2) # 计算偏移量 (假设图像中心为160) error = bottom_center_x - 160 # 发送数据:协议可定义为 "L,error,center_y\n" data_str = "L,%d,%d\n" % (error, bottom_center_y) uart.write(data_str) else: uart.write("L,999,999\n") # 发送丢失信号 # ... 显示、循环等后续代码 ...巡线逻辑解析:
- 我们不再关心“线”,而是关心“可行驶区域”。
- 取该区域底部中心点
(bottom_center_x, bottom_center_y)作为小车当前应该对准的“目标点”。 error = bottom_center_x - 160计算了目标点与图像中心线的水平偏差。STM32根据这个error值,使用PID控制器来调整左右轮差速,使小车对准赛道中心。- 当
error > 0,目标点在中心右侧,小车应右转。 - 当
error < 0,目标点在中心左侧,小车应左转。 - 发送
“L,999,999”表示丢失赛道,STM32可触发原地旋转搜索等行为。
6. 常见问题与排查思路
在开发过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| 模型加载失败 | 1..kmodel文件路径错误或损坏。2. 固件版本与模型不兼容。 3. 内存不足。 | 1. 使用绝对路径/sd/xxx.kmodel,确认文件已上传。2. 更新MaixPy固件到最新版,并在MaixHub上确认导出模型时选择了“MaixCAM”。 3. 在代码开头增加 import gc; gc.collect()进行内存回收。 |
| 摄像头无图像/花屏 | 1. 摄像头初始化参数错误。 2. 硬件连接问题。 | 1. 检查sensor.set_pixformat和sensor.set_framesize是否支持。2. 尝试 sensor.set_vflip(0/1)和sensor.set_hmirror(0/1)。3. 重新插拔摄像头排线。 |
| 识别框位置严重偏移 | 坐标映射计算错误。 | 检查代码中映射比例是否正确:原图坐标 = 模型输出坐标 * (原图尺寸 / 模型输入尺寸)。确保img_w,img_h与模型实际输入一致。 |
| 帧率(FPS)过低 | 1. 模型过大或复杂度高。 2. 图像预处理或绘图操作耗时过长。 3. 开启了不必要的显示。 | 1. 在MaixHub上尝试更小的模型,如YOLOv5n。 2. 减少 lcd.display()的调用频率,或降低显示分辨率。3. 简化屏幕绘制内容。 |
| 串口数据乱码或丢失 | 1. 波特率等参数不匹配。 2. 未处理数据粘包。 3. 电气连接不可靠。 | 1. 确认MaixCAM2与STM32的波特率、数据位、停止位、校验位完全一致。 2. 在数据帧首尾添加帧头帧尾(如 <B,100,200,30>),STM32端进行完整帧解析。3. 共地,检查TX/RX是否接反,使用示波器查看波形。 |
| 金属球反光导致识别不稳定 | 训练数据缺乏反光样本,模型未学习到该特征。 | 1.数据增强:在MaixHub训练时,务必开启色彩抖动、亮度变化等增强选项。 2.增加负样本:在数据集中加入一些高光区域但不是钢珠的图片。 3.图像预处理:在代码中尝试将RGB图像转为HSV色彩空间,对V(明度)通道进行限制,或使用图像滤波(如中值滤波)平滑高光点。 |
| 巡线时在交叉口误判 | 赛道区域标注不一致,或模型将交叉口误认为多个区域。 | 1.统一标注规范:交叉口应标注为一个完整的矩形区域,覆盖所有分支入口。 2.后处理逻辑:代码中取面积最大的区域作为当前赛道,避免多个小区域的干扰。 |
7. 最佳实践与工程优化建议
要让你的电赛视觉系统更稳定、更高效,可以参考以下经验:
模型训练与数据
- 数据质量优于数量:200张标注精准的图片,远胜于1000张标注粗糙的图片。确保标注框紧贴目标边缘。
- 模拟赛场环境:尽可能在类似比赛现场的光照(如日光灯、自然光)和背景下采集数据。
- 利用MaixHub的自动增强:训练时务必开启“自动数据增强”,它能显著提升模型的泛化能力。
- 模型量化:在MaixHub导出时,选择INT8量化。这会在几乎不损失精度的情况下大幅提升推理速度,降低内存占用。
代码与性能
- 固定帧率:在主循环中使用
time.sleep_ms()来稳定帧率,避免不必要的CPU满载。例如,设定为30FPS:time.sleep_ms(33)。 - 选择性显示:在最终比赛时,可以考虑关闭LCD显示或仅显示关键信息,以节省CPU资源。
- 错误恢复机制:在
while True循环内部加入try-except,捕获可能出现的运行时错误(如偶尔的I/O错误),并重新初始化硬件,防止程序完全卡死。
while True: try: # 主循环代码 pass except Exception as e: print("Error:", e) time.sleep_ms(1000) # 可选:重新初始化sensor或KPU # sensor.reset() # kpu.deinit() # kpu.load_kmodel(...)- 固定帧率:在主循环中使用
通信与系统集成
- 设计鲁棒的通信协议:除了逗号分隔,建议增加帧头(
<)和帧尾(>),并添加简单的校验和,例如“<B,160,120,25,205>”,其中205是前面所有字节的累加和取低8位。STM32端验证校验和通过后才使用数据。 - 状态机设计:在STM32端,设计清晰的状态机(如
SEARCHING,TRACKING,LOST),根据MaixCAM2传回的数据(有效坐标或丢失信号)进行状态切换,使小车行为更有序。 - 电源隔离:电机等大功率设备与MaixCAM2、STM32的电源最好隔离,或使用大电容稳压,防止电机启动瞬间导致电压跌落,引发系统复位。
- 设计鲁棒的通信协议:除了逗号分隔,建议增加帧头(
比赛现场调试
- 预留调试接口:除了主控通信串口,保留一个USB串口连接到电脑,用于比赛时实时打印日志、调整参数(如置信度阈值)。
- 参数可配置化:将置信度阈值、颜色阈值、ROI区域等关键参数设置为全局变量,甚至可以通过串口指令动态修改,方便现场快速适配环境变化。
- 准备多个模型:针对不同的光照条件(强光、弱光),训练2-3个模型备用。现场根据情况选择加载。
通过以上步骤,你就能搭建一个以MaixCAM2为核心、稳定可靠的视觉感知模块。它负责“看见”和“理解”环境,并通过串口将“决策建议”发送给执行机构(STM32),从而完成电赛题目要求的复杂任务。这套方案将先进的AI视觉能力与嵌入式系统的实时性相结合,是应对未来电赛视觉题目的有力武器。