无人机区域覆盖规划实战:最优路径与自定义策略
摘要
在精准农业、测绘、搜救等场景中,无人机需要高效地扫描整个目标区域。覆盖规划(Coverage Planning)旨在寻找一条最优路径,使无人机在最短时间内、以最小能耗完成对给定区域的全面覆盖。本文基于 MATLAB UAV Toolbox 的uavCoveragePlanner和uavCoverageSpace对象,详细讲解如何对复杂地理区域进行覆盖路径规划。我们将从定义感兴趣区域(ROI)开始,将其分解为凸子区域,自动生成扫掠路径,并演示如何自定义扫掠方向以优化连接距离,最终导出路径用于实际飞行。
1. 背景与问题描述
覆盖规划的核心是:给定一个二维地理区域,无人机需沿平行航线(类似“割草机”路径)扫过整个区域。典型的覆盖路径包含直线扫掠和转弯动作。频繁的转弯会增加飞行时间和能耗,因此优化扫掠方向以最小化转弯次数是提升效率的关键。
对于简单凸区域,最优扫掠方向通常垂直于区域的长轴。但实际中的凹多边形区域(如农田、不规则地块)若采用统一方向,会在狭窄凹陷处产生大量无效转弯,甚至使无人机飞越区域边界。一种常见的次优做法是使用凸包(Convex Hull),但这会导致无人机在非目标区域飞行,增加碰撞风险或进入禁飞区。
更优的策略是将凹区域分解为多个凸子区域,对每个子区域独立选择最佳扫掠方向,再通过连接路径串联整个任务。MATLAB 的coverageDecomposition函数和uavCoveragePlanner对象正是为此设计。
2. 定义感兴趣区域(ROI)
首先,我们创建地理坐标轴并加载底图(卫星图),设定经纬度范围,并计算区域的几何中心(用于后续坐标转换)。参考高度设为 400 米(无人机飞行高度)。
gax=geoaxes(Basemap="satellite");latlimits=[44.313544.3534];lonlimits=[-72.0227-71.9544];geolimits(latlimits,lonlimits);geocenter=[mean(latlimits),mean(lonlimits),0];% 参考点(经纬高)refHeight=400;% 覆盖飞行高度hold on;2.1 交互式或预定义方式选取区域
您可以通过两种方式定义区域多边形:
- 预定义:加载
predefinedROI.mat文件,其中包含多边形的顶点、起飞点和降落点。 - 交互式:在地图上点击选取起飞点、多边形顶点和降落点。
本例默认使用预定义方式,若您想手动选取,可将interactiveROI设为true。
interactiveROI=false;% 设为 true 则进入交互模式load predefinedROI.mat;% 加载预定义数据(包含 takeoffLat, takeoffLon, landLat, landLon, llapoints)helperPlotTakeoffROILanding(gax,takeoffLat,takeoffLon,landLat,landLon,llapoints);llapoints是一个 N×2 的矩阵,存储多边形顶点的经纬度坐标。辅助函数helperPlotTakeoffROILanding会在图上标记起飞点(▲)、降落点(●)和多边形轮廓。
若使用交互模式,则调用以下函数依次选取:
ifinteractiveROI[visLimitsLat,visLimitsLon]=geolimits;[takeoffLat,takeoffLon]=helperTakeoffSelectionFcn(gax,visLimitsLat,visLimitsLon);[llapoints,xyzpoints]=helperPolygonSelectionFcn(gax,visLimitsLat,visLimitsLon,geocenter);[landLat,landLon]=helperLandSelectionFcn(gax,visLimitsLat,visLimitsLon);end提示:交互式选择时,多边形需按顺序点击顶点,按 Enter 结束。得到的数据会保存在变量中。
3. 将感兴趣区域分解为凸子区域
复杂凹多边形不能直接应用扫掠规划,必须先分解。MATLAB 提供coverageDecomposition函数,基于局部 East-North 坐标系将多边形拆分为若干凸多边形。
首先将经纬度顶点转换为局部 ENU 坐标(以geocenter为参考原点),然后调用分解函数:
% 将 llapoints 转换为 ENU 坐标(忽略高度,仅用 XY)xyzpoints=lla2enu([llapoints,refHeight*ones(size(llapoints,1),1)],geocenter,"flat");subAreasEN=coverageDecomposition(xyzpoints(:,1:2));% 返回元胞数组,每个元素是凸多边形顶点(EN坐标)接着将各子区域的坐标转回经纬度(LLA),并存储于subAreasLLA中:
subAreasLLA=cell(1,numel(subAreasEN));fori=1:numel(subAreasEN)altitude=refHeight*ones(size(subAreasEN{i},1),1);localENU=[subAreasEN{i}(:,1),subAreasEN{i}(:,2),altitude];subArea=enu2lla(localENU,geocenter,"flat");subAreasLLA{i}=subArea(:,1:2);end此时,原始凹区域被划分成多个凸子区域(如图中不同颜色所示),每个子区域可单独规划扫掠路径。
4. 创建覆盖空间与路径规划
4.1 定义覆盖空间(uavCoverageSpace)
覆盖空间对象封装了待覆盖的多边形、传感器有效宽度、重叠率等参数。关键参数:
- Polygons:子区域的多边形顶点(经纬度)。
- UnitWidth:相邻平行扫掠线之间的间距(本例设为 100 米),与传感器覆盖宽度相关。
- Sidelap:相邻扫描带之间的重叠率(0 表示无重叠)。
- ReferenceHeight:飞行高度(400 米),用于确保坐标一致性。
- UseLocalCoordinates:设为
false表示使用经纬度坐标。 - ReferenceLocation:参考点(地理中心),用于内部坐标转换。
cs=uavCoverageSpace(Polygons=subAreasLLA,...UnitWidth=100,...Sidelap=0,...ReferenceHeight=refHeight,...UseLocalCoordinates=false,...ReferenceLocation=geocenter);调用show方法将覆盖空间可视化在地理坐标轴上(各子区域以不同颜色区分):
ax=cs.show(Parent=gax,LineWidth=1.25);4.2 创建覆盖规划器并生成路径
uavCoveragePlanner负责计算扫掠路径。我们只需指定起飞点和降落点(经纬度 + 高度,本例高度设为 0,但实际会被覆盖空间的高度覆盖):
cp=uavCoveragePlanner(cs);[wpts,solnInfo]=plan(cp,[takeoffLat,takeoffLon,0],[landLat,landLon,0]);返回的wpts是路径航点矩阵(每行 [lat, lon, alt]),solnInfo包含路径的详细信息,如各子区域顺序、转弯点、总连接距离等。
将路径绘制在地图上:
path1=geoplot(gax,wpts(:,1),wpts(:,2),LineWidth=1.5);hold off;查看总连接距离(包括从起飞点到第一个子区域、子区域间、以及最后一个子区域到降落点的距离):
connectionDistancePath1=solnInfo.TransitionCost% 输出:6.2648e+03(约 6265 米)注意:默认情况下,规划器会尝试最小化转弯次数,但子区域间的连接顺序是启发式或简单贪心得到的,可能并非全局最优。接下来的自定义将优化连接距离。
5. 自定义扫掠策略以优化路径
5.1 调整特定子区域的扫掠角度
根据应用需求,您可能希望扫掠方向与某种特征(如作物行、地形走向)对齐。我们可以使用setCoveragePattern方法为特定子区域指定扫掠角度(相对于正东方向,逆时针为正)。
例如,将第 2 和第 3 个子区域的扫掠角度设为 90 度(即南北方向):
cs.setCoveragePattern(2,SweepAngle=90);cs.setCoveragePattern(3,SweepAngle=90);角度改变后,规划器会重新计算该子区域内部的平行航线。
5.2 改变起始子区域以减少连接距离
通过观察,子区域 3 更靠近起飞点,且飞行结束后离降落点也较近。我们可以强制规划器从第 3 个子区域开始扫描,以缩短起飞到第一块区域的空飞距离。
在规划器的求解参数中设置StartingArea:
cp.SolverParameters.StartingArea=3;5.3 重新规划并比较优化效果
调用plan重新生成路径:
[wpts,custSoln]=plan(cp,[takeoffLat,takeoffLon,0],[landLat,landLon,0]);删除旧路径,绘制新路径:
delete(path1);hold on;h2=geoplot(gax,wpts(:,1),wpts(:,2),LineWidth=1.5);hold off;检查新的连接距离:
connectionDistancePath2=custSoln.TransitionCost% 输出:3.7921e+03(约 3792 米)相比默认路径的 6265 米,自定义后连接距离减少了近 40%,大幅提升了效率。
5.4 穷举求解器(可选)
若希望获得子区域间连接顺序的全局最优解,可将求解器设置为"Exhaustive"(穷举),它会尝试所有可能顺序,但计算时间随子区域数量指数增长。适用于子区域数量较少(≤8)的情况。
cp.Solver="Exhaustive";6. 导出航点用于地面站软件
规划完成后,可将航点导出为标准格式(如.waypoints文件),以便导入 QGroundControl 或 Mission Planner 等地面控制软件,直接用于实际飞行。
exportWaypointsPlan(cp,custSoln,"customCoverage.waypoints");该文件包含了每个航点的经纬度、高度以及期望的速度或停留时间等信息。
7. 仿真验证(可选)
您还可以将生成的航点导入uavMission对象,结合uavScenario进行动力学仿真,验证飞行轨迹的可行性,评估能耗、时间等指标。具体可参考文档Execute Coverage Plan Using UAV Mission。
8. 总结与扩展
本教程完整演示了以下流程:
- 定义地理区域(交互或预定义);
- 将凹区域分解为凸子区域;
- 创建覆盖空间并自动规划初始扫掠路径;
- 自定义子区域扫掠角度和起始顺序,优化连接距离;
- 导出航点用于实际任务。
通过合理设置扫掠方向和子区域顺序,您可以显著提高无人机任务效率,降低能耗。MATLAB 提供的工具链覆盖了从规划到仿真的完整环节,非常适合算法快速验证与部署。
下一步可以做什么?
- 对多个不相连的区域进行覆盖规划;
- 结合障碍物和禁飞区约束;
- 使用真实传感器模型生成覆盖过程中的感知数据。
参考资料
- MATLAB UAV Toolbox - Coverage Planning
- uavCoveragePlanner 文档
- uavCoverageSpace 文档
完整代码(整合版)
为了方便您直接运行,以下提供本示例的完整代码(默认使用预定义数据):
%% 1. 设置地图与区域gax=geoaxes(Basemap="satellite");latlimits=[44.313544.3534];lonlimits=[-72.0227-71.9544];geolimits(latlimits,lonlimits);geocenter=[mean(latlimits),mean(lonlimits),0];refHeight=400;hold on;%% 2. 加载预定义区域interactiveROI=false;load predefinedROI.mat;helperPlotTakeoffROILanding(gax,takeoffLat,takeoffLon,landLat,landLon,llapoints);% 若交互,取消注释下面代码% if interactiveROI% [visLimitsLat, visLimitsLon] = geolimits;% [takeoffLat, takeoffLon] = helperTakeoffSelectionFcn(gax, visLimitsLat, visLimitsLon);% [llapoints, xyzpoints] = helperPolygonSelectionFcn(gax, visLimitsLat, visLimitsLon, geocenter);% [landLat, landLon] = helperLandSelectionFcn(gax, visLimitsLat, visLimitsLon);% end%% 3. 分解为凸子区域xyzpoints=lla2enu([llapoints,refHeight*ones(size(llapoints,1),1)],geocenter,"flat");subAreasEN=coverageDecomposition(xyzpoints(:,1:2));subAreasLLA=cell(1,numel(subAreasEN));fori=1:numel(subAreasEN)altitude=refHeight*ones(size(subAreasEN{i},1),1);localENU=[subAreasEN{i}(:,1),subAreasEN{i}(:,2),altitude];subArea=enu2lla(localENU,geocenter,"flat");subAreasLLA{i}=subArea(:,1:2);end%% 4. 创建覆盖空间与规划器cs=uavCoverageSpace(Polygons=subAreasLLA,UnitWidth=100,Sidelap=0,...ReferenceHeight=refHeight,UseLocalCoordinates=false,...ReferenceLocation=geocenter);cs.show(Parent=gax,LineWidth=1.25);cp=uavCoveragePlanner(cs);[wpts,solnInfo]=plan(cp,[takeoffLat,takeoffLon,0],[landLat,landLon,0]);path1=geoplot(gax,wpts(:,1),wpts(:,2),LineWidth=1.5);connection1=solnInfo.TransitionCost;fprintf("默认路径连接距离:%.2f 米\n", connection1);%% 5. 自定义扫掠方向和起始区域cs.setCoveragePattern(2,SweepAngle=90);cs.setCoveragePattern(3,SweepAngle=90);cp.SolverParameters.StartingArea=3;[wpts2,custSoln]=plan(cp,[takeoffLat,takeoffLon,0],[landLat,landLon,0]);delete(path1);hold on;geoplot(gax,wpts2(:,1),wpts2(:,2),LineWidth=1.5);hold off;connection2=custSoln.TransitionCost;fprintf("优化后路径连接距离:%.2f 米\n", connection2);%% 6. 导出航点exportWaypointsPlan(cp,custSoln,"customCoverage.waypoints");运行以上脚本,您将看到地图上显示两个路径对比,并在命令窗口输出优化前后的连接距离。
希望这篇博客能帮助您掌握无人机覆盖规划的完整流程。如有疑问,欢迎在评论区交流讨论!