☰
ROS无人机二维码精准降落:V2版边降边调算法与PX4实战解析
2026/10/19 3:27:08 网站建设 项目流程

简介:无人机自动降落是物流配送与巡检作业中的关键环节,如何在高动态环境下实现厘米级落点精度,长期依赖悬停对正再下降的经典流程,但扰动会使对准失效。视觉伺服控制通过机载相机实时检测地面标记,将像素位姿解算为机体偏差,并在下降过程中动态修正水平位置,从而保证目标始终处于视野中心。基于ROS与PX4生态,结合ArUco标记检测、相机位姿估计、PID速度闭环等成熟技术,开发者可以搭建一套完整的视觉引导降落系统。该方案在室内外多场景中具备良好的可移植性,适用于行业无人机自动起降、精准物流投放等工程实践。本文从系统架构、控制策略与关键代码层面对比V1与V2实现的演进,重点解析边下降边修正的耦合逻辑、坐标系变换及参数整定方法,帮助开发者快速复现并优化精准降落能力。

1. ROS无人机二维码降落:V2版把“先对正再降落”改成了“边落边调”

做物流配送或巡检无人机的人大概都有这种体验:飞机在天上飞得挺好,最后那一米却比前面一千米都难——侧风一吹,本该落到停机坪圆心的飞机,落地时偏出去半米。传统做法是先悬停、对正、再下降,问题是下降过程中只要有一个扰动,之前的对正就白做了。这套V2版ROS无人机二维码精准降落系统,核心改动就是把“对正”从降落前挪到了降落中:下视相机持续检测地面二维码,实时解算飞机相对着陆点的水平偏差,在垂直下降的同时不断修正横向位置,真正做到目标始终在视野里、偏差始终在收敛中。适合正在做PX4视觉降落、或者想给无人机加自动起降能力的开发者和学生直接照着跑通。

2. 系统架构拆解:从摄像头图像到位置修正的完整链路

2.1 节点拓扑与数据流:四个节点各司其职

先把这套系统当成一条流水线来看。V2版降落项目基于ROS搭建,整体由四个角色构成:相机驱动节点、二维码检测节点、降落控制节点、MAVROS通信节点。相机节点负责输出下视图像,检测节点消费图像、输出二维码相对机体的位姿,控制节点把位姿换算成速度修正量,最后通过MAVROS把期望速度发给PX4飞控执行。

话题和数据流是这样的:相机节点发布/camera/down/image_raw(sensor_msgs/Image);二维码检测节点订阅图像,发布/aruco/pose(geometry_msgs/PoseStamped);控制节点同时订阅/aruco/pose和/mavros/state(飞控当前状态),计算后发布/mavros/setpoint_velocity/cmd_vel(geometry_msgs/TwistStamped);MAVROS负责跟飞控做双向消息转换。整条链路是单向的,没有反馈回路绕回检测节点,逻辑非常干净。

这里有个设计细节值得注意:控制节点不是每收到一张图像就发一次速度指令。图像频率可能是30fps,而offboard模式下的速度指令建议以10~20Hz稳定发送,频率过高反而会让飞控的速度环响应变得激进。常见做法是控制节点自己做节流,用一个定时器以固定频率取最新的/aruco/pose消息,计算一次并发一次指令。这样即使图像偶发丢帧,速度指令流也不会中断,PX4侧不会因为指令超时而退出offboard模式。

2.2 检测方案选型:为什么是OpenCV ArUco

二维码检测有QR码、AprilTag、ArUco好几个流派,这套V2版选ArUco是有明确理由的。QR码信息密度高,适合携带ID或指令,但从图像中精确估计位姿需要额外写PnP逻辑,不够直接;AprilTag在小尺寸下鲁棒性更好,但OpenCV主库没有现成模块,要额外引入第三方库;ArUco是OpenCV contrib仓库里开箱即用的标记检测方案,cv2.aruco模块直接提供detectMarkers和estimatePoseSingleMarkers,一套组合拳就把“检测+位姿解算”全做完。

