news 2026/8/29 5:51:57

从DOTA到YOLO:HBB水平框遥感数据集的转换实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从DOTA到YOLO:HBB水平框遥感数据集的转换实战指南

1. 从DOTA到YOLO:为什么需要转换数据集格式

第一次接触遥感图像目标检测时,我对着DOTA数据集里密密麻麻的四边形标注框发了好一会儿呆。这些被称为OBB(Oriented Bounding Box)的旋转框虽然能精准框住斜向停放的飞机或船舶,但主流的YOLOv5/v8等检测器默认只支持HBB(Horizontal Bounding Box)水平框。这就好比你想用标准螺丝刀拧三角螺丝——工具和零件不匹配。

DOTA数据集的特点确实令人印象深刻:

  • 图像分辨率普遍在4000×4000像素以上
  • 标注采用8个坐标点的多边形格式(x1,y1,x2,y2,...,x8,y8)
  • 包含15个典型遥感目标类别(船舶、储油罐、运动场等)
  • 单个图像可能包含数百个密集目标

而YOLO需要的却是简约的5参数格式:

类别索引 x_center y_center width height

这个转换过程就像把复杂的立体折纸展开成平面图纸。我最初尝试手动转换时,发现直接取旋转框的外接矩形会导致大量无效背景区域被包含,特别是对于长宽比悬殊的桥梁、船舶等目标。后来通过分析DOTA_devkit源码才找到正确解法——应该计算所有顶点坐标的最小外接水平矩形。

2. 数据准备与环境配置

2.1 获取原始数据集

建议直接从DOTA官网下载基准数据集,目前主流版本有:

  • DOTA-v1.0(2.8GB,15类别)
  • DOTA-v1.5(6.2GB,新增集装箱起重机类别)
  • DOTA-v2.0(35.4GB,18类别)

如果网络条件受限,可以使用我预处理过的HBB版本(包含已转换的YOLO格式标签):

# 百度云下载(密码:iw3w) wget https://pan.baidu.com/s/1UX7oX3_x5CrP_SxSA7XKXQ

2.2 安装关键工具包

处理过程中需要以下Python包:

# 基础环境 pip install numpy opencv-python pillow # 专用工具 pip install dota-utils shapely

特别提醒:建议使用Shapely 1.7.1版本,新版本在计算多边形几何时可能有API变动。我在Colab上测试时遇到过这样的报错:

AttributeError: 'Polygon' object has no attribute '_get_coords'

就是版本兼容性问题导致的。

3. 核心转换流程详解

3.1 标注文件解析实战

DOTA的标注文件是这样的文本格式:

imagesource:GoogleEarth gsd:0.146 ... 1 128 256 384 512 ... large-vehicle 0

我们需要提取的是每行末尾的8个坐标点和类别信息。用Python处理时要注意:

def parse_dota_label(label_path): with open(label_path) as f: lines = [l.strip() for l in f.readlines()] objects = [] for line in lines: if line.startswith('imagesource'): continue parts = line.split() if len(parts) < 9: continue # 提取8个坐标点(x1,y1,...,x4,y4) points = list(map(float, parts[:8])) # 获取类别和difficult标志 cls = parts[8] difficult = int(parts[9]) if len(parts) > 9 else 0 objects.append({'points': points, 'class': cls, 'difficult': difficult}) return objects

3.2 坐标转换关键算法

将旋转框转为水平框的核心是计算最小外接矩形。使用Shapely库的MultiPoint可以优雅实现:

from shapely.geometry import MultiPoint def obb_to_hbb(points): # 将8个坐标点转为4个顶点 vertices = [(points[i], points[i+1]) for i in range(0, 8, 2)] multipoint = MultiPoint(vertices) # 获取最小外接矩形 hbb = multipoint.minimum_rotated_rectangle # 返回矩形四个顶点 return list(hbb.exterior.coords)[:4]

但YOLO需要的是归一化的中心坐标和宽高,还需要进行二次转换:

def hbb_to_yolo(vertices, img_width, img_height): # 计算边界 x_coords = [p[0] for p in vertices] y_coords = [p[1] for p in vertices] x_min, x_max = min(x_coords), max(x_coords) y_min, y_max = min(y_coords), max(y_coords) # 计算中心点和宽高(归一化) x_center = ((x_min + x_max) / 2) / img_width y_center = ((y_min + y_max) / 2) / img_height width = (x_max - x_min) / img_width height = (y_max - y_min) / img_height return x_center, y_center, width, height

