简介:无人机目标检测源码包,面向计算机视觉领域的算法研究者与应用开发者,专注解决无人机图像尺寸大、检测目标小且分布密集的识别难题。该代码基于YOLOv8提出Drone-YOLO,通过改进颈部组件,引入三层PAFPN结构与夹层融合模块,并采用RepVGG模块作为下采样层,强化多尺度特征学习;在VisDrone2019数据集上,mAP0.5指标明显优于多种基线方法。压缩包共三个文件,以inscode工程配置、html说明页面和gitignore文件为主,压缩后仅7KB,包体虽小却适合快速获取与二次开发。借助该源码可复现Drone-YOLO的训练与推理过程,理解针对小目标的网络结构调整与参数设计思路,还可参考其在嵌入式设备上达到实时处理的工程化方法,适用于环境监测、交通监控、农业检测等场景。目前已有130人学习,对无人机视觉、小目标检测及边缘端部署感兴趣的读者可以直接参考使用。
1. Drone-YOLO:无人机视角下的目标检测,先过小目标这道坎
把 YOLO 跑出一个 demo 很容易,真正拿到一份能在无人机俯视场景下稳定工作的代码包,是另一回事。这个 Drone-YOLO 代码包解决的问题非常具体:高空俯视视角下目标普遍只有十几个像素,背景是密集的城市纹理,人和车互相遮挡,直接把通用检测模型拿来用,一上真机就翻车。它把数据集处理、模型训练、小目标增强和推理部署整条链路都串好了,附带完整源码。适合刚接触无人机视觉但已经会点 Python 的从业者,也适合在车流统计、低空巡检这类场景里被 mAP 卡住的老手拿去对照调参。
2. 数据集准备:把航拍原图做成能喂给 YOLO 的训练集,先算清小目标占比
2.1 为什么无人机检测的数据处理和通用目标检测不一样
通用目标检测数据集里,目标通常占画面的 5% 到 30%,人站在地面,相机和物体在同一水平线,遮挡和尺度变化都可控。到了无人机视角,情况完全不同:相机俯视,目标出现在画面中央偏下的区域;同一个目标从 50 米高度飞到 120 米高度,像素尺寸缩到原来的三分之一不到;密集场景下地面停车场的车辆几乎是挨在一起。这些差异直接决定了标注策略、数据划分和训练超参,不处理数据就去调模型,等于在沙滩上盖楼。
我拿到这个代码包时先看了一遍它的数据组织方式,它在源码里给出了一个清晰的目录约定。我一般拿到任何检测项目都会先确认这个,数据组织不乱,后面训练、验证、迭代才不折腾。目录通常按下面这种方式规划:
dataset/ ├── images/ │ ├── train/ # 训练原图,建议 1280x720 以上分辨率 │ ├── val/ # 验证原图,和 train 严格无重叠场景 │ └── test/ # 测试原图,最后评估才用 ├── labels/ │ ├── train/ # 与 images/train 一一对应的 txt 标注 │ ├── val/ │ └── test/ ├── classes.txt # 类别清单,每行一个类别名 └── drone.yaml # YOLO 训练所需的配置文件2.2 标注格式转换脚本:从 PASCAL VOC XML 到 YOLO txt
拿到手的数据集如果是 PASCAL VOC 格式,也就是每张图对应一个 XML 文件,而 YOLO 系列需要的是归一化后的 txt 文本标注,两者格式不兼容。代码包里给了一个转换脚本,逻辑是解析 XML 里的<object>节点,把xmin、ymin、xmax、ymax转成中心坐标加宽高的归一化数值。我贴一下核心逻辑:
import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_path, out_path, class_names): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_names: # 过滤掉不需要的类别 continue cls_id = class_names.index(cls) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h x_center = min(max(x_center, 0.0), 1.0) # 边界保护,防止标注越界 y_center = min(max(y_center, 0.0), 1.0) w = min(w, 1.0) h = min(h, 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines))2.3 关键一步:先统计小目标占比,再决定训练策略
这是一个很多人跳过但极其重要的步骤。在无人机场景下,如果不去统计目标尺寸分布,后面训练出来的模型大概率对远距离小目标视而不见。代码包里附带了一个统计脚本,用来计算每个目标框的像素面积,并按尺寸区间分桶统计:
import os from collections import Counter def analyze_bbox_sizes(label_dir, img_size=(1280, 720)): size_bins = {'tiny': 0, 'small': 0, 'medium': 0, 'large': 0} for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue _, _, _, w_norm, h_norm = parts w_px = float(w_norm) * img_size[0] h_px = float(h_norm) * img_size[1] area = w_px * h_px if area < 32 * 32: size_bins['tiny'] += 1 elif area < 96 * 96: size_bins['small'] += 1 elif area < 224 * 224: size_bins['medium'] += 1 else: size_bins['large'] += 1 total = sum(size_bins.values()) return {k: v / total * 100 for k, v in size_bins.items()}, total跑完后如果 tiny 和 small 两档加起来超过 40%,这个数据分布就一定要做针对性设计,比如提高训练分辨率、使用切片推理,或者给模型加 P2 小目标检测头。脚本里img_size=(1280, 720)需要和你的实际输入分辨率保持一致,不然统计出来的像素面积是错的。这里踩过坑:用 640 分辨率去统计 4K 航拍图的标注,统计结果偏差很大,得出来的结论完全没有参考价值。
注意:统计脚本的 img_size 参数指的是标注时参照的原图尺寸,不是训练时的输入尺寸。两者混淆是常见错误。
3. 模型选型与训练:小目标层、anchor 和最关键的那几个超参
3.1 无人机场景下怎么选 YOLO 版本
明确了数据分布之后,选模型就顺理成章。通用场景下 YOLOv8、YOLOv9、YOLOv10 都有各自的优势,但无人机目标检测的特殊之处在于:推理设备往往在机载边缘端,算力有限;目标尺寸偏小,需要足够大的特征图分辨率来保留细节。选型时我通常会先跑一个快速对比:把 YOLOv8n、YOLOv8s、YOLOv5u 在同一份无人机数据集上各训 20 轮,看验证集 mAP50 和推理耗时两条曲线。实际经验是:n 级模型参数量小,在边缘设备上能达到实时,但小目标 mAP 通常比 s 级低 4 到 6 个点;s 级是无人机场景性价比最高的档位,再往上到 m 级,精度收益有限,帧率反而掉得厉害。
这个代码包默认以 YOLOv8 为基础,配置里也保留了切换其他版本模型的入口。我建议新手直接用默认配置跑通,不要一上来就换模型架构,先建立正确的训练-验证循环,再谈优化。
3.2 训练配置文件:drone.yaml 里每一项都要懂
YOLO 的训练配置分为数据配置和模型配置两层。数据配置就是前面提到的 drone.yaml,它把数据集路径和类别数量告诉训练程序。一个可用的配置长这样:
# drone.yaml —— 数据配置 path: ./dataset # 数据集根目录,相对路径或绝对路径都可以 train: images/train # 训练图片目录,相对于 path val: images/val # 验证图片目录 test: images/test # 测试图片目录,评估用 nc: 3 # 类别数量:人、车、船只 names: ['person', 'car', 'boat']3.3 训练命令与关键超参:imgsz、mosaic、epochs 怎么定
训练命令本身不复杂,复杂的是参数怎么设。无人机场景下,最常见的做法是把imgsz从默认 640 拉到 1280。原因是 640 下特征图只有 80x40,一辆在 100 米高度下只占 20 个像素的汽车,经过 32 倍下采样后直接在特征图上消失了。拉高输入分辨率是最简单有效的小目标增强手段,代价是显存占用成倍增加、训练速度变慢。下面是代码包里给出的训练命令:
python train.py \ --data drone.yaml \ --weights yolov8s.pt \ --epochs 150 \ --batch-size 16 \ --imgsz 1280 \ --device 0 \ --mosaic 0.5 \ --close-mosaic 10 \ --patience 30参数说明如下:
--imgsz 1280:训练输入分辨率,小目标场景的核心参数。显存不够就降到 960,不要直接回 640。--mosaic 0.5:马赛克增强强度设为 0.5,不全关也不全开。无人机小目标在 mosaic 拼接时容易被裁切掉,这个值默认是 1.0,实测对小目标场景不太友好。--close-mosaic 10:最后 10 个 epoch 关闭 mosaic,这个参数很容易被忽略,但非常关键。mosaic 生成的是拼接图,分布和真实场景有偏差,最后阶段关闭能让模型在真实分布上收敛,一般能带来 1 到 2 个点的 mAP 提升。--patience 30:验证集指标连续 30 个 epoch 不提升就早停,防止无效训练浪费算力。
训练过程里我还会关注另一件容易翻车的事:训练损失曲线下降正常但验证 mAP 一直不动,这类问题八成出在验证集和训练集存在数据泄漏,也就是同一条航线不同帧的图片被拆到了两个集合里。无人机航拍数据相邻帧几乎一模一样,如果不按场景或飞行架次切分,验证集指标会虚高,最终模型上真机就失效。
3.4 显存不足时用什么方案
显存不够是最常见的问题。16GB 显存跑imgsz 1280 + batch 16 + s 级模型基本是极限,如果只有 8GB,建议按优先级调整:先降 batch 到 8,再降 imgsz 到 960,再换 n 级模型。不要直接开梯度累积去硬扛,梯度累积在数据分布复杂时容易让 BatchNorm 统计量不稳定。我一般会优先保 imgsz,因为小目标场景里分辨率比 batch 大小影响大得多。
4. 避坑:无人机检测项目里五个能让你白跑一周的坑
4.1 数据泄漏:训练集和验证集是同一条航线切出来的
现象:训练时损失收敛得漂亮,验证 mAP 高达 0.85,但把模型放到一段新航拍视频里检测,结果惨不忍睹,漏检严重。
原因:无人机采集数据是按航线持续拍摄的,相邻帧内容高度相似。如果只是按比例随机切分图片,同一场景的近似帧会同时出现在训练集和验证集,验证指标严重虚高,模型实际没有泛化能力。
解决:切分数据时必须以「飞行架次」或「连续视频片段」为单位。这个代码包的数据划分工具里可以设置按视频段目录切分而非按单帧切分,训练集占 80% 的架次,验证集占 20% 的架次,保证两个集合在空间上没有重叠。
4.2 小目标被下采样吃掉:输出层根本看不见 20 像素的物体
现象:大目标检测得很好,但远处的小目标几乎全部漏检,无论怎么调阈值都没用。
原因:YOLO 默认只有 P3、P4、P5 三层输出,最大下采样倍数为 32。一个 20x20 像素的物体在 32 倍下采样后不到 1 个像素,特征已经彻底丢失。
解决:查看代码包里的模型结构,确认是否包含 P2 层输出,也就是下采样 4 倍的特征图。如果没有,可以在配置文件里通过scale参数调整,或采用切片推理——把大图切成多块重叠的小图分别检测。这个代码包对应的模型配置里已经默认开启了 P2 层,不要自己去关闭它来换取推理速度。推理速度的优化应该放在部署环节做,不在训练阶段做。
4.3 mosaic 增强对小目标反而是毒药
现象:开了默认 mosaic 增强后,小目标 mAP 不升反降,而且训练后期损失曲线反复抖动。
原因:mosaic 把 4 张图缩放后拼成一张,小目标经过缩放变得更小,甚至被拼接边界切断。模型在训练时看到大量残缺的目标框,学到的是不完整的特征表达。
解决:参考训练命令里的做法,把--mosaic降到 0.5 或 0.3,并且设置--close-mosaic 10。从收到的反馈看,这两处参数调整基本能让小目标 mAP 回升 2 到 3 个点,代价是训练收敛稍慢。这个小目标场景下「mosaic 翻车」的经历,已经成了我做无人机检测的血泪经验:通用数据增强参数不能直接套用,必须先看自己数据的分布特征。
4.4 类别不均衡:稀类目标永远学不会
现象:数据里车有 2 万框,船只有 200 框。训练完成后,车的 mAP 到 0.9,船的 mAP 只有 0.15。
原因:默认 loss 函数里各类别权重相同,模型被高频类别主导,低频类别在训练中被淹没。无人机俯视场景很容易出现这种极端分布,尤其在水域面积小的区域,船的出现频率比车辆低两个数量级。
解决:先调整数据层面的配比,对含船样本的图像做重复采样,或者从训练集中抽取部分纯车场景让分布趋于均衡。数据层面处理完还有差距,就调整损失权重,把cls_loss里船这个类别的权重系数提高 2 到 3 倍。常见做法是在代码包的损失计算配置里增加一个class_weights列表,权重值按各类别样本数的倒数归一化。不要一上来就调损失权重,那是数据处理后无效的后手方案。
4.5 NMS 把密集目标一锅端
现象:地面停车场里相邻的车辆被模型检测成一个大框,或者两辆车并排时只出一个框,人群密集时漏检更严重。
原因:默认 NMS 阈值设为 0.45 是针对通用场景的。无人机视角下目标密集,相邻目标 IoU 天然偏高,宽松的 NMS 阈值会把两个置信度都接近的目标合并成一个。
解决:推理时把 NMS IoU 阈值降到 0.3 或 0.25。这个参数在训练时不体现,只在推理和验证阶段生效。降低阈值后误检会略有增加,需要配合置信度阈值上调来平衡。我一般把置信度设 0.35、NMS 设 0.3,在大部分无人机场景下可靠。
注意:以上参数需要搭配自己的验证集数据做小范围网格搜索,不同相机高度、不同地面纹理下的最优组合差异不小。
5. 推理优化:从 mAP 数字到飞机上能跑的实时检测
模型训练结束,真正决定项目成败的是推理阶段。先说评估方式:不要只看总的 mAP,要看分尺寸的mAP50。在无人机场景里,我通常关注mAP50_s,也就是小目标类别那一列,它才反映真实使用体验。如果小目标 mAP 低于 0.4,模型基本不具备实用价值。
推理侧最有效的手段是切片推理,也叫 SAHI 思路。把 4K 航拍图切成多块 640x640 的重叠子图分别推理,再把结果合并回原图坐标。切片能放大目标在输入图像中的相对尺寸,等效于把模型看到的“距离”拉近,实际使用中比单纯用大分辨率整图推理效果好,对显存也友好。常见的切片参数配置参考:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| slice_width | 640 | 切片宽度,越小目标越大,但耗时越高 |
| slice_height | 640 | 切片高度,保持和训练分辨率一致 |
| overlap_ratio | 0.2 | 重叠率,防止目标被切在边缘导致漏检 |
| postprocess | NMS | 对大图的重叠检测框做合并 |
切片数计算:一张 3840x2160 的图,按上述参数会产生约 7x4 共 28 个子图,在 Jetson 这类边缘设备上单帧处理时延约 90 到 130ms,可以满足手持巡检需求。机载实时性要求更高时,可以先把模型导出成 TensorRT INT8 格式,再把每帧等间隔抽取切片、只保留中间区域检测,能将时延压到 50ms 以内。
另外一件容易被忽略的事:切片宽度要和训练时imgsz保持一致,否则分布偏移会让精度掉一截。我看到很多开发者习惯性地把推理切片设成 512,而训练用的是 1280,结果小目标反而漏得更厉害,因为模型在训练时看到的目标尺度比例与推理时的输入比例不一致了。
导出 TensorRT 时常见做法是:
python export.py \ --weights runs/train/exp/weights/best.pt \ --format engine \ --half \ --imgsz 1280 \ --device 0导出后务必在实拍视频上验证一遍,INT8 量化有时会让很小的目标置信度压到阈值以下,出现整段漏检。从那以后我做无人机目标检测,每次推理优化完都强制走一遍完整流程:训练时的 imgsz 对齐推理切片尺寸、统计小目标占比、检查验证集划分、量化后跑实拍数据评估。这一步漏掉,前四步做得再好都白搭。希望这些经验能帮你少踩几个坑。
本文还有配套的精品资源,点击获取