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

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

论文阅读笔记: Segment Anything

文章目录 Segment Anything摘要引言任务模型数据引擎数据集负责任的人工智能 Segment Anything Model图像编码器提示编码器mask解码器解决歧义损失和训练 Segment Anything 论文地址: https://arxiv.org/abs/2304.02643 代码地址:https://github.com/facebookresear

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除