3.3 图像分块处理技巧

DOTA图像尺寸过大(平均4000×4000),直接输入网络会显存爆炸。我推荐使用滑动窗口分块:

def split_image(img, window_size=1024, overlap=200): height, width = img.shape[:2] patches = [] for y in range(0, height, window_size - overlap): for x in range(0, width, window_size - overlap): # 计算实际裁剪区域 x1 = max(0, x) y1 = max(0, y) x2 = min(width, x + window_size) y2 = min(height, y + window_size) patch = img[y1:y2, x1:x2] patches.append((patch, (x1, y1, x2, y2))) return patches

注意重叠区域(overlap)要设置合理,我测试发现200像素能较好避免目标被切割。

4. 实战中的常见问题解决

4.1 类别映射问题

DOTA的类别名称带有连字符(如"small-vehicle"),而YOLO通常用数字索引。建议创建映射文件:

# dota_classes.yaml names: 0: plane 1: ship 2: storage-tank ... 14: helicopter

4.2 小目标丢失问题

在转换过程中,有些小目标(<10像素)可能因坐标取整被过滤。可以通过以下方式缓解:

# 在转换前添加过滤条件 if width * img_width < 10 or height * img_height < 10: print(f"忽略小目标:{cls} at ({x_center},{y_center})") continue

4.3 图像格式兼容性

YOLO对PNG支持不如JPG稳定,建议批量转换:

# 使用Imagemagick批量转换 mogrify -format jpg -quality 90 *.png

5. 完整转换脚本示例

以下是经过实战检验的完整转换脚本:

import os import cv2 from tqdm import tqdm from shapely.geometry import MultiPoint class DOTA2YOLO: def __init__(self, src_img_dir, src_label_dir, dst_dir): self.src_img_dir = src_img_dir self.src_label_dir = src_label_dir self.dst_dir = dst_dir os.makedirs(os.path.join(dst_dir, 'images'), exist_ok=True) os.makedirs(os.path.join(dst_dir, 'labels'), exist_ok=True) def convert(self): img_files = [f for f in os.listdir(self.src_img_dir) if f.lower().endswith(('.png', '.jpg'))] for img_file in tqdm(img_files): # 处理图像 img_path = os.path.join(self.src_img_dir, img_file) img = cv2.imread(img_path) h, w = img.shape[:2] # 处理对应标注 label_file = img_file.replace('.png', '.txt').replace('.jpg', '.txt') label_path = os.path.join(self.src_label_dir, label_file) if not os.path.exists(label_path): continue yolo_labels = [] objects = self.parse_dota_label(label_path) for obj in objects: hbb = self.obb_to_hbb(obj['points']) xc, yc, bw, bh = self.hbb_to_yolo(hbb, w, h) yolo_labels.append(f"{obj['class']} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") # 保存结果 dst_label_path = os.path.join(self.dst_dir, 'labels', label_file) with open(dst_label_path, 'w') as f: f.write('\n'.join(yolo_labels)) # 转换并保存图像 dst_img_path = os.path.join(self.dst_dir, 'images', img_file.replace('.png', '.jpg')) cv2.imwrite(dst_img_path, img) # 其他工具方法同上...

使用时只需初始化并执行:

converter = DOTA2YOLO('DOTA/train/images', 'DOTA/train/labels', 'YOLO_DOTA') converter.convert()

6. 验证转换结果

转换完成后,强烈建议可视化检查:

import matplotlib.pyplot as plt import matplotlib.patches as patches def visualize(img_path, label_path, class_map): img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB) h, w = img.shape[:2] fig, ax = plt.subplots(1, figsize=(12, 8)) ax.imshow(img) with open(label_path) as f: lines = f.readlines() for line in lines: cls_idx, xc, yc, bw, bh = map(float, line.strip().split()) # 转换回像素坐标 x = (xc - bw/2) * w y = (yc - bh/2) * h width = bw * w height = bh * h rect = patches.Rectangle((x,y), width, height, linewidth=2, edgecolor='r', facecolor='none') ax.add_patch(rect) plt.text(x, y, class_map[int(cls_idx)], color='white', bbox=dict(facecolor='red', alpha=0.5)) plt.show()

