RMYC-合24的一个初步尝试
本文最后更新于 2024年12月29日 凌晨
序
在各种机缘巧合之下有幸在高中玩了俩年 RoboMaster,但其实在队伍中我一直都是负责工程机的改装及调试,基本上没怎么碰过步兵以及视觉相关的内容
趁着这次搞完了中学生涯中最后一场赛事,就来玩玩视觉组的任务( ̄▽ ̄)*
既然是随便玩玩,那当然要挑一个最基础的来,也就是合 24 了
不过虽然说是最基础的,实际上在小组赛中不知道什么原因也没几个队伍能够合出来,甚至在决赛里也有没合出来的
我寻思这应该不难啊,不过实际上会遇到什么困难也得实践一下才知道
再提一嘴,决赛中我看到有些队伍甚至能够做到秒合成,那我就把这个当作一个小目标吧(逃
背景信息
这里所提到的合 24,官方名称其实叫做能量机关
成功击打能量机关将会为团队带来一些 buff,例如伤害加成
不同赛区以及组别的能量机关任务不同,在中学组的比赛里,任务就是合 24
那什么是合 24 呢?
在能量机关上面有五块电子显示牌,上面能够显示 RoboMaster 可识别的的视觉标签
在合 24 这个任务中,从左到右第一块牌子将会是一个随机的加减乘除运算符,剩下四块是四个数字
在图中这个例子,运算符号是减号,数字分别为 8, 6, 6, 2
在这个情况下,机器人将需要按顺序击打 ‘8’, ‘6’, ’-‘, ’6‘, ‘2’
而这个算式的答案也就是 24
值得留意的是,尽管在这个例子中没表示出来,运算符号可以多次重用,但数字只能用一次
也就是说有机会出现 ‘3’, ‘x’, ‘4’, ‘x’, ‘2’ 这种把乘号用俩次的情况
五块显示牌每五秒钟刷新一次,而每次击打完一块牌后必须在一秒内击打下一块,否则会识别为超时无效
问题解剖
观察一下,我们其实可以把这个击打能量机关的任务拆解为 3 个小任务,这样能够更加清晰明了的提供代码思路以及解决方案
这三个任务顺序分别为
-
识别(视觉,数据结构)
-
计算(合 24 算式)
-
射击(包含一部分射击相关的计算)
识别
要完成整个任务,无论是击打还是计算,都离不开识别这一步
Robomaster 内置了视觉标签的视觉模型,所以可以调用内置的库来提取图传中所识别到的标签
1 |
|
最后这行代码中按照官方写法用
RmList()
把get_marker_detection_info()
的返回值进行了一次封装经过我的测试,这个函数的原本返回类型就是 list,这个
RmList()
的封装也只是把列表的起始项目从 0 变成 1 而已虽然不知道有什么用,况且源码也没有公开,那就暂且就先按照官方的来写,避免之后遇到奇奇怪怪的错误
<(_ _)>
再添加一行调试代码
1 |
|
运行,可见当图传相机的视角里若有视觉标签的话,raw_vision_info
的列表里会出现一些信息
1 |
|
可见,在返回的列表中第一个元素是表示总共识别到了多少个视觉标签,之后每 5 个元素为一组
组内第一个元素是标签 id,之后分别为在屏幕上的 x, y 坐标值,宽和高
注意,这个坐标值十分奇葩,他并不代表屏幕的像素点,而是以类似比例的形式决定的
屏幕左上角为 (0, 0),右下角为 (1, 1),那么屏幕中心也就是 (0.5, 0.5) 了
这个细节在计算过程中非常要命,因为设置常数的话 x, y 轴要分别处理
回到正题,运行代码的时候应该会发现,这个识别代码只会跑一次
也就是说这个识别只会在运行的那一刻处理图传捕捉到的那一帧
这个逻辑看似没问题,但只要稍微运行多几遍,就能发现这个识别并不稳定
有时候能识别到 5 个,有时候啥都没识别到,又有时候只能识别 3 - 4 个标签
机器人没动过,每次跑程序的结果都不一样,这太不稳定了吧 (#`-_ゝ-)
这种基于计算机视觉的识别在识别方面非常稳定,每次都能识别到标签,问题就在于我们想要的是一次性识别 5 个,这种对数量的要求就表现一般
那咋办?没关系,直接写个循环让他一直识别直到数量达到要求不就得了
1 |
|
这段代码中,在循环之前必须将 raw_vision_info
赋值,否则在判断循环条件时会报错
数据结构
识别完成,咱应该想想如何将这些原始数据处理成更快、更容易读取的数据结构
这里以我随便设计的一个数据结构做例子
1 |
|
这个是一个典型的 python 字典序,其中数据由每个键(key) 值(value) 对组成
其中我用了视觉标签的 id 作为键,然后将相对应的信息作为值
注意, 字典序的键和数据库类似,必须是唯一,考虑到视觉标签有机会重复,这里的值使用了列表
参考
marker2
,若有多个视觉标签那直接在列表中增加元素即可
视觉标签的信息也可以变成一个字典序,其中 x, y, w, h
作为键,分别代表坐标以及宽高
数据处理
构建完数据结构,我们可以写用于格式化的函数了
首先就是将视觉标签 id 转换为字符串,这个在以后也非常有帮助
1 |
|
直接参考官方文档对视觉标签 id 的释义写即可,十分无脑
然后我们可以开始写主要格式函数了
1 |
|
计算
获取算式
在计算之前,我们应该把机器人看到的算式还原回来,这里可能漏了讲,vision_ctrl.get_marker_detection_info()
函数所获取的数据是按照特定方式排序的,并非直接从左到右排
那也就是说我们必须得将获取到的数据进行整理,还原算式
为了方便后面的计算,这里的还原函数就以列表的形式返回算式
例如 ['+', '2', '1', '4', '8']
这样
1 |
|
这里的 markers.sort()
利用了 sort
函数内置的 key 参数
当 python 遍历列表中的元素并作比较时,可以使用 key 这个参数修改比较的值(顾名思义
若不传入 key ,Python 则会直接比较元素本身
若传入 key 参数,并且是个函数的话,sort 函数会把元素传入到 key 函数的参数,并且比较 key 的返回值
这个例子中,我传入了一个 lambda 函数(无名函数),t 作为这个函数的参数,“:” 后的参数为这个 lambda 函数的返回值
此处
sort
函数所使用的无名函数处理逻辑有问题,但在目前测试使用并无大碍之后有空再改吧 (#`-_ゝ-)
计算击打次序
在上一步中,getMarkers()
函数所返回的是一个列表
那我们同样应该基于这个列表创建一个击打次序的列表
举个例子,假设识别到的是 ['+', '2', '1', '4', '8']
那我们就需要返回一个 ['1', '8', '+', '4', '+', '2']
的列表以表示击打顺序
关于计算,我认为这个视频已经讲的非常清楚了
这个程序也是采用同样的遍历逻辑,只不过是用 python 写了一次罢了(
简单来说,就是分别处理加、减、乘、除、这四种情况