尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

智能车视觉实战:OpenART Plus上的AprilTag检测与AR叠加全攻略

智能车视觉实战:OpenART Plus上的AprilTag检测与AR叠加全攻略 先声明一下我不是什么视觉算法大佬就是普通参赛队的调车主力在OpenART Plus上折腾AprilTag折腾了一个多赛季。第二十届、第二十一届全国大学生智能汽车竞赛赛题里“虚拟元素”“AR标签”出现的频率越来越高很多队伍拿到带屏幕的OpenART Plus第一反应还是当普通摄像头用——找赛道线、判十字、看路障。但AprilTag这套东西一旦玩起来它能让你的车“看见”现实中不存在的元素然后在屏幕上叠加出虚拟路障、虚拟门、虚拟指示这就是我们常说的增强现实智能车玩法。这篇文章不堆理论就把我在实战里验证过的参数、思路和坑一次性倒给你想上车的新队伍可以少折腾好几周。1. 为什么我在智能车竞赛里盯上了OpenART Plus和AprilTag1.1 从“纯循迹”到“虚实结合”赛题里越来越常见的AR元素先说个我自己真实的心理变化。最早听到“增强现实智能车”这个组合时我觉得它离竞赛有点远总觉得AR是手机App或者AR眼镜才玩得转的东西。直到我仔细翻了几届全国大学生智能汽车竞赛的规则发现不少组别已经明确出现了“AR标定”“虚拟元素识别”这一类任务描述我才意识到竞赛赛道上的AR从来不是让你在镜头里看到一头恐龙而是让车模“看懂”一种现实中并不真实存在的任务要素。举个例子赛道旁边立一个纸箱纸箱上贴一张打印出来的AprilTag。车模远远看到这个标签就知道那里有一个“虚拟锥桶”需要执行绕行看到另一个ID的标签可能代表“虚拟车库”需要停车入库。纸箱本身没有任何特殊结构它纯粹是一个载体语义完全由标签本身决定。这其实就是增强现实最朴素的定义在真实场景之上通过算法叠加并解释虚拟信息再驱动真实设备产生响应。这种玩法对竞赛组办方来说也很友好改赛道只需要换标签贴纸不需要重新搭复杂的实物道具。对我们参赛队来说只需要在代码里维护一张“标签ID到动作”的映射表就能应对不同的赛题变化。所以它被越来越多组别采用是技术演进的自然结果不是凭空冒出来的噱头。也正因为如此OpenART Plus加AprilTag的组合在这几届比赛里几乎成了视觉处理方向的必备技能。1.2 OpenART Plus是什么一块能画屏、能跑Python的视觉模块OpenART Plus是逐飞科技面向智能车竞赛做的视觉模块核心是恩智浦的i.MX RT1062处理器主频能到600MHz运行环境兼容OpenMV IDE。这意味着什么用过OpenMV的同学应该秒懂打开IDE连接USB写Python脚本烧录运行整个开发链路非常短。但RT1062的算力比传统OpenMV平台强不少在RGB565的QVGA分辨率下跑AprilTag检测帧率可以稳到让人放心的程度。对于“实时AR”这种需求来说算力就是底气没有足够的帧率后面所有虚拟叠加和位姿输出都谈不上。真正让我觉得它适合做AR玩法的是板上自带的LCD屏。很多摄像头模块是没有屏幕的识别结果只能通过串口发出去或者回传到电脑上看。而OpenART Plus的LCD可以直接实时显示当前图像并且允许你在图像上画框、画线、写文字。这意味着什么意味着你可以在屏幕上把识别结果“可视化”出来给tag画一个红色框在它旁边写上“虚拟门”再画一条车模的预瞄线。调车的时候能直接看到算法在想什么而不是对着串口数字去脑补画面。等到答辩演示时这个屏幕又是一个极好的展示窗口裁判一眼就能看懂你的车在“理解”什么。除了LCD模块上还有RGB LED、按键、SD卡槽、串口和一堆GPIO。SD卡槽这个细节常被忽略但对调车来说意外地好用——你可以把某一帧图像直接存到SD卡里赛后拉出来逐帧分析为什么那个tag没识别出来。这种能力在排错时能省下大量时间比反复猜测光照、角度要高效得多。1.3 AprilTag不是二维码却比二维码更适合车模定位AprilTag是密歇根大学开源的一套视觉基准系统外形确实很像二维码但它在设计上跟二维码走的是完全不同的路线。二维码是为了存信息所以编码密度很高解码需要大量的图像处理AprilTag是为了让机器快速锁定位置和姿态所以编码更稀疏检测算法可以更快地找到它并且恢复出标签相对于相机的完整6自由度位姿——x、y、z三个方向的位置加上旋转角。这句话翻译成人话就是你的车不仅知道“前方有一个标签”还知道“标签在相机前方大概30厘米、偏右5厘米、带一点倾斜角度”。这些数据对车模控制来说太重要了。比如我们要判断虚拟锥桶到底在赛道左侧还是右侧不能只靠tag在画面里的像素坐标因为同一个像素位置在不同距离对应的实际偏移完全不同。但有了x_translation和y_translation之后主控可以直接拿到以厘米为单位的相对位置偏差误差是明确的、可控的。这也是AprilTag在竞赛里胜过普通二维码的核心原因它输出的是可以被控制算法直接使用的物理坐标而不是一个需要二次换算的像素坐标。2. AprilTag检测的工程化家族选型、图像参数与坐标解算2.1 TAG36H11为什么是竞赛默认家族选型对比OpenMV IDE里的find_apriltags()函数支持好几个家族TAG16H5、TAG25H7、TAG25H9、TAG36H10、TAG36H11。家族后缀里的数字前半部分代表网格规模后半部分代表校验位数。网格规模直接决定了标签的物理形态同样打印尺寸下网格更多的家族每个黑白格就更小要求相机离得更近才能看清校验位数则直接决定误检率校验位越多认错标签的概率越低。家族校验位数同尺寸下识别距离误检率竞赛推荐度TAG16H55bit较远较高不推荐TAG25H77bit中等中等备用TAG25H99bit中等中等备用TAG36H1010bit较近较低可用TAG36H1111bit较近最低默认首选我在竞赛场景里几乎只用TAG36H11原因就一条误检率最低。为什么这个指标如此关键因为智能车竞赛里漏检的代价是可以接受的。一帧没检测到tag下一帧补上就行车模速度再快也就几十毫秒的空白控制上完全来得及。但误检的代价是完全不可接受的——车把墙上一个像tag的图案认成了“虚拟锥桶”直接执行绕行动作轻则偏离路线重则冲出赛道。从我的经验来看TAG36H11虽然要求tag在画面里占得更大一点但换来的是极强的防误检能力这个交换非常划算。2.2 曝光、白平衡、增益让tag“看清”的第一步把AprilTag识别率低归咎于算法是新手最容易犯的错。我调试时遇过很多次tag忽大忽小、时有时无的情况反复调了一整天算法参数最后发现根本不是算法问题而是相机曝光参数在自动模式下乱跳。AprilTag是黑白编码图案检测器依靠的是黑白网格的边界和相对面积一旦曝光不稳黑色块在强光下变成灰色白色块在暗光下变成灰白整个tag的编码信息都会被压实检测器自然就认不出来了。我的第一原则所有图像参数全部锁死不能全自动。自动曝光在车模转向时会剧烈波动因为镜头对着的方向不同、画面平均亮度不同自动曝光会不断调整导致每一帧的tag像素表现都不一样。建议把曝光值固定室内赛道环境可以从exposure_us 3000左右起步根据现场实际亮度往上下调室外的话一般要降到1500甚至更低不然白色区域会直接过曝变成一片白板。增益也要固定自动增益在暗光下会把噪点放大而噪点会啃食tag网格的边缘让方格的边界变得毛糙。白平衡在荧光灯下尤其容易偏色荧光灯的光谱不是连续的自动白平衡会在不同时间点漂移把白色块调成偏蓝或偏黄。解决办法就是直接关掉自动白平衡手动给一组平衡值让tag的白色块在画面里看起来是接近纯白的即可。import sensor sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.set_auto_exposure(False, exposure_us3000) sensor.set_auto_gain(False, gain_db18) sensor.set_auto_whitebal(False, rgb_gain_db(40, 40, 40))这段配置看起来简单但它是整个AprilTag稳定检测的地基。我强烈建议拿到新场地之后第一件事就是打开IDE的预览窗口一边调整这三个参数一边观察图像里tag边缘是否清晰锐利确认没问题了再写业务逻辑。2.3 tag_size与位姿输出单位一处配错全盘皆输find_apriltags()里面有个参数叫tag_size很多新手不设或者乱设导致x_translation、y_translation、z_translation的输出数值完全不可信。这里必须搞清楚算法在解算位姿时需要知道tag的真实物理尺寸作为参照。原理不复杂本质上是针孔相机模型下的比例关系——tag在图像传感器上占了多少像素相机焦距是多少tag真实边长是多少这几个量一旦确定距离就能被反解出来。但如果你给的tag_size和实际打印尺寸对不上整个比例就错了输出的坐标会系统性地偏大或偏小。我的做法是打印标签时直接用固定边长比如6厘米或者10厘米不要随手缩放到一个不规整的尺寸。在代码里就写对应的毫米数比如6厘米就写成tag_size60。这样find_apriltags返回的x_translation、y_translation、z_translation单位就是厘米。这里有个很简单的验证方法把tag放在车模正前方1米处看输出z_translation是不是接近100。如果明显偏大或者偏小先别怀疑算法出bug回头检查tag_size填没填对。另外要注意不同PDF阅读器打印时如果设置了“缩放以适应纸张”实际印出来的尺寸会和设计尺寸不一致打印完之后用尺子量一下别想当然地认为自己设了10厘米就是10厘米。3. 把AR效果“画”出来LCD叠加到串口联动的完整链路3.1 屏幕上的虚拟门LCD叠加绘制实操OpenART Plus的LCD屏是AR玩法最佳的输出窗口。检测到tag之后我们可以直接在图像上绘制各种虚拟元素让屏幕上的画面呈现出一种虚实结合的视觉效果。这个功能最初我以为是用来炫技的但实际调试后发现它最大的价值其实是“让算法状态可见”。你不需要在脑海里想象tag检测得好不好所有信息都在屏幕上。下面这段代码是一个最简单的虚拟门叠加效果。当检测到ID为0的tag时在屏幕上画出方框、十字和一行“VIRTUAL GATE”的文字同时画一条虚拟门线。import sensor, image, time, lcd lcd.init() sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time2000) while True: img sensor.snapshot() for tag in img.find_apriltags(familiesimage.TAG36H11, tag_size60): img.draw_rectangle(tag.rect(), color(255, 0, 0), thickness2) img.draw_cross(tag.cx(), tag.cy(), color(0, 255, 0)) if tag.id() 0: img.draw_string(80, 20, VIRTUAL GATE, color(255, 255, 0), scale1.5) img.draw_line((tag.cx() - 40, tag.cy(), tag.cx() 40, tag.cy()), color(0, 255, 255), thickness2) lcd.display(img)第一次跑通这个效果时画面里tag被红框锁住中央顶着绿色十字旁边浮着黄色文字那种“车在透过镜头理解世界”的感觉确实很奇妙。但要注意一点LCD的绘制是有开销的字符串和画线如果每帧都做会挤占宝贵的检测时间。所以正式的赛事代码里我一般不会把LCD显示和核心检测放在同一个循环里跑而是加一个开关平时跑车关掉展示调试时再打开。3.2 把tag位姿交给主控通信协议怎么定屏幕上的虚拟元素只是给人看的车模真正要执行动作还得靠串口把位姿数据实时发给主控板。通信协议不需要复杂但一定要有帧头和结束标记否则主控端很容易在数据流里切分错位置。我常用的协议是固定长度6字节帧头0xA5、tag ID、x偏移、y偏移、z距离、结束标记0x5A。为了节省带宽坐标全部先乘10转成整数再发送主控端收到后除以10还原成厘米。from pyb import UART uart UART(3, 115200, timeout_char1000) while True: img sensor.snapshot() detected False for tag in img.find_apriltags(familiesimage.TAG36H11, tag_size60): data bytearray() data.append(0xA5) data.append(tag.id()) data.append(int(tag.x_translation() * 10)) data.append(int(tag.y_translation() * 10)) data.append(int(tag.z_translation() * 10)) data.append(0x5A) uart.write(data) detected True if not detected: uart.write(bytearray([0xA5, 0xFF, 0x00, 0x00, 0x00, 0x5A]))这里有个细节容易被忽略当一帧图像里检测不到tag时一定要主动发一个“无目标”的帧而不是什么都不发。为什么因为主控端需要用这个空帧来清除上一帧的目标状态否则车模已经过了tag主控还拿着最后一帧的坐标继续执行动作可能就会过度反应。ID0xFF这个特定值就代表“当前没有看到任何标签”主控收到后可以安全地把目标清空。这个空帧逻辑是很多人调车时出现“车过站了还停一下”这类诡异问题的真正原因。3.3 “检测-分类-输出”三段式框架换赛题只改一张表随着赛道里的tag数量增加把识别逻辑和决策逻辑混在一起写会很痛苦。我的做法是把整个视觉模块的程序抽象成三段式检测、分类、输出。检测部分只负责调用find_apriltags拿到原始位姿数据分类部分维护一张tag ID到动作的映射表输出部分只负责按协议把分类结果发出去。决策代码不关心tag是怎么被识别出来的只关心当前应该执行什么动作。这样每一层都可以单独调试出问题也能快速定位。TAG_ACTIONS { 0: SLOW_DOWN, 1: AVOID, 2: PARKING, 3: BOOST } def parse_tags(img): for tag in img.find_apriltags(familiesimage.TAG36H11, tag_size60): action TAG_ACTIONS.get(tag.id(), UNKNOWN) send_to_mcu(action, tag.x_translation(), tag.y_translation(), tag.z_translation()) def send_to_mcu(action, x, y, z): # 按协议封装发送 pass这样设计的最大好处是换赛题时只需要改TAG_ACTIONS这张表。今年比赛要求把ID1当成锥桶避障明年改成停车入库代码层面几乎不用动。我在备赛后期基本已经形成了一套自己的模板新赛题下来第一天就能把视觉框架搭好剩下的时间全花在调参和稳定性测试上这比从头写代码高效太多。4. 实战玩砸过的坑反光、目标尺寸与帧率延迟4.1 一场由反光引发的翻车打印介质与多帧确认我第一次把AprilTag贴到亚克力板上试跑时识别率惨不忍睹。tag在画面里忽远忽近刚才还在1米外下一帧距离解算直接跳到3米车模执行绕行时犹豫不决最后直接冲出了赛道边界。我一度以为是算法参数没调好反复加滤波、改阈值折腾了一晚上没有效果。第二天到场地仔细一看发现问题根源是亚克力板表面的反光在灯光下tag的黑色网格被反射成一片亮斑检测器把这一块当成了白色编码信息直接被破坏。从那之后我定了一条死规矩打印tag必须用哑光纸最好再覆一层哑光膜。高光相纸、亚克力板、塑封袋这些东西虽然好看耐用但在灯光下就是识别杀手。尤其是比赛现场经常有大型灯架和补光灯角度稍微一偏反光问题就会被放大。如果你的队伍已经踩了反光的坑第一件事不是调算法是换介质。光照突变是另一个更隐蔽的坑。车模跑过窗口、灯箱下面或者顶棚阴影交界处时画面平均亮度会在几帧内剧烈变化如果曝光没有锁定tag就会瞬间丢失。解决办法除了固定曝光外我还在主控端加了一道多帧确认逻辑连续3到5帧都检测到同一个tag才判定为有效目标中间一旦出现断裂就重新计数。这个“有效帧数”设置量其实很小但效果立竿见影误动作率直接下降了一大截。4.2 识别距离不够先算tag该贴多大很多队伍第一次试跑时发现车要到离tag很近的地方才能识别出来比如三五厘米才开始有反应。这个问题说穿了就是tag在画面里的像素尺寸太小。AprilTag检测器对tag的分辨率有下限标签在图像里的边长如果小于三四十个像素网格结构就难以被还原识别自然失败。所以射程不够的时候与其折腾算法不如先算算物理尺寸。我这里有一个粗略的对应关系可以拿来做赛前预算。不同物理边长的tag稳定识别距离大概在这个范围tag物理边长稳定识别距离参考5cm0.2 - 1.0m10cm0.5 - 2.0m15cm0.8 - 3.0m20cm1.0 - 4.0m当然这个数值会受到镜头焦距、分辨率、光照条件的综合影响但大方向不会偏。如果你希望车模在8米外就开始执行减速动作却贴了一个5厘米的小tag那再怎么调参数都没用必须换大码。这里还要提醒一点放大打印tag时别忽略tag周围的白边AprilTag检测是依赖标签外边框的打印时四周至少留出和一个小方格一样宽度的白边检测器才能真正把tag和背景区分开。4.3 帧率、延迟与算力开销别让炫技拖垮控制OpenART Plus的算力在嵌入式视觉模块里算相当能打的了但也不是无限资源。如果把分辨率开到VGA再在每帧里叠加大量绘线、文字、动画效果帧率会明显下降tag的定位精度和车模的响应速度都会跟着变差。我的经验是QVGA分辨率起步优先保证检测帧率不低于25fps图像分辨率再高识别延迟大了也是白搭。tag的绘制反馈只在调试模式里开启比赛模式里全部关掉。更关键的是要理解帧率和延迟的区别。OpenART Plus检测完一帧串口发出数据主控还要解析、处理、控制电机整个链路的延迟才是真正影响动作时机的。视觉那边已经做到很短了如果主控每100毫秒才读一次串口那再快的视觉也白搭。我自己在联调时习惯这样分工视觉模块以尽可能高的频率往外发数据主控固定10毫秒读一次串口发现车模动作延迟明显改善。很多队伍只盯着视觉端的帧率忽略了主控端读取频率结果视觉优化了半天问题其实出在接收端。5. 竞赛场景下的进阶玩法与调优路线5.1 把AprilTag当绝对定位信标路径纠偏思路前面讲的都是把tag当作“触发器”看到就执行动作。更进阶的玩法是把tag当作绝对定位信标用来修正车模的全局坐标。智能车如果用编码器做里程计跑久了轮子打滑就会积累误差位置越算越偏。但如果场地关键位置贴几个AprilTag车模每次经过时都能通过视觉重新确认自己的位置和朝向相当于做了一次全局坐标校正。具体做法是把tag当作landmark视觉模块检测到后利用x_translation、y_translation和旋转角反算出车模相对于这个信标的位姿。因为信标的全局坐标是事先标定好的把两者做一次坐标变换就能得到车模当前的全局位置。这个思路和SLAM里的landmark定位是同源的只是我们不需要动态建图只需要修正误差。配合简单的PID纠偏车模在暂时看不清赛道线的时候也能靠跨段的tag位置估算硬撑着往前走一段。当然这里对相机安装角度、tag的固定位置都有标定要求跑通之后整个系统的鲁棒性会上一个台阶。在OpenART Plus的IDE里AprilTag检测函数还会返回rotation相关的角度信息可以用来判断车模相对tag的偏航角。比赛场上每个tag都按同一朝向摆放那么这个角度就直接等同于车模的航向偏差比拿两组位置坐标去算角度要方便得多。5.2 多tag共存的策略最近优先与任务分级赛道里不会只有一个tag一般会同时出现好几个。多tag带来的一个直接问题是画面里同时出现两个tag时该听谁的如果只是简单地在循环里处理最后一个车模在不同tag之间游移时会频繁切换目标行为会变得很神经质。我的处理策略是“最近优先”检测到多个tag时只取z_translation最小的那个作为当前主目标因为最近的tag通常代表车模当前最需要响应的元素。另外一个思路是给tag ID做分级比如0到9作为导航类信标只更新定位信息不触发动作10到19作为动作类元素触发减速、绕行、停车20以上作为状态类标签表示赛道终点或者特殊区域。分级之后逻辑上会清晰很多代码里只要一条if就能区分“这个tag只是参考不需要停车”和“这个tag必须立刻响应”。这个设计是我在调试一个多车道交互场景时想出来的当时赛道上tag数量太多不加分级根本没法调。5.3 答辩演示模式让AR效果被裁判一眼看懂最后这点不是技术但我认为它对竞赛队伍极其重要。很多队伍代码能力很强但答辩时讲不清楚AR到底是什么评委听得一头雾水。我的建议是做一个专门的展示模式车模放在桌面静止队员用手拿着不同ID的tag在镜头前缓慢移动LCD屏幕上实时显示对应的虚拟元素和动作提示。比如拿到ID0的tag屏幕边框变黄显示“虚拟门允许通过”换成ID1的tag屏幕显示“虚拟锥桶右转绕行”。这个展示模式实现起来并不复杂就是前面第三节讲的检测分类输出框架再加上一个展示开关而已。但它带来的效果非常直观评委能看到传感器图像、看到识别框、看到虚拟元素随手中标签实时变化整个“增强现实”的链路一目了然。比对着PPT解释几十页算法流程有效得多。我有一次在答辩现场演示这个功能时一个评委还专门蹲下来凑近屏幕仔细看了几秒那几秒我觉得比讲十页PPT都值。说实话AprilTag这套东西入门不难难在让它持续稳定地为你工作。我见过不少队伍第一周就能把demo跑得飞起然后整个赛季都困在反光、光照、帧率这些问题里出不来。如果你也准备在车模上玩AR我的建议很简单先别急着做花活找一张哑光纸打印一个TAG36H11贴到1米远把识别距离、坐标数值稳定性、串口延迟一项一项测满意了再谈虚拟门、再谈多车联动。稳这一个点比铺开十个点都管用。这条路我替你验证过值得走。
返回列表