Robomaster Logs
记录一下大一打了一年的比赛
Process
Enrolling, and Learning
开学没多久数院同学告诉我二餐前面多了个牌子,关于Robomaster,并说周末有个开放日,恰好刚开学周末闲着没事就去逛了,展示跑来跑去的平衡步兵和工程的机械臂取矿,大屏幕上会有往年的比赛.感觉能自动瞄准某些图案的有腿的平衡车很有意思,不过当时好像自瞄出什么问题了反正大家好像在手喵打靶挑战.当时还忽悠我们有视觉自动机械臂对矿来着,好像也没见到.反正就想象一些 神秘平衡控制方案,神秘机械臂3维空间轨迹规划,神秘神经网络识别之类的有意思内容,虽然我几乎都没接触到.
于是就决定报名了.机械首先排除,电控不光是控制还有硬件所以也排除,就去了视觉或者说算法那个部门.招新是四道题,第一题是写个图上的神秘处理和寻路一看OI经典dij速通,第二题好像是图像检测,OpenCV的接口是不会背的,所以告诉AI有哪些特征让他帮我写.第三题写完一半发现运行效果死活不对,遂放弃去做第四题,是Kalman滤波题,但当时不知道啥是滤波,遂抄一下公式然后调参.等招新快结束告诉我们第三题内参给的不对,于是把那一问改对,后面小问懒得做就交了.
此时还不太明白kalman凭什么比直接拟合好.
后来还有面试问了啥忘了.好像有 临近比赛维护遗留老方案和测试厉害新方案选哪个.我猜测标准答案是前者虽然我肯定倾向于后者(雾.
然后国庆节之类的时候给大家一些神秘培训.鉴定为作用极小.雷达之类的后面就没用到过.单目视觉感觉说了什么又好像什么也没说,Kalman自己看ai学一学,但感觉理解不是很深,后来靠大一的新生研讨课没活整遂决定在新生研讨课上讲Kalman学会的.ros2那场靠copilot的tab不用记api当场完成作业还被疑惑了.
Before Winter Holiday
后来分项目,但我没看过比赛,也不是很想看.毕竟我一直的心态就是我只是来搞神秘技术的.同时鉴于对队伍的信心,当时的想法是自瞄啊能量机关啊这种传统玩意难道不是都开发差不多了吗,新人进去感觉没什么事干.而雷达听起来不知道是干什么的.模拟器听起来和以前游戏开发比较像,当然要搞点新的.遂第一志愿反导,子弹打飞镖看起来就很刺激很有技术含量,接下来可能是带控镖和决策.最后看起来是国赛不上场三件套.
成功分到反导.居然是一个足足有2个人的组.除了还有我还有一个Z同学.那看起来这项目很重要了.内容就是,对面有一个机器人,会从指定地点发射一个油条大小近似油条形状的飞镖打你的指定地点,你需要用你方的机器人发射子弹把它打下来.
这个居然不是新项目,当时应该有一点失落吧.不过无所谓,看起来他们才刚走了一点,说不定反而能让我们去做真正有意思的部分呢.
这时候老队员讲解去年他们的思路:机器人只有一个相机所以无法获取深度,此时的想法是先用yolo识别飞镖的红蓝灯,从而获得飞镖相对你的方向,也即在一条射线上,同时他们认为飞镖一定在固定的起点终点确定的平面上,所以和平面做交点即可确定其3维空间中的位置.于是扔进3维滤波器里,就可以预测它一定时间后的位置. (是他们讲的吗,可能也有些部分是看代码看的,不过感觉放在一起说吧).
而我和Z同学都觉得什么平面交点方案太抽象了.何况新赛季飞镖可以装机翼左右移动.Z倾向于上雷达直接获取飞镖位置(第一性原理?).我当时觉得比较合理的方式是双面,以及多车通信实现的双目.一个机器人比赛如果不多机通信感觉就很没有感觉啊!就像玩机器人不用格式塔.
当时的规划大概是,先看看前人写的代码,然后把任务分成识别,和识别后的打击两大部分.我主要负责打击让Z负责识别,但分的也不是很严格.感觉看代码的时候被很多的调试内容和实现细节给分散精力了,一行行看的做法还是太蠢了.抓抓输入输出之类的显得明智得多.记得当时可能主要在用copilot?还让他总结每个代码在干什么速通了一下.
然后当时代码里印象比较深的是预测完飞镖未来位置了之后,你要保证子弹和飞镖恰好相遇,所以一方面他们用滤波器和调参估计每一步的时间,从而可以算出若时刻发送打出子弹的命令,那么子弹接下来的轨迹. 一方面,先预测(现在)时刻,飞镖的位置所需的弹道,此时子弹需要走才能到时刻飞镖位置,则令,迭代五六次求解.很有道理虽然我好像不是很会分析收敛速度.这部分是看代码看的.
当时前人告诉我们调试困难是反导开发的重要问题,于是我说搞模拟器,当时的想法是除了识别,后面的部分应该都可以模拟,至少可以确定我们的代码是没问题的..考虑他们的飞镖只是随机形状看起来也比较沉应该抛物线成分占主导.
这里似乎犯了第一个比较大的错误,由于当时模拟器组在搞Unity的模拟器,所以我觉得也许应该拿过来用,所以找模拟器的组人要了一个毛坯的反导版(有场地,有一辆车,有一个会飞的飞镖),但: 1. 他们也没太有精力管我们,所以后来的功能基本都得我自己做. 2. Unity和Ros2的通信在各种linux发行版中的兼容性极差.当时先用的wsl,折腾wsl里的gpu方案,弄了一个只有opengl可以不能vulkan的方案,忘记因为什么原因wsl搞不定,也试了Vmware虚拟机,原因是啥忘了.然后老代码是ros2 jazzy只能适配ubuntu 24,但他们用的ros2和unity通信插件只能用ros2 humble,只适配ubuntu 22,当时我同时有5,6套linux ubuntu环境... 3. 最关键的一点他们的模拟器不具有时间的快放/慢放功能,而且3D渲染实际上是多余的,导致性能跑不了真机的1s 100fps,而低帧率下效果和高帧率区别还挺大.
寒假前基本在搞模拟器,进度应该是到能在模拟器里能跑流程.但鉴于刚才说的最关键的一点.,所以除了让我熟悉主逻辑以外,在后期基本没有任何用处.
与此同时,根据搜集的资料判断,十几二十厘米的双目是不够在十几米外保证足够的精度的
这学期基本还只在某些周末或值班的时候去,还没有理解RM的真面目...
Winter Holiday
寒假集训我们仍然按照计划的识别和打击两部分并行推进.
前期作息正常了两天,然后鉴于飞镖的灯很暗,所以想显眼需要在晚上,这意味着我们调试基本在晚上进行.同时前期白天哨兵总是被很多人用(后期搞出来了新哨兵没人用旧的了),导致调试时间迅速移动到晚上,作息颠倒过来用.
负责人H建议我们把项目合并到自瞄(我们反导一共两个人归自瞄可能管理.实际上所有发射子弹的都归自瞄).我们就把必要模块复制粘贴进去了.因为项目本来就是前一年自瞄分叉出来的所以大部分是相同的.期间发现他们主分支上某个新功能是爆炸的,以及发现他们的神经网络推理部分不知道怎么的就是用不了,干脆自己另外写了一个,才实现对onnx的推理和CPU上的结果基本相同.
识别方面,主要是采数据,训网络,然后之后再想办法加些启发式的过滤规则过滤掉网络不会的部分.然后当时的想法侧重于:鉴于人类也很难从静态图片中找到目标,所以完美的识别一定是要用一段时间的数据做持续预测.还发散过一些LSTM之类的想法.
但实际上卡住识别的是神秘的推理速度.我们发现我们训的新模型计算量和老模型差不多,但速度却慢很多很多.导致原模型fps能跑100新的常常跑20-30.这个被Z同学研究了很久发现问题在于pt转换onnx的时候,如果有topk层,会把数据类型转成某种特定数据类型,然后这种数据类型很慢,而如果先转onnx,再用cpp追加一个topk就不会发生这种事.不知道前人写自瞄代码的时候是不是也发现了,但显然没人告诉我们.
而打击方面,思路是先用车把整条飞镖的轨迹录制下来,然后用他们调一个滤波器,最后再解决给定指定空间坐标能用子弹打中的问题.结果第一阶段就被卡住了.我们在它面前挥舞飞镖却总是出问题.我现在想起来的大概有:
- ros2的tf模块不同节点进程间通信似乎会造成较大的延迟,于是我把原来一个单独节点处理tf的换成了手写的自己实现的一个变换树(tftree)并集成进同一个进程.
- 我已经成了vibe coding玩家,但当时却没有明确说明和外部每处交互的每个坐标系约定.而ai并不从逻辑推断,或者有些也推不出来,导致很多是爆炸的.哦而且当时没有意识到imu到坐标系变换的问题.也花了一些时间.
- imu零飘.让电控调了很多次,同时试图自己调:计算第一秒的漂移速度并直接减掉,但没什么用.电控怎么调也似乎都不能用.最后直接让电控把电机编码器的不飘的发过来校对.然后Z写了个滤波器去滤波imu的角度和编码器的角度之间的插值,基本解决了漂移问题.(相当于大方向听编码器的,短时间内用imu的数据保证连续)
寒假结束才录到几条数据.
于此同时把我非常复杂的滤波器模型弄上去:用Singer模型预测三维坐标,然后把预测落点(已知),目标点关于飞镖的方向角的导数 都作为虚拟观测量提供.同时注意到抛物线形势下方向角的观测方差应该前期大后期小.反正写了这么个玩意然后用optuna机械化调参.但拟合效果并不好.反正到不了要求(子弹速度25m/s,所以要求大致预测0.5s左右,误差不能超过10cm).当然简单的方案也不行.最后鉴定为数据录的不是很"现实".
最后的进度就是,新的网络能跑了,车可以跟着我们的飞镖转头.同时有一些一定程度上的"坏数据",以及五六个不同的滤波器方案.
寒假其实也考虑过其他方案:直接把车派到某个地方,随便朝一个定点狂射,那么只要把弹频拉到60就百发百中了.但当时把这个方案否了原因包括:
- 当时告诉我们的是弹频最大应该到30左右
- 除了镖架和基地,你不能"预知"一个定点一定在轨迹上.如果你能做到这点:通过飞镖前期的轨迹预测一个定点一定在轨迹上,那么你朝着每刻预测的打就是原来的方案.
- 我个人觉得原来那种任意点打击方式比这种优雅很多.所以我不是很倾向于搞这个.
当时还说应该搞看两发:用第一发的数据指导第二发,显然这个也依赖录数据,所以核心任务一直定位录精确的飞镖数据.
寒假集训期间还有团建活动.期间有羽毛球,结束后找了个别墅"轰趴".就是里面有一些老游戏机,乒乓球桌,台球桌,桌上足球,和一个KTV房. 不过老街机太没容错,乒乓球太挑水平,台球打一会也就没人打了,最后唱了一晚上KTV.
Before and During Changsha Contest
给我们的预期是在区域赛录到足够多的别人的飞镖数据,测试识别,这样国赛就比较有把握.
最后我们是带着一个老哨兵上能调的程序去了区域赛,计划是区域赛之前的几天中给我们一天就能迁移到新车.
结果一天都没有,新哨兵(腿哨)始终有人用,且他们把导航决策的优先级排得比我们高,没有我们做适配,测试的机会.整个区域赛都没机会上场.
区域赛为期一周左右,来回都是卧铺慢慢晃悠到长沙.去的路上总结了一下原来模拟器的问题,重新写了一个.直接用cpp后端+threejs前端可视化,把可以调时间速度,真实的云台控制(写了个两层PID),可配置各种外参内参,不需要改任何配置 作为重点.又写了一个录数据的工具,但由于不明原因,回放的时候总是不能得到一样的轨迹(甚至不接近),也没大有紧迫性(功能性问题可以用模拟器调),就先坏着只剩录像功能了.
直到区域赛最后,我们终于意识到我们确实是没有上场测试的机会了,找了个相机接在电脑上让场上队员录了一些飞镖当训练数据.
Before National Contest
回来之后,负责人跟我们说,我们的哨兵会在很靠前的位置打,很难像我们原来预期的一样在远离飞镖平面的地方打.同时确实原来的方案进度太差了.所以尝试直接把车放在飞镖所在的平面内打,打法就是直接yaw上指向飞镖,pitch上向上扫,那么高弹频下大概率打中.(让电控把老哨兵弹频拉到40).于是重写了一版代码.结果发现问题在于,如果你的子弹想能打飞镖(有提前量),你的相机就压根看不见飞镖.
于是先试着给车加了个辅喵相机.结果不知道什么原因这个相机的识别效果和原来的相比效果奇差.此时搞出来的东西包括:让ai只用启发式规则/goal找飞镖(似乎是轻信了A/某个新闻),结果搞出来了训练集100%测试集一坨的究极过拟合.最后效果还是不如传统方案.当时应该是因为辅喵相机视场角大,导致飞镖所占像素变少,飞镖变小,那么yolo的检测头可能找不到它.考虑的是,先用传统方案找"可能有的",然后再把目标附近的一小块图像用一个分类神经网络判断有没有.
这个识别方案没有经过充分尝试,因为复杂度累到一定程度上我们决定把辅喵相机换成原来那种相机.就没有这个问题了.
当时也写了很多打击策略:首先是最简单的yaw上指向飞镖,pitch上指向定点.但容易发现如果你的pitch上和飞镖一起动,那么更小的相对速度会导致更长的打击时长,也就是更大的打击机会(把飞镖抽象成平面内有长度的线段,那么枪口pitch向上运动的过程中,能打到线段的时长差不多是长度/相对速度),所以有匀加速向上,和在飞镖的pitch的基础上加一个匀加速两种.最后又写了匀速滤波器尝试直接滤波器.
于是在暑假前的两次实验中,成功把飞镖打下来了.第一次实验大概一共打中了2次,第二次11次中了9次.
反思这种平面内的方案的好处是完全不需要在乎算的准不准.只要你有一个比较近似的向上扫pitch-v曲线就能大概率打中.
于是开香槟.接下来就解决:1. 当时是把下面的相机拆了用来看飞镖,那么真实上场需要两个飞镖一起. 2. 迁移到新车上,不能再因区域赛的没有车用上不了场.
暑假开始后,整个步兵哨兵车组迅速搬到临港(复旦的场地)训练,而飞镖不去.那么我们要适配新车继续呆在学创的意义不大,于是我们也要去.(而且那边条件比学校好多了).让飞镖复活了很多年前的镖架一起拉过去用.(期间我说别用电动机给我们整个手摇的也不是不能用,或者乳胶管和滑轨整一个).
于是到了临港,迅速面临:1. 镖架是坏的.不过经过试验搞出了一个:A挡位启动,给电机重新烧参数,切到B挡位 能成功启动的仪式.而电动机带着链条在上面帕金森,和遥控器零点漂移导致自己会动都没有解决. 2. 这次提前一个月就没车用了.最后大概有两周半时间平均每天能用4个小时的样子吧.
而我们把代码迁移过去后,发现这个车原来只是 启动程序后看向对手镖架所在固定方向的逻辑都会出问题:车不断点头.追下去发现imu随机间歇持续读不到数据,自然会导致车以为自己没动,就动大了,表现维点头; 同时即使读到数据,头也会小幅不断震颤不收敛.(回忆,我们发的是ref相对当前的增量).我们试过给头加个0.5的系数能收敛但还是先震颤.看飞镖也会抖.我们判断这和指向定点时是两个不同的问题.
最后暑假集训进度大概是一个会抖头的迁移后代码(解决了一些协议约定不一致之类的问题),兼容切换,让电控做了一下 腿车可以趴下让机体获得一个pitch,使得枪管pitch足够大(大概0到60?,而原来可能是-25到35这种).
临港结束也没搞出来,到了深圳后通过消融实现 发现飞镖那边问题核心在于两个相机的方案后我们有一个时间戳处理不对:我们用跑神经网络前的图像和跑神经网络后的imu匹配,而神经网络的吞吐可以但延迟可能比较大,就爆炸了.紧急修了之后我们赶紧找飞镖调参结果发现车坐起来后又开始抖了!抖着的状态把它放平就不抖,再抬起来就抖.
大概是这样错过了最后的上场机会吧.这时候老师仍然觉得不是电控的问题.直到我们某一天观察导航调试,我说他们画这个反馈曲线是不是可以通过震动最开始的先后顺序看一看到底怎么回事,最后发现即使我们的current pitch+命令pitch增量始终恒定的情况下它都不收敛.而且在临港试过原来的车不抖.所以我们没招了,而也没机会了.
鉴于反导项目寄了在深圳闲了一天,然后负责人说我们得找点事干.所以我们熬夜帮自瞄加训神经网络解决他们看基地顶板和前哨识别不行的问题,他们把网络部署上了英雄,但没测试不敢全上.第二天赢浙大输广工回家了.彻底结束.
Reflections
战队的核心目标是赢,我的核心目标是做点有趣的牛逼的技术.我想这使得我一段时间不喜欢新的平面内方案而比较喜欢原来的复杂的平面外打击方案.
如果说本赛季最大的两个问题:
- 我觉得调试的主要问题是我们自己程序写错了,所以上来就写模拟器.但一方面评估不够,另一方面面临的大量问题是车的神秘问题.我到最后把各种外参乱填加imu随机丢弃之类的神秘机制都很难在模拟器里复现抖.
- 不应该追求一个 只有每一环都必须完美的方案. 或者低估了其难度. 同时项目管理上寒假提出这个方案就应该更仔细的评估之后再扔
总的来说,可能软件搞多了.围攻/乐高那种硬件不出错的玩意玩多了.这导致形成了有问题肯定是软件的锅,以及肯定是自己的锅的思维习惯.
同时比较骄傲,当然就要挑战最牛最难的方案的想法,也就要承受搞不出来的风险啊.
而对战队来说,视觉和电控可能太分隔了.通信协议变了等也没有被通知到.抖动等视觉和电控都可能造成的问题也没能良好解决. 同为视觉之间彼此可能也太分隔了.我们发现imu的问题很久后,让他们换imu,结果国赛上这玩意还是因为imu出问题了. 我可能也完全把自己当成这个分功能的齿轮,没有去谋求和更多人,尤其是视觉部外的人交流.
感觉一年的经历,一些接触硬件的经历也是很有利的.模拟和现实的差距的体会,kalman之类的东西也是因此才能学到.但明年估计不会搞了,毕竟不太符合"天马行空"的期待,解决一些工程问题的时间远比想象中的长也很搞人心态.
Myths
本来这一节是写的最早的,但也经常忘了把问题写上来,所以一点也不全.
- 当尝试使用伪造数据训练模型时,如果对一个识别模型来说,识别数据是否是伪造的难度低于其任务的难度,且可以通过伪造的部分得到答案,那么模型显然可以把自己训练成识别伪造而不是完成本来的任务.可能的解决方案比如GAN类的架构.但这太麻烦了被放弃.
- 基于这个想法考虑去随机拼贴一些假图,不过效果好像并不好.
- 用微信传输会改变ONNX文件内容?有人能理解吗?
- openvino编译onnx文件时,是否存在topk层会影响中间层被编译后的类型,topk层导致推理速度变慢
- ros2的tf树跨进程通信显然有延迟,消息轮询也有延迟.而运动过程中来自不同来源的机体姿态数据会让最终的机体姿态出现偏移.
- 一定要关注时间戳是否匹配.
- 电控的PID问题.通过反馈曲线发现.注意到当PID过冲太大后,一个神秘的控制策略(协议是发送reference-current这个差量)就会导致类似间歇震动.