简介:来自大疆2022无人飞行智能感知技术竞赛的完整项目包,记录了香港理工大学参赛队伍通过全部目标、斩获全国第28名的技术方案,适合无人机竞赛选手、智能感知与自主导航方向的研究者参考。资源共81个文件,压缩后约40.2MB,主要包含srv/msg通信定义、py/cpp/h算法模块、launch/xml/yaml运行配置、md/pdf文档说明,以及可复现实验的rviz、pgm地图与gif演示录屏。项目以RoboMaster-main和airsim_ros_pkgs为核心,覆盖视觉感知(目标检测)、雷达与激光定位、路径规划、PID/MPC控制等模块,并配有工程说明PDF和构建笔记,便于按目录逐层理解从底层驱动到上层决策的完整链路。目前已有100人学习下载,对准备无人飞行竞赛或开展相关课题的人来说,是一份能直接对照学习算法部署与系统集成的实战样例。
1. 2022 无人飞行智能感知竞赛工程包:从全国第 28 名复盘一份完整飞行代码
2022 年全国无人飞行智能感知技术竞赛,我们队代表某高校参赛,把预设目标全部 Pass,最终排名全国第 28。这份压缩包就是当时留下来的一整套工程现场:主工作区里塞满了 ROS 工作区、目标检测脚本、仿真桥接包、导航平滑控制包,还有录制好的实飞 GIF 和几十页工程说明。它不是什么练习 Demo,是一份真实上过赛场、拿过名次、踩过无数坑的工程代码。适合三类人:准备同类竞赛的学生、做无人机感知与自主导航课题的研究者、想从零搭一套视觉避障链路的从业者。按这份包复现,能少走至少两个月弯路。
2. 读懂工程结构:主工作区、检测脚本与启动文件的三处入口
拿到压缩包先别急着catkin_make。我先说结论:这个工程包的可运行部分可以拆成三块——纯 ROS 主工作区、检测算法脚本目录、仿真与导航配套的 ROS 功能包。三块之间靠launch文件拼接,哪一块先编译、哪一块后启动,直接决定你复现时会不会翻车。
2.1 拿到压缩包先看什么:package.xml、build.md 与 README
我复盘工程的习惯是,先看文档再看代码,后看代码再回文档。这个包里有三个入口文件:
build.md:作者记录的编译顺序和踩坑记录。README.md:工程概况和运行说明。package.xml:ROS 包的依赖清单,能看出要装哪些系统依赖。
你不需要先跑代码,先花十分钟把这三个文件过一遍。很多人复现失败,都是因为跳过了依赖检查,直接catkin_make,然后卡在缺某个库上。以这个包为例,主工作区里的package.xml会声明它依赖 image_transport、cv_bridge、gstreamer 相关插件,这些依赖不装齐,编译能过、运行必崩。
# 建议先做三件事:看构建说明、看依赖、看启动文件命名 cat build.md cat README.md cat package.xml这三个文件解决三个问题:build.md告诉你编译顺序,package.xml告诉你系统依赖,README.md告诉你整个系统的启动方式。我的经验是,如果build.md里写了某个需要手动设置的参数,第一时间抄到自己的笔记里。这类竞赛工程包的作者通常不会写废话,每一条记录背后都是一次真实的返工。
2.2 launch 与 scripts:仿真、实机两种模式怎么切
README 里会提到工程的启动方式,核心入口基本都在根目录下的launch文件夹内。这个包里的launch文件有两类:一类负责启动真机相关节点,一类负责启动仿真器桥接节点。区分方法很简单,看 launch 里节点名和参数里是否包含仿真相关的命名空间。仿真模式下,图像话题来自仿真器;实机模式下,图像话题来自相机驱动。很多参赛队伍最后在赛场翻车,就是因为实机部署时没有把话题 remap 到正确的位置。
我把这个包里的启动逻辑归纳为三层:
- 先启动底层通信与硬件节点,包括飞控串口、相机驱动、传感器话题。
- 再启动感知节点,订阅图像话题,发布检测结果。
- 最后启动决策与控制节点,订阅检测结果,发布速度控制指令。
实际运行时,如果你只跑仿真,只需要启动第二、三层和仿真的桥接层。如果你要上实机,就必须先确认第一层的启动程序正常,否则后面的检测节点会一直报“等待图像话题”的警告。我一般会先用rostopic list看一眼当前环境里到底有没有图像话题,再决定要不要改 remap 参数,避免对着错误的话题名调半天算法。
2.3 检测脚本在链路里的位置:detect.py 到 newdetect.py 的演变
这个包里有多个检测脚本:detect.py、detect2.py、newdetect.py、spine.py。一开始我也以为它们是功能模块,后来认真对齐了文件修改时间和代码风格才发现,这是一条清晰的迭代线。
detect.py:第一版检测脚本,大概率是简单的颜色阈值加轮廓筛选,写得很粗糙,但运行稳定。detect2.py:第二版,在上一版基础上加了形态学处理和更多目标类别分支。newdetect.py:最终用于比赛的版本,新增了模型推理和置信度过滤逻辑,里面有很多阈值参数。spine.py:小工具脚本,负责骨架化或中心线提取,用来辅助检测结果的后处理。
所以后来我复现的时候,只在新版脚本上修改参数,不再回看旧脚本。如果你想用这份代码参加今年的比赛,同样建议直接从newdetect.py入手,旧脚本当参考资料看就好。真正上场比赛的版本,通常都在命名最“new”的那个文件里,这是竞赛工程包的普遍规律。
3. 跑通目标检测:从环境准备到置信度阈值调优
目标检测是整套系统的眼睛。我在拿到这份包后做的第一件事,就是把newdetect.py跑起来,然后对着赛场的录屏视频去验证它的检测效果。如果你也想复现,这一章的步骤完全可以照抄。
3.1 环境依赖与最小复现步骤
先确认你的 ROS 版本和 OpenCV 版本。这个工程包是用 ROS 1 写的,图像处理部分依赖 OpenCV 的 Python 接口。我这里给出我实测过的最小复现流程:
# 安装 ROS 及相关图像依赖(以 Ubuntu 18.04 + ROS Melodic 为例) sudo apt install ros-melodic-cv-bridge ros-melodic-image-transport sudo apt install python3-opencv python3-numpy # 编译主工作区 cd ~/robo_pkg catkin_make source devel/setup.bash这段流程的关键有三点:第一,cv_bridge 必须和 OpenCV 版本匹配,Python2 和 Python3 混用会导致回调函数里图像编码报错;第二,必须先把主工作区编译通过,再单独跑检测脚本;第三,source devel/setup.bash要写进~/.bashrc,避免每次开终端都丢失包路径。编译期间如果报gstreamer相关错误,先别急着编译主工作区,把 gstreamer 插件装齐再回来,否则会出现“编译成功但运行时报错”的隐性问题。
跑检测脚本前,我习惯先用一张静态图做推理测试。这个包里有一个录屏 GIF,你可以先把 GIF 拆成帧保存成本地图片,再喂给检测脚本,确认模型能正常出框:
# 拆帧保存,便于调试目标检测 mkdir frames ffmpeg -i demo.gif -vf "fps=5" frames/frame_%04d.png3.2 参数设置:图像话题、目标类别与置信度阈值
检测脚本里影响结果的核心参数有两组:一组是输入输出话题名,一组是检测阈值。不同的队伍会把阈值参数写在脚本头部,有些写在 yaml 配置文件里。这份工程包里的检测脚本把参数直接放在了文件顶部,方便快速修改。我复现时主要调这几个参数:
--input_topic:订阅的图像话题,默认是/camera/rgb/image_raw,实机环境要根据相机驱动改。--conf_thresh:置信度阈值,我常用 0.35~0.5,调太低会有大量误检,调太高漏检严重。--iou_thresh:NMS 的 IoU 阈值,一般保持默认 0.4~0.5。--target_classes:需要检测的目标类别列表,根据赛事规则过滤掉无关类别。
以下是我实际用过的一组跑通参数:
input_topic = "/camera/rgb/image_raw" output_topic = "/detect/result" conf_thresh = 0.45 # 低于该值的检测框直接丢弃 iou_thresh = 0.4 # 重叠超过该值的框会被抑制 target_classes = ["person", "car", "cone"]这里特别说明一下conf_thresh。比赛现场的光照、运动模糊和仿真环境差别很大,仿真里 0.3 就能稳定检测的目标,实机上可能 0.5 都出不了框。我的建议是先用 0.3 跑一遍录屏,标出所有误检位置,再逐步把阈值抬高,直到误检数量可以接受、漏检不致命为止。不要迷信仿真里调好的参数,场上光线一变,检测结果就是两个世界。
3.3 后处理与检测结果的可信度判断
检测脚本输出的是像素坐标框,决策模块拿到这些框还要换算成机体坐标系的方位和距离。newdetect.py里会输出检测框的中心点、宽度和高度,并在话题里发布。这个环节最容易出的问题是,你看着rqt_image_view里检测框已经标出来了,但决策节点死活不响应。原因是检测结果话题里的坐标系约定和决策模块不一致。
我一般会做两个检查:第一,rostopic echo看一眼检测结果消息的类型和字段,确认是自定义消息还是标准消息;第二,打开脚本里的坐标系变换部分,确认像素坐标转换时用的相机内参是否正确。比赛期间,我们曾经因为用了仿真场景的相机内参去跑实机,导致检测框中心点偏了大概二十多个像素,在空中这个偏差会被放大成半米多的位置误差。复盘的时候,就是这个问题让我们少拿了好几个目标。
检测结果可信度的另一个判断指标是稳定帧数。我建议你在代码里加一个简单的状态机:目标必须连续出现 3 帧以上才确认,否则视为误检。这个逻辑在原工程里没有显式实现,但我在实机测试里发现非常必要。单帧出框可以是巧合,连续三帧出框基本就是真目标了。
4. 仿真到实机的关键链路:仿真桥接包、导航包与平滑控制
这个工程包最值钱的部分不是检测脚本,而是“仿真能通、实机能飞”的那条链路。目录里的仿真桥接包、导航包和平滑控制包,分别对应了感知后的路径规划、速度平滑和底层控制。我不建议拆开单独看,要把它们当成一个闭环整体来理解。
4.1 仿真桥接包:让仿真器变成 ROS 话题源
仿真桥接包的作用是把仿真器的图像、姿态、状态数据转换成 ROS 话题,同时把 ROS 的速度控制指令反向写回仿真器。这样上层算法可以完全不用关心数据是从仿真来的还是真机来的。这个思路非常工程化,相当于给整个感知决策系统造了一个可以随时替换的“数据接口层”。
我复现这个包时注意到一个细节:仿真器里的图像话题延迟和真机相机延迟差别很大。仿真环境几乎没有曝光和传输延迟,检测框非常稳定;实机上,相机曝光时间长、运动模糊严重,检测结果的抖动会明显变大。所以在仿真里看着很稳定的控制参数,上实机前一定要把图像话题的延迟加进你的控制回路里估算。
# 查看图像话题的发布频率和延迟 rostopic hz /camera/rgb/image_raw rostopic delay /camera/rgb/image_raw仿真器里hz一般稳定在 30 左右,实机上如果低于 15,说明相机驱动参数有问题,或者传输带宽不够。这时候直接调算法没意义,先解决图像帧率问题,再谈检测和控制。我跑比赛那段时间,一半的“算法玄学问题”最后都被证明是数据链路问题。
4.2 导航包与路径规划:仿真到实机的任务执行层
这个包的导航模块不是普通的自主漫游导航,而是面向赛事任务的目标点序列导航。赛事规则会给定一系列目标点,无人机需要按顺序飞过或检测,导航包负责接收目标点列表,规划路径并输出给底层控制。路径规划部分用到的思路和经典 ROS move_base 方案一致:用全局代价地图做整体路径搜索,用局部代价地图做实时避障。
我当时把这个包的导航逻辑简化为三步:第一步,接收目标点(x, y, z);第二步,调用路径规划算法生成全局轨迹;第三步,结合检测结果做局部避障修正。实机飞行时,最怕的不是全局路径不对,而是局部避障太激进。导航包里如果速度上限设置过高,飞控会频繁急刹,机身姿态一乱,图像就会模糊,检测成功率直线下降。
所以在调参时要特别关注导航包里的最大速度、最大加速度两个参数。我一般会把最大速度限制在 3m/s 以内,最大加速度限制在 2m/s² 以内,保证相机不会因为运动模糊丢帧。速度调快了省时间,但漏检造成的返工时间往往远超省下来的时间。
4.3 平滑控制包:速度指令里的滤波与限幅逻辑
smooth_control是整个工程里我觉得最值得学习的模块。它的作用不是自己飞,而是把上层导航和感知发过来的速度指令做滤波和限幅处理后,再发给飞控。为什么需要它?因为感知检测是有抖动的,目标框的中心点会来回跳,直接把这个跳动的角度差值换算成速度指令,飞机会像抽风一样左右摇摆。
平滑控制包通常做三件事:
- 滑动窗口均值滤波,去掉突变的速度指令。
- 最大角速度限幅,防止转弯过猛。
- 加速度限幅,防止急加速和急减速。
我在这份包的基础上加了输出限幅,效果立竿见影:
# 平滑速度指令的关键做法:限幅 + 一阶低通 prev_cmd = 0.0 alpha = 0.6 # 滤波系数,越小越平滑,响应越慢 def smooth_cmd(raw_cmd, max_out=2.0): global prev_cmd filtered = alpha * raw_cmd + (1 - alpha) * prev_cmd filtered = max(-max_out, min(max_out, filtered)) prev_cmd = filtered return filtered这段逻辑里alpha是最需要调的参数。alpha太大,速度指令跟随快但不平滑;alpha太小,指令很平顺但飞机会反应迟钝。我之前调到一个很舒服的值:偏航角速度的alpha用 0.4,前后速度的alpha用 0.7。原因是偏航角速度对图像稳定性影响最大,必须更保守。如果没有这个平滑模块的限幅,实机飞行时飞控很容易进入震荡状态,机身一抖,检测框全乱,整条闭环就崩了。
5. 复现避坑:五个让我返工的真实问题
这一章全是我自己复现这份工程时踩过的真实问题,每一条都对应着比赛准备期间的半天到一天返工时间。全是血泪教训。
5.1 编译卡在找不到 gstreamer 相关依赖
现象:catkin_make编译主工作区时报错,提示找不到 gstreamer 的头文件。解决:安装 gstreamer 开发包和插件,而不是只装运行库。这个包里的图像传输依赖 gstreamer 插件,编译报错只是头文件缺失,实际运行时会缺更多动态库。我最后重装了完整的 gstreamer 插件集才彻底解决。如果你也遇到这类问题,别试着绕过去,直接 sudo apt 装全libgstreamer1.0-dev和gstreamer1.0-plugins-*,然后再重新编译。
5.2 图像话题对不上导致检测节点一直黑屏
现象:启动 launch 后检测节点不报错,但rqt_image_view里黑屏。原因:实机相机的图像话题名和检测脚本默认话题名不一致,检测节点根本没订阅到真实图像。解决:用rostopic list确认相机话题名,然后在 launch 文件里加 remap 重映射。这一类问题占了复现阶段一半以上的排查时间,问题根源不在算法,在消息通道。
5.3 仿真里全 Pass,实机却漏检目标
现象:仿真环境里所有目标都能稳定检测,上实机后漏检严重。原因:实机画面存在运动模糊、光照不均和曝光波动,仿真环境过于理想,检测算法在图像退化情况下没有鲁棒性。解决:把仿真器里的图像加高斯噪声、随机亮度抖动后做数据增强,重新训练或调阈值。这个步骤在竞赛准备期非常关键,越早加入越好。比赛现场没有时间让你重新调参数,平时的仿真增强就是后悔药。
5.4 平滑控制调过度,飞机飞得“发飘”
现象:加了平滑控制包后,飞机不再急转急停,但飞行轨迹明显滞后,目标都飞过头了才反应过来。原因:平滑系数alpha调得太小,速度指令被过度滤波,导致决策延迟过大。解决:把alpha从 0.3 逐步调到 0.7,每次调整后重新飞一遍测试航线,观察响应速度和稳定性的平衡点。这一类问题在仿真里很难发现,因为仿真器的动力模型和真机不一致,必须靠实机测试才能感知。
5.5 回放 GIF 与日志时间戳对不上
现象:用包里的 GIF 做结果分析时,发现检测日志里标记的目标位置和 GIF 画面对不上。原因:GIF 是录屏软件抓的,检测日志是节点内部时间戳,两者没有硬件级同步。解决:用日志里的检测时间戳去截取对应帧画面,而不是用 GIF 的显示时间。后来我自己做验证时,直接在代码里把检测结果画到帧上再编码成视频,彻底规避了时间戳不同步的问题。
6. 用 GIF 回放做系统级验证:检测框与日志的交叉核对
包里的 GIF 是整个系统闭环运行的直接证据,但它只能作定性参考,不能作为调试依据。我最后再做一层验证:把 GIF 拆帧,与检测日志的时间戳交叉对齐,确认每个目标的检测框位置和飞行时间是否一致。具体做法是,写一个脚本读取检测日志中的时间戳和坐标输出,再把对应帧画面裁剪出来保存:
# 用日志时间戳截取检测对应帧 ffmpeg -i demo.mp4 -ss <timestamp> -frames:v 1 frame_<idx>.png然后把每帧结果按时间排序,检查是否有目标在应当出现的时间里没有框出来。这个方法能帮你定位系统级的时序问题,比如“检测到了但决策没反应”或者“决策发了指令但飞控没执行”。如果日志里检测正常、帧画面也有框,但飞行轨迹不对,问题就在控制链路;如果帧画面有目标但日志里没有检测框,问题就出在感知链路。
我最后养成的习惯是:每改一个参数,都重新录一段带检测框的视频,再拆帧检查,绝对不只看终端输出。从那以后我每次部署这套系统,都强制走一遍拆帧回放和日志时间戳对照的流程,这套方法在赛前帮我们排掉了很多只在特定时刻出现的致命问题。如果你也准备拿这份工程包上赛场,强烈建议把这一步做成自动化脚本,希望帮到你。
本文还有配套的精品资源,点击获取