RoboMaster 之 深大源码RP_Infantry_Plus阅读整理

2023-11-01 00:59

本文主要是介绍RoboMaster 之 深大源码RP_Infantry_Plus阅读整理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

RoboMaster 之 深大源码RP_Infantry_Plus阅读整理

闲来垂钓碧溪上。这回遇到硬茬了,任务是啃完全套RM代码,想必是一场恶战,记录一下笔记:

文章目录

    • RoboMaster 之 深大源码RP_Infantry_Plus阅读整理
      • 一、万事万物从Readme开始
        • 1.功能介绍
        • 2.效果展示
          • ① 装甲板检测:
          • ② 大小符识别:
        • 3.环境依赖
        • 4.文件架构
        • 5.文件描述
        • 6.实现方案(重点标注为学习点)
          • (1) 装甲板识别
          • (2) 大小符击打
        • 7.通讯协议
        • 8.配置与调试
          • (1) 配置:
          • (2) 自启动:
          • (3) 调试:
      • 二、装甲板识别 ArmorDetector
      • 三、角度解算 AngleSolver
      • 四、串口通信
      • 五、参考资料

一、万事万物从Readme开始

1.功能介绍

本套代码完成了对RoboMaster2019赛场上大小符以及机器人装甲板的识别,通过自定的通讯协议将视觉处理后的信息发送给下位机,由单片机处理视觉信息并控制云台的运动


2.效果展示
① 装甲板检测:

在6米内的识别准确率几乎100%,辅以ROI操作和多线程,处理一张图片的时间在1ms以内,跑整套代码仅需4-6ms,能够做到实时检测

autoaim
② 大小符识别:

深度学习和传统方法结合,可以做到实时预测旋转之后的位置,加上roi和多线程操作之后,处理一张图片的时间在2ms之内,加上读图也只需要4-6ms。

rune_desc


3.环境依赖
  • ubuntu 16.04.3
  • OpenCV 3.4.4
  • Qt Creator 5.10.1
  • CMake 3.5.1
  • GCC 5.4.0

4.文件架构
深大开源/
├── depends(系统动态链接库)
├── main.cpp(主函数:1.引入配置文件 2.设置串口 3.设置多线程)
├── extraFile
│   ├── AimXMl
│   │   ├── calib_no_4_1280.yml(标定配置)
│   │   └── param_config.yml(参数配置)
│   ├── caffemodel(caffe框架网络结构)
│   │   ├── armornet_iter_200000.caffemodel
│   │   ├── deploy.prototxt
│   │   ├── lenet_iter_200000.caffemodel
│   │   └── lenet_train_test_deploy.prototxt
│   └── Rune
│       ├── lenet(lenet网络结构)
│       │   ├── deploy.prototxt
│       │   ├── lenet_iter_80000.caffemodel
│       │   ├── lenet_iter_80000加了负样本.caffemodel
│       │   └── lenet_iter_80000_旋转后.caffemodel
│       ├── xml
│       │   └── Mono_out.xml
│       └── yolo(yolov2训练结果)
│           ├── tiny-yolov2-trial3-noBatch_235000.weights
│           ├── tiny-yolov2-trial3-noBatch_80000.weights
│           ├── tiny-yolov2-trial3-noBatch.cfg
│           └── 单通道
│               ├── tiny-yolov2-trial3-noBatch_80000.weights
│               └── tiny-yolov2-trial3-noBatch.cfg
├── include
│   ├── Aim
│   │   ├── AngleSolver.hpp(RectPnPSolver及其子类AngleSolver的定义)
│   │   ├── ArmorDetector.hpp(struct ArmorParam/struct matched_rect/struct candidate_target/class ArmorDetector定义)
│   │   └── Settings.hpp(Settings类定义)
│   ├── Camera
│   │   └── video.h(static uint32_t gFormatTransferTbl[]、class Video、class FrameBuffer)
│   ├── header.h(opencv、c++、Qt等头文件)
│   ├── ImageConsProd.hpp(ImageConsProd类定义)
│   ├── Rune(class Detect)
│   │   ├── Detect.h
│   └── SerialPort
│       ├── CRC_Check.h
│       └── serialport.h(class SerialPort)
├── includes (相机 SDK 包含头文件)
├── RP_Infantry_Plus.pro
├── RP_Infantry_Plus.pro.user
└── src├── Aim│   ├── AngleSolver.cpp│   └── ArmorDetector.cpp├── Camera│   └── video.cpp├── ImageConsProd.cpp├── Rune│   ├── Detect.cpp└── SerialPort├── CRC_Check.cpp└── serialport.cpp