ArUco的检测原理是:先把图像转灰度,做自适应阈值分割提取候选四边形,再对四边形内部做码字解码,最后通过角点匹配确定标记ID。由于用的是二进制编码,它对遮挡和光照变化比QR码更耐造——只要标记的四角中至少三个可见,就能完成解码和位姿估计。在降落场景里,这意味着无人机高度较高、二维码在画面中占比很小时,依然能稳定检测到。

V2版用的是DICT_ARUCO_ORIGINAL字典,标记边长按0.2米打印。选择0.2米不是拍脑袋:这个尺寸在0.5米高度时占据画面约40%的比例,在3米高度时仍能可靠检出。如果码印得太小,3米以上就解不出位姿;印得太大,降落后期二维码会飞出画面。V2版用单码方案的思路是“让码覆盖检测范围的两端”,优先保证降落末端的近距离精度,远距离只要求“能认出码、能粗对齐”即可。

2.3 坐标系变换与位姿解算:从像素到机体误差

二维码检测节点返回的tvec是相机坐标系下的平移向量,但降落控制需要的是机体坐标系下的偏差,中间差着一个坐标系变换。ROS约定相机坐标系是:x向右、y向下、z向前,也就是光轴方向是z正方向。下视相机一般朝下安装,机头方向与相机x轴的夹角可能是0度、90度或者180度,这直接决定tvec怎么映射到机体的前向和右向。

假设相机水平朝下安装且机头与相机x轴对齐,那么相机系到机体系的变换就是固定角度旋转。tvec的z分量近似等于相机到二维码的高度,但注意这个高度和气压计/激光雷达测到的高度含义不同——tvec的z是斜距在光轴方向的投影,只有相机严格垂直朝下时才算真正的高度。所以V2版的做法是把tvec的z当作“视觉高度”用于降落决策,同时用飞控的气压计高度做逻辑门的兜底校验,两个高度差太多时说明检测异常,直接切换为悬停等待。

像素坐标到位姿用的是PnP算法。OpenCV的estimatePoseSingleMarkers内部就是solvePnP:输入是二维码四个角点的像素坐标、二维码的实际边长、相机内参和畸变系数,输出是rvec和tvec。这里最容易被忽略的参数是相机内参矩阵,如果直接把默认值填进去,tvec在近距离可能偏差几厘米,在远距离偏差会放大到几十厘米。V2版的工程里把相机内参做成独立参数文件,仿真用Gazebo的相机参数,真机用标定板跑一遍camera_calibration再填进去。

2.4 V2版控制策略:位置误差如何换算成速度指令

降落控制本质上是串级控制:外环是我们写的视觉位置环,内环是PX4自带的速度控制器。我们不给位置指令,而是直接给期望速度,好处是省去了位置环的微分运算,飞控自己处理速度闭环的平滑性。V2版的核心逻辑可以概括成三句话:水平偏差大时,垂向速度压小,给横向修正留时间;水平偏差小且高度低时,加速下压触地;目标丢失时,悬停在原地小幅搜索,不乱飞。

下降速度与水平偏差耦合是V2版最关键的改动。V1版的做法是预先设定一个固定下降速度比如0.3m/s,然后横向PID独立修正,结果是在强风下飞机一边下降一边横移,到触地瞬间水平偏差还没收敛完。V2版改成了“偏差越大、下降越慢”的分段策略:偏差大于0.3米时垂速降到0.1m/s,相当于悬停修正;偏差小于0.05米且高度低于0.3米时垂速提到0.8m/s快速触地。中间区间用线性规则过渡。这样既保证了落点精度,又不会让降落过程拖太久。

控制频率统一为10Hz,PID输出限幅在±0.5m/s。比例系数Kp取1.0,积分Ki取0.05,微分Kd取0.1。这个参数组合在仿真里表现稳定,真机因为视觉数据有延迟,微分项容易放大噪声,建议真机先把Kd降到0.02再飞。需要特别提醒的是,PID的输出单位是速度,而输入是米,所以Kp的量纲是1/s,相当于每秒修正当前偏差的100%,理解这个量纲后调参就心里有数了。

