资讯动态

无人机视角船只检测:YOLO11训练调参与小目标漏检避坑指南

发布时间:2026/10/25 10:43:01 来源:尧图企业网站定制
简介这份资源面向无人机视觉与水上目标识别方向的学习者和开发者提供基于 ultralytics YOLO11 的船只检测完整方案可用于水上目标识别、海上搜救等场景适合具备一定深度学习基础、希望快速复现或二次开发的人员。压缩包共约 2000 个文件整体 676.5MB包含 1059 个 txt 标签、284 张 jpg 图像、169 个 py 脚本、88 个 yaml 配置及少量 cpp、js、html 等推理与展示文件覆盖数据、训练、部署多个环节。数据集已标注为 YOLO 与 VOC 两种格式并划分 train、val、test附 data.yaml可直接用于 yolov5 至 v12 等算法训练类别为 boat船只。资源同时提供使用教程与模型评价指标曲线图便于读者掌握训练流程、评估模型效果并迁移到自有数据。已有 89 人学习适合作为水上目标检测项目的起步模板与参考实现。1. 无人机视角船只检测为什么通用 YOLO 模型在水面上会集体翻车把 YOLO11 直接套到无人机航拍的海面画面里十有八九第一轮推理结果会让你怀疑人生近处船只框得还行中远距离的船要么漏检要么被框成一片模糊的白色噪点。这不是模型不行而是无人机视角 水面背景这个组合本身就带着几个反直觉的坑。水面高光反射、船只尾迹、远处海天交界线都会让在 COCO 上表现优异的通用权重直接失灵。ultralytics 这套框架把训练、验证、导出、推理串成了一条流水线但流水线跑得通不代表结果能用。这篇笔记面向的是手里有无人机航拍素材、想做水上目标识别或海上搜救任务的工程师。我会把从数据集组织、YOLO11 训练配置、到无人机视角特有的参数调整和排错路径讲清楚让你拿到一份「船只检测数据集 训练好的模型」之后知道每一步在干什么、参数为什么这么设、哪里最容易翻车。海上搜救场景对漏检的容忍度极低一个漏掉的小目标可能意味着一条人命所以召回率优先于精度这个基调会贯穿全文。2. 数据集怎么组织从原始航拍到 YOLO11 能吃的格式2.1 无人机船只数据集的三个典型来源与标注策略无人机视角的船只数据常见来源无非三类一是自采的航拍视频抽帧二是公开的无人机海事数据集三是用仿真环境合成的画面。自采数据的优势是场景贴合劣势是标注成本高公开数据集省事但类别定义和你的任务未必一致合成数据可以批量生成小目标但域间隙domain gap会让模型在真实画面上打折扣。我一般会以自采为主、公开数据做补充、合成数据只用来预训练。标注策略上船只检测和通用目标检测最大的区别在于目标尺度跨度极大。同一帧画面里近处的渔船可能占 800×600 像素远处的快艇只有 12×8 像素。如果按常规的标注规范只框可见船体远处目标会小到模型根本学不到特征。我的做法是对小于 16×16 像素的目标标注时适当外扩 2 到 4 个像素保证标注框至少覆盖船体的主要轮廓对超过画面 1/3 的近处大船正常标注即可。类别上如果只是搜救任务建议先做单类「boat」不要一上来就分渔船、货轮、快艇类别越多小目标越难学。标注工具用 LabelImg 或 CVAT 都行导出 YOLO 格式的 txt。每张图的标注文件里每行是class_id x_center y_center width height全部归一化到 0 到 1。这里有个血泪经验无人机画面里经常出现船只被浪花或尾迹部分遮挡的情况标注时只框可见部分不要脑补被遮挡的船体否则模型会学到错误的边界。2.2 目录结构与 data.yaml 的最小可用配置YOLO11 对数据集的目录结构有固定要求ultralytics 会在训练时按data.yaml里的路径去读。一个能直接跑通的最小结构如下boat_dataset/ ├── images/ │ ├── train/ # 训练集图片jpg/png │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片可选 ├── labels/ │ ├── train/ # 与 train 图片同名的 txt │ ├── val/ │ └── test/ └── data.yamldata.yaml的内容# 数据集根路径建议写绝对路径避免相对路径踩坑 path: /home/user/boat_dataset train: images/train val: images/val test: images/test # 类别数单类船只就写 1 nc: 1 # 类别名称顺序必须和标注文件里的 class_id 对应 names: 0: boat逻辑说明path是数据集根目录train/val/test是相对于path的子路径。ultralytics 会拼接path train去找图片然后自动把images替换成labels去找同名标注。参数上nc必须和标注文件里出现的最大 class_id 加一一致写错了训练时不会报错但类别会错乱。names的键必须是整数从 0 开始连续。提示如果验证集和训练集用了同一批图片mAP 会虚高搜救任务里这种自欺欺人最要命。建议按 8:1:1 划分且划分时保证同一段视频的帧不要同时出现在训练和验证集里否则相邻帧高度相似会导致验证指标失真。2.3 用脚本做一次数据集体检尺寸、类别、空标注在开训之前我习惯跑一个体检脚本把数据集的坑提前暴露出来。下面这段代码检查图片能否正常读取、标注是否越界、有没有空标注文件import os from pathlib import Path from PIL import Image root Path(/home/user/boat_dataset) splits [train, val, test] bad_images, empty_labels, out_of_range [], [], [] for split in splits: img_dir root / images / split lbl_dir root / labels / split for img_path in img_dir.glob(*): # 1. 检查图片是否可读 try: w, h Image.open(img_path).size except Exception as e: bad_images.append((str(img_path), str(e))) continue # 2. 检查对应标注 lbl_path lbl_dir / (img_path.stem .txt) if not lbl_path.exists(): empty_labels.append(str(img_path)) continue lines lbl_path.read_text().strip().splitlines() if len(lines) 0: empty_labels.append(str(img_path)) continue for line in lines: parts line.split() if len(parts) ! 5: out_of_range.append((str(lbl_path), 字段数不对)) continue cls, x, y, bw, bh parts # 3. 归一化坐标必须在 0~1 之间 vals [float(x), float(y), float(bw), float(bh)] if any(v 0 or v 1 for v in vals): out_of_range.append((str(lbl_path), line)) print(坏图:, len(bad_images)) print(空标注:, len(empty_labels)) print(越界标注:, len(out_of_range))逻辑说明这段脚本做了三件事——用 PIL 打开图片确认文件没损坏检查每张图是否有对应的标注文件且非空校验归一化坐标是否在 0 到 1 之间。参数上不需要额外配置改root路径即可。跑完如果「空标注」数量超过总数的 5%说明标注工作有遗漏需要回去补「越界标注」通常是因为标注工具导出时坐标没归一化或者手动改标注时写错了。3. YOLO11 训练配置从预训练权重到无人机视角的参数微调3.1 ultralytics 安装与版本确认ultralytics 的安装本身不复杂但热搜里那个could not find a version that satisfies the requirement ultralytics的报错我见过太多次。绝大多数情况是 Python 版本太低或者 pip 源的问题。YOLO11 要求 Python 3.8 以上推荐 3.10 或 3.11。安装命令# 建议在虚拟环境里装避免污染系统环境 python -m venv yolo_env source yolo_env/bin/activate # 升级 pip老版本 pip 解析依赖容易出问题 pip install --upgrade pip # 安装 ultralytics会自动带上 torch 等依赖 pip install ultralytics # 验证安装和版本 yolo version逻辑说明python -m venv创建独立环境避免和系统里其他包冲突。pip install --upgrade pip这步很多人跳过但老版本 pip 在解析 ultralytics 的依赖树时确实会报找不到版本。yolo version会打印当前 ultralytics 版本确认装上了再往下走。如果还是报could not find a version检查一下 Python 版本是不是低于 3.8以及网络是否能正常访问 PyPI。注意不要用pip install ultralytics --pre去装预览版预览版的 API 可能和稳定版不一致训练脚本会莫名其妙报错。生产任务用稳定版。3.2 用 yolo11n.pt 做迁移学习训练命令与关键参数YOLO11 有 n/s/m/l/x 五个尺寸无人机视角船只检测我一般从yolo11n.pt或yolo11s.pt起步。n 版推理快适合机载实时s 版精度略高适合地面站后处理。训练命令yolo detect train \ modelyolo11n.pt \ data/home/user/boat_dataset/data.yaml \ epochs150 \ imgsz1280 \ batch8 \ device0 \ workers4 \ patience30 \ projectboat_runs \ nameexp_n_1280逻辑说明modelyolo11n.pt加载 COCO 预训练权重做迁移学习比从零训练收敛快得多。data指向刚才的data.yaml。epochs150是上限配合patience30做早停——30 轮验证指标不提升就停省时间。imgsz1280是无人机视角的关键参数下面单独讲。batch8在 8GB 显存上比较稳显存大可加到 16。device0指定第一块 GPUCPU 训练把 device 设成cpu但会很慢。workers4是数据加载线程数根据 CPU 核数调整。参数上最需要解释的是imgsz。YOLO 默认 640但无人机画面里小目标多640 下远处船只可能只剩几个像素特征提取网络根本抓不住。把imgsz提到 1280小目标的像素面积变成原来的 4 倍召回率会有肉眼可见的提升。代价是显存占用和训练时间增加推理速度也会下降。如果机载算力有限可以训练用 1280、推理用 960 或 640但精度会掉一些需要自己权衡。3.3 无人机视角必调的超参mosaic、scale、copy_pasteultralytics 的默认数据增强对通用数据集够用但无人机视角有它的特殊性。几个我每次都会动的参数yolo detect train \ modelyolo11s.pt \ data/home/user/boat_dataset/data.yaml \ epochs200 \ imgsz1280 \ batch8 \ mosaic0.8 \ scale0.6 \ copy_paste0.3 \ degrees5.0 \ fliplr0.5 \ hsv_h0.015 \ hsv_s0.5 \ hsv_v0.4逻辑说明mosaic0.8表示 80% 的概率做马赛克增强把四张图拼成一张。这个增强对小目标特别有用因为拼图后小目标会出现在不同位置和尺度上。但注意mosaic 在训练最后 10 轮建议关掉ultralytics 有close_mosaic参数默认 10让模型在真实分布上收尾。scale0.6是随机缩放幅度无人机高度变化导致目标尺度变化大这个值可以适当放大到 0.6 到 0.8。copy_paste0.3是复制粘贴增强把标注的船只抠出来贴到其他位置对增加小目标样本量很有效但贴的时候要注意别贴到天空区域否则模型会学到「船在天上」这种荒谬特征。degrees5.0是随机旋转角度无人机航拍会有轻微倾斜但不会像卫星图那样任意角度所以给 5 度就够了给太大反而引入不真实的样本。hsv_s0.5和hsv_v0.4是饱和度和明度扰动水面反光会导致亮度剧烈变化这两个值调大一点能提升模型对光照的鲁棒性。提示copy_paste在 ultralytics 里需要分割标注才能发挥完整效果检测任务下它做的是简单的矩形区域复制。如果你的数据集只有检测框这个增强的效果有限可以设成 0.1 到 0.2 保守使用。4. 训练过程排查loss 不降、mAP 虚高、显存爆炸怎么处理4.1 看训练日志的三个关键指标ultralytics 训练时会在终端和runs/目录下输出日志。我盯三个东西box_loss、cls_loss、mAP50。box_loss是边界框回归损失正常应该在前 20 轮快速下降然后趋于平缓如果它一直震荡不降多半是学习率太大或者标注框有问题。cls_loss是分类损失单类任务下这个值应该很快降到接近 0如果居高不下检查data.yaml里的nc和names是否和标注一致。mAP50是 IoU 阈值 0.5 下的平均精度搜救任务我更关注mAP50-95和召回率因为高 IoU 下的精度才反映框得准不准。如果训练到 50 轮mAP50还在 0.1 以下基本可以停了回去查数据集。常见原因是标注坐标没归一化、图片和标注文件名不匹配、或者data.yaml路径写错导致读到了空数据集。4.2 验证集 mAP 高但实际推理漏检过拟合与分布偏移这是无人机船只检测里最迷惑人的坑验证集mAP50跑到 0.9拿一段新拍的视频去推理漏检一片。原因通常是验证集和训练集来自同一段视频相邻帧几乎一样模型只是记住了这些帧没有真正学到船只的通用特征。解决办法前面提过按视频段划分数据集而不是按帧随机划分。另一个原因是分布偏移。训练集是晴天正午拍的推理时是黄昏或者阴天水面颜色和反光完全不同。缓解办法是在训练集里尽量覆盖不同光照、不同海况、不同飞行高度。如果实在覆盖不了推理前做一次简单的直方图均衡化或者用测试时增强TTAyolo detect predict加augmentTrue可以开启 TTA但速度会慢 2 到 3 倍。4.3 显存不够时的降级路径imgsz1280加batch8在 8GB 显存上可能直接 OOM。降级顺序我一般这么走先把batch降到 4再把imgsz降到 1024最后才考虑换更小的模型。不建议一上来就降imgsz到 640那样小目标检测基本废了。如果显存实在紧张可以用梯度累积模拟大 batchyolo detect train \ modelyolo11n.pt \ data/home/user/boat_dataset/data.yaml \ imgsz1280 \ batch4 \ accumulate4 \ device0accumulate4表示每 4 个 batch 才更新一次权重等效于 batch16但显存占用还是 batch4 的水平。代价是训练速度会慢一些因为多了等待时间。5. 避坑与常见问题五条血泪经验现象一训练 loss 正常下降但推理时所有船都被框成同一个位置。原因通常是标注文件里的坐标没有归一化比如写成了像素坐标而不是 0 到 1 的比例。解决方法是回到 2.3 的体检脚本检查out_of_range的输出把所有越界标注重新导出。现象二could not find a version that satisfies the requirement ultralytics反复出现。原因可能是 Python 版本低于 3.8或者 pip 源里没有对应版本的包。解决方法是python --version确认版本然后pip install ultralytics -i https://pypi.org/simple指定官方源重试。现象三训练到一半突然报 CUDA out of memory。原因可能是imgsz或batch设太大也可能是workers太多导致数据加载占用显存。解决方法是降batch到 4 或 2workers降到 2如果还不行就降imgsz到 1024。现象四验证集 mAP 很高但模型导出 ONNX 后推理结果和 PyTorch 不一致。原因通常是导出时的输入尺寸和推理时的预处理不匹配。解决方法是导出时明确指定imgsz推理时用同样的尺寸做 letterbox 预处理不要用 OpenCV 默认的 resize。现象五小目标漏检严重大目标框得还行。原因可能是imgsz太小或者mosaic增强关闭太早。解决方法是把imgsz提到 1280mosaic保持 0.8 到 0.9close_mosaic设成 15 而不是默认的 10让模型多学一会儿小目标。6. 进阶技巧用切片推理把远处小船捞回来即使imgsz1280无人机画面里最远处那几个像素级的船只还是可能漏掉。这时候可以用切片推理SAHISlicing Aided Hyper Inference的思路把大图切成有重叠的小块每块单独推理再把结果合并。ultralytics 本身不直接提供 SAHI但可以自己写一个简单的切片推理脚本import cv2 import numpy as np from ultralytics import YOLO model YOLO(boat_runs/exp_n_1280/weights/best.pt) img cv2.imread(test_frame.jpg) h, w img.shape[:2] # 切片参数每块 640x640重叠 128 像素 tile, overlap 640, 128 stride tile - overlap all_boxes [] for y in range(0, h, stride): for x in range(0, w, stride): # 保证切片不越界 x2, y2 min(x tile, w), min(y tile, h) x1, y1 max(0, x2 - tile), max(0, y2 - tile) crop img[y1:y2, x1:x2] results model.predict(crop, imgsz640, conf0.25, verboseFalse) for box in results[0].boxes: # 把切片坐标映射回原图 bx1, by1, bx2, by2 box.xyxy[0].tolist() all_boxes.append([bx1 x1, by1 y1, bx2 x1, by2 y1, box.conf[0].item()]) # 用 NMS 合并重叠框 def nms(boxes, iou_thr0.5): boxes sorted(boxes, keylambda b: b[4], reverseTrue) keep [] while boxes: best boxes.pop(0) keep.append(best) boxes [b for b in boxes if iou(best, b) iou_thr] return keep def iou(a, b): xx1, yy1 max(a[0], b[0]), max(a[1], b[1]) xx2, yy2 min(a[2], b[2]), min(a[3], b[3]) inter max(0, xx2 - xx1) * max(0, yy2 - yy1) area_a (a[2] - a[0]) * (a[3] - a[1]) area_b (b[2] - b[0]) * (b[3] - b[1]) return inter / (area_a area_b - inter 1e-6) final nms(all_boxes) print(f切片推理得到 {len(final)} 个目标)逻辑说明这段脚本把原图按 640×640 切片重叠 128 像素每块单独推理然后把框映射回原图坐标最后用 NMS 去重。参数上tile和overlap需要根据目标大小调目标越小tile可以越小、overlap越大但推理次数会成倍增加。conf0.25是置信度阈值搜救任务可以降到 0.15 提高召回代价是误检增多需要人工复核。这个方法的代价是推理时间变成原来的十几倍不适合机载实时但适合地面站对关键帧做精细搜索。我一般只在搜救任务的最后确认阶段用平时还是用整图推理。最后说个习惯每次训练完我都会拿一段完全没参与训练的视频跑一遍人工数漏检和误检而不是只看验证集的 mAP。验证集指标是给自己看的真实场景的漏检才是要命的。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