5.文件描述
文件名用途
AngleSolver.cpp对目标进行角度解算(DJI开源)
ArmorDetector.cpp进行装甲板检测
video.cpp将工业相机 SDK 封装成类
ImageConsProd.cpp处理多线程以及数据传输
Detect.cpp进行大小符装甲检测
serialport.cpp与下位机通讯的通讯协议

6.实现方案(重点标注为学习点)
(1) 装甲板识别
  1. 自瞄流程图:
peocess
  1. 基本原理:

    如果 last_result 为 true,则检测区域在上一帧的附近,也就是用到了 ROI 方法,如果超过一定帧数未检测到目标,则逐渐扩大搜索范围,直到33帧仍然未检测到目标则全图搜索,此时将与 ROI 有关的变量全部清零

    ② 对检测区域内的图进行二值化,然后再用某种方法将图片中红色或蓝色的区域提取出来,之后膨胀,跟灰度二值化图片逻辑与,最后得到一张只有红/蓝灯条的二值图。提取红蓝区域的方法有以下两种:

    • 一种方法是用 RGB 的红蓝通道相减,根据设定的阈值得到一张二值图,这种方法虽好,但是在识别蓝色的时候,有时候无法排除掉日光灯干扰,除此之外,操作简洁,耗时低,国赛选用这种方法。
    • 另一种是先将图片转化成 HSV 颜色空间再用通道范围将红色/蓝色提取出来(网上可找到对应的表),这种方法可以排除很多干扰,但是近距离的时候装甲板灯条发白,如果膨胀不到位会出现灯条断裂的情况,膨胀的卷积核过大又会造成预处理耗时过久,因此要权衡一下。

    ③ 在当前二值图内找到所有的轮廓点,用最小旋转矩形将他们包围,此时得到一个个单独的旋转矩形,根据装甲板灯条的几何特征首先筛除掉一些旋转矩形

    bool if1 = (fabs(rrect.angle) < 45.0 && rrect.size.height > rrect.size.width); // 往左
    bool if2 = (fabs(rrect.angle) > 60.0 && rrect.size.width > rrect.size.height); // 往右
    bool if3 = max_rrect_len > _para.min_light_height; // 灯条的最小长度+
    bool if4 = (max_rrect_len / min_rrect_len >= 1.1) && (max_rrect_len / min_rrect_len < 15); // 灯条的长宽比
    

    ④ 将这些灯条两两再组成一个大的旋转矩形(也就是候选装甲板),根据一些限制条件筛除掉不符合条件的装甲板,将剩下的待选装甲板放入一个向量中。(其中利用灯条的角度差信息angleabs 为大连交通大学开源)

    bool condition1 = delta_h > _para.min_light_delta_h && delta_h < _para.max_light_delta_h;
    bool condition2 = MAX(leni, lenj) >= 113 ? abs(yi - yj) < 166\&& abs(yi - yj) < 1.66 * MAX(leni, lenj) :abs(yi - yj) < _para.max_light_delta_v\&& abs(yi - yj) < 1.2 * MAX(leni, lenj); 
    bool condition3 = lr_rate < _para.max_lr_rate;
    bool condition4 = sentry_mode ? angleabs < 25 : angleabs < 15 - 5; // 给大点防止运动时掉帧
    

    ⑤ 如果向量中没有元素,则说明没有找到目标;如果只有一个,就是最终选择的装甲板;有两个以上元素的话,就要进行进一步筛选,最容易误识别的地方为装甲板灯条和它两边的两根灯条,经过分析,我们发现在这种情况下如果用 ROI 操作,包围真正装甲板的那个旋转矩形的角度是最小的,我们可以根据这个特征进行筛选,具体思路请看代码。

    ⑥ 找到装甲板之后对其进行 pnp 解算,得到云台转至其中间所需要的 pitch 和 yaw 值,角度解算请参考 DJI 开源的算法,距离信息我们实际测量得到了灯条的长度和实际距离的函数关系,直接代入即可求得距离,误差在 20cm 之内。

    哨兵比较特殊,我们对其进行特殊处理,如果接收到电控传来哨兵模式,我们会对其装甲板匹配条件进行宽松处理,并且对其使用多分类识别装甲板贴纸上的 ID ,在10帧之内如果未找到目标依旧给电控发送找到目标的标志位,直到超过上述帧数才发送未识别到目标,这样可以防止灯条被打灭就失去目标导致云台突然停一下的问题,打灭了之后云台依旧会向前运动,可以快速推掉哨兵。

    algo

