一开始先说点掏心窝子的
搞了几年智能汽车竞赛,从最开始对着黑白摄像头调阈值调到怀疑人生,到后来换上了OpenART Plus这类带屏幕、带算力的视觉板子,最大的感受就是:赛道上的玩法彻底变了。以前我们拼的是谁的特征提取更稳,现在拼的是谁能把视觉信息玩出花来——而AprilTag就是这朵花的花蕊。
AprilTag不是新东西,工业机器人、无人机视觉定位用了很久了,但在智能汽车竞赛里大规模流行起来,也就是这几年的事。原因很简单:它检测稳定、计算量小、自带ID信息和空间位姿解算,不需要你训练任何模型。搭配OpenART Plus来玩增强现实,效果非常直接——你不需要在电脑上做复杂的图像处理,板子端就能实时检测标签、算坐标,然后在LCD屏上把虚拟元素叠在真实图像上,形成一套完整的AR导航体验。
这篇文章,我打算把项目从零拆一遍:为什么要用OpenART Plus、AprilTag背后的原理怎么理解、实际代码怎么写、参数怎么调、竞赛现场会踩哪些坑。看完之后,你至少可以独立复现一个“贴了AprilTag就能让小车识别、定位、并在画面上绘制AR指引线”的完整案例。
1. 内容整体设计与思路拆解
1.1 为什么是AprilTag,而不是二维码或者颜色块
先说结论:颜色块只适合“粗略区分区域”,二维码承载信息多但识别和解码成本高,AprilTag是两者之间的完美折中。
AprilTag本质上也是一种二维码家族,但它的设计初衷是“精确几何定位”。它包含一个二维的编码图案,配合特定的检测算法,能在图像中快速找到标签的四角坐标,并基于相机内参解算出标签相对于相机的三维位姿(位置和方向)。这就意味着,你不仅可以知道“看到了哪个标签”,还能知道“标签在小车前方多少厘米、偏左还是偏右、旋转了多少度”。
如果你的视觉方案只靠颜色识别,至少会遇到三类问题——光照一变,阈值就废了;颜色相近的元素互相混淆;摄像头看到的面积大小无法直接换算成真实距离。AprilTag这三类问题全部规避掉了,它靠的是黑白图案本身的几何特征做检测,而不是依赖颜色或者灰度绝对值。竞赛赛场上光再乱,只要图案没有被彻底糊死,检测基本不受影响。
1.2 为什么选OpenART Plus作为载体
OpenART Plus本质上是OpenMV生态里的一个增强版视觉模块:自带一块LCD屏幕、一颗高性能摄像头、完整的MicroPython开发环境,最关键的是IO口可以直接输出PWM、串口数据给主控MCU,并且本身可以脱离PC独立运行。
在智能汽车竞赛里,这非常关键。很多车队把视觉模块当“眼睛”,把主控板当“大脑”。OpenART Plus的身份就是那双“眼睛里的视网膜”——摄像头负责采集画面,板子上的处理器负责跑AprilTag检测、位姿解算、AR绘制,然后把计算结果通过串口或IO口发给主控,主控去控制电机和舵机。整个过程毫秒级完成,小车不会因为视觉处理太慢而一头撞上路肩。
我最欣赏它的还有一点:调试体验好。LCD屏幕直接显示带AR叠加的画面,你能亲眼看到检测框、坐标轴、虚拟路径是怎么一点点跟踪真实标签的。这在竞赛现场是救命级的特性——你不需要反复插拔SD卡导图,也不需要打开串口终端盯着乱七八糟的数据流,一眼就能确认算法工作正不正常。
1.3 增强现实在这个项目里解决什么问题
说句实话,竞赛里对“增强现实”的用法,很多人理解得太窄。以为是做个酷炫特效,实际上AR是帮助你“减少传感器依赖、提升决策精度”的交互层。
举个例子,传统巡线方案通常要靠灰度传感器数组或者摄像头找赛道边缘。但如果赛道元素中有180度大掉头、发卡弯、障碍物绕行区域,纯巡线在入弯角度错误时很难自我修正。如果我们在入弯前的路边贴上几个AprilTag,小车可以在2米开外就识别到标签的位姿,然后立刻计算出“当前车速下提前转向应该在哪条虚拟轨迹上”,在LCD画面上生成一条AR引导线。驾驶员(或者自主控制逻辑)顺着这条引导线修正方向,过弯成功率大幅提升。
这就把AR从“显示层”变成了“决策层”。这套逻辑放到比赛里,就是实打实的成绩提升。
2. 核心细节解析与实操要点
2.1 了解你的摄像头:OpenART Plus的镜头参数与成像
在动手写代码之前,建议先把摄像头和镜头的参数摸清楚。成像质量决定了AprilTag检测的稳定性和位姿解算的精度。
OpenART Plus的OV5640传感器支持最大2592×1944分辨率,但实际跑AprilTag的时候,我建议不要跑满分辨率。原因有两条:第一,高分辨率意味着每帧的数据量和计算时间指数级上升,实时性会崩;第二,AprilTag检测算法本质上只关心黑白几何边缘,中低分辨率下反而能滤掉部分高频噪点,检测更稳。
竞赛中最常用的三组分辨率如下,你可以根据自己的场景做取舍。
| 分辨率 | FPS参考 | 适用场景 |
|---|---|---|
| QVGA(320×240) | 40-60 FPS | 常规识别、高速行驶 |
| 320×240裁剪ROI | 60-100 FPS | 只关注固定区域的标签 |
| VGA(640×480) | 15-25 FPS | 需要更高定位精度或绘制复杂AR |
| 1080P(1920×1080) | 8-12 FPS | 离轨调试、桌面演示,不适合高速竞赛 |
我个人在竞赛场景下常用QVGA,跑出来的位姿数据已经足够支撑小车决策。如果发现某一类标签远距离识别不稳定,优先裁剪ROI而不是拉高分辨率。
镜头方面,OpenART Plus标配的镜头通常视野在60°到90°之间,视野越大,同样距离下标签成像越小;视野越小,远距离标签成像越大但近处可能装不下整张标签。竞赛环境下我建议控制在70°左右的标准镜头,同时保证标签在画面中至少占30×30像素,否则AprilTag的解算会出现明显的抖动。
2.2 AprilTag家族选型:tag36h11还是tag25h9
AprilTag有多个编码家族,OpenMV固件里最常用的是tag36h11和tag25h9。这个选择会影响单帧能检测的标签数量上限、最小可识别尺寸和误检率,很多人没注意,其实挺关键的。
tag36h11的编码密度高,有587个独立ID,误检率极低(官方给出的误检概率在万亿分之一量级),但单个标签的图案更复杂,同样尺寸下需要的像素面积更大。tag25h9的ID数量只有35个,编码密度低,但图案更稀疏,小尺寸下更容易被识别出来。
我的建议很直白:
- 竞赛地图元素超过35个,选tag36h11。比如你要给每个路口、每个障碍区、每个目标点都编独立ID。
- 竞赛地图元素较少、标签贴得比较远,选tag25h9。它对距离的容忍度更高。
- 混用也不是不行,但不建议,因为两份家族字典会同时占用固件资源,且误检的边界行为更难以预期。
实际项目里,我的大多数赛场方案都固定在tag36h11,原因就是不想在“误识别”这种低阶问题上浪费比赛时间。几百个ID带来的扩展性也让我可以在不同赛题复用同一套字典。
2.3 硬件接线与安装高度:靠细节取胜
OpenART Plus接主控,核心是UART串口或者PWM/GPIO两种方式。UART适合传结构化数据(标签ID、坐标、距离、偏航角),GPIO适合传离散信号(检测到A标签、压线等)。我通常的做法是:板子用UART把最关键的决策信息包成协议帧发给主控,同时保留一路GPIO作为“盲区补丁”——比如当识别到某个关键标签时,直接拉高一个引脚,让主控无需解析协议也能立刻反应。
安装高度和角度,是新手最容易忽略的变量。AprilTag检测的有效性受“入射角”影响很大,标签平面和相机光轴夹角越大,解算误差越大。如果相机安装得太低,看标签就像一个斜躺的菱形,虽然也能识别出ID,但距离和方向角的数据就越不准。
我实测下来的经验:
- 相机离地高度建议20-35cm,太低容易丢远距离标签,太高会增大近处盲区。
- 俯仰角尽量保持水平,最多向下倾斜15°以内,别为了“看更近的赛道”而压太多。
- 正对着标签行驶时精度最高,让标签尽量出现在画面中心,边缘处的镜头畸变会直接影响位姿准度。
- 如果必须大倾角安装,记得在代码里做镜头畸变校正,OpenMV里有lens_corr函数,性价比很高。
2.4 认识“相机标定”的意义
AprilTag位姿解算的核心,不是靠玄学,而是靠“针孔相机模型”。简单来说,三维空间中的一个点,投影到二维图像上时,满足一个数学关系。这个关系里含有相机内参——焦距(fx, fy)、主点坐标(cx, cy),以及畸变系数。
默认情况下,OpenART Plus的内参是按出厂镜头预设的,够用,但不是最优。对竞赛来说,球馆、马路边、室内灯光、赛道上空的不同环境,都会影响画面的亮度与对比度,但不会改变镜头本身的物理内参。所以你可以花半小时,用OpenMV自带的标定工具或者PyCharm连板子,对当前镜头做一次内参标定,把结果存进flash里。
标定之后,AprilTag解算出来的tx、ty、tz精度会显著提升,AR引导线的贴合度也会更“跟手”。尤其在贴地元素和小尺寸标签场景下,标定前后的差别肉眼可见。
3. 实操过程与核心环节实现
3.1 开发环境搭建:从IDE到第一个检测脚本
OpenART Plus的开发,官方推荐OpenMV IDE,它自带固件烧录、串口终端、帧缓冲区查看,安装包在官网直接下载就能用。连上板子后,IDE会识别到一个虚拟串口,点击连接按钮,右下角会出现实时画面。
环境搭建阶段有三个容易踩坑的地方,提前说:
- 驱动问题。某些USB转串口芯片在Windows下不认,需要手动安装驱动。IDE识别不到板子时,先打开设备管理器看看有没有未知设备。
- 固件版本。OpenART Plus与经典OpenMV Cam的固件不完全一样,官方会放出专属固件包,别刷错。更新的时候选对应的.dfu或.elf文件。
- 中文路径。MicroPython的源码文件如果放在含中文或空格的路径下,偶尔会出现导入异常。建议工程目录用纯英文。
环境就绪后,第一个AprilTag检测脚本可以直接用官方库里的find_apriltags。最基础的代码如下:
import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time = 2000) sensor.set_auto_gain(False) # 关闭自动增益,固定曝光 sensor.set_auto_whitebal(False) clock = time.clock() while True: clock.tick() img = sensor.snapshot() for tag in img.find_apriltags(families=image.TAG36H11): img.draw_rectangle(tag.rect(), color=(255, 0, 0)) img.draw_cross(tag.cx(), tag.cy(), color=(0, 255, 0)) print("ID:", tag.id(), "dist:", tag.z_translation()) print(clock.fps())这段代码做了几件事:关闭自动曝光和自动白平衡(保证画面亮度一致,后续调参才有意义),循环采集画面,并对每一帧做AprilTag检测,检测到后绘制外框和中心十字,同时打印ID和距离。
你可能注意到,这个脚本没有做任何“AR”效果,但它是所有AR选项的地基。
3.2 深入find_apriltags:从参数到输出
find_apriltags的输出对象是AprilTag类,常用的属性有这些:
| 属性 | 含义 | 竞赛用途 |
|---|---|---|
| id | 标签ID | 区分不同元素 |
| cx, cy | 标签中心像素坐标 | 用于侧向偏差判断 |
| x_translation | 标签相对相机的X轴偏移(米) | 左右偏差控制 |
| y_translation | 标签相对相机的Y轴偏移(米) | 前后距离 |
| z_translation | 标签平面相对相机的距离(米) | 主距离判断 |
| x_rotation, y_rotation, z_rotation | 标签相对相机的旋转角(弧度) | 姿态评估 |
| rect | 四角像素坐标 | 绘制外框 |
| family | 标签家族 | 知道是谁家的 |
参数方面,几个关键的我逐个说。
img.find_apriltags(families=image.TAG36H11, roi=..., threshold=300, max_nb_detections=10, decimate=1.0)- families:选择标签家族,可以传一个或多个。同时检测家族多,计算开销也大,没必要时只保留一种。
- roi:感兴趣区域。只检测画面中的一部分,速度提升明显。竞赛中如果标签永远出现在画面中下部,那ROI可以精准框住这个区域,丢掉无效画面的计算。
- threshold:检测阈值,默认300。值越高,对噪点的敏感性越低,误检少,但也可能漏掉模糊的小标签。通常我建议保持在200到500之间调试。
- max_nb_detections:一帧最多检测几个标签。默认是10,够用,如果想极端提速可以降到5。
- decimate:降采样系数。传入2.0表示先在半分辨率下粗检,再回原始分辨率精检,对速度优化非常明显。缺点是远距离小标签可能直接粗检阶段就被过滤掉。
我的经验是,QVGA分辨率下,decimate用1.0;VGA分辨率下,decimate用2.0,两者速度与精度的平衡最舒服。
3.3 从标签到AR:如何把3D位姿变成画面上的虚拟元素
AR效果的本质,是把一个三维坐标系的“虚拟物体”,通过相机的投影模型映射到二维图像上,再叠加显示。在OpenMV里,有多种方式绘制虚拟内容。
方式一:绘制检测框与坐标轴。这是最直观的AR基础形态,直接把虚实结合的第一个层次做出来。
for tag in img.find_apriltags(families=image.TAG36H11): img.draw_rectangle(tag.rect(), color=(255, 0, 0), thickness=2) # 假设y轴向上,绘制坐标系轴线的简化做法: corners = tag.corners() img.draw_line(corners[0][0], corners[0][1], corners[1][0], corners[1][1], color=(0, 255, 0), thickness=2) img.draw_line(corners[0][0], corners[0][1], corners[3][0], corners[3][1], color=(0, 0, 255), thickness=2)这段代码用标签的四个角点,绘制出彩色的坐标轴,一条边代表X方向,另一条代表Y方向。当标签转动时,线条也跟着转——这就是最简单的AR坐标系可视化。
方式二:绘制虚拟导航线。这个更适合智能汽车竞赛场景。假设你在下一个路口贴了一个标签,小车只需要在画面中确定标签中心坐标,然后从画面底部中心画一条“引导线”连到标签中心,这条线就是视觉上的AR导航路径。
但严格意义上的AR“引导线”,不应该只是简单连线,而应该结合位姿解算把终点投影回画面。做法是:根据tag的z_translation和x_translation,推算标签相对车辆坐标系的三维位置,然后通过投影公式计算终点在图像上的像素位置。OpenMV的image模块里提供了投影相关的工具函数,比如用tag.corners()结合坐标换算可以实现同类的效果。
如果画面比较抖,还可以对投影坐标做平滑滤波,这里用简单的指数移动平均就很有效:
smooth_x = 0 smooth_y = 0 alpha = 0.6 # 在每帧检测到标签时: smooth_x = alpha * tag.cx() + (1 - alpha) * smooth_x smooth_y = alpha * tag.cy() + (1 - alpha) * smooth_yalpha越大,跟踪越跟手但越抖;alpha越小,轨迹越平滑但滞后越明显。车辆高速行驶时,建议alpha取0.7左右。
方式三:AR弹窗信息板。通过LCD的图形叠加,在标签旁边画一个半透明的信息板,显示当前ID、距离、偏航角。OpenARTPus的lcd屏幕支持直接显示图形层,并且支持颜色填充。你可以用img.draw_rectangle加fill参数实现信息板的绘制。
img.draw_rectangle(tag.cx() + 10, tag.cy() - 10, 80, 40, color=(0, 0, 0), fill=True) img.draw_string(tag.cx() + 16, tag.cy() - 4, "ID:%d D:%.2fm" % (tag.id(), tag.z_translation()), color=(255, 255, 255))这种信息叠加对调试特别有用,因为你不需要外接显示器,在车模的LCD屏上就能看到所有关键状态。
3.4 完整案例:AR标签识别与引导线绘制
把上面的内容串成一个可直接烧录运行的完整案例。功能包括:
- 检测tag36h11标签;
- 绘制检测框、中心点、坐标轴;
- 在标签右侧绘制AR信息板;
- 从画面底部中心画一条AR引导线指向标签中心;
- 通过串口按固定格式上报标签数据和位姿。
import sensor, image, time, ustruct from machine import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time = 2000) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) uart = UART(3, 115200, timeout_char = 1000) # 平滑滤波 smooth_x, smooth_y = 0, 0 alpha = 0.7 clock = time.clock() while True: clock.tick() img = sensor.snapshot() tags = img.find_apriltags(families=image.TAG36H11) target_tag = None for tag in tags: img.draw_rectangle(tag.rect(), color=(255, 0, 0), thickness=2) img.draw_cross(tag.cx(), tag.cy(), color=(0, 255, 0)) # 画简单坐标轴 corners = tag.corners() img.draw_line(corners[0][0], corners[0][1], corners[1][0], corners[1][1], color=(0, 255, 0), thickness=2) img.draw_line(corners[0][0], corners[0][1], corners[3][0], corners[3][1], color=(0, 0, 255), thickness=2) # 选择离画面中心最近的标签作为“目标标签” if target_tag is None: target_tag = tag else: if abs(tag.cx() - img.width()//2) < abs(target_tag.cx() - img.width()//2): target_tag = tag # 信息板 info_str = "ID:%d D:%.2fm" % (tag.id(), tag.z_translation()) img.draw_rectangle(tag.cx() + 10, tag.cy() - 10, 90, 24, color=(0, 0, 0), fill=True) img.draw_string(tag.cx() + 14, tag.cy() - 6, info_str, color=(255, 255, 255)) if target_tag is not None: # 平滑跟踪 smooth_x = alpha * target_tag.cx() + (1 - alpha) * smooth_x smooth_y = alpha * target_tag.cy() + (1 - alpha) * smooth_y # AR引导线:画面底部中心 -> 标签中心 img.draw_line(img.width()//2, img.height(), int(smooth_x), int(smooth_y), color=(0, 200, 255), thickness=3) # 串口发送 data = ustruct.pack("<hBfff", target_tag.id(), 1, target_tag.x_translation(), target_tag.y_translation(), target_tag.z_translation()) uart.write(data) print(clock.fps())这个代码里最关键的设计有两个。一是“选离画面中心最近的标签”,这在多个标签同时入镜时非常有用,保证车辆跟踪的是正前方那个,而不是旁边那个。二是串口用二进制协议打包,比文本更高效,主控端解析也更简单。
3.5 位姿坐标到AR投影:理解y轴与tilt
很多人第一次用AprilTag的 z_translation 时,会疑惑“为什么距离数值这么大或者这么小”。这涉及两个容易被忽略的点。
第一,标签的y轴是向下还是向上,和你的相机安装方向有关。OpenMV官方文档里说得很清楚,AprilTag的坐标定义不是标准的“图像坐标”,而是一个右手坐标系。默认情况下,标签平面的y轴指向下方,这意味着在俯视安装时,y_translation可能是负值,代表标签在相机视野的下方。竞赛应用里,我们只需要关心x_translation(左右)和z_translation(距离),y方向就不必太纠结。
第二,位姿解算对“标签倾斜”敏感。当你行驶过程中,车身颠簸导致相机轻微抬头或低头,z_translation本身会变化。更稳的做法是持续追踪多帧数值,用滑动窗口平均,而不是单帧直接跳变响应。
我写过一个简易的中值滤波,取最近5帧的z_translation排序后取中间值,效果比指数平均更抗突变,实测在颠簸路面上表现很好。中值滤波带来的副作用是大概多1-2帧的延迟,高速场景可以接受。
3.6 LCD显示方向与AR叠加的对应关系
OpenART Plus的LCD方向默认是横向的,但如果你把板子竖装在车架上,画面可能颠倒了。解决办法是调用lcd.rotation()或者img.rotation_corr()对画面进行旋转。
这里有个坑:RGB565图像旋转会引入额外计算开销,QPIXEL格式的旋转速度会快很多。所以如果你需要竖屏显示+AR叠加,可以考虑把摄像头设为GRAYSCALE或者RGB565,然后在显示阶段用lcd.rotation()旋转整个屏幕,而不是先旋转图像再做检测。检测依然在原始方向上完成,只是显示方向旋转了,节省计算量。
另一个细节是,LCD屏上的颜色顺序是RGB565,和OpenMV绘制函数的color元组一样是(R, G, B)顺序,不要搞反。很多人在电脑调试时看到正常,一上LCD就发现红蓝互换,就是颜色通道顺序没对上。
4. 常见问题与排查技巧实录
4.1 画面卡顿与FPS过低
现象:检测到标签后,LCD画面明显变慢,FPS跌到个位数。
排查思路:
- 先看分辨率。QVGA如果跑不满30FPS,大概率是固件内Debug模式在持续输出图像到USB。断开IDE的“帧缓冲区”查看功能,或者直接拔掉USB线用电池供电运行。
- 检查decimate参数。VGA分辨率建议配合decimate=2.0,可以释放大量算力。
- 检查是否同时开启了多个家族检测。families参数传入了TAG36H11和TAG25H9两套字典时,每帧检测耗时接近翻倍。
- 检查代码里的绘图操作。draw_string在每帧绘制大量字符会有开销,建议只对目标标签绘制详细信息。
实用技巧:把帧率显示从print改成只在LCD上绘制一个小的数字块,减少串口输出的IO等待。串口print本身也是阻塞操作,会严重影响fps。
4.2 灯光下误检与漏检
现象:室内灯光频闪环境下,标签轮廓抖动,偶尔出现“幽灵标签”(识别出并不存在的ID);或者距离稍远就彻底漏检。
原因分析:LED灯管存在50Hz或100Hz频闪,手机摄像头和OpenMV的全局快门可以捕捉到闪烁导致帧间亮度变化极大。关闭自动增益之后,如果曝光时间恰好落在灯光暗周期,画面整体偏黑,标签就检测不到了。
解决办法:
- 把曝光时间固定为一个大于灯光周期的值(比如20ms),这样每帧的进光量就是灯光明暗周期的平均值,画面亮度稳定。固定曝光的具体值是曝光时间微秒,20ms就是20000,代码:
sensor.set_auto_gain(False) sensor.set_auto_exposure(False, 20000) - 还有一种方案是开启“抗频闪模式”,部分OpenMV固件支持sensor.set_flicker_mode(True)。如果固件版本没有这个API,手动固定曝光最稳妥。
漏检的另外一个原因是标签尺寸太小。经验法则是:标签成像对角长度至少占到画面短边的1/10,低于这个比例,解算置信度大幅下降。解决办法是降低安装高度、换长焦镜头,或者在标签周围加一圈白色边框增强边缘对比度。
4.3 位姿数据抖动剧烈
现象:标签静止不动,但z_translation在±10cm范围内乱跳,画的AR引导线左右乱甩。
排查步骤:
- 确认相机曝光正常,画面没有过曝或过暗。过曝时标签白色区域和背景连成一片,四角提取就偏了。
- 确认标签表面平整。标签纸如果贴在弧形或皱褶表面,四角坐标提取本身就有偏差。
- 确认镜头没有脏污。用手指摸过镜头后,画面边缘会出现一层雾,对AprilTag解算影响很大。
- 如果以上都没问题,那就是内参不准带来的系统性误差。做一次lens_corr校正加上内参标定。
代码里加一行镜头校正:
img.lens_corr(strength = 1.8, zoom = 1.0)strength值需要调试,通常在1.5到2.5之间。注意这个操作会增加耗时,QVGA下还好,VGA下会明显掉帧,量力而行。
4.4 多标签场景下的目标错选
现象:画面里同时出现多个标签,小车选错了目标,冲向了错误的标识。
这是竞赛里最容易出现的逻辑错误。解决方式是在代码里加入“优先级”概念——不是选离画面中心最近的,而是按ID优先级来选。比如比赛规则规定必须先到A标签再到B标签,那就应该先查找ID=A的标签,识别到后再切到ID=B。
可以采用状态机的方式来管理:
CURRENT_TARGET = 1 # 当前目标ID for tag in img.find_apriltags(families=image.TAG36H11): if tag.id() == CURRENT_TARGET: # 执行跟踪与AR绘制 pass用一个全局变量管理当前目标,当检测到指定标签且到达后,将CURRENT_TARGET更新为下一个ID。这样即使其他标签在画面里晃,也不会干扰决策。
4.5 高速运动时的拖影与模糊
现象:小车全速前进时,画面出现拖影,标签边缘拉丝,检测时有时无。
这是快门速度不够导致的。解决办法是在固定曝光模式下,把曝光时间降低。设置曝光时间最快可以到1ms甚至更短:
sensor.set_auto_exposure(False, 1500) # 1.5ms曝光但曝光时间降低会带来画面变暗的问题。此时开大光圈、增加补光灯、或者把增益关到最低但适当拉高一点gain,都是可行手段。我最推荐的还是加补光灯,简单直接,效果立竿见影。注意补光灯不要用透镜式的聚光灯,最好用一块柔光板均匀打亮整个赛道区域,避免在标签表面形成高光反射。
4.6 排除问题速查表
| 现象 | 可能原因 | 快速排查操作 |
|---|---|---|
| 一帧都检测不到 | 标签家族设置错误 | 打印tag.family()确认 |
| 检测到但ID不对 | 标签字典或ID混淆 | 打印全部tag.id() |
| 距离数值跳变 | 曝光时间未固定 | 固定曝光到20ms |
| 引导线左右乱晃 | 平滑系数过小 | 增大alpha或加滑动窗口 |
| 画面太黑 | 曝光时间过短 | 加补光灯或延长曝光 |
| 画面太白 | 曝光时间过长 | 缩短曝光到20ms以下 |
| FPS掉到个位数 | 分辨率过高或IDE占用 | 断开前端显示,降分辨率 |
| 标签边缘有虚影 | 对焦不准 | 转动镜头对焦环,锁定后螺纹胶固定 |
5. 进阶玩法与竞赛策略扩展
5.1 标签路径记忆与虚拟赛道生成
前面提到的都是单帧检测,进阶一点,可以建立“路径记忆”机制。小车每经过一个标签,就把该标签的ID、位姿数据、触发时间点记录下来。跑完一圈后,你可以把这一串路径数据上传到电脑端,绘制出完整的赛道地图。
这个方法在赛前练习时价值极高。你不需要拉皮尺测量赛道每个元素的精确位置,只需要开着小车弯腰走一圈,让视觉系统自动记录AprilTag的相对坐标,就能重建赛道拓扑图。这在“未知赛道现场编程”的赛制里,能节省大量宝贵的调试时间。
5.2 AR遮盖与车库识别
另一种玩法是“动态AR内容生成”。比如比赛要求小车识别三个不同颜色的车库,但允许你自由布置标签。你可以给每个车库贴上不同ID的AprilTag,然后在OpenART Plus的LCD上,根据当前识别到的车库ID,动态绘制不同的入场路径箭头和停车线。
这相当于是把AR从一个“固定的叠加层”,变成了“条件触发的动态引导层”。当车头识别到标签ID=3时,屏幕上绿色的引导箭头变成红色停车标记,主控同时收到一个刹车指令。整个交互逻辑全部由视觉板完成,主控只需要执行简单指令。
5.3 串口协议设计:从坐标到决策
如果只是显示AR效果,板子本身的LCD就够了。但要真正驱动机器人,必须把视觉结果传给主控。我推荐的串口帧结构是:
| 字节 | 内容 | 说明 |
|---|---|---|
| 0 | 帧头0xA5 | 固定值 |
| 1 | 标签数量 | 0-10 |
| 2~3 | 标签ID | uint16,高字节在前 |
| 4~7 | x_translation | float32,左右距离 |
| 8~11 | y_translation | float32,前后距离 |
| 12~15 | z_translation | float32,直线距离 |
| 16 | 校验和 | 前面所有字节累加和 |
以上是单个标签的19字节数据块,多个标签则重复ID+坐标部分。主控收到0xA5后进入解析状态,按长度读取并校验,然后更新控制逻辑。
这个协议我用了很多场比赛,稳定性很好。要留意的坑是:MicroPython的float在串口发送时用ustruct.pack("<f", val),和主控端C语言的IEEE 754 float完全兼容。
5.4 多机协同与标签广播
如果你在做一个多车编队或者“车+机械臂”联合项目,AprilTag还能扮演“广播站”的角色。每个机器人背上贴一个独特ID的标签,其他机器人通过视觉识别后,就能获得队友的相对位置和方向。这比UWB定位便宜得多,也比单纯靠颜色区分抗干扰能力更强。
OpenART Plus的算力可以同时检测多个AprilTag,因此一辆车能实时跟踪编队里其他三辆车的位置,并在LCD上显示缩略的编队拓扑图。虽然这超出了智能汽车竞赛的基础范畴,但作为创新加分项,很多评委都会眼前一亮。
最后分享一点赛场体会
做了这么多场竞赛,见过太多队伍在最后关头因为视觉问题翻车。AprilTag技术本身并不复杂,资料也很多,但把它稳定地跑在赛车上,考验的是对参数、环境和硬件的整体把控。我自己每次赛前都会做三件事:一是把镜头对焦环用螺纹胶固定住,防止颠簸震松;二是把曝光和增益参数写死在配置文件里,赛前绝不轻易改动;三是备好两套标签打印件,一套大号一套小号,现场根据场地尺寸快速替换。
我建议你也建立一套自己的“赛前视觉自检流程”——开机画面是否正常、固定曝光下的亮度是否稳定、三米外标签是否稳定识别、LCD上的AR叠加是否平滑跟手。把这四项检查压缩到三分钟以内,你在赛场上就能节省出大量用来排查环境问题的时间,把精力真正花在跑线策略上。