3. 核心代码走读:检测、解算、控制三段关键实现

3.1 二维码检测节点:图像订阅与位姿发布

检测节点是整条链路的眼睛。V2版用Python写,逻辑集中在ArucoDetector类里。完整代码如下:

#!/usr/bin/env python3 import rospy import cv2 import numpy as np from cv_bridge import CvBridge from sensor_msgs.msg import Image from geometry_msgs.msg import PoseStamped class ArucoDetector: def __init__(self): rospy.init_node('aruco_detector') self.bridge = CvBridge() self.marker_length = rospy.get_param('~marker_size', 0.20) dict_name = rospy.get_param('~dictionary', 'DICT_ARUCO_ORIGINAL') self.aruco_dict = cv2.aruco.Dictionary_get( getattr(cv2.aruco, dict_name)) self.params = cv2.aruco.DetectorParameters_create() # 相机内参:fx fy cx cy 由标定得到,仿真里取Gazebo参数 fx, fy, cx, cy = 420.0, 420.0, 320.0, 240.0 self.camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=float) self.dist_coeffs = np.zeros((4, 1)) self.pose_pub = rospy.Publisher('/aruco/pose', PoseStamped, queue_size=1) rospy.Subscriber('/camera/down/image_raw', Image, self.image_cb, queue_size=1, buff_size=2**24) def image_cb(self, msg): img = self.bridge.imgmsg_to_cv2(msg, 'bgr8') corners, ids, _ = cv2.aruco.detectMarkers( img, self.aruco_dict, parameters=self.params) if ids is None: return rvec, tvec, _ = cv2.aruco.estimatePoseSingleMarkers( corners, self.marker_length, self.camera_matrix, self.dist_coeffs) # 优先处理我们关心的那个ID,防止误触发 target_id = 1 idx = np.where(ids.flatten() == target_id)[0] if len(idx) == 0: return i = idx[0] t = tvec[i][0] # [x, y, z],相机系 pose_msg = PoseStamped() pose_msg.header.stamp = msg.header.stamp pose_msg.header.frame_id = 'camera_down' pose_msg.pose.position.x = t[0] pose_msg.pose.position.y = t[1] pose_msg.pose.position.z = t[2] self.pose_pub.publish(pose_msg)

代码逻辑说明:detectMarkers返回的corners是每个二维码的四个角点像素坐标,ids是识别到的ID数组;找到目标ID后,用estimatePoseSingleMarkers解算位姿,取tvec[0][0]得到相机系下的[x, y, z]。queue_size=1是为了只保留最新一帧图像,避免处理延迟累积;buff_size=2**24是ROS订阅大图时的常见做法,防止高分辨率图像因为缓冲区太小而丢消息。发布的话题时间戳直接复用了图像时间戳,这样控制器可以通过时间戳判断视觉数据有没有过期。

参数说明:marker_size是打印出来的二维码实际边长,单位是米,这个值和相机内参一样是硬参数,填错直接导致高度和水平偏差整体偏差一个比例系数。fx、fy、cx、cy在仿真里可以直接用Gazebo的相机模型参数,真机必须用标定值,否则近距离还能用,超过1米误差就开始指数增长。dist_coeffs先用零向量占位,仿真没问题,真机如果镜头畸变明显,需要从标定结果里填入实际的径向和切向畸变系数。

3.2 降落控制器:边下降边修正的动态调整逻辑

控制器节点是V2版的核心,它负责把视觉位姿变成速度指令。关键代码如下:

