简介:无人机视角下的船只检测与水上目标识别是海上搜救、港口监控等任务的关键环节。这套基于Ultralytics YOLO11的完整资源包面向目标检测初学者与无人机视觉研究者,内置标注数据集、训练好的模型权重及配套使用教程,可直接用于YOLOv5/v8/v9/v10/v11/v12等算法的训练与推理。压缩包共2000个文件、约676.5MB,以txt标注文件、yaml配置、Python脚本为主,并含jpg示例图像、md说明文档及C++/JS部署代码,覆盖数据、训练、评估、部署全链路。数据集已划分train/val/test并附带data.yaml,提供YOLO与VOC格式标签,类别为boat(船只),同时包含模型评价指标曲线图。目前已有89人学习,适合从零上手无人机水面目标检测与搜救场景应用的开发者。
1. 无人机视角船只检测:一个压缩包能不能直接扛起海上搜救的活
搜救无人机飞出去,图传画面里一艘救生艇往往只有十几个像素,通用目标检测模型在陆地上跑得不错,到了水面上几乎全漏。YOLO11 无人机视角船只检测这个方向,核心就是把检测能力从「能看见船」推进到「在复杂水面上认出船、找到人、支撑搜救决策」。
标题这个压缩包把三样东西打包在一起:无人机视角的水上目标识别数据集、训练好的 YOLO11 权重、以及可以直接落地的检测流程。对刚入手航拍视觉的开发者,它省掉了最耗时的采集和标注环节;对已经在做海上搜救项目的工程师,它是评估这个方向值不值得投入的现成基准。
这篇笔记按「为什么难 → 怎么跑通 → 坑在哪 → 怎么调优」往下拆,命令可以直接执行,参数说明和踩坑记录放在对应章节。中间我加了不少自己踩过的坑,尤其最后那章的建议,建议先看完再动手调参。
2. 为什么无人机视角的船只检测这么难:任务拆解与 YOLO11 选型逻辑
把通用目标检测模型直接搬到水上航拍,先死掉的往往不是网络结构,而是任务假设。通用检测默认目标「直立、纹理清晰、占画面比例不小」,无人机视角这三条全不满足。下面从成像、网络、数据三个层面拆开看。
2.1 无人机视角的成像特点:目标小、背景杂、尺度跨度大
无人机飞行高度从 50 米到 300 米不等,同一艘船的成像宽度可能相差 6 倍以上。50 米高度能看清甲板纹理和船名,300 米高度就只是一个明暗块。海上搜救里更极端的情况是 10 米长的橡皮艇,在 1080p 画面里只占约 20×10 像素,和一层浪花碎末在尺度上几乎没有区别。这让「按目标尺寸设计特征」的通用检测器非常吃力,漏检几乎不可避免。
背景复杂度是第二个杀手。水面不是单一颜色,有波纹、有太阳耀斑、有云影、有船行尾迹,还有海岸线上的防波堤、栈桥和岸上建筑物。陆上模型把「矩形 + 纹理 + 边缘」当船,到了水面上这些线索全部失真;而搜救任务的低空机动飞行还会带来运动模糊和视角突变,画面质量比车载摄像头还低一档。
第三个特点是目标形状极度离散。货轮是长方形,拖船近似方形,帆船是细长三角形,落水人员几乎就是一个点。固定的 anchor 设计很难同时覆盖这些几何形态,所以做水上目标识别时,类别设计和数据分布往往比网络选型更先决定成败。尤其是「人员落水」这类目标,它在图像里的语义信息极弱,模型能做的更多是「疑似目标定位」,而不是真正「认出人」。
2.2 YOLO11 的架构改进对水上目标识别意味着什么
YOLO11 是 Ultralytics 在单阶段检测器上的最新迭代,几个关键结构变化对航拍场景是有实际增益的。backbone 里引入了注意力机制模块,让网络在大量水面背景中更容易聚焦到稀疏的目标区域;特征融合结构做了轻量化改造,参数量和计算量比上一代更小,这对无人机机载算力非常友好。检测头仍保持无 anchor 的 decoupled 设计,分类和回归分支独立,训练收敛更稳。
对做海搜的工程师来说,YOLO11 最实用的特性是「预训练权重 + 微调」的路径非常短。直接拿通用基准数据集上训练的权重跑无人机画面,效果通常不理想,因为预训练数据里几乎没有俯视小目标样本;但这个权重作为初始化,在船只数据集上微调几十个 epoch 就能快速收敛,远比从零训练省时间。单阶段检测器的推理速度也保证了实时的处理余量,搜救飞行的图传回传链路里这是硬指标。
需要提醒的是,YOLO11 的精度优势主要体现在常规尺度目标上。对 20 像素以下的落水人员这类极端小目标,网络结构本身只能「少漏」,不能做到「全检」,最终要靠推理分辨率、图像预处理和跟踪后处理一起补。选型时也不要只看精度数字:同一套数据下,n/s 这类轻量模型跑高分辨率往往比 l/x 大模型跑低分辨率更实用,因为航拍小目标对分辨率更敏感。
2.3 数据集里应该有什么:标注格式、类别设计和训练/验证划分
拿到压缩包先别急着跑命令,先摸清数据集结构。一个合格的 YOLO 风格水上目标数据集,至少包含 images 目录、labels 目录、一个 data.yaml 类别描述文件,以及训练/验证划分。labels 下每个 txt 文件名与对应图片名一致,每行描述一个目标,共 5 列数字。
| 字段 | 含义 | 取值范围 |
|---|---|---|
| class_id | 类别索引,必须与 data.yaml 中 names 顺序一致 | 0 到类别数减 1 |
| cx | 目标框中心点 x 坐标,相对图片宽度归一化 | 0~1 |
| cy | 目标框中心点 y 坐标,相对图片高度归一化 | 0~1 |
| w | 目标框宽度,相对图片宽度归一化 | 0~1 |
| h | 目标框高度,相对图片高度归一化 | 0~1 |
类别设计直接决定模型能否支撑海搜任务。如果 data.yaml 里只有「船」一个类,模型学出来就是一个「有没有物体」的判别器,指挥员拿到结果还得自己判断是货轮还是救生艇,没有决策价值。我一般会至少拆成三类:大型船只(货轮、客轮)、小型船只(橡皮艇、救生艇、渔船)、落水人员。如果数据量撑不住三类,至少要把小型船只单独拆出来,因为它是搜救任务里优先级最高的目标。
训练/验证划分在这里有个典型陷阱:无人机视频常是同一架次连续拍摄的,如果随机划分,同一段航线的相邻帧会同时进训练集和验证集,验证指标虚高到没有参考意义。划分前要先按视频片段分组,同一个架次的帧只能进一边。另一个检查点是标签质量:框是否贴边、倒影和浪花有没有被标进去、类别标号是否从 0 开始。这类问题训练时不会报错,只会静默拉低指标,属于最典型的黑匣子。
3. 从解压到跑通推理:YOLO11 船只检测模型的最小复现路径
解压 zip 后,目录里通常会有 weights/best.pt、images、labels、data.yaml 这几样。第一步不是急着训练,而是先用训练好的模型做一次推理,确认压缩包内容完好、环境可用,再谈微调和部署。
3.1 环境准备:虚拟环境、依赖安装与可复现性
建议用 Python 3.10 到 3.12,单独建虚拟环境,不要直接装到系统 Python。航拍项目的依赖经常互相打架,独立环境是最便宜的后悔药。
# 解压之后先建独立环境,避免污染系统 Python python -m venv venv_drone source venv_drone/bin/activate # Windows PowerShell 用 venv_drone\Scripts\Activate.ps1 pip install ultralytics # 会自动带上 torch、torchvision 等依赖 python -c "import ultralytics; print(ultralytics.__version__)"pip install ultralytics 会拉取 torch,安装包体积比较大,耐心等。没有 NVIDIA GPU 的机器会自动装 CPU 版,推理能跑但速度慢;有 GPU 先确认 nvidia-smi 输出的驱动和 CUDA 版本,再决定要不要手动装对应 torch 版本。装完后打印 ultralytics 版本号,能正常输出就说明基础环境通了。
import torch print("CUDA 可用:", torch.cuda.is_available()) print("设备:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")注意:如果 torch.cuda.is_available() 返回 False,先检查驱动和 CUDA 版本是否匹配,不要急着重装整个环境。
3.2 用训练好的权重跑一次无人机视角推理
环境就绪后,先用命令行跑一张图验证权重文件没有损坏。结果图会默认存到 runs/detect/predict 目录,打开看框是不是贴住目标。
# 对单张图片或整个目录推理,结果默认存到 runs/detect/predict yolo predict model=weights/best.pt source=samples/ imgsz=1280 conf=0.25 iou=0.5 save=True这里几个参数后面会反复用到。imgsz=1280 是航拍小目标的关键设置,默认 640 在无人机画面上会漏掉大量小艇;conf=0.25 表示置信度低于 0.25 的框直接丢弃,阈值越低召回越高,误检也越多;iou=0.5 是 NMS 去重阈值,港口密集场景需要降。第一次跑完先别急着调参,看结果图里框的贴合程度再决定。
处理视频或批量帧时,我会用 Python API,方便把检测结果结构化输出到日志里:
from ultralytics import YOLO model = YOLO("weights/best.pt") results = model.predict( source="sea_rescue_clip.mp4", # 无人机视频或图片目录 imgsz=1280, # 航拍小目标建议不低于 1280 conf=0.3, # 置信度阈值,按场景在 0.2~0.5 之间调 iou=0.5, # NMS 阈值,密集停靠场景降到 0.3~0.4 stream=True, # 视频逐帧返回,避免一次载入全部帧爆内存 ) for frame_id, r in enumerate(results): boxes = r.boxes.xyxy.cpu().numpy() # 目标框坐标,格式 [x1, y1, x2, y2] scores = r.boxes.conf.cpu().numpy() # 每个框的置信度 cls_ids = r.boxes.cls.cpu().numpy().astype(int) for s, b, c in zip(scores, boxes, cls_ids): if s >= 0.3: # model.names[c] 是类别名,检查这里和 data.yaml 是否一致 print(f"第{frame_id}帧: 类={model.names[c]} 置信度={s:.2f} 框={b}")stream=True 会让 predict 逐帧生成结果而不是一次性加载整个视频,长航拍视频必须开,否则内存会被撑爆。xyxy 坐标是像素值,cpu().numpy() 是把 GPU 上的 tensor 转成普通数组,方便后面做轨迹关联或地理编码。model.names 保存了类别名列表,如果打印出来的类别名和 data.yaml 对不上,说明标签错位,后面训练会出问题。
3.3 检查数据集完整性:目录结构、标签格式和类别文件
推理跑通后,回头把数据集完整检查一遍。这个步骤花十分钟,能省掉训练跑了一半发现数据有问题的翻车。
tree -L 2 -d . # 看整体目录结构 head -5 train/labels/000001.txt # 抽查标签格式,应为 5 列数字 wc -l train/labels/*.txt | tail -1 # 统计标签文件总行数,粗看目标总数 find train/labels -size 0 | head -5 # 找空标签文件,空文件要清理 cat data.yaml # 确认类别名称、数量、路径标签每行必须是「class_id cx cy w h」五个数,空格或 Tab 分隔,坐标都归一化到 0~1。wc -l 统计出来的总行数就是目标总数,拿这个数除以类别数,能粗看有没有类别不平衡;目标总数只有几百的话,不要指望微调后指标有多高。空标签文件会让训练时该图直接跳过,如果空文件占了三分之一,说明采集时大量帧没内容,训练集会虚胖。
data.yaml 检查要重点看两处:names 列表的顺序和 labels 里的 class_id 是否从 0 开始且一一对应;path 字段指向的目录在当前机器上是否存在。如果 zip 里没有独立 val 目录,可以自己写脚本从 train 里划 10%~20% 出来,但注意按视频架次分组,不能随机抽帧。数据集检查完,整个复现路径才算真正闭环。
4. 水上目标识别避坑清单:海上搜救场景里最容易翻车的 5 个地方
这 5 个坑是无人机视角船只检测复现时最常见的翻车点,全部按「现象 → 原因 → 解决」记录。前三个在推理参数和数据集层面就能修,后两个要回到训练和数据采集。
4.1 小目标漏检:为什么 imgsz=640 在无人机画面上不够用
现象:微调时验证集 mAP 看着有 0.8,把模型拿到 300 米高度实拍视频上,小艇全程没框,误检反而集中在大船周围。
原因:训练和推理分辨率不匹配。imgsz=640 时,20 像素的小艇在下采样后的特征图上只剩 1~2 个像素,特征几乎丢光;而验证集里的目标往往比实拍更大,尺度分布对不上,于是指标虚高。
解决:推理 imgsz 提到 1280 或 1536,召回肉眼可见提升;微调用同样分辨率。显存不够时,用 n/s 小模型配高分辨率,而不是大模型配低分辨率。另外训练时把 Mosaic 增强关掉或降启用概率——Mosaic 会把小目标切碎,对航拍小目标检测常常是负优化,这个参数值得单独实验。
4.2 水面反光和倒影导致的误检
现象:平静海面上,船头正下方多出一个船的框;晴天太阳耀斑区域被识别成白色船体,且置信度还不低。
原因:倒影和水面反光在纹理上真的和船体很像,模型学到的是「竖长结构 + 高亮区域 = 船」。训练集里如果有大比例带倒影的样本且被标注成船,这个错误模式会被固化。
解决:训练时把倒影区域显式标为背景,或单独加一个 ignore 类;推理后处理用形状过滤,船体宽高比一般在 1:1 到 4:1,倒影通常是细长结构,按宽高比直接滤掉;视频场景加 ByteTrack 这类轻量跟踪器,闪烁出现又消失的框大概率是反光误检,丢弃即可。这个坑在太阳高度角低的清晨和黄昏最严重。
4.3 港口密集停靠场景下 NMS 把相邻船只压掉
现象:港口里并排停靠的船,输出框把两三艘船圈成一个大框,中间的船被吞掉,统计船舶数量时误差很大。
原因:相邻目标重叠度高,默认 NMS IoU 阈值(0.5~0.7)把两个预测当成对同一目标的重复检测,置信度稍低的框被删除。这在港口、锚地这类高密度场景特别常见。
解决:推理时把 iou 降到 0.3~0.4,保留相邻预测;训练时确保数据集里有足够多的密集停靠样本,让模型学到对相邻目标输出错开的中心点;多类别场景不要开 agnostic_nms,否则不同类别的重叠目标也会互相压制。
4.4 白天训练的模型在黄昏和阴雨天失效
现象:同一套权重,中午实拍没问题,黄昏低角度阳光下漏检率明显上升;阴天浪花多时误检变多,海况差的时候几乎不可用。
原因:光照色温和对比度变化导致特征分布偏移,训练数据的时间分布太集中。航拍画面里水面颜色随太阳高度角变化极大,模型没见过的光照条件就是另一个域,泛化无从谈起。
解决:训练时把 HSV 增强参数调大,h 从 0.02 提到 0.05 以上,s 和 v 从 0.5 提高到 0.8,模拟不同时段;采集数据按架次覆盖早、中、晚和阴、晴,至少保证验证集里有黄昏样本;推理侧可以用 CLAHE 做预处理增强对比度。别指望模型泛化到完全没见过的光照条件,数据覆盖才是根治办法。
4.5 类别不平衡:搜救目标被货轮样本淹没
现象:货轮、客轮检测很稳,救生艇和落水人员类别的召回率只有个位数,训练损失正常下降,但小类的指标纹丝不动。
原因:数据集中大目标类别样本多、小目标类别样本少,分类损失被大样本类别主导,网络把容量都花在学货轮上。搜救任务最关心的两类恰好是样本最少的两类,这个错位很致命。
解决:最直接的是给 loss 加类别权重,训练配置里设 class_weights,把救生艇和人员的权重拉到其余类的 5~10 倍;数据层面做小类过采样,或把含小艇的图重复进训练集;条件允许的话,单独训练一个高分辨率专用小目标模型,只负责找小艇和人员,和大船模型做级联。级联方案在海搜项目里最常见也最可靠,代价是推理链路多一跳。
5. 让模型在海搜任务里更可靠:微调、推理参数与部署的进阶技巧
5.1 用自己的数据微调时的参数建议
拿到压缩包只是起点。把模型用到自己的辖区海域,我一般会用自己的航拍视频抽帧标注,然后以压缩包里的 best.pt 为初始化跑一轮微调。关键参数如下:
from ultralytics import YOLO model = YOLO("weights/best.pt") # 以压缩包权重为初始化,而不是通用预训练权重 model.train( data="datasets/sea/data.yaml", epochs=60, imgsz=1280, # 与推理分辨率保持一致 batch=8, freeze=10, # 冻结前 10 层,小数据下收敛更稳 lr0=0.0005, # 微调学习率要比默认低,避免破坏已有特征 class_weights={0: 1.0, 1: 1.0, 2: 6.0}, # 索引对应 data.yaml 的 names 顺序 name="sea_finetune", )freeze=10 适合几百张量级的微调,数据量过千后可以降为 0 全量微调;lr0 我习惯从默认值往下调一个数量级,用已有权重起步时这个差别很大。class_weights 的索引要和 data.yaml 里的 names 顺序严格对应,顺序错了损失权重就张冠李戴。
5.2 部署验证:别只盯着 mAP
yolo export model=runs/train/sea_finetune/weights/best.pt format=onnx imgsz=1280导出 ONNX 后推理速度明显提升,适合机载端部署;等模型稳定了再考虑量化到 int8,海上低对比度画面量化后掉点比常规场景更狠,要实测后再决定。
验证方面,我每轮微调完不是先看 mAP,而是先跑一遍最难的那段黄昏实拍视频,肉眼过一遍漏框和误检,再回来看指标表格。mAP 平均掉几个点可能无所谓,黄昏小艇一框不漏才是海搜真正要的指标。我自己踩过一回:mAP 涨了 0.03,实拍视频里落水人员却全漏,原因是验证集和实拍场景根本不同域。后来我把验证流程固定成「最难视频优先」,小模型先过,大模型再过,这个习惯一直沿用到现在。干这一行,指标是给别人看的,最难那段视频是自己心里的一根弦。希望帮到你。
本文还有配套的精品资源,点击获取