遥感技术的快速发展,特别是在高分辨率遥感图像的获取能力上的显著提升,已经大大拓宽了遥感数据在环境监测、灾害评估、城市规划及军事侦察等领域的应用范围。在这些应用中,遥感目标检测作为一项基础而关键的技术,其研究和发展受到了广泛关注。遥感目标检测旨在从遥感图像中自动识别并定位地表特定目标,其挑战在于需要处理大尺寸、高复杂度的图像,并且需要在多变的环境条件下保持高准确率和鲁棒性。随着深度学习技术的快速进步,基于深度学习的目标检测算法,尤其是YOLO系列算法,已经成为遥感目标检测领域研究的热点。YOLO算法以其快速、准确的特点,在实时目标检测领域展现出了显著的优势,而其后续版本的不断优化和改进,进一步提升了在遥感图像上的适用性和性能。
我们使用NWPU VHR-10数据集实现实现遥感图像目标检测。
NWPU VHR-10数据集是一个专为遥感图像目标检测任务设计的高分辨率(Very High Resolution, VHR)遥感图像数据集。它由西北工业大学遥感图像研究团队(NWPU)发布,广泛用于目标检测与相关研究。数据集图像是从谷歌地球和Vaihingen数据集中裁剪出来的,然后由专家手动注释。Vaihingen数据由德国摄影测量、遥感和地理信息学会(DGPF)提供:http://www.ifp.uni-stuttgart.de/dgpf/DKEPAllg.html.
数据集GitHub链接如下:
GitHub - Gaoshuaikun/NWPU-VHR-10: NWPU VHR-10 dataset
也可以通过下面的链接直接下载:
数据集一共有10个类别:
airplane | 飞机 | ![]() |
ship | 船 | ![]() |
storage tank | 储罐 | ![]() |
baseball diamond | 棒球场 | ![]() |
tennis court | 网球场 | ![]() |
basketball court | 篮球场 | ![]() |
ground track field | 地面田径场 | ![]() |
harbor | 港口 | ![]() |
bridge | 桥 | ![]() |
vehicle | 车辆 | ![]() |
该数据集目录如下:
其中,ground truth目录内是每张图像的标签,negative image set是无标签的图像,我们可以将其用作测试,positive image set是有标签的图像,与ground truth内的txt文件按照文件名一一对应。
标签格式如下:
- (575,114),(635,162),1
- ( 72,305),(133,369),1
- (210,317),(273,384),1
- (306,374),(344,420),1
- (447,531),(535,632),1
- (546,605),(625,707),1
- (632,680),(720,790),1
每一行都代表一个目标。每一行的格式如下:
- (x1,y1),(x2,y2),a
x1,y1表示检测框左上角点的坐标,x2,y2表示右下角点的坐标。a表示类别序号(1-10分别对应飞机-车辆)。
我们可以通过下面的代码,对标签与图像进行可视化。
【代码】show_data.py
- from pathlib import Path
- import cv2
- import numpy as np
- import matplotlib.cm as cm
-
-
- def generate_colors(n, colormap_name='hsv'):
- colormap = cm.get_cmap(colormap_name, n)
- colors = colormap(np.linspace(0, 1, n))
- colors = (colors[:, :3] * 255).astype(np.uint8) # 将颜色值转换为0到255的整数
- return colors
-
-
- # 数据集路径
- dataset = Path('NWPU VHR-10 dataset')
- # 图像路径
- images_dir = dataset / 'positive image set'
- # 标签路径
- ground_truth_dir = dataset / 'ground truth'
-
- # 标签框的颜色
- colors = generate_colors(10)[:, ::-1].tolist()
- # 标签名
- names = ['airplane', 'ship', 'storage tank', 'baseball diamond', 'tennis court', 'basketball court', 'ground track field', 'harbor', 'bridge', 'vehicle']
-
- for image_path in images_dir.glob('*.jpg'):
- image = cv2.imread(str(image_path))
- ground_truth = ground_truth_dir / (image_path.stem + '.txt')
-
- # 读取标签
- labels = []
- for line in ground_truth.open('r').readlines():
- line = line.replace('\n', '')
- if line:
- labels.append(eval(line))
-
- # 遍历所有标签
- for (x1, y1), (x2, y2), cls in labels:
- cv2.rectangle(image, (x1, y1), (x2, y2), tuple(colors[cls - 1]), 2)
- cv2.putText(image, names[cls - 1], (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 0.75, colors[cls - 1], 2)
-
- cv2.imshow('image', image)
- cv2.waitKey(0)
-
可视化结果如下:
数据集转换的目的有两个:
(1)数据集划分
数据集可划分为训练集与验证集。训练集用于训练模型,验证集用于评估模型。通常来说,训练集与测试集数量的比例为7:3,在训练集数据量较小时,可适当调整为8:2。
(2)转化数据集格式
首先说明YOLO数据集的文件结构:
├─images
│ ├─train
│ └─val
└─labels
├─train
└─val
images目录下分为train目录与val目录,分别存放训练集的图像与验证集的图像;labels目录下同样包含train目录与val目录。我们需要把数据集转换为这样的文件结构。
图像支持jpg、jpeg、png、bmp等常见格式,标签为txt格式,图像与标签按照文件名一一对应,例如001.jpg的标签为001.txt。
txt文件中,,一行表示一个标签。每个检测框检测框需要由5个信息确定,格式要求如下:
- c x y w h
c表示类别,x表示检测框中心点的横坐标,y表示检测框中心点的纵坐标,w表示检测框的宽,h表示检测框的高。五个信息之间用空格分隔。
需要注意的是,x、y、w、h都是归一化之后的值。即若图像的宽高为800, 600,中心点的坐标为(400, 300),检测框的宽为80像素,高为60像素,则
接下来,我们可以通过以下代码,完成数据集的转换。
【代码】mkdata.py
- from pathlib import Path
- import random
- import cv2
- import shutil
-
- # 设置随机数种子
- random.seed(0)
-
- # 数据集路径
- ground_truth_dir = Path('NWPU VHR-10 dataset/ground truth')
- # 图像路径
- positive_image_set = Path('NWPU VHR-10 dataset/positive image set')
-
- # 创建一个data目录,用于保存数据
- data_dir = Path('data')
- data_dir.mkdir(exist_ok=True)
-
- # 创建图像与标签目录
- images_dir = data_dir / 'images'
- images_dir.mkdir(exist_ok=True)
-
- labels_dir = data_dir / 'labels'
- labels_dir.mkdir(exist_ok=True)
-
- # 创建训练集与验证集目录
- for set_name in ['train', 'val']:
- (images_dir / set_name).mkdir(exist_ok=True)
- (labels_dir / set_name).mkdir(exist_ok=True)
-
- # 所有图像
- image_paths = list(positive_image_set.glob('*.jpg'))
- # 所有标签
- labels = []
- for image_path in image_paths:
- # 读取图像
- image = cv2.imread(str(image_path))
- ground_truth = ground_truth_dir / (image_path.stem + '.txt')
- # 获取图像宽高
- im_h, im_w = image.shape[:2]
-
- # 读取标签
- lines = []
- for line in ground_truth.open('r').readlines():
- line = line.replace('\n', '')
- if line:
- (x1, y1), (x2, y2), c = eval(line)
- # 计算中心点和宽高
- x = (x1 + x2) / 2
- y = (y1 + y2) / 2
- w = abs(x2 - x1)
- h = abs(y2 - y1)
-
- # 归一化
- x = x / im_w
- y = y / im_h
- w = w / im_w
- h = h / im_h
-
- # 把序号变为索引
- c -= 1
- lines.append(f'{c} {x} {y} {w} {h}\n')
-
- # 保存标签
- labels.append(lines)
-
- # 合并图像路径与标签
- data_pairs = list(zip(image_paths, labels))
- # 打乱数据
- random.shuffle(data_pairs)
-
- # 取80%的数据为训练集,剩下的为测试集
- train_size = int(len(data_pairs) * 0.8)
- train_set = data_pairs[:train_size]
- val_set = data_pairs[train_size:]
-
- # 遍历训练集与测试集
- for set_name, dataset in zip(['train', 'val'], [train_set, val_set]):
- # 遍历每张图像与标签
- for image_path, label in dataset:
- # 复制图像到新的文件夹
- shutil.copy2(image_path, images_dir / set_name / image_path.name)
- # 生成标签到新的文件夹
- with open(labels_dir / set_name / (image_path.stem + '.txt'), 'w') as f:
- f.writelines(label)
-
运行代码后,项目的目录结构如下:
通过运行代码,生成了data目录,data目录就是YOLO要求的数据集格式。data目录下有images与labels,images用于存放训练集与验证集的图片,labels用于存放训练集与验证集的标签。
YOLO代码的调用主要依赖于Ultralytics团队开源的ultralytics库。可以通过下面的链接访问ultralytics官网,官网中提供了使用说明与代码案例。通过使用ultralytics库,仅使用几行代码就可以完成YOLO模型的构建。ultralytics库中支持YOLOv3-YOLOv11算法的训练,除了标准的目标检测外,还支持关键点检测、旋转框检测、实例分割等任务。
Home - Ultralytics YOLO Docshttps://docs.ultralytics.com/ultralytics库可以通过pip命令进行安装,不过为了方便后续对代码的修改,建议下载ultralytics库的源码,源码可以通过下面的链接下载。
GitHub - ultralytics/ultralytics: Ultralytics YOLO11 🚀https://github.com/ultralytics/ultralytics若没有加载Git,可以直接点击Download ZIP下载源码
我们打开下载后的代码只有ultralytics这一个目录是我们需要的,其它文件和目录都不需要,我们可以将ultralytics目录复制到项目目录中。
使用ultralytics的核心是“配置”。
通过对数据的配置,能够将整理好的数据集应用于训练;
同时,通过对模型的配置,能够实现使用不同的YOLO算法进行训练。
首先要建立一个yaml文件,我们可以将其命名为dataset.yaml,yaml文件可以放置在ultralytics的同级目录下。yaml文件内容如下:
- path: D:/Projects/Python/work/VHR-10/data
- train: images/train
- val: images/val
- test:
-
- # Classes
- names:
- 0: airplane
- 1: ship
- 2: storage tank
- 3: baseball diamond
- 4: tennis court
- 5: basketball court
- 6: ground track field
- 7: harbor
- 8: bridge
- 9: vehicle
yaml文件分为5个重要的组成部分:
path | 数据集的绝对路径 |
train | 训练集的图像目录 |
val | 验证集的图像目录 |
test | 测试集的图像目录 |
names | 标签索引与标签名 |
path为数据集的绝对路径,程序会自动在path路径下寻找images目录与labels目录。
trian为训练集的图像目录,val为验证集的图像目录,这两个目录都是相对于path的路径。程序同时会自动在labels目录中匹配训练集与验证集的标签。
test是测试集的目录,测试集不是必须的,若没有测试集,此项可以置空。
names是索引与标签的对应。此处的索引对应标签格式中的 c(类别)。
至此,数据集的配置完成。
ultralytics支持YOLOv3至YOLO11的大多数模型,需要注意的是,YOLOv4与YOLOv7虽然给出了介绍,但实际上并不支持模型的训练。此外,YOLO的第11个版本的命名。并不是“YOLOv11”,而是去掉中间的“v”,命名为YOLO11。
第一步,选择适合的模型并下载预训练权重。
我们以YOLO11为例,点击YOLO11的详情页,向下翻可以找到这样一个表格。
表格记录了YOLO11 5种规格模型的具体信息,模型规模从小到大依次为:n、s、m、l、s。
表格种还提供了模型在COCO数据集中的验证集指标(mAP,越高越好),以及推理时间、参数量、计算量等。我们可以选择合适的模型进行训练,为了实现遥感目标检测,我们使用规格最小的YOLO11n作为演示。
点击YOLO11n的蓝色模型名,即可下载模型的预训练权重,使用预训练权重有助于提升检测精度与收敛速度。
我们可以将下载好的预训练权重放置在ultralytics的同级目录下。
第二步,设置训练代码。
在ultralytics的同级目录下,创建文件train.py。此时,代码目录如下:
train.py中,只需要四行代码,就可以完成YOLO模型的训练。代码如下:
【代码】train.py
- from ultralytics import YOLO
-
- if __name__ == '__main__':
- yolo = YOLO('yolo11n.yaml')
- yolo.train(data='dataset.yaml', epochs=100, batch=3)
yolo11的配置文件在ultralytics/cfg/models/11/yolo11.yaml中,但我们输入的是yolo11n.yaml,系统将会自动在yolo11.yaml解析规格为n的配置,加载YOLO11n模型。同理,若想使用YOLO11s模型,则指定yolo = YOLO('yolo11s.yaml')即可。
使用此代码进行训练,将自动加载先前下载的yolo11n.pt作为预训练权重,若更改模型,还需要下载其它规格的预训练权重。
运行train.py文件,即可开始训练模型。
出现如下界面后,即为训练成功。
等待模型训练完成,得到结果如下:
从图中我们可以看出,最终所有类别的平均mAP50为0.757,mAP50-95为0.422。
我们也可以看到其它类别的指标,mAP50最高的为地面田径场(ground track field),mAP最低的为篮球场(basketball court)。
训练时,模型会自动生成一个runs目录,我们训练的结果将会保存在runs目录中。
runs目录下会生成一个detect目录与一个mlflow目录,mlflow目录是利用mlflow平台进行训练过程的可视化,我们暂时先不关注这个目录。
detect目录内保存了检测任务的所有训练结果,第一次训练命名为“train”,第二次训练命名为“train2”,以此类推。我们需要找到最后一次训练的目录,查看结果。也可以在训练时控制台的输出中,找到相应的结果目录。
其中,labels.jpg是对标签的统计。
results.png是训练过程中各个指标的记录。
val_batch*_labels.jpg与val_batch*_pred.jpg分别表示验证集的某个batch最终的真实标签与预测结果。
真实标签:
预测结果:
我们可以发现,在245.jpg中,有三个网球场没有识别出来,且误识别了一个飞机;
574.jpg中,误识别了一些飞机;
547.jpg中,桥的识别出现了重叠的情况;
等等。
我们可以根据预测的情况来分析模型的状况,通过观察上面的结果,我们发现模型还有很大的改进空间。
接下来,我们尝试通过YOLOv5算法来实现。
首先打开YOLOv5模型详情页,下载yolov5n模型的权重,并将下载后的权重放置在train.py的同级目录下。
模型修改如下:
【代码】train.py
- from ultralytics import YOLO
-
- if __name__ == '__main__':
- # yolo = YOLO('yolo11n.yaml')
- yolo = YOLO('yolov5n.yaml')
- yolo.train(data='dataset.yaml', epochs=100, close_mosaic=0, batch=3)
仅需要将yolo11n.yaml改为yolov5n.yaml,就能实现算法的替换。YOLOv5训练结果如下:
最终的mAP50为0.751,mAP50-95为0.415,略逊于YOLO11。