ARTICLE DETAIL

资讯详情

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

遥感图像目标识别实战:YOLOv5切片、训练与调参全指南

遥感图像目标识别实战:YOLOv5切片、训练与调参全指南 简介面向计算机视觉与遥感应用学习者的YOLOv5目标识别项目资料包完整覆盖模型训练、验证与推理的关键环节支持自定义遥感图像数据集训练适合本科及研究生毕业设计、课程项目、竞赛复现以及初学者技术学习。资源包共156个文件以Python脚本、YAML配置文件、PT权重文件和图像样本为主体辅以shell脚本、说明文档及备份文件整体约242.81MB目录结构清晰便于按模块查阅与二次开发。除核心代码外还提供Docker环境配置与训练事件文件可帮助用户快速复现训练环境并跟踪模型迭代过程降低环境搭建门槛同时附有经导师认可的高分项目说明涵盖算法原理与调参思路。目前已有42人学习下载资料描述称代码均经运行验证可放心作为项目基础进行扩展优化或直接用于教学研究。资源来源于网络分享仅用于学习交流请勿商业使用。1. YOLOv5目标识别用在遥感图像上先想清楚这三件事遥感图像目标识别说白了就是把卫星或低空无人机拍回来的大影像里飞机、舰船、车辆、储油罐这些地面目标找出来并框住。现在做这块很少有人再从零设计网络YOLOv5几乎是默认的baseline理由很直白——训练链路完整、显存门槛不高、开源权重多、部署生态成熟改造起来比两阶段检测器省事得多。但遥感图和自然照片有本质差别目标小、密度高、方向随意直接把COCO上那套配置搬过来mAP会很难看。这篇按一个「项目资源包」的完整落地顺序来写数据切片、格式转换、训练调参、推理部署以及那些训练时一定会踩的坑。适合正在做遥感解译工程、毕业设计或算法预研的人读完能照着重现也知道参数往哪个方向试。2. 遥感图像和自然图像差在哪数据形态决定训练策略2.1 遥感目标的三个尺度真相小、密、乱先说目标尺度。自然图像检测数据集中目标通常占画面的比例不小COCO里一个行人可能占整张图的十分之一。遥感图像完全不是这个概念一张0.5米分辨率卫片上的小汽车车长4米折算下来只有8×3像素左右一架普通客机翼展按30米算也只有60来像素。YOLOv5的默认anchors是从COCO目标统计来的最小的anchor是4×4像素级别但它真正擅长的是几十到几百像素的目标再往下纯靠低层特征硬扛。这就是遥感场景第一个反直觉点所谓大场景目标其实是小目标。第二个是密度和背景双高。港口里并排几十条船、停车场里几百辆车是常态密集排列时NMS很容易把相邻目标当场抑制掉。同时遥感背景不是自然照片里那种干净草地城市肌理、云层、农田、水体全混在一起复杂纹理在模型眼里经常长得很像一个「目标」。第三个是朝向任意飞机抬头低头的方向都有舰船在水道里拐弯YOLOv5这类水平框检测器对方向不敏感但水平外接矩框会让目标特征掺进大量背景。这三点叠加决定了遥感场景做YOLOv5很多参数和策略得跟自然图像项目反着来。提示判断一个模型能不能识别某个遥感目标先数目标有几个像素、占切片多大比例。小于16×16像素的目标单提分辨率不如先检查切片参数是否合理。我在项目里拿到新数据集的第一件事不是开训而是写一段脚本统计所有目标框的宽度和高度分布看中位数落在哪个区间。小于16像素按小目标策略走16到64之间按常规走大量几百上千像素的大目标则要把切片尺寸加大。这一步花二十分钟能省掉后面来回调参的几天时间。2.2 大图必须先切片不切图直接训练是最大误区遥感影像一景动辄几万乘几万像素把整景图直接喂给YOLOv5它内部第一步就resize到训练尺寸比如640×640。一个原始大图里10像素的目标缩放后连1像素都不到直接消失。这就是「不切图训练必翻车」的数学原因跟显存够不够没关系。常见做法是把大图按滑窗切成统一尺寸的切片。切片尺寸是第一个要调的参数切大了切片里照样大量小目标显存吃紧切小了目标被切碎上下文不够。我在遥感项目里一般从1024×1024起步配合训练输入1280来用目标平均像素特别小就切到640让目标在输入图像里占比更大目标大时切到1536甚至2048。切片重叠率默认10%~15%边界目标会被切成两半重叠切片能保证至少有一部分保留完整。切片后如果大量目标被切掉超过40%面积就把重叠率提到20%以上。还有一个训练与推理之间的隐蔽坑训练切片和推理切片的尺寸最好保持一致。很多人在训练时用1024切片推理时却把整景大图直接resize进模型精度崩掉还找不到原因。实际上遥感目标识别和遥感图像语义分割在这一点上思路完全一致——大图切块、模型逐块推理、结果拼回全图只是语义分割关心每个像素归属目标识别还要在拼图后再叠加一次全图NMS。凡是做过遥感图像融合这类多源数据项目的人对「源图坐标系」的统一维护都有体会切片也是同一个道理。2.3 标注坐标系与格式水平框、旋转框和多传感器差异遥感公开数据集里标注形态有三种水平框、旋转框、像素级分割掩膜。DIOR、NWPU VHR-10这类以目标检测为主的数据集大多是水平框DOTA系列以旋转框为主一些SAR舰船数据集也倾向旋转框。YOLOv5原生只吃水平框归一化cx、cy、w、h所以当你拿到的是DOTA风格的四点旋转框第一个决定就是要不要转成水平外接矩框。我一般先做一版水平外接框的baseline把四点坐标取min/max就是水平外接框写脚本几十分钟搞定。代价是框内背景比例上升密集舰船会产生大量重叠的水平框跨类别误检也会变多。如果baseline跑完发现这类问题占比高再考虑带角度信息的OBB变体但训练复杂度、anchors设定、后处理全都要改不是第一优先。另外所有切图和转换都在像素坐标系下完成不要混进地理坐标。栅格分辨率GSD不统一时同一个目标在不同源数据里像素数可能差十几倍直接冲击训练集的尺度分布所以多来源数据一般要分别做归一化和配准再考虑光学加SAR或红外的多模态目标识别。类别不均衡也是遥感数据躲不开的问题油罐、舰船这类目标动辄上万个立交桥、体育场可能只有几十个。类别数量差三个数量级时模型梯度基本被多数类带走少数类的AP几乎为0。常见做法是按类别数量做重采样或者对少数类调高cls_loss权重但权重调过头会带来误检。这个问题的根治还是数据采集权重只是补偿手段。维度自然图像COCO类遥感图像图像尺寸1280×720上下单景可达数万×数万目标典型尺度占图5%~50%几像素到几百像素目标密度单图几个到十几个单切片可达几十个以上背景复杂度相对可控城市/云/农田/水体混杂标注主格式水平框水平框或旋转框输入策略整图resize切片加重叠3. 把遥感标注变成YOLO能吃的格式VOC转YOLO与大图切片脚本3.1 一个可复现的资源包目录怎么组织拿到一个「项目资源包」我第一件事不是看权重而是看目录结构。一个能落地的遥感YOLOv5工程常见做法是把资源包拆成下面四块. ├── dataset/ # 数据资产不混入脚本 │ ├── raw/ # 原始大图 原始标注(JSON/XML) │ ├── slices/ # 切片后的图像 │ └── slice_labels/ # 与切片一一对应的YOLO txt标签 ├── tools/ # 一次性脚本 │ ├── voc2yolo.py # 标注格式转换 │ ├── slice_image.py # 大图切片 │ └── inspect_labels.py # 标签可视化与校验 ├── configs/ │ ├── remote.yaml # 数据集配置 │ └── hyp_remote.yaml # 超参数配置 └── weights/ # 预训练权重与训练产物为什么这么拆dataset目录保持干净所有格式转换和切片都是可重复执行的一次性脚本源数据永远原样保留想改切片参数随时重跑。很多人直接在raw上改了标注又切了图后面发现标注有错或切片尺寸选错只能全部重来。把原始数据当作只读资产是给自己留后悔药。3.2 标注工具与格式转换把VOC XML转成YOLO txt遥感图像标注这块白天用LabelImg做水平框最顺现在更推荐支持旋转框输出的开源标注器比如X-AnyLabeling这类工具。原始标注尽量保留旋转框后面转水平框还是做OBB都由自己决定。无论标注工具输出什么落到工程里最终都要统一成YOLO格式一行一个目标类别 中心点x 中心点y 宽度 高度四个坐标都是除以图宽高的归一化值。# tools/voc2yolo.py import os import xml.etree.ElementTree as ET CLASSES [airplane, ship, storage_tank, vehicle] # 与 data yaml 的 names 严格一致 def clip(x, lo, hi): return max(lo, min(hi, x)) def convert_one(xml_path, label_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: raise ValueError(f未知类别 {name} in {xml_path}) # 宁可报错不要静默跳过 box obj.find(bndbox) x1 clip(float(box.find(xmin).text), 0, img_w - 1) y1 clip(float(box.find(ymin).text), 0, img_h - 1) x2 clip(float(box.find(xmax).text), 0, img_w - 1) y2 clip(float(box.find(ymax).text), 0, img_h - 1) cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{CLASSES.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) label_path os.path.join(label_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(label_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: convert_one(./Annotations/0001.xml, ./labels)这段脚本有三个容易被忽略的细节。第一宽高必须取XML里size节点的值不要自己用OpenCV重新读图片尺寸。标注工具记录的size和实际图片一旦出现细微不一致所有归一化坐标全部偏移而且这种错误在监督训练里最早出现为「验证集mAP可以但新图全歪」。第二坐标做了clip防止标注时手滑画的越界框污染训练数据。第三出现未知类别直接抛异常第一时间暴露标注不一致而不是等训练完看指标才知道类别对不上。3.3 大图切片滑窗切图标签跟着图一起走切片脚本的核心是「图切到哪标签坐标就变换到哪」。下面是我常用脚本的核心逻辑去掉了进度打印和日志保留必须的参数和边界处理# tools/slice_image.py import os import cv2 def slice_big_image(img_path, label_path, slice_size1024, overlap0.15, min_keep_px5, out_img./slices, out_lbl./slice_labels): img cv2.imread(img_path) H, W img.shape[:2] step int(slice_size * (1 - overlap)) # 先把标签反归一化回像素坐标 objects [] with open(label_path) as f: for line in f.read().splitlines(): c, cx, cy, bw, bh map(float, line.split()) x1 (cx - bw / 2) * W y1 (cy - bh / 2) * H x2 (cx bw / 2) * W y2 (cy bh / 2) * H objects.append((int(c), x1, y1, x2, y2)) slice_id 0 for y0 in range(0, H, step): for x0 in range(0, W, step): # 最后一个切片回退一步保证输出尺寸恒等于 slice_size if x0 slice_size W: x0 W - slice_size if y0 slice_size H: y0 H - slice_size x1s, y1s x0, y0 x2s, y2s x0 slice_size, y0 slice_size new_objects [] for cls, x1, y1, x2, y2 in objects: nx1, ny1 max(x1 - x1s, 0), max(y1 - y1s, 0) nx2, ny2 min(x2 - x1s, slice_size), min(y2 - y1s, slice_size) if nx2 - nx1 min_keep_px or ny2 - ny1 min_keep_px: continue orig_area (x2 - x1) * (y2 - y1) keep_area (nx2 - nx1) * (ny2 - ny1) if keep_area / max(orig_area, 1e-6) 0.6: continue # 目标被切掉的面积超过 40% 就丢弃 nc_x ((nx1 nx2) / 2) / slice_size nc_y ((ny1 ny2) / 2) / slice_size nw (nx2 - nx1) / slice_size nh (ny2 - ny1) / slice_size new_objects.append(f{cls} {nc_x:.6f} {nc_y:.6f} {nw:.6f} {nh:.6f}) if len(new_objects) 0: continue # 完全没有目标的白切片不写减少训练时的无效计算 crop img[y1s:y2s, x1s:x2s] cv2.imwrite(os.path.join(out_img, f{slice_id:05d}.jpg), crop) with open(os.path.join(out_lbl, f{slice_id:05d}.txt), w) as f: f.write(\n.join(new_objects)) slice_id 1三个参数是真正决定切片质量的overlap0.15意味着步长是切片尺寸的85%边界目标会出现在相邻两个切片里训练时重复出现是有意的但超过0.2后训练数据冗余急剧上升同样epoch花的时间多收益递减。min_keep_px5表示被切得只剩几个像素的目标直接丢掉这种碎片学不出有效特征还会把噪声当目标。keep_area比例我默认0.6目标贴边被切掉一半时丢弃因为一个只露出30%的舰船人都不一定能分类强喂给模型只会制造矛盾样本想保留更多边界目标就降到0.4但要留意验证时重叠切片拼接的重复框。切片做完一定要可视化校验把切片图和txt标签画回同一张图上随机抽50张看框和目标是否贴合。这一步别省标注工具里看着完全没问题的数据经过格式转换加切片加坐标变换三重处理什么错误都可能出现。跳过检查直接训练的出来的指标莫名上不去回头查多半是切片标签画错了位置。4. 训练自己的遥感数据集目录、超参数与anchors的调法4.1 数据集配置与训练命令从一份yaml开始yolov5训练自己的数据集第一步是把数据组织和yaml写对。很多第一次做遥感项目的人卡在这里半天问题往往不是命令不会敲而是目录结构和类别顺序没对齐。# configs/remote.yaml train: ./dataset/slices # 切片后的训练图片目录 val: ./dataset/slice_labels # 验证切片图目录 # train 和 val 要分成两个目录不要用同一个目录随机分 nc: 4 names: [airplane, ship, storage_tank, vehicle]对应训练目录里每张图旁边一定有一个同名的txt标签文件这是YOLO系列的约定。检查方式统计labels目录里的txt数量和图片数量应该一致再随机抽几个txt打开确认每行第一个数字在0到nc-1之间。类别顺序这里要格外慎重训练到一半发现类别顺序错了重跑不心疼之前的权重基本作废数据标注阶段就要锁死这个顺序表。python train.py \ --data configs/remote.yaml \ --weights weights/yolov5s.pt \ --img 1280 \ --batch 8 \ --epochs 300 \ --cache ram \ --device 0 \ --project runs/remoteimg1280是遥感小目标场景里最有效的单项改动但代价是显存和训练时间都涨得厉害同一批数据640输入训练一轮大约只需要1280四分之一的算力。batch8对应12GB显存在1280输入下的常见取值显存只有6GB就把img降到102432GB以上才考虑batch16。cache ram是把图片预载入内存遥感切片一般几千到几万张ram缓存能明显缩短训练时间如果数据量太大占满内存触发swap反而更慢这时候换--cache disk。另外如果某些切片是全黑或损坏的ram缓存会在训练一开始就报读取错误这也是排查方向之一。4.2 网络结构与anchors从yolov5网络结构图看小目标该靠哪层yolov5网络结构图里主干输出的三个检测层通常叫P3、P4、P5分别对应步长8、16、32的特征图。P380×80负责小目标P440×40中目标P520×20大目标。遥感图像目标平均像素经常小于32×32理论上主要靠P3层接住。如果一个切片里同时存在几像素的小车和几百像素的储油罐尺度跨度横跨三个检测层默认anchors分布就不够用了。解决思路有两个。第一个是重算anchors。yolov5在训练开始前会对数据集自动做一次anchor聚类把训练日志里Best Possible Recall这一项打出来。这个值如果低于0.98说明anchor数量或分布配不上数据要在data yaml里手工预设anchor或者换更大的预训练模型档位。第二个思路是给极小目标加P2检测头步长4的特征图分辨率更高很多做遥感小目标优化的结构改动都从这里入手。代价是计算量大增而且P2层特征语义性弱数据量不够时反而掉点。我一般只在平均目标像素小于12×12的项目里尝试P2头其他情况靠提升输入分辨率和切片参数解决。4.3 超参数怎么调别上来就全动yolov5超参数文件里关键项是这几个lr0初始学习率、momentum动量、weight_decay权重衰减、mosaic马赛克增强、mixup混合增强、fliplr/flipud翻转、degrees旋转、hsv_h/hsv_s/hsv_v颜色扰动。遥感图像有两个特殊影响要处理一是fliplr/flipud这类几何增强对航拍目标本身没问题但如果目标朝向有明确含义翻转会隐含语义变化遥感目标识别一般不管这个全开二是hsv颜色扰动要谨慎遥感影像的光谱是地物本征信息的载体饱和度过大或色调偏移会改变植被、水体、建筑物的相对表现模型容易学到错误的光谱关联。常见做法是hsv_h从默认0.015降到0.005hsv_s从默认0.7降到0.3。mosaic和mixup在遥感场景的贡献方向不一样。mosaic把四张图拼成一张小batch里能见到更多目标对密集场景友好mixup把两张图按比例混合目标会变透明小目标本来像素就少混合后特征大概率消失我一般数据量大时把mixup设0数据量小时保留0.1附近。loss权重里背景误检严重时调高obj_pw类别混淆严重时调高cls_pw一个影响「是不是目标」一个影响「到底是什么」。训练策略上小数据集先冻结主干前20个epoch传--freeze 10固定backbone让检测头先对齐遥感目标的尺度分布再解冻全模型。几千张的小数据集直接全量微调几乎必过拟合。提示遥感目标检测指标崩掉时先想超参数是不是原样搬的自然图像默认值。图像形态差异大超参数不存在通用只有先跑通再小步调优这个顺序是通用的。4.4 预训练权重与训练过程的监控预训练权重优先用COCO的官方权重遥感目标与COCO差异不小但底层边缘、纹理、形状特征通用。模型档位选择上s模型在12GB显存跑1280输入能到batch 8l/x模型在切片数据几万张以内很容易过拟合。我的做法是先用s把整个pipeline跑通看验证集每个类别的AP分布确认数据量充分且小目标AP被明显拉低才换m或l。一上来就x模型显存爆、训练时间翻倍、指标不一定涨是常见翻车路线。训练过程中不要只盯总loss。box_loss、cls_loss、obj_loss三条曲线要分开看P和R曲线也要看趋势。我发现很多遥感项目有个共性现象验证集Precision很高但Recall很低说明大量目标被漏掉通常是小目标AP拖累反过来Precision低Recall高就是误检太多。这两条曲线分开看才能决定下一步动NMS参数还是动loss权重。5. 遥感YOLOv5训练避坑记录五个最容易翻车的地方5.1 OOM不全是batch的锅也可能是缓存和resize在作怪现象训练刚启动就报CUDA out of memory或者第一个epoch快结束时OOM。 原因遥感切片多是1024~1280输入batch8是常规值调低batch还溢出就要查另外两个点。一是--cache ram把系统内存占满数据加载进程崩溃二是训练时开了--multi-scale动态缩放让部分batch被放大到更高分辨率。 解决先固定--img 1024 --batch 8把pipeline跑通再逐步加码开multi-scale前确认显存余量至少20%cache ram要盯系统内存总容量不确定就换cache disk。我记得有一次排查半天最后发现是数据目录里混进了一张损坏的JPGOpenCV读出来是空的ram缓存读到那张图时把内存打爆了。5.2 loss变成NaN先查标签再查学习率现象训练到某个epochbox_loss或cls_loss突然变NaN之后曲线全部失效。 原因最常见的是标签里有非法值类别id等于nc、坐标出现负数或大于1、txt里混进空行。切片脚本坐标变换时如果图宽高为0或者label文件末尾多了一个换行符就会产生这类脏数据。第二个常见原因是学习率过大加mosaic导致数值溢出1280输入比640更容易触发。 解决训练前写个标签检查脚本遍历所有txt校验每个值是否在合法区间这个脚本必须进tools目录每次跑新数据集都要执行标签无误后把lr0从0.01降到0.001mixup设0重跑。遥感切片数量大脏标签概率比自然图像标注高这条几乎必踩。5.3 小目标漏检密集场景里NMS把目标「饿死」了现象验证集mAP0.5有80%推到新大图时小目标大量漏检或者密集码头场景里一个框套住好几条船。 原因NMS默认IoU阈值0.45密集排列的船之间IoU很容易超过0.45置信度稍低的目标直接被杀掉。另一个隐藏原因是推理时整景resize训练用1280切片推理直接resize进模型小目标尺寸缩水好几倍必然漏检。 解决推理设置--iou 0.3并打开--agnostic-nms前者减少密集目标误抑后者处理多类别重叠框更稳推理必须切片切片尺寸和训练一致还是漏检就回头看anchors的Best Possible Recall低于0.98就重算anchors。5.4 验证集指标虚高从一个区域随机切分是自欺欺人现象训练时val mAP稳定在85以上换一个地区或季节的新图性能腰斩。 原因很多项目把一景大图切成几百个切片后随机分成train和val训练集和验证集来自同一景图空间重叠严重模型其实记住了具体地物的纹理而不是目标的通用特征。这是遥感目标识别最隐蔽的坑。 解决按「景」划分数据集。一景原始图要么全进train要么全进val不允许它的切片同时出现在两边。多来源数据还要按传感器、时相、地域分层抽样。如果资源包里的数据已经被切好且来源信息丢失至少按文件名前缀聚类后划分前缀相同的切片不能跨train和val。5.5 背景误检爆炸云、建筑、农田都在报警现象验证mAP还过得去跑全景大图时输出几百上千个框假目标集中在云边缘、立交桥、规则农田纹理处。 原因遥感背景复杂YOLOv5默认conf_thres0.25在自然图像上合适在遥感上经常不够加上没有显式喂负样本模型对「背景纹理长成目标形状」这件事没有免疫力。 解决第一层提高推理阈值conf_thres先调到0.35到0.4看误检率下降幅度代价是召回下降要用可视化确认。第二层收集误检区域作为背景图加入训练集只放图不放标签这是工程里最常见的hard negative做法加几百张往往有明显效果。第三层才考虑调obj_loss权重权重调过头会抑制所有正样本属于伤敌一千自损八百。6. 部署推理与模型瘦身验证精度、小目标后处理与剪枝量化6.1 推理参数conf与NMS在小目标上的取舍遥感推理有两个后处理参数我每次都要调。conf_thres背景误检多就往上加到0.35到0.4宁可漏几个也不能让结果图没法看iou_thres密集目标场景从默认0.45降到0.3配合--agnostic-nms让跨类别的重叠框不互相抑制因为遥感里舰船和码头的吊机经常叠在一起。推理大图务必切块切块尺寸和训练一致推理完按源图坐标回贴重叠区再做一次全图NMS去掉重复框。6.2 剪枝与量化模型瘦身时小目标精度最先受伤边缘部署是遥感项目的常见终点比如RK3568盒子、树莓派4B或5上跑自己训练的yolov5模型通道剪枝和int8量化是最常走的两条瘦身路线。训练后结构化剪枝能省30%左右的参数量但对小目标的伤害来自低层特征通道被裁剪int8量化对小目标AP的伤害往往是整体的两倍以上因为小目标的量化噪声比本来就大。我的习惯是剪枝和量化前先记录一份baseline尤其按目标尺寸分层的AP每做一步重测一次如果小目标AP下降超过三到五个点就停止压缩改用更大输入分辨率或换小模型档位补偿而不是硬压。6.3 验证不止mAP按尺寸分层看AP按类别看混淆矩阵mAP0.5的高低反映不出「小目标AP掉到多少」。我现在的习惯是训练完必看三样东西val脚本输出的按类别AP表找到拖后腿的类别混淆矩阵的漏检行搞清楚哪两个类别互相混淆随机抽样可视化把GT框、预测框、置信度画在同一张切片上肉眼过一遍重点看密集区域和背景复杂区域的框质量。这三样都过了心里才有底敢说模型能交付。这套从数据切片、格式转换、训练调参到部署验证的流程覆盖了我做过的大部分遥感目标识别项目希望帮到你。本文还有配套的精品资源点击获取
返回列表