(2) 大小符击打
  1. 流程图:
rune
  1. 基本原理:

    ① 在 include/Detect.h 中的 sParam.use_yolo 变为 1,则使用 yolo 来检测没有打过的扇叶,若为 0 则根据 lastData 是否有效来进行 ROI 区域的检测。

    ② 对 ROI 区域或者整图进行二值化,在 src/Detect.h 可以选择二值化的方法

    ③ 检测所有轮廓,统计所有轮廓的子轮廓的个数,排除子轮廓数目大于 1 的轮廓,以及通过面积和长宽比进行排除,得到候选的扇叶

    bool condition1 =  whrio < param.flabellum_whrio_max && whrio > param.flabellum_whrio_min;// 长宽比
    bool condition2 = area > param.flabellum_area_min;// 最小面积
    

    ④ 如果得到的扇叶个数大于 1,可以使用 lenet 或者选择面积最小的得到最终的未打过的扇叶。在 include/Detect.h 里面的 sParam.use_lenet 可以控制是否使用 lenet 进行分类

    ⑤ 在得到的最终扇叶中,再次寻找所有轮廓,寻找装甲板的中心。其中的条件为长宽比,矩匹配,面积筛选

    bool condition1 = whrio < param.armor_whrio_max && whrio > param.armor_whrio_min;// 长宽比
    bool condition2 = rev < param.armor_rev_thres;// 矩匹配阈值
    bool condition3 = area > param.armor_area_min;// 最小面积
    

    如果为小符,就可以不用进行预测,直接将数据发送给下位机但如果是大符,就需要进行圆周预测,预测方法是根据旋转矩形的角度预测出切线的位置,然后再绕着装甲点旋转一定角度,近似到圆周上,由于预测角度小,所以预测点和真实打击点基本吻合

    ⑦ 对得到的装甲板数据进行保存和发送,其中装甲板的象限数据是通过箭头坐标以及旋转矩形的角度进行确定,圆心是通过象限以及给定的半径进行确定

    rune_algo


7.通讯协议

自定的通讯协议一共有 30 个字节,除去校验和预留的数据位,还有 12 个字节的 float 型数据 和 6 个字节的标志位可供使用,能够完成大部分数据的传输。

Byte0Byte1Byte2Byte3Byte4Byte5Byte6
0xA5cmdIDCRC8_Checkpitch_datapitch_datapitch_datapitch_data
Byte10Byte11Byte12Byte13Byte14Byte15Byte16
yaw_datadist_datadist_datadist_datadist_dataflag1flag2
  • 0xA5 -帧头
  • cmdID : 8 bit int - 命令模式(0 不处理,1 为红色自瞄,2 为蓝色自瞄, 3 4 5 6 7 8 为大小符)
  • pitch_data : 32 bit float - 接收视觉解算出来的云台 pitch 值
  • yaw_data : 32 bit float - 接收视觉解算出来的云台 yaw 值
  • dist_data : 32 bit flaot - 接收视觉解算目标到相机的距离值
  • flag1 : 8 bit int - 是否瞄准到中心(大小符用) / 哨兵模式 (stm32 -> PC)
  • flag2 : 8 bit int - 是否找到目标(自瞄)/ 吊基地模式(stm32 -> PC)
  • flag3 : 8 bit int - 是否识别到大小符
  • flag4 : 8 bit int - 是否击打过大小符
  • flag5 : 8 bit int - 装甲板是否贴脸(已弃用)

8.配置与调试
(1) 配置:

