遥感目标检测实战:YOLOv5小目标识别全流程调参与部署避坑
简介面向计算机视觉与遥感应用方向的YOLOv5目标识别项目资源包适合高校学生、教师及企业研发人员用于毕业设计、课程实践或初期项目展示。资源完整收录基于YOLOv5的遥感影像目标检测代码、模型权重、配置文件及说明文档代码均通过运行验证且为答辩95分的优秀成果具备较高参考价值。压缩包内共156个文件以py源码、yaml配置、pt权重为核心辅以png/jpg图像样例、sh训练脚本、md文档、TensorBoard训练日志及Dockerfile环境配置总计约242.81MB结构清晰便于按模块学习与二次开发。对于入门者可结合说明文档和日志理解检测流程对于进阶者可基于现有代码替换数据集或调整参数快速开展实验。已有42人下载学习作为算法理解、实验复现或功能扩展的起点。1. 遥感图像目标识别为什么值得用 YOLOv5场景、痛点与资源包定位拿到一个 YOLOv5 算法在遥感图像目标识别中的应用项目资源包很多人的第一反应是“赶紧跑通一个 demo”但真正做过遥感目标检测的人会告诉你最难的不是让训练脚本跑起来而是把一张动辄 1 亿像素的大尺寸遥感影像变成模型能吃得下的输入。遥感影像里的目标往往只有几十个像素背景却是大片的地物、阴影和噪声YOLOv5 虽然是通用检测器里的成熟方案直接套用到遥感场景一样会翻车。这个项目资源包解决的就是这么一件事把数据预处理、环境配置、训练调参、部署推理这些零散的工程步骤串成一条能复现的路径让从业者不用从零踩坑。这篇文章适合两类人一类是刚接触遥感目标检测、需要快速跑通 YOLOv5 全流程的研究生另一类是要把模型放到实际业务里的算法工程师你们关心的不是概念而是参数怎么设、损失曲线怎么看、切图重叠度取多少。2. 数据与环境的准备遥感图像标注和 YOLOv5 环境配置2.1 遥感图像标注从 GEOJSON 到 YOLO TXT 的转换逻辑遥感图像和普通自然图像最大的区别在于尺寸和坐标系统。普通照片可能是 1920×1080直接喂给 YOLOv5 就能训练遥感影像往往来自卫星或无人机单张 TIF 可能是 20000×20000 像素而且带有地理参考信息标注文件通常是 GeoJSON 格式里面存的是经纬度或投影坐标。YOLOv5 需要的标注格式是每张图片对应一个 txt 文件每一行是class_id x_center y_center width height坐标全部归一化到 0 到 1 之间。所以做遥感目标检测第一步要处理的数据格式转换代码逻辑并不复杂但坐标系的坑很多。我一般会写一个 Python 脚本用 GDAL 读影像的仿射变换参数把 GeoJSON 里的地理坐标映射到像素坐标再做归一化。import json from osgeo import gdal, ogr def geojson_to_yolo(geojson_path, tif_path, output_txt_path, class_map): 将 GeoJSON 标注转为 YOLO 格式的 txt 文件 :param geojson_path: 标注文件路径 :param tif_path: 对应遥感影像路径用于读取地理变换参数 :param output_txt_path: 输出 txt 路径 :param class_map: 类别名到 id 的映射例如 {ship: 0, car: 1} ds gdal.Open(tif_path) geo_transform ds.GetGeoTransform() # geo_transform[0] 是左上角 xgeo_transform[3] 是左上角 y # geo_transform[1] 是像素宽度geo_transform[5] 是像素高度通常为负 x_origin geo_transform[0] y_origin geo_transform[3] pixel_width geo_transform[1] pixel_height geo_transform[5] with open(geojson_path, r, encodingutf-8) as f: data json.load(f) lines [] for feature in data[features]: # 假设 geometry 是 Polygon取外包矩形作为检测框 coords feature[geometry][coordinates][0] class_name feature[properties].get(class, unknown) if class_name not in class_map: continue # 计算地理坐标的最小外接矩形 xs [c[0] for c in coords] ys [c[1] for c in coords] min_x, max_x min(xs), max(xs) min_y, max_y min(ys), max(ys) # 地理坐标转像素坐标 px_min (min_x - x_origin) / pixel_width px_max (max_x - x_origin) / pixel_width py_min (min_y - y_origin) / pixel_height py_max (max_y - y_origin) / pixel_height # 转 YOLO 格式 x_center (px_min px_max) / 2 y_center (py_min py_max) / 2 w px_max - px_min h py_max - py_min # 归一化需要知道影像宽度和高度 width ds.RasterXSize height ds.RasterYSize x_center_norm x_center / width y_center_norm y_center / height w_norm w / width h_norm h / height class_id class_map[class_name] lines.append(f{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {w_norm:.6f} {h_norm:.6f}) with open(output_txt_path, w) as f: f.write(\n.join(lines)) print(f转换完成共处理 {len(lines)} 个目标)这段代码里有一个关键细节就是pixel_height通常是负值因为遥感影像的坐标原点在左上角y 轴向下但地理坐标的 y 轴是向上的。如果不注意这个正负号转换出来的标注框会上下颠倒训练出来的模型精度直接归零。另一个参数是class_map遥感场景里经常会出现背景类别比如草地、水体这类不需要检测直接在脚本里过滤掉就行。标注完成之后影像的大尺寸问题是绕不开的。YOLOv5 的输入尺寸一般在 640×640 到 1280×1280 之间直接把 20000 像素宽的影像缩放到 640 像素小目标就彻底看不见了。通常做法是裁剪成若干个小 patch比如 1024×1024 大小然后让标注框跟着一起裁。这个裁剪需要做重叠覆盖不然目标正好落在两块的边界上就会被截断。资源包里如果有split_images.py这类工具通常会用 15% 到 30% 的重叠率这是我试下来比较稳妥的区间后面避坑章节再细说。2.2 用 Conda 搭建 YOLOv5 训练环境依赖冲突的处理经验环境配置是新手开始碰壁的第一步YOLOv5 的官方仓库已经维护得比较干净但遥感图像处理库和深度学习框架的版本冲突几乎每个人都会遇到。常见的坑是 GDAL 和 PyTorch 两个库对 Python 版本的要求不一致或者 OpenCV 版本不兼容。我一般会重新创建一个干净的 conda 环境指定 Python 3.9把 PyTorch 和 GDAL 环境隔离避免互相污染。# 创建独立环境指定 Python 版本 conda create -n yolo_remote python3.9 -y conda activate yolo_remote # 先安装 PyTorch 系列优先用官方源或者镜像加速 # 下方是 CPU 版本的安装方式GPU 版本需要自行根据 CUDA 版本到官方选择命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 克隆 YOLOv5 仓库到本地 git clone https://github.com/ultralytics/yolov5.git cd yolov5 # 安装 YOLOv5 项目依赖 pip install -r requirements.txt # 遥感图像处理需要的额外库 pip install gdal pip install shapely # 处理 GeoJSON 多边形几何关系时用到这里说明一下每个参数的意义。conda create -n后面的名字可以随便取python3.9是版本约束不要用 Python 3.12因为有些库的预编译包还没跟上。pip install torch torchvision这条命令我特意注明了 CPU 版本如果你有 NVIDIA GPU就需要先去官网查对应的 CUDA 编译版本比如pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118这里的 cu118 表示 CUDA 11.8。安装 GDAL 最稳妥的方式是pip install gdal如果提示找不到合适的 wheel可以试试 conda 安装conda install -c conda-forge gdal因为 GDAL 的编译链比较长用 conda 预编译包能省很多时间。YOLOv5 的requirements.txt里包含了 numpy、opencv-python、matplotlib 等常用依赖其中 OpenCV 的版本需要注意新版本可能和 PyTorch 读取图像的底层逻辑存在兼容问题表现为训练中断或者读取的图片全黑。出现这个现象时可以尝试把 opencv-python 降级到 4.8 之前的版本。另外shapely不是 YOLOv5 的必需依赖是我做地理数据空间操作时加上的如果你的标注文件已经处理成了 xml 格式可以不用装。最后跑一个python train.py --help来验证环境是否装成功了如果这个命令能列出参数列表说明环境基本没问题。很多人在这一步会看到一些 warning 提示比如 CUDA 不可用没关系先确认 CPU 能跑通就行后面调 GPU 是另外一个排查方向的事情。3. 模型训练与关键参数调整针对遥感小目标优化3.1 训练命令与超参数初始化从 epoch 到 imgsz 的合理设置环境就绪之后第一个要跑通的训练命令很简单但参数不能照着自然图像的默认值来。遥感图像的目标尺寸普遍偏小默认的 640×640 输入会把小目标缩到没法看。如果你做的是航拍车辆检测建议输入尺寸设在 1280如果是卫星图像里的轮船检测1024 是个起步门槛。我把一次典型的遥感目标训练命令写在下面。python train.py \ --img 1280 \ --batch 8 \ --epochs 100 \ --data ../datasets/remote_ship.yaml \ --weights yolov5s.pt \ --project runs/train \ --name ship_det \ --hyp data/hyps/hyp.scratch-low.yaml \ --patience 20这里每个超参数都值得说道。--img 1280决定了网络输入的图像尺寸这是影响小目标检测效果最直接的因素直接提升到 1280 通常能带来 3 到 5 个点的 mAP 提升但显存开销是 640 的 4 倍所以--batch 8在 24G 显存的卡上是比较稳的设置。--epochs 100是训练轮数遥感数据集通常不像 COCO 那么大100 轮基本能收敛但需要配合--patience 20来提前停止也就是说如果连续 20 轮 mAP 没有上升训练自动结束可以省一半时间。--weights yolov5s.pt用的是 YOLOv5s 这个最小模型遥感场景我建议先用 s 版本跑通流程再尝试 m 或 l。因为遥感影像的纹理复杂s 模型容量不够可能会导致欠拟合但 l 模型的训练速度会慢好几倍训练策略上可以先小后大。--hyp data/hyps/hyp.scratch-low.yaml是超参数配置文件里面写了学习率、动量、数据增强的初始值。新手最容易忽略的是这个文件遥感图像做数据增强时上下翻转和左右翻转要谨慎开启因为卫星图上的目标可能有朝向语义比如车辆的头尾方向翻转后会让模型产生混淆。建议在 hyp 文件中把flipud: 0.0和fliplr: 0.5改成更保守的值。另外一个很实际的建议是样本量。遥感数据集通常是自己标注的几百张图就算不错了这时候不要盲目追求大 batchbatch size 与学习率的联动关系是线性相关的。batch 从 16 降到 8学习率也要从默认的 0.01 对应下调到 0.005否则训练曲线会剧烈震荡。3.2 锚框调整用聚类让 YOLOv5 适应小目标尺寸YOLOv5 自带了锚框的自动调整机制但自动调整不等于针对你的数据优化。默认锚框是从 COCO 数据集的 80 类目标统计出来的对遥感图像来说往往尺寸偏大。YOLOv5 训练时会把检测头部对应的锚框与数据集中的真实框做匹配如果数据集中最小的目标比最小锚框还小很多网络就很难去适配表现为小目标漏检或者置信度很低。YOLOv5 里面有一个脚本可以直接对你的训练集做聚类重新生成锚框。在utils/autoanchor.py中实现了 k-means 算法你只需要在完整训练前跑一次检查命令它会自动评估默认锚框与你的数据集是否匹配算出最佳召回率。如果差异大它会提示你重新计算。我习惯的做法是直接运行检测脚本把结果写回模型文件里。python train.py \ --img 1280 \ --batch 8 \ --epochs 0 \ --data ../datasets/remote_ship.yaml \ --weights yolov5s.pt \ --noautoanchor这个命令会把epochs设为 0实际上只做数据集分析和自动锚框计算不进行训练。YOLOv5 训练时有一个逻辑如果检测到anchors和当前数据的匹配度低于某个阈值就会自动重新聚类计算。但更可控的方式是先用上述命令跑一遍然后进入yolov5/models/yolov5s.yaml文件找到anchors部分把计算出来的新锚框写进去。在遥感场景下你会看到聚类出来的锚框往往极端的长条形或者极小比如船只目标可能长宽比达到 5:1这是一个核心特点。默认锚框难以覆盖这种长条形状所以聚类后手动替换是有必要的。替换完用--noautoanchor启动训练避免训练脚本每次都重新计算锚框导致结果不一致。3.3 评估指标解读mAP 0.5 和 0.5:0.95 到底哪个可信训练跑完以后很多人只看一眼 mAP 就下结论这不太严谨。遥感目标检测的评估指标和自然图像一样有mAP0.5和mAP0.5:0.95但遥感场景里两者的差异可能巨大。原因在于小目标的 IoU 计算对像素偏移非常敏感一个 30×30 像素的小目标预测框偏移几个像素IoU 就掉到 0.3 以下。我在实际项目中遇到过这样一个情况模型的mAP0.5达到了 0.92看起来挺漂亮但mAP0.5:0.95只有 0.48这意味着模型虽然能框出目标的大致位置但边界框不够精准。对于遥感应用来说如果是做目标计数或者密度统计0.92 可以接受但如果你是要做精确的占地面积测量或者目标跟踪这种框的精度就不达标需要从回归损失入手继续调优。YOLOv5 的训练结果文件夹里通常会生成混淆矩阵图在遥感场景里要重点看背景这一类。遥感底图里有很多纹理复杂的地物比如树冠阴影、车顶的空调外机这些很容易被误检成目标。混淆矩阵里如果background那一行的值偏高说明误检严重这时候不是加强模型容量而是要检查标注里是否漏标了大量相似物。另一个用来观察的指标是P_certainty或者置信度分布图YOLOv5 的labels.png里会画出标注框的中心点分布和尺寸分布。如果中心点分布明显偏向图像边缘说明切图时没有处理好目标跨边界的问题如果尺寸分布图显示大部分目标小于 0.02 倍图幅你要考虑进一步增大输入尺寸或者检查标注框是不是没有贴合目标边缘。这些图表新手很容易当作摆设但老手看模型的第一眼就是看这些图而不是看最终 loss 数字。4. 遥感图像目标识别避坑五个常见的训练与推理问题4.1 大图裁剪导致目标被切断现象训练集 mAP 很高验证集也不低但把一张完整的遥感大图切成 patch 做推理时框出来的一半目标显示不完整甚至出现两个目标被框成一个的奇怪结果。原因切图时没有留重叠区域目标恰好处于 patch 边缘被截断。YOLOv5 训练时看到的本来就是完整的目标图而推理时给它看的是残缺的模型自然会乱。解决切图时设置重叠率通常我会用 20%也就是说 stride 设置为 patch 宽度的 80%最后推理时通过 NMS 去重。另外一个工程痛点是大尺寸影像推理时全图缩放会导致目标缩得太小切完以后再做小目标检测这也是行业里常说的“遥感检测的边界坑”。4.2 标注框过小导致检测器完全忽略现象标注的框只有十几个像素训练时 loss 正常下降但推理时这类目标完全检测不到。原因YOLOv5 的训练过程会在每个特征层上匹配目标小目标由浅层的高分辨率特征图负责但锚框与目标尺寸的匹配比例有一个阈值。如果你的目标宽高小于该层特征图上一个锚框的某个比例就会被忽略。解决不要强行修网络结构先做离线分析查看labels.png的框大小分布。如果目标普遍小于 10×10 像素建议在切图时不要使用 1024 的 patch而是把 patch 设定为 512 大小这样目标在 patch 中的相对尺寸就变大了模型更容易学到特征。4.3 背景地物与目标特征相似导致误检现象模型的置信度很高但框出来的对象根本不是目标。比如把屋顶的太阳能板误检成船只把阴影误检成车辆。这类误检在遥感图像里很难根除因为俯视视角下目标纹理本来就少。原因俯视图像丢失了目标的侧面轮廓不同类别在俯视视角下的特征会趋同。解决方向有两个。第一个是数据层面在训练集中增加背景负样本专门裁剪一些你不希望检测到的地物图片放在 background 目录里标注为空 txt。第二个是模型层面在hyp.scratch-low.yaml里关闭过强的 HSV 颜色增强因为遥感图像的颜色具有地物真实性比自然图像稳定得多增强过度反而让模型忽视了颜色区分度。4.4 验证集划分不当导致精度虚高现象mAP 看起来不错但放到新区域的遥感影像上表现非常差精度大幅下跌。原因遥感影像存在空间自相关性同一张影像切出的 patch 之间内容重合度高。如果你把同一张大图的 patch 既放训练集又放验证集那么验证集和训练集的分布几乎一样精度自然高得离谱。解决划分数据集时必须以“图像或区域”为单位不能以 patch 为单位随机打散。我习惯的做法是按照地理位置来划分比如城市 A 的数据用于训练城市 B 的数据用于验证。虽然这样做精度会下降不少但那才是真实泛化性能的表现。4.5 显存不足与训练中断排查现象训练刚开始就报CUDA out of memory或者跑到一半杀掉了日志显示Killed。原因遥感场景普遍用大输入尺寸1280 的输入batch 168G 显存的卡基本就爆了。另一个隐藏原因是数据加载线程数开得太大出现了内存溢出。解决先降低 batch size 能撑过训练然后查看 GPU 的利用率如果 GPU 利用率不到 60%可能是 CPU 数据读取跟不上将--workers参数调高到 8 或 12。如果是显存问题还可以考虑开启梯度累积YOLOv5 官方没有直接提供命令行参数但可以修改 train.py 实现等效效果或者干脆采用--batch 4配合--accumulate 4来模拟 batch 16 的效果。5. 模型部署与推理优化让模型在普通机器上跑起来训练完的模型是.pt文件在实际应用中往往要部署在推理服务器甚至边缘设备上一个完整的 YOLOv5 遥感项目资源包通常也会包含导出与推理的案例。最常见的是把 PyTorch 权重转成 ONNX 作为中间格式再部署到不同平台。# 导出 ONNX 模型固定输入尺寸为 1280 python export.py \ --weights runs/train/ship_det/weights/best.pt \ --img 1280 \ --batch 1 \ --include onnx \ --simplify这一步需要注意的是--simplify参数它会让 ONNX 模型裁剪掉一些冗余计算节点推理速度提升明显而且一般不会影响精度。转换后的 ONNX 模型可以用下面的 Python 脚本跑一次前向推理import onnxruntime as ort import cv2 import numpy as np # 读取预处理后的图 patch大小 1280x1280 img cv2.imread(patch_001.tif) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 # 调整尺寸并增加 batch 维度 img_resized cv2.resize(img, (1280, 1280)) img_input np.transpose(img_resized, (2, 0, 1))[None, ...] session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name outputs session.run(None, {input_name: img_input}) # outputs 包含 boxes, conf, cls 三个数组直接解析即可这个脚本是标准的 ONNX Runtime 推理流程核心参数都在接口里。providers可以指定用 CPU 还是 GPU如果在服务器上可以使用CUDAExecutionProvider来获得几十倍的加速。遥感推理时单张 patch 的速度在 0.1 秒到 0.5 秒之间是正常的低于这个值要考虑模型是否被过度压缩了。在部署阶段模型的精度验证比训练时更重要。我一直保留一个习惯无论用.pt还是.onnx格式推理结果都要和原始验证集重新计算一次 mAP不要直接拿训练日志里的数字当一个指标。因为转换过程中可能会因为算子支持问题导致结果微变如果 mAP 下降超过 1 个点建议检查输入图像的归一化方式和数据增强配置这个坑在遥感场景中比自然图像更容易碰到。真正把模型跑起来以后尽量固定输入尺寸ONNX 模型支持动态分辨率但遥感大图的缩放和切图逻辑混在一起很容易让尺寸参数出错固定了反而少出问题。YOLOv5 在遥感目标识别里的应用沉下心来把数据切图、锚框调整、重叠推理这些基础功夫做扎实模型的精度自然就上去了。希望这些踩坑记录能帮你在遥感目标检测这条路上少走几趟弯路。本文还有配套的精品资源点击获取