#!/usr/bin/env python3 import rospy from geometry_msgs.msg import TwistStamped from geometry_msgs.msg import PoseStamped from geometry_msgs.msg import TwistStamped class LandingController: def __init__(self): rospy.init_node('landing_controller') self.pid_x = PID(kp=1.0, ki=0.05, kd=0.1, limit=0.5) self.pid_y = PID(kp=1.0, ki=0.05, kd=0.1, limit=0.5) self.vx = 0.0 self.vy = 0.0 self.vz = -0.3 self.last_pose_time = rospy.Time.now() self.vel_pub = rospy.Publisher( '/mavros/setpoint_velocity/cmd_vel', TwistStamped, queue_size=1) rospy.Subscriber('/aruco/pose', PoseStamped, self.pose_cb) self.timer = rospy.Timer(rospy.Duration(0.1), self.control_loop) # 10Hz def pose_cb(self, msg): self.last_pose_time = msg.header.stamp # 视觉高度近似可用;异常时抛弃,避免跳变 self.height = msg.pose.position.z # 相机系到机体系的简化映射:x->前向,y->右向 self.dx = msg.pose.position.x self.dy = msg.pose.position.y def control_loop(self, event): now = rospy.Time.now() # 视觉数据超过0.3秒没更新,认为目标丢失 if (now - self.last_pose_time).to_sec() > 0.3: self.vx, self.vy, self.vz = 0.0, 0.0, -0.1 self.publish() return h = self.height dx, dy = self.dx, self.dy err = max(abs(dx), abs(dy)) # 下降速度与水平偏差耦合,这是V2的核心 if h > 2.0: self.vz = -0.5 # 远距离快速下降 elif h < 0.25 and err < 0.05: self.vz = -0.8 # 对正后加速触地 elif err > 0.3: self.vz = -0.1 # 大偏差时降速修正 else: self.vz = -0.4 # 正常下降 self.vx = self.pid_x.update(dx) self.vy = self.pid_y.update(dy) self.publish() def publish(self): cmd = TwistStamped() cmd.header.stamp = rospy.Time.now() cmd.twist.linear.x = self.vx cmd.twist.linear.y = self.vy cmd.twist.linear.z = self.vz self.vel_pub.publish(cmd)

逻辑说明:控制循环用rospy.Timer固定在10Hz跑,每次从最新位姿计算输出。视觉数据超过0.3秒没更新,说明二维码丢失或检测中断,此时输出垂速-0.1缓慢下降并保持水平速度为零,相当于原地悬停等待目标重新出现。这个“丢失即悬停”的设计在V1版里是没有的,V1在丢失二维码后会把上一帧的偏差持续送入PID,导致飞机带着旧误差横移,非常危险。

参数说明:err > 0.3m时垂速压到0.1m/s,是为了让水平修正先收敛;h < 0.25m 且 err < 0.05m时认为已经对准,垂速提高到0.8m/s快速触地,减少地面效应影响时间。PID输出限幅0.5m/s是硬限制,防止视觉跳变时飞机猛地横甩。注意这里发送的是TwistStamped,也就是线速度指令,PX4的速度控制器会负责平滑过渡,不需要我们在外部再做加速度限制,这也是省心的地方。

3.3 V1到V2:状态机到底多了什么

把V1和V2的逻辑摆在一起对比,差别就更清楚了。V1的降落状态机只有四个状态:搜索二维码、悬停对正、垂直下降、触地停机。问题出在“悬停对正”和“垂直下降”是严格串行的,一旦进入下降状态,就不再消费视觉数据,下降过程中被风一吹就彻底偏掉。

V2把状态机改成:粗搜索下降、动态修正下降、对正触地、丢失悬停。动态修正下降状态下,每一个控制周期都会拉取最新视觉数据,依次判断“偏差大不大、高度低不低、数据新不新”,再决定垂速和横移速度。相看只是少了几个状态,但控制架构从开环下降变成了闭环修正,落点精度从原来的30厘米级别提升到5厘米以内,这就是“动态调整位置”的意义。

实现层面V2还做了一件事:把PID控制器单独抽成类,支持积分限幅和输出限幅,这样调参不用改状态机代码,只改参数。工程上这个抽象很有用,因为真机调试时你大概率要在Kp和Kd之间来回折腾,如果PID逻辑散落在状态机里,每次改参数都要碰逻辑代码,很容易改出新bug。把控制率和状态流转分开,是这类飞行控制项目的通用习惯。

4. 环境搭建与运行:仿真先行,真机不慌

4.1 依赖清单与安装命令