此代码除了相机可能与其他学校不同,其他只需要修改一下路径即可运行,需要修改的路径如下:

  • main.cpp 中的 config_file_name
  • src/Aim/ArmorDetector.hpp 中的 net
  • include/header.h 中的 camera_yaml yolo_model_file yolo_txt_file lenet_model_file lenet_txt_file
  • extraFile/AimXMl/param_config.yml 中的 intrinsic_file_720

刚开始的配置参数是集合在一个 yaml 文件中,在实际调车过程中,发现还是直接修改程序中的代码更快捷方便一些,因此后续几乎没有用过 yaml 进行配置,当然,为了程序规范,建议还是用外部配置文件来更改参数,以下是深大之前用的 yaml 配置文件(已弃用),有需要可以在文件中添加配置或者删除配置。

%YAML:1.0
---
# For Debug Image
show_image: 1
save_result: 0
# Parameter for Armor Detection System
min_light_height: 10
light_slope_offset: 30
max_light_delta_h: 720
min_light_delta_h: 20
max_light_delta_v: 100
max_light_delta_angle: 30
near_face_v: 100
max_lr_rate: 1.99
max_wh_ratio: 5.12
min_wh_ratio: 1.03
target_max_angle: 20
small_armor_wh_threshold: 3.33
binary_classfication_threshold: 166
# Parameter for Camera
intrinsic_file_720: "/home/nuc/kevin/RP_Infantry_Plus/extraFile/AimXMl/calib_no_4_1280.yml"
(2) 自启动:

赛场上只有三分钟准备时间,因此我们需要让程序在 PC 开机之后自启动,这就需要写一个自启动脚本,并且,为了防止程序异常中断,我们还用了看门狗来保护程序,原理就是不断检测程序是否在运行,若没有运行,则立刻运行,如果在运行,就隔一段时间再去检测。同样,只需要修改一下路径就可以运行。(我们发现PC 意外断电时有几率将二进制文件给损坏,导致程序无法运行,因此为了保险起见,在开机时重新 make 一下程序),为了防止极端情况,在程序意外中断 10 次之后我们直接重启 PC (一般不会遇到这种情况)。

#!/bin/bashsec=1

这篇关于RoboMaster 之 深大源码RP_Infantry_Plus阅读整理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/319348

相关文章

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

mybatis-plus如何根据任意字段saveOrUpdateBatch

《mybatis-plus如何根据任意字段saveOrUpdateBatch》MyBatisPlussaveOrUpdateBatch默认按主键判断操作类型,若需按其他唯一字段(如agentId、pe... 目录使用场景方法源码方法改造首先在service层定义接口service层接口实现总结使用场景my

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

MyBatis-Plus 与 Spring Boot 集成原理实战示例

《MyBatis-Plus与SpringBoot集成原理实战示例》MyBatis-Plus通过自动配置与核心组件集成SpringBoot实现零配置,提供分页、逻辑删除等插件化功能,增强MyBa... 目录 一、MyBATis-Plus 简介 二、集成方式(Spring Boot)1. 引入依赖 三、核心机制

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

Mybatis-Plus 3.5.12 分页拦截器消失的问题及快速解决方法

《Mybatis-Plus3.5.12分页拦截器消失的问题及快速解决方法》作为Java开发者,我们都爱用Mybatis-Plus简化CRUD操作,尤其是它的分页功能,几行代码就能搞定复杂的分页查询... 目录一、问题场景:分页拦截器突然 “失踪”二、问题根源:依赖拆分惹的祸三、解决办法:添加扩展依赖四、分页

Python按照24个实用大方向精选的上千种工具库汇总整理

《Python按照24个实用大方向精选的上千种工具库汇总整理》本文整理了Python生态中近千个库,涵盖数据处理、图像处理、网络开发、Web框架、人工智能、科学计算、GUI工具、测试框架、环境管理等多... 目录1、数据处理文本处理特殊文本处理html/XML 解析文件处理配置文件处理文档相关日志管理日期和

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

MyBatis-Plus 自动赋值实体字段最佳实践指南

《MyBatis-Plus自动赋值实体字段最佳实践指南》MyBatis-Plus通过@TableField注解与填充策略,实现时间戳、用户信息、逻辑删除等字段的自动填充,减少手动赋值,提升开发效率与... 目录1. MyBATis-Plus 自动赋值概述1.1 适用场景1.2 自动填充的原理1.3 填充策略

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe