简介:这份资源面向计算机视觉、机器人开发方向的学习者与工程实践者,提供一套基于YOLO与ROS的无人机气球目标跟踪系统完整实战包,可用于理解实时目标检测与飞行控制如何协同工作。压缩包共188个文件,约24.03MB,以C与C++源码为主体,辅以CUDA加速文件、Python脚本、YAML配置、launch启动文件及msg消息定义,另含少量图片与文档,覆盖图像采集、检测推理、跟踪逻辑与无人机接口交互等模块。资源包含项目源码、系统标定流程、效果展示与流程教程,读者可据此梳理从数据预处理、目标检测到实时反馈控制的完整链路,并参考目录结构理解各功能模块的划分方式。目前已有445人学习,适合希望将YOLO检测落地到ROS机器人平台、需要可运行工程范例的开发者参考。
1. 无人机气球跟踪到底在做什么:从 YOLO 检测框到 ROS 速度指令的闭环
气球跟踪听起来像个玩具问题,真做起来才发现它把无人机视觉感知里最典型的几个环节全串起来了:相机标定、YOLO 推理、目标框到三维方向的映射、ROS 节点间的数据流、飞控速度指令的下发。我最早接这个需求时以为两天能跑通,结果光相机标定和坐标系对齐就折腾了一周。这套方案的核心链路是:机载相机采集图像,YOLO 检测气球在画面中的像素位置,结合标定得到的内参把像素坐标反投影成相机坐标系下的方向向量,再通过 ROS 话题把期望速度发给飞控,让无人机朝气球方向平移。它适合已经会装 ROS、跑过 YOLO 推理、想找一个完整闭环练手的开发者,也适合做无人机视觉感知课程设计或毕设的同学。整条链路不依赖昂贵设备,一台带相机的无人机、一个气球、一台地面站就能复现。下面按标定、检测、ROS 集成、联调排坑的顺序拆开讲,每一步都给可抄的命令和参数。
2. 相机标定与坐标系对齐:像素坐标怎么变成无人机能用的方向
2.1 为什么必须先标定,不标定会偏多少
YOLO 给你的是像素坐标 (u, v),飞控要的是机体坐标系下的方向。中间隔着一个相机内参矩阵 K 和外参(相机相对机体的安装位姿)。不标定内参,你连焦距和主点都不知道,反投影出来的角度误差在画面边缘能到十几度,气球稍微偏一点无人机就朝错误方向飞。常见做法是用棋盘格标定板跑一遍张正友标定,ROS 生态里最顺手的是camera_calibration包。标定板我一般用 8x6 内角点、方格边长 25mm 的打印板,贴在硬纸板上保证平整,翘边是标定误差的常见来源。
标定命令如下,--size是内角点数(列 x 行),--square是方格实际边长(米):
# 启动相机驱动后,运行标定节点 rosrun camera_calibration cameracalibrator.py \ --size 8x6 \ --square 0.025 \ image:=/camera/image_raw \ camera:=/camera采集时要在画面里把标定板移动到上下左右、远近、倾斜各个姿态,X、Y、Size、Skew 四个进度条都变绿再点 CALIBRATE。标定完成后点 SAVE,会生成ost.yaml,里面就是内参矩阵和畸变系数。逻辑说明:--size 8x6表示棋盘格内部角点是 8 列 6 行,不是方格数,这是新手最容易填错的地方;--square 0.025必须和你打印出来的实际边长一致,用尺子量,别信打印设置的标称值。畸变系数在广角镜头上影响很大,反投影前一定要先做去畸变,否则边缘检测框对应的方向会系统性偏移。
2.2 外参:相机装在哪,决定方向向量怎么转
内参解决「像素到相机坐标系方向」,外参解决「相机坐标系到机体坐标系」。如果你的相机光轴和机体前向完全对齐、相机在机体中心,外参可以近似为单位阵,但实际安装总有偏差。我一般用两种方式拿外参:一是机械测量加手动微调,二是用已知位置的标定物做手眼标定。对气球跟踪这种精度要求不极端的场景,机械测量够用,但俯仰角一定要量准,相机往下倾斜 10 度你没算进去,无人机就会一直飞高。
把内参和外参写进一个配置,反投影的核心计算是这样的:
import numpy as np # 从 ost.yaml 读出的内参 K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) # 相机到机体的旋转(示例:相机前视,绕X轴下倾10度) theta = np.deg2rad(10) R_bc = np.array([[1, 0, 0], [0, np.cos(theta), -np.sin(theta)], [0, np.sin(theta), np.cos(theta)]]) def pixel_to_body_direction(u, v): # 像素坐标转归一化相机坐标 ray_cam = np.linalg.inv(K) @ np.array([u, v, 1.0]) # 转到机体坐标系 ray_body = R_bc @ ray_cam return ray_body / np.linalg.norm(ray_body)逻辑说明:np.linalg.inv(K) @ [u,v,1]把像素点变成相机坐标系下的一条射线方向,这一步假设已经去畸变。R_bc是相机到机体的旋转,方向定义要统一,我习惯用「相机坐标系到机体坐标系」的旋转矩阵,别和它的转置搞混,这是坐标系翻车的头号原因。返回的ray_body是单位方向向量,它的 y 分量(机体前向)和 x 分量(机体右向)之比就决定了无人机该往哪偏。参数上,fx, fy, cx, cy直接来自标定文件,theta来自你的实际安装角,量的时候用手机水平仪贴着相机底面测,比目测靠谱。
注意:标定和反投影全程要保证单位一致,
--square用米,那平移量的单位也是米,混用毫米会让速度指令差三个数量级。
3. YOLO 检测气球:模型选型、推理封装与 ROS 话题对接
3.1 选 YOLOv8 还是 v11,预训练权重怎么用
气球是个单类别目标,形状规整、颜色鲜明,属于 YOLO 最容易拿下的场景。模型选型上,我一般直接用 YOLOv8n 或 YOLOv11n 这种 nano 版本,机载算力有限时推理帧率比精度更重要。别一上来就上 x 版本,无人机上跑不动,帧率掉到个位数,跟踪直接抖成筛子。预训练权重从官方发布渠道拿yolov8n.pt或yolo11n.pt,在自己的气球数据集上微调。数据集不用大,我实测 300 到 500 张不同光照、不同背景、不同距离的气球图,标注单类别balloon,训练 50 到 100 epoch 就能到可用精度。
训练命令:
yolo detect train \ data=balloon.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20逻辑说明:data=balloon.yaml里写训练集、验证集路径和类别名,格式是 YOLO 标准的path/train/images结构。imgsz=640是推理和训练分辨率,机载部署时如果算力紧张可以降到 416 或 320,但小气球在低分辨率下容易漏检,这个权衡要实测。patience=20是早停,验证集 20 轮不涨就停,省时间。lr0=0.01是初始学习率,微调预训练模型时这个值合适,从零训练要更小。训练完看混淆矩阵和 PR 曲线,气球这种单类别,重点看召回率,漏检比误检对跟踪的伤害大得多,因为漏一帧目标就丢了。
3.2 把 YOLO 推理封成 ROS 节点,话题怎么设计
ROS 集成的关键是话题设计。我一般让检测节点订阅/camera/image_raw,发布一个自定义消息,里面包含检测框中心、宽高、置信度和类别。别直接发图像,带宽扛不住。消息定义可以简单点,用geometry_msgs/Point发归一化中心坐标加一个置信度字段,或者自定义BalloonDetection.msg。节点里用 OpenCV 的cv_bridge把 ROS 图像转成 numpy 数组喂给 YOLO。
import rospy import cv2 import numpy as np from cv_bridge import CvBridge from sensor_msgs.msg import Image from geometry_msgs.msg import Point from ultralytics import YOLO rospy.init_node('balloon_detector') bridge = CvBridge() model = YOLO('best.pt') # 微调后的权重 pub = rospy.Publisher('/balloon/center', Point, queue_size=1) def callback(msg): frame = bridge.imgmsg_to_cv2(msg, 'bgr8') results = model(frame, imgsz=640, conf=0.4, verbose=False) boxes = results[0].boxes if len(boxes) == 0: return # 取置信度最高的框 best = boxes[boxes.conf.argmax()] x1, y1, x2, y2 = best.xyxy[0].cpu().numpy() h, w = frame.shape[:2] # 归一化中心坐标,避免分辨率变化影响下游 cx_norm = ((x1 + x2) / 2) / w cy_norm = ((y1 + y2) / 2) / h pub.publish(Point(cx_norm, cy_norm, float(best.conf))) rospy.Subscriber('/camera/image_raw', Image, callback) rospy.spin()逻辑说明:conf=0.4是置信度阈值,气球场景可以设低一点到 0.3 提高召回,但太低会引入背景误检,我一般从 0.4 起调。boxes.conf.argmax()取最高置信度框,多气球场景要改成遍历发布多个目标。归一化中心坐标是关键设计,下游反投影时乘回图像宽高即可,这样换相机分辨率不用改下游代码。queue_size=1保证只处理最新帧,避免旧帧堆积导致控制延迟,这是实时跟踪的常见坑。发布频率取决于推理速度,nano 模型在普通 CPU 上大概 10 到 20 FPS,够用。
提示:
cv_bridge的编码要和相机驱动发布的一致,bgr8和rgb8搞反会导致颜色通道错位,YOLO 对颜色敏感,检测框会飘。
4. 从检测框到速度指令:控制节点、参数整定与联调
4.1 控制律怎么写,比例控制够不够
拿到气球在画面中的归一化中心后,控制目标就是让中心回到画面正中 (0.5, 0.5)。最简单有效的是比例控制:水平偏差乘一个增益得到机体右向速度,垂直偏差乘增益得到升降速度,前向速度给一个固定值或根据框的大小(距离估计)来调。框越大说明气球越近,前向速度该减小甚至停住。我一般用框面积占比做粗略距离估计,面积超过阈值就停止前进。
import rospy from geometry_msgs.msg import Point, Twist rospy.init_node('balloon_controller') cmd_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1) Kp_yaw = 1.2 # 水平偏差到右向速度 Kp_throttle = 0.8 # 垂直偏差到升降速度 v_forward = 0.5 # 基础前向速度 area_stop = 0.25 # 框面积占比超过此值停止前进 def control(msg): err_x = msg.x - 0.5 # 水平偏差 err_y = msg.y - 0.5 # 垂直偏差 cmd = Twist() cmd.linear.x = v_forward if msg.z < area_stop else 0.0 cmd.linear.y = -Kp_yaw * err_x # 注意方向符号 cmd.linear.z = -Kp_throttle * err_y cmd_pub.publish(cmd) rospy.Subscriber('/balloon/center', Point, control) rospy.spin()逻辑说明:err_x为正表示气球在画面右侧,无人机该往右飞,但机体右向是 y 轴,符号取决于你的坐标系定义,-Kp_yaw里的负号要实测确认,方向反了无人机会越飞越偏,这是联调第一坑。Kp_yaw和Kp_throttle是核心整定参数,从 0.5 起调,太大振荡,太小响应慢。area_stop用置信度字段msg.z代替面积是简化写法,实际应该传框面积,这里为了消息复用做了妥协,正式项目建议自定义消息带上框宽高。前向速度v_forward别给太大,0.5 m/s 在室内够用,室外要结合定位和避障。
4.2 联调顺序:先地面验证再上飞
联调千万别直接上飞。我一般分三步:第一步,相机对着屏幕播放的气球视频,看检测框和/balloon/center数值是否稳定;第二步,把无人机拿在手里,对着真实气球移动,看/cmd_vel输出方向对不对,这一步能抓出符号错误;第三步,低空悬停状态下开控制,人随时准备接管。ROS 里用rostopic echo /cmd_vel和rqt_plot把偏差和速度画出来,振荡和延迟一眼就能看出来。常见做法是先用rqt_image_view确认检测框,再用rqt_plot看控制量,两个工具配合能定位大部分问题。
注意:
/cmd_vel的话题名和消息类型要和你飞控的桥接节点一致,PX4 和 ArduPilot 的桥接方式不同,别想当然。
5. 避坑与排查:气球跟踪最容易翻车的五个地方
5.1 检测框抖动导致速度指令高频振荡
现象:无人机悬停时小幅高频抖动,/cmd_vel数值快速跳变。原因:YOLO 逐帧检测没有时序平滑,框中心在相邻帧之间跳几个像素,比例控制直接放大成速度抖动。解决:在控制节点里加一阶低通滤波或滑动平均,v = alpha * v_new + (1-alpha) * v_old,alpha取 0.3 到 0.5。更稳的做法是引入卡尔曼滤波或简单的目标跟踪器(如 ByteTrack)做帧间关联,但气球单目标场景,低通滤波就够。
5.2 标定畸变没去,画面边缘方向系统性偏移
现象:气球在画面中心时跟踪正常,一到边缘无人机就朝错误方向飞。原因:广角镜头畸变没校正,边缘像素反投影方向偏差大。解决:反投影前用cv2.undistort配合标定得到的畸变系数去畸变,或者直接用image_proc节点发布去畸变后的图像再喂给 YOLO。标定时畸变系数要标出来,别只存内参。
5.3 坐标系符号搞反,无人机越追越远
现象:无人机朝气球反方向飞。原因:机体坐标系定义、相机安装方向、控制增益符号三者之一反了。解决:拿在手里测试,气球在画面右侧时手动确认无人机该往哪飞,对照/cmd_vel的 y 分量符号,反了就改增益符号。别在飞的时候试,摔了没后悔药。
5.4 推理延迟导致控制滞后
现象:无人机响应慢半拍,气球移动后要过一会儿才跟。原因:YOLO 推理耗时加上 ROS 话题队列堆积。解决:queue_size设 1,只处理最新帧;模型换 nano 版本;imgsz降到 416;如果还慢,考虑 TensorRT 加速。延迟超过 100ms 跟踪就会明显滞后。
5.5 光照变化让检测时好时坏
现象:室内正常,一到窗边或室外就漏检。原因:训练集光照单一,模型过拟合到特定亮度。解决:数据集里刻意加入逆光、阴影、过曝样本,训练时开 HSV 增强。实测加 50 张极端光照图,召回率能提十几个点。
6. 进阶:用框面积做距离估计并加一个安全距离保持
基础版只让气球居中,进阶一点是保持固定距离。框面积和距离平方成反比,可以标定一个面积-距离曲线:把气球放在已知距离(比如 1m、2m、3m)各拍一组,记录框面积占比,拟合出distance = k / sqrt(area)里的 k。然后控制前向速度让距离收敛到设定值,比如保持 2m。
import math # 标定得到的系数,面积占比 area 对应距离 K_DIST = 0.15 def forward_speed(area, target_dist=2.0): if area <= 1e-6: return 0.0 dist_est = K_DIST / math.sqrt(area) err = dist_est - target_dist # 距离误差转前向速度,限幅 v = max(-0.5, min(0.5, 0.8 * err)) return v逻辑说明:K_DIST必须自己标定,不同相机焦距、不同气球大小这个值都不一样,别抄别人的。0.8 * err是比例增益,max/min限幅防止速度指令过大。距离估计精度不高,气球大小变化或部分遮挡都会影响,所以只用来做粗保持,别指望厘米级。验证方法:把气球固定在已知距离,看dist_est和实际值的偏差,偏差大就重新标K_DIST。
我自己的习惯是每次换相机或换镜头,标定和K_DIST一定重做,这两个参数不复用。这套方案从标定到跟踪闭环,认真做一周能跑通,但联调排坑的时间往往是写代码的两三倍,耐心点,符号和坐标系多验证几遍。希望帮到你。
本文还有配套的精品资源,点击获取