这套V2版项目在仿真里跑通只需要四样东西:Ubuntu 20.04、ROS Noetic、PX4固件(带Gazebo仿真)、MAVROS。安装顺序有讲究,先装ROS,再装MAVROS,最后编译PX4固件,避免依赖互相覆盖。

# 1. 安装ROS Noetic桌面完整版 sudo apt install ros-noetic-desktop-full # 2. 安装MAVROS sudo apt install ros-noetic-mavros ros-noetic-mavros-extras # 3. 初始化MAVROS地理信息数据(用于地球模型) sudo /opt/ros/noetic/lib/mavros/install_geographiclib_datasets.sh # 4. 编译PX4固件,这里需要拉取固件源码,建议用稳定分支 git clone https://github.com/PX4/PX4-Autopilot.git --recursive cd PX4-Autopilot make px4_sitl gazebo-classic

命令说明:ROS Noetic对应Ubuntu 20.04,如果机器是22.04需要换成ROS2 Humble,但本项目接口是按ROS1写的,迁移到ROS2要改话题类型和构建系统,工作量主要在控制器节点。MAVROS的install_geographiclib_datasets.sh是很多人会漏掉的步骤,不执行会出现MAVROS启动时报坐标系转换失败的怪问题。PX4固件只用于仿真,make px4_sitl gazebo-classic会启动一个带着无人机模型和世界场景的Gazebo仿真环境。

还需要单独安装OpenCV的ArUco模块。ROS Noetic的python环境里通过cv_bridge调用OpenCV,ArUco模块在python3-opencv包中自带,安装cv_bridge时通常会带上。如果编译cv_bridge时OpenCV版本低于4.2,cv2.aruco可能无法导入,直接升级OpenCV即可。

4.2 启动流程:四个终端跑通一次降落

整套系统启动需要四个终端,每个终端一个角色。顺序固定:先启动仿真和飞控,再启动MAVROS,最后启动视觉和控制节点。

# 终端1:启动PX4仿真环境 cd ~/PX4-Autopilot make px4_sitl gazebo-classic # 仿真起来后,飞控会自动进入SITL模式,等待MAVROS连接 # 终端2:启动MAVROS连接飞控 roslaunch mavros px4.launch fcu_url:="udp://:14540@127.0.0.1:14557" # 终端3:启动二维码检测节点 roslaunch aruco_landing aruco_detect.launch # 终端4:启动降落控制节点 roslaunch aruco_landing landing_controller.launch

启动说明:终端1里PX4启动后会打印飞控的Nuttx命令行,说明SITL已经运行,这时不要关这个终端,关闭它整个仿真就没了。终端2的MAVROS通过UDP与仿真飞控通信,看到 "FCU: Connected" 即为成功。终端4启动后,控制器会先输出垂速-0.5快速下降,直到视觉检测到二维码。降落过程中如果看到Rviz或图像窗口里二维码被绿色方框框住,说明检测节点工作正常。

落地验证有两个指标:一是落点与二维码中心的水平偏差是否在5厘米内,这个可以在仿真里通过检视无人机模型位置和二维码模型位置直接量出来;二是落地瞬间的水平速度是否小于0.1m/s,这个可以从飞控日志的vehicle_local_position_setpoint里看到。仿真跑通后,真机步骤是把相机话题换成真实的USB相机话题,并把相机内参替换成标定值,其余逻辑不用动。

4.3 参数配置:控制效果靠这组YAML调

V2版的launch文件把参数集中在YAML里,不用改代码就能调控制行为。这里给一份常用的参数配置模板:

# aruco_detect 节点参数 marker_size: 0.20 # 二维码边长,米,必须和实际打印一致 dictionary: DICT_ARUCO_ORIGINAL target_id: 1 # 降落目标的ArUco ID camera_topic: /camera/down/image_raw # landing_controller 节点参数 control_freq: 10.0 # 控制频率,Hz max_vxy: 0.5 # 水平速度限幅,m/s max_vz: 0.8 # 最大下降速度,m/s detect_range: 2.0 # 高于此高度不做修正,只管下降 align_threshold: 0.05 # 水平对正阈值,米 fast_land_height: 0.25 # 低于此高度且已对正则加速触地 slow_vz_err: 0.3 # 水平偏差大于此值则压降速 pid_kp: 1.0 pid_ki: 0.05 pid_kd: 0.1

