无人机自主降落这件事,卡住过很多人。GPS在开阔地干活还行,一旦靠近机库、物流站点、移动起降平台,误差直接飙到米级,最后那几米全靠人肉遥控,完全谈不上“自主”。我做过几个巡检和物流配送的项目,最终方案都收敛到同一条路上:机载相机识别地面二维码,解算相对位姿,引导飞控完成精准降落。这篇文章把我在ROS环境下做V2动态二维码识别与精准降落实战中的方案选型、实现细节、参数整定和踩坑记录完整拆出来,给正在搞同类项目的朋友一份能直接上手的参考。
这个方案的核心链路是:机载相机实时采集图像 → 检测并识别二维码 → 提取角点并解算无人机与二维码之间的三维相对位姿 → 通过MAVROS或自研协议把误差发给飞控 → 内环PID控制无人机逐步逼近并最终降落到二维码中心上方。整个系统跑在ROS环境下,相机、识别节点、位姿解算、控制节点各司其职,数据流清晰,每一步都能单独调试。适合以下人群参考:正在做无人机视觉降落的开发者、用PX4/ArduPilot做自主飞行的朋友,以及想搞懂“动态二维码”场景(比如移动起降平台、船载停机坪)如何实现位姿追踪的人。
- 项目整体设计与方案选型
1.1 为什么是二维码而不是其他视觉方案
视觉降落的主流方案有好几种:Apriltag、ArUco、二维码(QR Code)、深度学习目标检测加姿态估计、甚至光流加超声波。我最早用的是深度学习方案,训练了一个YOLOv5模型识别停机坪“H”标志,然后估计位姿。实验室里效果不错,真机一跑就露馅:模型泛化差、换一个光照环境精度就垮,而且没有角度尺度信息,解算位姿还得额外估计,精度根本稳不住。
二维码方案优势非常明确:只需要一个单目相机就能解算完整6D位姿,四个角点的像素坐标、二维码的物理边长、相机内参,一套PnP解算就能拿到无人机相对二维码的x、y、z偏量,不需要深度传感器。视觉特征尖锐、编码冗余度高,识别稳定性比深度学习方案高一个数量级。实测在光照变化、轻微遮挡、斜视情况下,ar_track_alvar的识别帧率依然稳定,这对无人机控制环来说太重要了。
我需要特意强调“V2动态二维码”中的“动态”二字。早期V1版本只能识别静态贴在地面上的二维码,无人机只能从固定起飞点升空再降回原点。V2版本需要支持两种情况:一种是二维码贴在可移动起降平台上,平台会移动甚至旋转,无人机要追着平台降落;另一种是机载相机快速降落过程中二维码在图像中会快速变大、跑出视野,识别算法需要稳定追踪。两种都要求识别节点具备实时更新位姿的能力,不能像静态识别那样检测一次就完事。
1.2 ROS在系统中的角色划分
整个系统跑在ROS通信架构下,好处是每个模块都可以独立启动、独立调试、随时可视化。我用的是ROS Noetic(Ubuntu 20.04),实际上换成ROS 2 Humble,思路完全一致,只是部分包名和话题名要调整。
上电后的总线结构大致是这样:USB相机节点发布原始图像话题,ar_track_alvar订阅这个话题做二维码检测,输出包含位姿信息的TF变换;我写了一个位姿解算节点,把这个相对位姿转换成飞控能用的偏差量;控制节点订阅偏差量,跑一个位置环PID和偏航控制,通过MAVROS把速度指令发给PX4飞控。整条链路每个环节都可以用rqt_graph检查连接,用rviz验证位姿是否正确。
为什么不用现成的“一键降落”功能?因为PX4自带的Land模式只依赖距离传感器和GPS,落点完全不可控。要让无人机“精准”降落到二维码正上方,必须自己接管水平位置控制,在垂直方向可以保留飞控原生的定高或降落逻辑。这个分工也是我在多次炸机教训之后才总结出来的。
1.3 模块化设计与数据流设计
我习惯把整个系统按“感知-解算-控制”三层切分,每层之间只通过ROS话题和TF树传递数据,不搞跨层直接调用。
感知层的输出是目标检测结果和位姿,不关心无人机怎么飞;控制层只关心偏差量,不关心偏差量是怎么解算出来的。这样做的直接收益是调试效率大幅提升——我可以在仿真环境里只启动感知层,手动把二维码放在不同位置,检查位姿解算是否准确,完全无需起飞真机。
数据流设计上,最容易被忽略的是时间戳同步。ROS的话题通信默认带时间戳,但多个传感器数据到达控制节点的时刻不一样,如果直接用最新数据,控制环会引入随机延迟。我用message_filters做时间同步,或者在控制节点里缓存最近100ms内的位姿数据,按时间戳取最新有效值。实测下来,固定时间戳同步后,降落过程的震荡幅度减少约30%。
- 环境搭建与硬件选型
2.1 机载计算平台与飞控的选择思考
很多入门用户习惯在笔记本电脑上装ROS跑算法,真机调试才意识到问题:笔记本太大太重,挂载在无人机上严重改变重心和动力学特性。我的建议是预算允许的情况下直接上NVIDIA Jetson Orin Nano或Xavier NX,算力足够跑图像处理和ROS节点,功耗控制在15W以内,重量几十克,对无人机载荷影响小。如果只是验证算法,用树莓派4B也能跑,但ar_track_alvar在640x480分辨率下帧率会掉到15fps左右,勉强够用但不推荐。
飞控我先后用过Pixhawk 6C和CUAV V5+,都跑PX4固件。选择PX4而非ArduPilot的原因主要是MAVROS生态更成熟,safety相关的Fail-safe机制比较清晰,而且官方对offboard模式的支持很完善。如果你用ArduPilot,其实也可以走DroneKit或MAVROS,不过你后续查阅资料时会发现,社区里视觉降落相关的案例大多基于PX4。
2.2 相机选型与安装位置要点
相机是整个感知链路的信息源,选型直接决定系统上限。我用过普通USB摄像头(罗技C920)、工业相机(海康MV系列)、以及双目相机(Intel RealSense D435)。最终固定使用单目工业相机加定焦镜头,理由很简单:二维码位姿解算只需要单目相机加已知物理尺寸,就能算出完整的6D位姿,双目反而引入更多标定误差。
相机安装位置和角度非常讲究。我把相机安装在机身前下方,俯仰角约45度,向正下方略微倾斜。这样做有两个好处:一是起飞阶段二维码在远处就能提前进入视野,二是降落过程中即使无人机压得很低,相机也能看到机身正下方,不至于丢失目标。V1版本我踩过坑:相机水平安装,降落最后阶段二维码完全跑出画面,系统直接失联,无人机乱飘。45度下视安装之后,追踪稳定度提升非常明显。
2.3 从零搭建ROS环境
很多新手一上来就卡在ROS安装上。如果用的是Ubuntu 20.04,装ROS Noetic;Ubuntu 22.04装ROS 2 Humble。这里我只讲ROS 1 Noetic的命令,ROS 2对应关系可以类推。
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' sudo apt install curl curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo apt update sudo apt install ros-noetic-desktop-full echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc你可能会想,手动敲这么多命令太麻烦,能不能直接“一键安装”。社区里鱼香ROS提供了一键安装脚本,我帮朋友装虚拟机时实测过,确实能省掉不少零碎配置的功夫,适合新环境快速铺底。不过我自己还是习惯手动安装,因为一键脚本装完,你往往不清楚系统里到底多了哪些东西,后面出了问题无从排查。对想深入学习ROS的读者,我依然建议至少手动装一遍,跑通一个talker和listener,理解ROS节点、话题、消息的本质,后面写代码会顺畅得多。
装完ROS后,还需要装MAVROS、图像传输、二维码识别等包:
sudo apt install ros-noetic-mavros ros-noetic-mavros-extras sudo apt install ros-noetic-usb-cam ros-noetic-cv-bridge ros-noetic-image-transport sudo apt install ros-noetic-ar-track-alvar ros-noetic-ar-track-alvar-msgs我用的是ar_track_alvar作为二维码识别的主力库,原因是它天生输出TF和位姿消息,和ROS集成成本最低。如果喜欢用ArUco,也可以装ros-noetic-aruco-detect,但后续需要自己封装位姿输出,多一层工作。
- 二维码识别与位姿解算核心实现
3.1 二维码生成与贴装规范
在实际降落场景中,二维码不是随便找一个就能用的。我用的是ar_track_alvar默认支持的AR标签。生成AR标签有两种方式:一种是用ar_track_alvar包里的createMarker工具命令行生成;另一种是利用在线生成器,注意选择AR tag类型而不是普通QR Code。
我的建议是自己用命令行生成,可以精确控制ID和尺寸:
rosrun ar_track_alvar createMarker -id 0 -size 10.0 -o marker0.png这里的-size 10.0表示二维码的物理边长是10厘米。生成后打印出来,贴在起降平台中央。贴装时有几个硬性要求:二维码必须平整,不能有褶皱;周围至少保留二维码边长2倍以上的纯色区域,防止环境纹理干扰识别;平台表面最好用哑光材质,反光材质会直接导致识别闪烁。
V2动态场景下,如果二维码贴在移动平台上,还要考虑平台的边缘是否会被识别为额外特征。我在移动巡检机器人顶上贴二维码时,特意在机器人外壳覆盖了一层防滑哑光贴纸,然后在二维码四周留了5厘米纯黑边框,识别率从82%提升到了97%以上。
3.2 ar_track_alvar的工作原理与参数配置
ar_track_alvar是一个基于OpenCV的AR标签检测库,核心流程是:图像二值化 → 轮廓提取 → 四边形筛选 → 内部编码解码 → 角点亚像素优化 → 基于相机内参和标记物理尺寸输出姿态。它的设计非常巧妙,标签内部有7x7的编码矩阵,可以识别出ID,即使部分遮挡也能解码。
驱动相机,然后启动识别节点,这是我的启动命令:
roslaunch usb_cam usb_cam-test.launch roslaunch ar_track_alvar ar_track_alvar.launch实际项目中,我不用默认launch文件,而是自己写一个配置完整的launch:
<launch> <node name="ar_track_alvar" pkg="ar_track_alvar" type="individualMarkersNoKinect" output="screen"> <param name="marker_size" value="10.0"/> <param name="max_new_marker_error" value="0.08"/> <param name="max_track_error" value="0.2"/> <param name="cam_image_topic" value="/usb_cam/image_raw"/> <param name="cam_info_topic" value="/usb_cam/camera_info"/> <param name="output_frame" value="camera_link"/> <param name="marker_resolution" value="10"/> </node> </launch>marker_size必须和生成的二维码物理尺寸完全一致,单位是厘米。output_frame设成相机的光学坐标系,这样发布的TF就是从相机坐标系到二维码坐标系的变换。marker_resolution是编码矩阵分辨率,默认10,和你生成的标签匹配就行。
3.3 相机标定与坐标系关系
识别算法解算位姿的前提是相机内参准确。这一步省不得,内参不准,后面所有位姿数据都是错的。我用的是ROS标准的camera_calibration工具:
rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.024 image:=/usb_cam/image_raw camera:=/usb_cam拿一张棋盘格标定板,在相机前以不同角度、不同距离移动,程序会自动采集样本,标定完成后会生成calibrationdata.tar.gz。把里面的ost.yaml里的内参矩阵、畸变系数复制到相机节点配置里,并确保camera_info话题发布的是标定后的数据。
坐标系关系是整个系统最容易绕晕的地方。我建议用一个独立的TF树来梳理:map(或odom)→base_link(机体)→camera_link(相机)→marker_0(二维码)。无人机飞控输出的是base_link在map中的位置,ar_track_alvar输出的是marker_0相对于camera_link的位姿。要让控制环使用,需要把marker位姿变换到base_link坐标系下。
这个变换通常由TF库自动完成。只要相机的外参(即camera_link到base_link的变换)正确配置,你监听TF即可直接获得二维码在机体坐标系下的相对位置。相机外参可以用tf2_ros的static_transform_publisher静态发布:
rosrun tf2_ros static_transform_publisher 0.1 0 0 -0.7854 0 0 base_link camera_link上面命令的意思是相机位于机体前方10cm、安装俯仰角约45度(-0.7854弧度)。实际数值以你安装位置为准,一定要用尺子量准,别大概估,这个误差会直接传导到降落精度。
3.4 PnP位姿解算的细节
ar_track_alvar内部已经通过PnP求解得到了位姿,但你如果自己写识别节点,就需要理解这个过程。PnP问题的输入是:二维码四个角点的像素坐标、二维码物理边长、相机内参矩阵和畸变系数。输出是二维码坐标系相对于相机坐标系的旋转矩阵和平移向量。
使用OpenCV实现非常简单:
import cv2 import numpy as np object_points = np.array([ [-length/2, length/2, 0], [ length/2, length/2, 0], [ length/2, -length/2, 0], [-length/2, -length/2, 0] ], dtype=np.float32) # image_points 为检测到的四个角点,按相同顺序排列 retval, rvec, tvec = cv2.solvePnP(object_points, image_points, camera_matrix, dist_coeffs)其中rvec是旋转向量,tvec是平移向量,分别代表二维码坐标系到相机坐标系的旋转和平移。把旋转向量用cv2.Rodrigues转成旋转矩阵,就能进一步做坐标变换。
实际工程中,我还会用cv2.solvePnPRansac替代solvePnP,尤其是动态场景下角点检测容易受运动模糊影响产生离群点,RANSAC能自动剔除异常值,位姿稳定性明显更好。这在快速降落场景中尤为重要——二维码在图像里迅速变大,运动模糊严重,普通PnP偶尔会蹦出几十厘米的跳变,RANSAC之后基本消除。
- 精准降落控制逻辑与实操实现
4.1 为什么要自研降落控制,而不是直接调用飞控Land
PX4的Land模式本质是“垂直下降”,水平位置锁定靠GPS或光流。在室内或GPS信号差的环境,GPS位置不可用,水平锁定直接失效;就算GPS可用,米级精度也不足以应付二维码降落。我们必须自己接管水平位置闭环,把视觉位姿解算出的二维码相对位置作为位置环输入,通过offboard模式发送速度控制指令给飞控。
垂直方向的处理,我采用两段式策略。第一阶段,无人机在距离二维码高度1.5米以上时,只用水平位置控制,垂直速度保持一个较小恒定值或完全手动控制;当高度低于1.5米后,进入“锁定下降”模式,水平继续用视觉闭环,垂直以每秒0.3米左右的定速缓慢下降,同时持续检查视觉位姿是否有效。一旦二维码丢失,立即暂停下降悬停原地,等识别恢复再继续。这个策略在V1版本就验证有效,V2版本在动态场景下也扛住了,逻辑简单但非常可靠。
4.2 基于MAVROS的offboard控制实现
MAVROS是ROS和PX4之间的桥梁。飞控在offboard模式下,接受来自MAVROS的/mavros/setpoint_velocity/cmd_vel话题的速度指令。我的控制节点订阅二维码位姿和当前无人机状态,计算水平速度指令并发布。
下面代码是核心控制逻辑的示意,我做了简化,去掉了大量的状态判断和日志打印,但保留了完整思路:
#!/usr/bin/env python3 import rospy import tf import math from geometry_msgs.msg import TwistStamped, PoseStamped, Point class LandingController: def __init__(self): rospy.init_node('landing_controller') self.vel_pub = rospy.Publisher('/mavros/setpoint_velocity/cmd_vel', TwistStamped, queue_size=1) self.arm_service = rospy.ServiceProxy('/mavros/cmd/arming', mavros_msgs.srv.CommandBool) self.set_mode_service = rospy.ServiceProxy('/mavros/set_mode', mavros_msgs.srv.SetMode) self.tf_listener = tf.TransformListener() # 控制参数 self.kp_xy = 0.8 self.kp_z = 0.5 self.max_xy_vel = 0.5 self.descend_height = 0.30 rospy.sleep(2) def get_marker_pose_in_body(self): try: (trans, rot) = self.tf_listener.lookupTransform('base_link', 'marker_0', rospy.Time(0)) return trans except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException): return None def run(self): rate = rospy.Rate(30) while not rospy.is_shutdown(): marker_pos = self.get_marker_pose_in_body() if marker_pos is None: rate.sleep() continue # marker_pos 表示二维码在机体坐标系下的相对位置 # 水平方向速度与二维码位置成比例,负反馈 vx = max(-self.max_xy_vel, min(self.max_xy_vel, self.kp_xy * marker_pos[0])) vy = max(-self.max_xy_vel, min(self.max_xy_vel, self.kp_xy * marker_pos[1])) vz = self.kp_z * (marker_pos[2] - self.descend_height) vz = max(-0.3, min(0.0, vz)) # 只允许向下,最大下降速度0.3m/s twist = TwistStamped() twist.header.stamp = rospy.Time.now() twist.twist.linear.x = vx twist.twist.linear.y = vy twist.twist.linear.z = vz self.vel_pub.publish(twist) rate.sleep() if __name__ == '__main__': ctrl = LandingController() ctrl.run()注意这里我把所有正负方向和单位都省略了,真实项目里一定先在仿真里验证方向。marker_pos代表二维码在机体坐标系的坐标,如果marker_pos[0]为正,说明二维码在机体前方,无人机需要向前飞,所以速度指令为正。这些方向关系错一个符号,结果就是无人机朝反方向猛冲。
4.3 控制频率、延迟与PID参数整定
控制频率上,我的经验是30Hz足够。MAVROS底层的mavlink通信链路通常是几十Hz,速度指令太高会堆积,太低会导致飞行不平滑。30Hz配合PX4内部姿态环,能够做到水平方向厘米级收敛。
PID参数整定,我用的不是理论推导,而是一套很实用的实验方法:先把kp设得保守偏小,比如0.3,观察无人机响应;如果无人机在二维码上方来回振荡,说明kp过大,减小;如果收敛太慢、偏离很久才回到目标,说明kp过小,增大。D项一般设0,因为视觉位姿本身带噪声,微分会放大噪声。I项可以设一个很小的值,比如0.01,用来消除静态误差。
动态二维码场景下,目标的位姿一直在变,PID跟踪滞后问题暴露得很明显。我的改进方法是引入一个“目标位置预测”。具体来说,在控制节点里缓存最近5个二维码位置数据,做一阶线性拟合,预测目标平台接下来0.2秒的位置,以预测值作为控制输入。实测后,降落成功率从78%提升到93%,平台移动速度0.5m/s时依然能稳定追踪。
4.4 从起飞到降落的状态机控制
单靠P控制律是不够的,整个降落流程必须用状态机来管理,防止飞手误操作、数据丢失等异常情况。我设计了6个状态:READY、TAKEOFF、SEARCH、TRACK、DESCEND、LANDED。
- READY:无人机上电,飞控自检,视觉节点启动。检测到二维码进入视野后,切换到TAKEOFF。
- TAKEOFF:发送offboard模式指令,按设定高度起飞,起飞到1.5米后自动进入SEARCH状态。
- SEARCH:无人机悬停,旋转机头寻找二维码。找到二维码并连续稳定识别10帧,切换TRACK。
- TRACK:水平跟踪二维码,目标保持在图像中心,高度不变。跟踪误差小于10厘米后,进入DESCEND。
- DESCEND:进入下降阶段,水平闭环保持,垂直以0.3m/s下降。下降过程中一旦视觉丢失超过0.5秒,立即切换回TRACK或SEARCH。
- LANDED:检测到触地(高度变化率突变或电量电流变化),切换到LANDED状态,解锁电机。
这个状态机我建议用Python的enum加上一个简单的循环实现,每个状态一个transition函数,代码结构清晰,后面加异常处理也方便。V1版本我在一个while循环里写了一堆if-else,最后每个分支都很难维护,V2重构后出问题好查多了。
- 动态场景适配与进阶实战
5.1 动态二维码的核心:从静态识别到持续追踪
V2版本最重要的改动,是把“检测-降落”的流程升级为“检测-跟踪-降落”三阶段。静态降落只需要识别成功一次,然后一直往下飞就行;动态场景下,二维码位置持续变化,识别节点必须保持高频输出,并且当二维码短暂出视野后,控制逻辑要能平滑过渡,而不是直接崩溃。
我用两个num参数来保障连续性:一个是max_new_marker_error,控制新检测到的二维码的误检率,设0.08已经比较严格;另一个是max_track_error,控制在跟踪过程中允许的误差,设0.2。这两个值的意思有点反直觉:检测新目标时要求严格,防止误检导致乱飞;但跟踪已确认目标时放宽条件,防止稍微模糊一点就丢失。实际测试中,这组参数让二维码在快速移动时保持率提高了40%。
5.2 动态平台速度与飞行模式匹配
移动起降平台的典型场景是:船载停机坪、移动巡检车顶部平台、物流接驳点。动态降落时,二维码在图像中的运动特征和静态完全不同,如果控制参数还是静态那套,会掉队甚至丢目标。
我通过实验得出了一个大致的匹配表,供参考:
| 平台运动状态 | 最大水平追踪速度 | 推荐下降起始高度 | 推荐kp_xy | 视觉丢失允许时间 |
|---|---|---|---|---|
| 静止 | 0 m/s | 1.2 m | 0.6 | 2.0 s |
| 缓慢移动(≤0.3m/s) | 0.3 m/s | 1.5 m | 0.8 | 0.8 s |
| 中等移动(0.3-0.8m/s) | 0.8 m/s | 2.0 m | 1.2 | 0.5 s |
| 快速移动(>0.8m/s) | 1.2 m/s | 2.5 m | 1.5 | 0.3 s |
表格里的数值是我在实验场地上反复试出来的,不是理论最优值,但作为起点非常合适。核心规律是:平台移动越快,越要提前进入追踪,控制增益要更大,同时视觉丢失容忍时间越短,因为平台跑太快,一旦丢目标再找回的概率很低,还不如直接中止降落。
动态降落中还要处理平台旋转的情况。二维码在图像中会旋转,ar_track_alvar输出的yaw角也可以用来调整降落方向。对于通常的四旋翼无人机,不需要精确控制偏航角对齐二维码方向,只要水平位置对准后,起落架形状一般不会和平台方向冲突。但如果你用的是X型起落架且平台有方向要求,就需要把二维码的yaw偏差量传入偏航控制通道,过程类似水平位置闭环,只是通道不同。
5.3 多机协同与二维码切换策略
做集群项目时,同一区域内可能有多个二维码。ar_track_alvar默认会发布所有可见二维码的TF,但降落控制必须选择“目标二维码”。我的做法是:在起降点布置ID固定的二维码(比如ID 0),在停机坪周围布置辅助定位二维码(比如ID 1、2),无人机在SEARCH阶段扫描所有可见标签,优先选择ID 0。如果ID 0不可见,则选择距离机头方向最近的一个辅助标签,先飞过去,到达后再寻找ID 0。
TF树中多个标签同时存在并不会冲突,因为每个标签有独立坐标系名称。真正要注意的是同一个标签在不同相机画面中重复检测时的时间戳处理。在多机场景下,每架无人机只处理自己机载相机发布的位姿,不要跨机订阅别人的话题,否则必定出现控制紊乱。
5.4 仿真验证与低成本测试方案
在真机大动作之前,我强烈建议先用Gazebo仿真把整个链路跑通。Gazebo加PX4加MAVROS的仿真环境搭建起来比较繁琐,但收益巨大。可以这样启动仿真:
git clone https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot make px4_sitl gazebo仿真里装一个虚拟相机插件,在降落平台模型上贴二维码纹理,飞行控制节点不需要改一行代码,就能验证位姿解算、控制逻辑、状态机切换。我很多参数的初值就是在仿真里先调出来的,真机上只做微调。
如果不想上仿真,还有一个低成本的裸机测试方案:用一个小型四旋翼固定在空中,底下放二维码,手动平移二维码模拟平台运动,看机载电脑是否稳定解算位姿。这个方法不需要完整飞行控制,可以单独验证感知链路,排查识别问题非常高效。
- 常见问题与避坑指南
6.1 识别不稳定:光照、反光与模糊
二维码识别最常见的问题是反光。打印的二维码贴在光滑表面或者用相纸打印,反光会导致图像中二维码区域亮度溢出,黑白色块对比度下降,误检率飙升。解决办法:用哑光纸或覆膜哑光打印;实验场地灯光别太强;适当增加相机曝光时间。另外,户外强光下,建议在相机镜头前加偏振镜,效果立竿见影。
运动模糊是动态场景下另一大敌人。二维码在图像中快速移动时,如果相机曝光时间太长,图像会拖影。我通过v4l2-ctl把相机曝光时间调到2ms以下,快门速度提升后,运动模糊明显改善。代价是画面整体变暗,需要配合提高ISP增益,或者选用感光度更好的工业相机。
6.2 位姿跳变与平滑滤波
即使识别不丢,位姿偶尔也会出现几十厘米的跳变。直接把这个跳变喂给控制环,飞机会猛地抖一下。解决办法是在控制节点里加入一个低通滤波或者中值滤波。我的经验是滑动中值滤波效果最好,取最近5帧位姿的中值,能有效剔除离群点,同时保持信号延迟很小。
另一种情况是跳变来自摄像头自动曝光或自动白平衡。推荐把相机设置成手动模式,固定曝光、固定增益、固定白平衡,否则图像亮度一变化,二维码角点提取位置就会偏移,位姿也跟着飘。
6.3 控制发散:方向错误与增益过大
无人机疯狂加速冲出二维码范围,十有八九是控制符号反了。排查方法是:先把无人机安全固定在测试台架上,手动移动二维码往右,看飞控发出的速度指令是往右还是往左。如果方向错误,对marker_pos[0]取负号即可。这个低级错误,我在没有充分测试的情况下上真机遇到过,几秒内无人机侧翻,差点伤到人。所有闭环控制代码,务必先在测试台架上验证方向再装桨。
增益过大导致的发散也常见。表现是无人机在二维码上方来回振荡,而且振幅越来越大。立即减小kp_xy和kp_z,别想着加D项去拯救。视觉控制环天生延迟高,D项很容易把噪声放大,让情况更糟。
6.4 通信中断与紧急停止机制
无人机实际飞行中,通信链路易受干扰,定位数据还会偶发丢失。我的紧急停止逻辑分三层:第一层是视觉丢失,立即悬停等待识别恢复;第二层是MAVROS失联,飞控根据PX4自身的遥控信号失效策略自动执行返航或降落(这层需要提前配置PX4参数,比如NAV_RCL_ACT设为1,失联返航,或设为0,保持末位置悬停);第三层是飞手手动接管,拨动遥控器模式开关从offboard切回position或stabilize,这个优先级最高,永远有效。
安全方面再啰嗦一句:任何视觉降落实验,测试场地周边必须留有足够缓冲空间,人员远离飞行路径。必备急停按钮,遥控器上把急停通道设好,紧急情况下直接切到急停。
- 实战闭环与经验总结
这个项目从V1做到V2,前后折腾了几个月,炸过机、丢过目标、也遇到过怎么调参数都稳不住的无助时刻。回看最值得沉淀的经验有三条。
第一条,系统分层设计是解决问题的基础。感知、解算、控制三层完全解耦,每一层单独验证、单独优化,出问题定位很快。如果一开始图省事把代码揉在一起,后续任何改动都可能引入不可预知的连锁问题。
第二条,参数整定必须从仿真到实机分步做,不要一上来就全真机测试。Gazebo仿真至少能帮你验证逻辑正确性,实机调参时再做小幅度增量调整。我见过太多项目卡死在“真机上疯狂试PID”这一步,最后连问题出在感知还是控制都说不清。
第三条,动态场景下不要追求识别率100%。识别偶尔丢几帧不可怕,可怕的是丢失后控制逻辑不知道该怎么办。把精力花在“丢失后的平滑回退”上,远比死磕识别算法收益大。我的最终方案里,识别帧率90%左右,配合0.5秒容忍期和平滑滤波,实际降落成功率反而比追求99%识别率的方案更高。
二维码视觉精准降落这个方向,做透了之后可以自然延伸到很多场景。比如移动平台对接、无人机自动充电、物流站点精确投放、无人机编队着舰模拟。核心的视觉位姿解算和视觉伺服控制能力是通用的,换一种标志物、换一种飞行器,底层逻辑几乎不用变。
我现在的做法是把这套系统直接当模板用,新项目需要定位降落时,复制一份代码,改改二维码ID、调调控制参数就能快速跑起来。希望这篇实战记录也能帮你省掉几个月的弯路。如果你也在搞类似的东西,欢迎一起交流踩坑经验。