ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

夜间航拍船只目标检测数据集构建与YOLOv5训练实战

夜间航拍船只目标检测数据集构建与YOLOv5训练实战 简介目标检测是计算机视觉领域的核心任务之一但在夜间低照度、无人机航拍视角下水面船只的检测面临目标对比度低、灯光倒影干扰、小目标密集等严峻挑战。数据质量直接决定模型性能上限而构建一个面向特定场景的高质量数据集是训练可靠检测模型的基础。本文以YOLOv5为技术框架系统梳理了夜间航拍船舶检测数据集的构建流程涵盖原始素材采集、筛选、标注规范、目录组织与质量检查并给出了完整的训练与验证结果。针对黑夜环境下小目标占比高、场景分布复杂等特点提出了提升输入分辨率、统一标注口径、场景重采样等工程化策略能够有效提升模型在实际水域巡检、港口监控、应急救援等场景中的鲁棒性。对于从事无人机视觉、船舶识别和夜间目标检测的研究者与工程师本文提供了一套可直接参考的数据组织与模型训练实践方案。 做目标检测的人应该都有这种体会模型结构、训练技巧研究得差不多了最后真正卡住进度的往往是数据。尤其当任务场景是“黑夜航拍船只”这三个条件叠加时问题会更明显。白天再强的检测器到了夜晚那些低对比度的船只轮廓、灯光倒影、水面波纹噪声面前准确率都会肉眼可见地下滑。我最近把一批夜间无人机航拍视频整理成了标准的目标检测数据集采用YOLOV5目录格式共1个类别船舶并完整跑通了一遍训练和验证。这篇博文就把数据来源、目录组织、标注规范、训练效果和踩坑经验一次说清楚给正在做夜间水面目标检测、无人机视觉、水域巡检的朋友一个可直接参考的样本。1. 为什么我会盯上“黑夜航拍船只”这个细分场景1.1 夜间水面监控的典型应用场景夜间船只检测不是一个伪需求。航道上的船舶流量监测、港区作业安全、渔业资源管理、水域应急救援、水利设施巡检这些场景在夜晚都有强烈的自动化识别需求。夜间往往是小型船舶违规航行的高发时间段也是水上搜救最考验效率的时候。无人机夜间巡逻已经逐渐成为标配手段但人工盯着视频画面找船效率太低尤其在多路视频同时回传的时候漏看几乎是必然的。目标检测模型在这里的角色就是把“画面里有没有船、船在哪个位置”这个重复劳动先自动化掉再由人工对报警结果做二次确认。1.2 为什么公开数据集很难直接拿来用市面上公开的船舶数据集其实不少有SAR遥感图像里的船只检测有港口摄像头视角的船舶识别还有大量白天场景下的航拍船只数据。但真正能直接用于“夜间无人机低空视角”这一具体场景的公开数据几乎找不到。差距集中在三点。第一是光照条件。公开数据集绝大多数是白天或者黄昏场景夜间低照度下目标与背景对比度极低、高光过曝、灯光倒影混杂模型没见过这些特征推理时自然崩溃。第二是拍摄视角。很多船舶数据集是水平视角或者卫星遥感俯视角而无人机低空巡逻是大角度俯视船体的形态、朝向、遮挡关系完全不同。第三是目标尺度。无人机在百米高度看船几十米长的船在1080P画面里通常只有几十个像素宽属于典型的小目标而公开数据集里大多数目标占比都偏大无法模拟这种尺度分布。另外还有格式问题。很多研究型数据集是COCO或VOC格式真的要拿来跑YOLOV5还得写脚本转换、做归一化、切分训练验证集。所以我才决定按YOLOV5目录格式整理一份专门针对黑夜航拍场景的船只数据集让拿到手的人不用再做格式适配直接就能开始训练。2. 原始素材从哪来采集设备、筛选标准与夜间预处理2.1 采集设备与飞行参数低照度传感器比普通可见光更稳先说设备。夜间航拍要实现可用的船只识别最忌讳的就是拿普通可见光相机硬拍。普通相机在夜间为了维持画面亮度会拉高ISO结果就是满屏彩色噪点船体边缘彻底糊掉这种情况下就算标注做得再仔细模型也很难学到有效特征。我在采集时优先使用低照度CMOS传感器行业级无人机基本都有这个配置部分架次还同时挂载了红外热成像相机。低照度传感器的好处在于它能在相对较低的增益下获得足够干净的灰度细节船体轮廓和背景水面之间保留了一定的亮度层次。但它也有弱点就是对强光源容易过曝比如夜间船只甲板上的强光照明这一块在采集时需要通过曝光补偿和动态范围设置来缓解。飞行参数方面我主要集中在50米到150米的飞行高度云台俯仰角从30度到接近90度这样覆盖的目标尺度和观测角度比较全。同一高度、同一视角拍摄的图太多会让模型过拟合到固定的姿态上所以每次飞行的路线和高度都会刻意错开。2.2 原始素材筛选模糊帧、过曝帧必须剔除采集回来的原始素材是视频流不能直接全部抽帧进数据集。我整理时用了三道筛选。第一道筛掉完全不可用的帧。整帧过曝、整帧黑场、大雾导致目标完全不可辨这一类直接删除。第二道筛掉运动模糊明显的帧。无人机飞行中会有震动和位移夜间快门速度如果不够快船体边缘会出现拖影这种目标即使人眼能看出来标注出来的框也不准模型学到的会是“一团糊状物”。第三道是做帧间去重。同一个目标在连续视频帧里可能出现几十次如果全保留训练集里大量相似样本会导致模型严重过拟合到特定位置和姿态。实际操作中我按“同一目标最多保留5到10帧”的原则做抽帧控制同时注意覆盖不同的水域环境——内陆河道、水库湖区、近岸海域、港口码头都要有避免数据单一化。2.3 预处理思路保留原始光照只做分辨率和隐私处理很多人在制作数据集时习惯先做一堆图像增强把亮度拉高、对比度增强、降噪美化觉得“看得更清楚”就是更好的数据。我在这个项目里恰恰反着来除了统一分辨率和去掉EXIF信息基本不做额外的图像处理。理由是YOLOV5训练阶段本身内置了HSV增强、Mosaic、MixUp、随机仿射变换等数据增强策略会自动将原始图像变换出大量不同的亮度、色偏、对比度版本。如果数据集阶段就把图像p得太干净反而会让训练分布偏离真实部署场景——真到了夜间部署时传感器噪点回来了亮度波动也回来了模型反而撑不住。所以我的预处理只做两件事。第一统一把图像分辨率处理到1920×1080部分4K素材缩放到该分辨率保证标注坐标归一化之后训练时不会因为尺寸差异产生额外偏差。第二移除EXIF信息避免航拍照片中记录的位置、时间等隐私信息外泄。如果采集时同时拿到了红外图像会做灰度化后归档不做额外的配准融合因为训练阶段逐通道处理比强行融合更可控。3. YOLOV5目录格式落地images/labels结构、txt标注与data.yaml配置3.1 标准目录结构images、labels、data.yaml三个部分YOLOV5的目录格式核心就两个文件夹加一个配置文件逻辑非常直接。images文件夹存图片labels文件夹存与图片同名的txt标注文件data.yaml描述数据集路径和类别信息。结构如下night_ship_yolo/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── README.txt图片文件名与标注文件名必须严格一致。比如images/train/000001.jpg对应的标注文件是labels/train/000001.txt。YOLOV5训练时就是靠这个同名匹配机制去关联图片和标注的命名不一致会直接导致该图片在训练时被跳过或报错。训练集、验证集、测试集的划分我按12000比2000比1000来切从源头保证三部分场景分布基本一致而不是简单随机切片后某一类场景全部挤在训练集里。随机切片对于普通数据集没什么问题但夜间场景的差异太大如果验证集里全是完全无光的画面训练集里全是岸边灯光场景验证指标就没有参考意义。3.2 标注规范框住整个船体而不是框住灯光标注工具我用的labelImg选择YOLO输出格式。每张图片会生成一个txt文件文件里每一行代表一个目标格式固定为class_id x_center y_center width height其中class_id从0开始计数后面的四个数值全部归一化到0到1之间。x_center和y_center是目标中心点相对图片宽度和高度的比例width和height是目标框宽度和高度相对图片宽度和高度的比例。归一化这件事必须严格否则训练时目标框位置会乱掉。标注规范是这个数据集最重要的一环也是最容易出问题的一环。夜间船只最显眼的往往是船上灯光人眼第一反应是“把灯框起来”这是大忌。如果只框灯光模型学到的特征是“画面里发亮的点”一旦灯光被遮挡、目标背对镜头或者灯光熄灭检测就彻底失效。正确做法是沿着船体的可见轮廓画矩形框。即便夜间船体轮廓不清晰也要结合前后帧中船的完整形态来判断船体范围框住整个船身而不是框住灯光高光区。对于只露出一部分的船比如被桥梁或岸堤遮挡直接框住可见部分即可。这里给一个实际标注文件中的内容示例0 0.624805 0.438172 0.031250 0.046875 0 0.741136 0.384503 0.056250 0.072975这两行表示图中出现了两艘船class_id都是0第二艘船的目标框明显比第一艘大对应的可能是一艘更靠近镜头的船。3.3 单类别data.yaml配置与标签文件示例单类别数据集的data.yaml配置很简单但有几个细节需要注意。我的配置如下train: ./images/train val: ./images/val test: ./images/test nc: 1 names: [ship]train、val、test三个字段可以写相对路径也可以写绝对路径。如果data.yaml文件放在数据集根目录下用相对路径没问题如果你习惯把数据集放在一个路径训练脚本在另一个路径运行建议写绝对路径或者做好符号链接。我见过不少人在这一步踩坑train路径写错之后训练时数据加载量为0日志里还不会报硬错误直到训练完才发现模型什么都没学进去。nc代表类别数量单类别就是1。names列表里的名称顺序必须与标注文件中的class_id一一对应class_id为0的就是ship。3.4 标注质量自检越界、缺失、空文件一次查清标注完成后我强烈建议先跑一遍质量检查脚本而不是直接开训练。我写过一个小脚本专门检查下面几类问题每个txt文件是否存在且内容非空标注的class_id是否在0到nc-1之间归一化坐标是否越界比如x_center大于1或者width小于0images目录和labels目录是否存在文件缺失或文件名不一致目标框面积是否过小比如宽高都小于图片尺寸的千分之一这种标注大概率有问题脚本核心逻辑用Python写并不复杂import os from pathlib import Path data_root Path(night_ship_yolo) for split in [train, val, test]: img_dir data_root / images / split lbl_dir data_root / labels / split img_files {p.stem: p for p in img_dir.glob(*.jpg)} lbl_files {p.stem: p for p in lbl_dir.glob(*.txt)} for stem in img_files: if stem not in lbl_files: print(f[missing label] {img_files[stem]}) else: with open(lbl_files[stem], r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] if len(lines) 0: print(f[empty label] {lbl_files[stem]}) for line in lines: parts line.split() if len(parts) ! 5: print(f[bad format] {lbl_files[stem]}: {line}) continue cid, x, y, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cid ! 0: print(f[class out of range] {lbl_files[stem]}: {line}) if x 0 or x 1 or y 0 or y 1 or w 0 or h 0 or x w / 2 1 or x - w / 2 0: print(f[bbox out of bounds] {lbl_files[stem]}: {line}) for stem in lbl_files: if stem not in img_files: print(f[missing image] {lbl_files[stem]})这一步能帮你把标注层面的低级错误在进训练前全部清掉。夜间标注比白天更容易出错因为标注员本身也看不清船体边界质量检查是必不可少的环节。4. 数据集规模与场景分布黑夜不是单一光照小目标是主要形态4.1 基础统计图片数、目标数、平均每帧目标数这份数据集我这里整理完的规模如下统计项数值总图片数15000张训练集12000张验证集2000张测试集1000张标注目标总数约48000个单图平均目标数3.2个图像分辨率1920×1080从视频中抽帧时我采用时间间隔抽样和帧间隔抽样相结合的方式避免连续帧之间目标位置几乎不变导致的数据冗余。4.2 黑夜场景的细分构成无光、背景光、月光、薄雾黑夜不是一个单一的光照条件。同样是夜间完全无光、岸边有灯光、月光充足、薄雾笼罩这四种场景下的图像特征差异非常大。如果训练集里大量是岸边灯光场景模型在开阔水域的完全无光环境下基本没法用。我按场景把数据做了划分场景类型占比主要难点完全无光月光微弱约30%目标对比度极低人眼都难以分辨岸基灯光或城市背景光约35%背景灯光和水中倒影极易被误检为船月光充足约20%水面形成亮带目标轮廓与波纹混杂薄雾或水汽约15%目标边缘模糊整体亮度偏低训练时如果发现模型在某种场景下特别容易失效优先去查这类场景在数据集中的占比而不是急着调参。4.3 目标尺度分布为什么小目标占比这么高统计所有标注框的像素尺寸后我得到了一个非常典型的小目标分布目标短边小于32像素的约占总目标数的52%短边在32到96像素之间的约占35%短边大于96像素的仅占13%这个分布符合航拍船只检测的物理规律。无人机在100米高度飞行时一艘十几米长的渔船在1080P画面里的宽度通常只有50到100像素更远处的目标直接缩到二三十像素以下。小目标占比过高会带来两个训练问题一是模型整体mAP会被小目标拉低二是如果只用640×640的训练分辨率32像素的目标会被缩到10像素左右特征几乎消失。所以这个数据集在使用时训练分辨率建议至少用1280后面会展开说。4.4 目录里附带文件说明文档和标签映射除了images、labels、data.yaml我还在数据集根目录放了README.txt说明数据来源、场景分布、标注规范、训练时的建议参数。这些信息看起来不起眼但实际使用时能省大量沟通成本。5. 用这份数据集跑通YOLOV5训练环境配置、超参数与实测指标5.1 训练环境版本组合与显存预算训练环境方面我使用YOLOV5 7.0分支搭配Python 3.9、PyTorch 2.0、CUDA 11.8。这套组合在稳定性上比较成熟YOLOV5的依赖项在requirements.txt里都锁好了直接安装不会出幺蛾子。硬件上我在RTX 3080 10G显卡上训练YOLOV5s。这里要提醒一句如果直接拿1920×1080原图分辨率训练10G显存完全不够。YOLOV5训练时会按img参数将输入图做letterbox缩放所以我把训练分辨率设为1280×1280batch size设为8显存占用大约8.5G刚好放下。如果你显存只有6G建议把分辨率降到960或者640但要做好小目标检测效果下降的心理准备。5.2 三处关键配置data.yaml、模型结构、训练参数用这份数据集训练需要改三个地方。第一处是data.yaml把train、val路径指向本机实际路径。第二处是模型结构文件我用的models/yolov5s.yaml把里面的nc参数从80改为1。第三处是训练命令核心参数如下python train.py \ --data data.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 50 \ --batch-size 8 \ --img 1280 \ --device 0 \ --name night_ship这里最值得说的是img参数。前面统计过这份数据集里小目标占比超过一半如果训练分辨率只有640航拍画面里那些几十像素的船缩放后只剩几个像素模型根本学不到有效的边缘和纹理特征。把分辨率拉到1280后小目标的特征保留程度明显改善mAP的差距非常显著。我对比过同一份数据在640和1280两种分辨率下训练1280的小目标mAP高出接近10个百分点。代价是训练时间和显存占用增加了但这个代价在航拍小目标场景下完全值得。5.3 训练结果实测mAP、PR与漏检情况在训练集12000张、验证集2000张、测试集1000张的划分下YOLOV5s训50轮的结果如下指标数值Precision0.898Recall0.913mAP0.50.921mAP0.5:0.950.786小目标单独mAP0.50.864mAP0.5达到0.921整体水平的可接受度较高。但真正值得关注的是小目标单独mAP0.5为0.864比整体低了约6个百分点这在小目标占比过半的数据集里是正常现象。训练过程中我观察到前20轮mAP上升速度很快30轮后逐渐变缓40到50轮基本收敛。如果loss曲线在中后段出现先降后升优先检查两件事学习率是否过高以及数据里是否存在标注框越界或者类别id错误的问题。比起盲目调学习率先把标注质量查一遍往往更有效。5.4 推理验证detect命令与可视化检查训练完成后测试集推理命令如下python detect.py \ --weights runs/train/night_ship/weights/best.pt \ --source ../night_ship_yolo/images/test \ --img 1280 \ --conf 0.25 \ --save-txt \ --save-conf推理结束后建议把输出目录里所有带检测框的图片过一遍重点看两件事。第一有没有把水面倒影、岸边灯光、桥墩误检成船。第二画面边缘的小目标有没有漏检。可视化检查比只看数值指标更能暴露问题。6. 黑夜航拍最容易翻车的三个坑倒影误检、低对比漏检、标注口径6.1 倒影和灯光反光造成的误检黑夜航拍船只检测里最常见的误检来源是水面倒影和灯光反光。夜晚的水面就像一面镜子月亮倒影、岸边路灯倒影、船只灯光在水面上的长条亮带这些区域的形状和亮度在某些角度下非常接近船体特征模型很容易把它们当成目标检出来。处理这个问题的核心在标注阶段。遇到倒影场景时只标真实船体不标倒影区域。即便倒影的轮廓比船体还清晰也不能标注进去否则模型会把“水面上拉长的亮带”也当成船的特征。如果训练完成后还是会出现倒影误检我会收集一批误检样本把它们作为难例加入训练集再补一轮训练。这种针对性的难例挖掘比单纯增加epochs效果好得多。6.2 低对比度小目标造成的漏检漏检通常出现在完全无光的环境下。船体与水面之间的亮度差异只有几个灰度级目标在画面里又是几十像素的小块模型很难提取到有效特征。这种情况下我建议组合拳式处理。第一确保训练时开了Mosaic增强和MixUp。Mosaic把四张图拼接成一张目标周围会拼接出各种不同背景能有效提升模型对低对比度目标的适应能力。第二把训练分辨率保持在高位数1280以上。第三如果部署设备的算力允许把模型从YOLOV5s升级到YOLOV5m甚至YOLOV5l更大的骨干网络能获得更好的特征表达能力。但这三者是有优先级的对于航拍小目标场景提升输入分辨率永远是性价比最高的手段模型参数量的提升只能作为辅助。6.3 标注口径不一致导致模型学了错误特征这是我这个数据集项目里最深刻的一次翻车。第一轮标注时我对“夜间船怎么标”没有做出足够严格的规范结果三位标注员的理解各不相同有人只标注灯光有人把灯光的高光扩散区域全部框进去还有人老老实实沿着船体轮廓标。训练出来的模型对灯光大小极度敏感一艘船如果灯光小、船体大模型会漏检如果岸边有个高亮灯箱形状类似灯光的反而会被误检成船。发现这个问题后我统一了标注规范——框住整个船体而不是框住灯光然后让同一个人对全部15万张左右的标注结果做复检修正。修正后模型mAP0.5从0.871提升到0.921涨了5个百分点。这个案例说明标注口径统一这件事再怎么强调都不过分它属于“训练还没开始就已经决定上限”的环节。想让数据质量可控就得在标注阶段定好规范并安排复检环节而不能指望算法层面去弥补标注混乱。6.4 如果你在自己采集的夜间航拍数据上效果不好拿到这份数据集训练完你再拿自己的夜间航拍视频去测效果可能不理想。原因未必是数据有问题更可能是分布差异。建议按这个链路排查第一步对比你自己的图像与训练集图像的亮度分布和对比度分布。夜间传感器的型号不同、增益参数不同画面特征可能差异巨大。第二步把训练集图像和推理图像放在一起可视化对比看看目标呈现的方式是否接近。第三步如果是传感器差异导致的分布偏移不要只调参数把你自己的数据小批量标出来在这份数据集训练出的模型基础上做微调。第四步如果目标出现在雨夜、强雾、强逆光等训练集覆盖不足的场景最有效的动作是补数据而不是改训练策略。7. 使用这份数据集时我建议你额外做的事7.1 针对“完全无光”样本较少的重采样策略这份数据集里岸基灯光和城市背景光场景占了35%完全无光场景占30%。如果你的应用场景是远离岸边的开阔水域完全无光才是主要工作环境那么训练时建议按场景重采样提高完全无光样本在每轮训练中的出现权重。实现方式不复杂根据标注文件或图片文件名中的场景标签把样本分成几组对少数场景做重复采样让每个场景在训练时贡献的比例接近。我测下来把完全无光场景的采样权重提高到1.5倍后模型在该场景下的召回率提升明显而岸边灯光场景的mAP几乎没有下降。7.2 从单类别扩展到多类别船只检测单类别数据集适合先跑通流程、验证模型但实际业务里往往需要区分渔船、货船、客船、执法船等。扩展多类别不用推倒重来在现有标注基础上增量标注即可。只需要把data.yaml的names改成新类别列表nc改成对应数量结构文件里的nc也跟着改然后用单类别权重做迁移学习初始化用小规模多类别样本微调就能收敛得很快。7.3 转换为COCO/VOC格式或迁移到YOLOV8虽然这份数据集本身就是YOLOV5格式但实际项目中经常需要转换格式。转成COCO可以用脚本读取每个txt换算回像素坐标写入标准COCO json需要把类别映射表、图片尺寸对应关系都处理对。转成VOC也是类似思路把归一化坐标转回绝对值并写入XML。现在很多人直接上YOLOV8好消息是YOLOV8同样支持这种YOLO格式的txt标注只需要把data.yaml改成YOLOV8的写法path: /absolute/path/to/night_ship_yolo train: images/train val: images/val test: images/test nc: 1 names: [ship]训练命令从train.py换成了yolo detect train dataxxx modelyolov8s.pt本质上不复杂格式兼容做得很好。7.4 数据合规与二次使用建议最后关于数据合规需要提醒一句。训练数据的采集和发布一定要保证来源合规涉及真实地理位置信息的航拍数据建议做脱敏处理不要携带可识别的个人隐私信息。如果你基于这份数据训练出了模型二次发布或商用时注意确认原始素材的授权范围不要直接用采集自非授权区域的视频做商用模型训练。这类问题在数据项目里属于底线不能忽视。关于这份数据集我最大的体会是夜间航拍的数据质量直接决定模型上限而标注规范的统一比任何算法调参都关键。如果你只做一件事先把船体与灯光的标注口径理清。数据集的整理不是把图片简单丢进文件夹它是对业务场景理解的一次系统性沉淀希望这套数据集和这篇文章能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表