参数说明:detect_range控制“多高才开始修正”,设为2.0意味着3米高度时只下降不修正,等到进入2米范围才启动PID,这样能避免远距离tvec噪声大导致的抖动。slow_vz_err是V2耦合逻辑的关键参数,调大意味着飞机更早减速等待修正,落点更准但降落更慢;调小则降落快但可能落偏。pid_kd在真机上要从0.1降下来,因为视觉数据有几十毫秒延迟,微分项容易把噪声放大成高频抖动。

真机调试时我习惯先调max_vz和slow_vz_err,把“性子”调稳了再动PID三件套。顺序大概是:先保证下降过程不打摆,再追求落点精度。如果发现飞机在修正时来回震荡,优先减小Kp或Kd,而不是调Ki;如果发现落点总是同一个方向偏几厘米,优先检查相机安装外参,PID是救不回来固定偏差的。

5. 避坑记录:二维码降落最容易翻车的5个问题

5.1 最后30厘米二维码飞出视野

现象:飞机在1米高度修正得很好,越往下降飞机开始左右找码,最后几十厘米干脆在二维码边缘反复横跳,落点偏移严重。

原因:下视相机的视场角是固定的,高度越低,地面可视范围越小。假设视场角是60度,0.3米高度时可视范围只有大约0.35米宽,而0.2米的二维码在画面里几乎顶满画框,稍有偏差就出画,检测直接丢失。

解决:两个方向任选。第一是换广角镜头,视场角到100度以上,0.2米高度还能看到40厘米见方的区域;第二是改双码方案,远处用大码引导,近处用套在小码上做精调。如果不想动硬件,把降落触发高度从0.25米抬到0.4米,让飞机在二维码出画前先完成触地判断,也能缓解。

5.2 落点总是固定偏一个方向

现象:每次降落落点和二维码中心偏差都在8到10厘米左右,而且方向基本一致,左右PID调参怎么调都压不掉这个静态偏差。

原因:这不是控制问题,是外参标定问题。相机安装角度和机体系之间有微小偏差,比如相机歪了2度,在1米高度就会带来大约3.5厘米的固定偏差;而二维码和机体头向对齐也有偏差,叠加起来就是稳定偏差。PID的积分项虽然能消除一部分,但相机本身歪了时,水平修正在视觉闭环里是“自以为对齐了”。

解决:在检测节点里加一个相机到机体的固定旋转补偿矩阵,把安装角偏差标定出来。常见做法是让飞机悬停在二维码正上方,读取tvec的平均值,反推安装角偏差,然后把补偿量写进launch参数。记住:视觉降落系统的静态精度上限由外参标定精度决定,PID只能解决动态误差。

5.3 仿真里完美,真机一起飞就乱窜

现象:Gazebo里降落次次精准,换到真机上,飞机在2米高度就开始横移抖动,甚至出现一次修正过冲半米的情况。

原因:仿真里的相机内参和真实相机不一致是最常见的原因。Gazebo的相机模型是理想针孔模型,畸变系数为零;真实相机的镜头畸变、焦距误差会直接污染tvec计算。另外仿真里传感器没有延迟,真机的视觉节点从拍照到发布位姿有50到100毫秒延迟,控制回路的相位滞后完全不一样。

解决:真机起飞前必须做两件事。第一,用棋盘格标定相机内参,把fx、fy、cx、cy和畸变系数填进参数文件;第二,把控制频率从10Hz降到6到8Hz,同时把PID的Kd降到接近零,用更保守的参数先验证基本逻辑。标定内参这件事在项目里我会强制走一遍流程,不然所有视觉闭环的调试都是在跟一个未知黑匣子搏斗。

5.4 offboard模式下飞机越飞越高