7. 高效训练技巧

转换后的数据集可以这样配置YOLOv5训练:

# dota.yaml train: ../YOLO_DOTA/images/train val: ../YOLO_DOTA/images/val nc: 15 names: ['plane', 'ship', 'storage-tank', 'baseball-diamond', 'tennis-court', 'basketball-court', 'ground-track-field', 'harbor', 'bridge', 'large-vehicle', 'small-vehicle', 'helicopter', 'roundabout', 'soccer-ball-field', 'swimming-pool']

启动训练时建议调整锚点参数:

python train.py --img 1024 --batch 8 --epochs 100 --data dota.yaml \ --weights yolov5s.pt --hyp data/hyps/hyp.scratch-low.yaml

我在RTX 3090上测试发现,使用--img 1024配合--batch 8能在显存占用和检测效果间取得平衡。对于小目标密集的场景,可以尝试以下改进:

  • 增加--img-size到1536
  • 使用更密集的锚点配置
  • 添加小目标检测层
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 1:13:09

StructBERT中文语义系统容器化部署:Docker Compose编排实践

StructBERT中文语义系统容器化部署&#xff1a;Docker Compose编排实践 1. 为什么需要本地化的中文语义匹配工具&#xff1f; 你有没有遇到过这样的问题&#xff1a; 用现成的文本相似度API比对两段完全不相关的中文内容——比如“苹果手机续航怎么样”和“今天天气真好”&am…

作者头像 李华
网站建设 2026/8/19 14:57:57

基于STM32F103的智能烟雾报警系统设计与实现:从硬件搭建到软件编程

1. 项目背景与核心功能 烟雾报警器是家庭和工业场所安全防护的基础设备。传统报警器功能单一且误报率高&#xff0c;而基于STM32F103的智能系统通过实时AD采样和动态阈值算法大幅提升了可靠性。我在实际测试中发现&#xff0c;市售的普通报警器在厨房油烟环境下误触发率高达30%…

作者头像 李华
网站建设 2026/8/21 16:30:41

深入解析GDSII二进制结构:从文件头到图素层的逐字节剖析

1. GDSII文件格式概述 GDSII&#xff08;Graphic Data System II&#xff09;是集成电路设计领域最常用的版图数据交换格式&#xff0c;它采用二进制形式存储芯片设计中的所有几何图形和层次结构信息。这个格式最早由Calma公司在1970年代开发&#xff0c;后来成为半导体行业的实…

作者头像 李华
网站建设 2026/8/27 2:22:56

Python智能客服机器人实战:从NLP处理到生产环境部署

痛点分析&#xff1a;传统客服系统到底卡在哪 去年做外包项目时&#xff0c;我接手过一套“上古”客服系统&#xff1a;前端是 jQuery&#xff0c;后端是同步阻塞的 Flask&#xff0c;意图识别靠关键词 if-else&#xff0c;高峰期 CPU 飙到 90%&#xff0c;用户平均等待 8 秒才…

作者头像 李华
网站建设 2026/8/21 17:17:44

GLM-4.7-Flash从零开始:基于FastAPI构建RESTful微服务封装

GLM-4.7-Flash从零开始&#xff1a;基于FastAPI构建RESTful微服务封装 你是不是也遇到过这样的问题&#xff1a;好不容易跑通了一个大模型&#xff0c;结果发现它只在Web界面里能用&#xff1f;想集成进自己的系统、写个自动化脚本、或者对接客服后台&#xff0c;却卡在API封装…

作者头像 李华
网站建设 2026/8/25 15:02:37

基于PLC的交通灯毕设:从零搭建控制逻辑与硬件接线实战指南

基于PLC的交通灯毕设&#xff1a;从零搭建控制逻辑与硬件接线实战指南 摘要&#xff1a;许多自动化专业学生在完成“基于PLC的交通灯毕设”时&#xff0c;常因缺乏工程经验而陷入逻辑混乱、硬件接线错误或仿真调试困难等困境。本文面向PLC新手&#xff0c;系统讲解交通灯控制的…

作者头像 李华