现象:降落过程中明明指令是下降速度,飞控的高度反而缓慢上升,或者下降速度明显小于指令值,拉都拉不下去。

原因:MAVROS的setpoint速度话题用的是NED坐标系,x轴指向北、y轴指向东、z轴指向下。如果直接把相机系的tvec塞进cmd_vel,或者把ENU坐标系下的速度当NED发过去,飞控收到的垂向速度和本文期望的符号可能相反,水平修正也可能沿着错误方向去追。

解决:在控制器输出前统一做一次ENU到NED的转换。具体规则是:NED的x等于ENU的y,NED的y等于ENU的x,NED的z等于ENU的z取反。放在代码里就是cmd.twist.linear.x = vy_body、cmd.twist.linear.y = vx_body、cmd.twist.linear.z = -vz_body。这个问题V1版项目里很隐蔽,仿真因为PX4默认配置可能不报错,真机一开就露馅。控制逻辑里所有坐标系变换都应该集中在一个函数里,做好注释,别散在状态机各处。

5.5 光照一变,二维码时有时无

现象:场景里灯光正常时检测稳定,模拟阴天或室内开灯后,二维码检测率掉到一半以下,降落过程频繁进入丢失悬停状态。

原因:ArUco检测依赖图像的二值化阈值,固定阈值在光照变化时会让码块的前景和背景混在一起,角点提取不稳定。室内暖黄光、反光地面、树叶阴影都会造成局部亮度突变。

解决:调整 DetectorParameters 里的自适应阈值参数。具体做法是设置adaptiveThreshWinSizeMin=3、adaptiveThreshWinSizeMax=23、adaptiveThreshWinSizeStep=10,让检测器在不同窗口尺寸下自适应计算局部阈值,不要用全局固定阈值。此外打印二维码时用哑光纸材而不要用反光的照相纸,在强光环境能明显减少角点提取失败。这项调参经验是仿真里学不到的,必须拿到实际光照环境里试。

6. 二次开发:从“落得准”到“落得稳”

V2版解决了“落得准”,但真机场景里更磨人的是“落得稳”。我拿到这套代码后改动最多的有三个地方:高度融合、航向对齐、触地判定。

先说高度融合。现在控制器直接用tvec的z当高度,视觉一丢就失去高度参考。我的改法是用一阶卡尔曼滤波融合两个来源:tvec的z和飞控的激光雷达高度,视觉有效时更新视觉高度,视觉无效时用气压计高度外推。这样即使短时间丢码,高度估计不会跳变,飞机不会因为高度突变猛拉油门。代码里只需要在控制器里加一个SimpleKF类,预测方程用匀速下降模型,观测方程分别接视觉高度和雷达高度,每周期更新一次。

再说航向对齐。很多停机坪有方向要求,需要机头对准某条线再落地。V2版只做了水平位置修正,没有修正偏航角。要加的话,可以把ArUco的rvec转成欧拉角,取yaw,再做一个独立的偏航PID,输出到cmd.twist.angular.z。注意偏航修正只在低空启用,高于1米时偏航角对降落位置没有影响,提前修反而浪费修正时间。

最后是触地判定。现在靠“高度小于0.25米且偏差小于0.05米”触地,但如果二维码下面垫了泡沫垫,视觉高度在触地瞬间还有偏差。我的做法是加一个电流或油门阈值判断:当期望垂速为负、实际垂直速度接近零且油门持续高于某个阈值时,直接切断offboard模式,切到自稳模式落地。这个逻辑在PX4里通过检测/mavros/state的armed状态和当前模式来做。

验证方法上,我会在Gazebo里加一个模块化的风场插件,分别做2m/s侧风和阵风测试,看落点偏移和修正时间的变化。真机测试前,把飞控日志的/mavros/local_position/pose和/aruco/pose拉出来做时间对齐,看视觉位置和真实位置的偏差趋势,确认没有固定偏差再做第二遍飞行。从那以后我每次改降落逻辑,都会先在仿真里加一阵3m/s的侧风再上真机,这个习惯帮我少翻了好几次车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询