从DOTA到YOLO:遥感图像标注格式转换实战指南

遥感图像分析正在成为计算机视觉领域的重要分支,而DOTA数据集作为该领域的标杆性资源,其丰富的航空影像和多边形标注为研究者提供了宝贵素材。但当我们想要将这些数据应用于流行的YOLO目标检测框架时,标注格式的差异就成了第一道门槛。本文将带您深入理解两种标注格式的本质区别,并提供一套完整的Python转换方案。

1. 理解DOTA与YOLO标注格式的核心差异

DOTA数据集采用多边形标注体系,每个物体由四个顶点坐标精确描述其轮廓,这种表示方法能够很好地捕捉遥感图像中物体的实际形状和朝向。典型的DOTA标注格式如下:

x1 y1 x2 y2 x3 y3 x4 y4 category difficult

相比之下,YOLO系列模型使用归一化的中心坐标和宽高比来表示物体位置,这种简化表示更适合实时检测任务。YOLO格式的每行标注包含:

class_id center_x center_y width height

关键转换原理:我们需要将四个顶点坐标的多边形表示,转换为能够包含整个物体的最小外接矩形。这个过程中需要考虑几个技术细节:

  1. 坐标归一化处理(除以图像宽高)
  2. 多边形顶点到外接矩形的计算
  3. 类别ID的映射转换
  4. 困难样本的过滤策略

注意:DOTA数据集中标注的"difficult"标记需要特别处理,通常我们会保留difficult=0的样本,而过滤掉difficult=1的极端困难样本。

2. 环境准备与数据组织结构

在开始转换前,我们需要确保开发环境配置正确,并按照标准结构组织数据集文件。

2.1 基础环境配置

推荐使用Python 3.8+环境,并安装以下依赖库:

pip install numpy shapely pillow

核心库的作用说明:

  • NumPy:高效处理数值计算
  • Shapely:处理几何图形操作
  • Pillow:图像尺寸读取

2.2 数据集目录结构

规范的目录结构能大幅减少后续路径配置问题。建议按如下方式组织:

DOTA_dataset/
├── images/
│   ├── P0001.png
│   ├── P0002.png
│   └── ...
└── labelTxt/
    ├── P0001.txt
    ├── P0002.txt
    └── ...

文件命名对应规则:每个图像文件(PXXXX.png)都有对应的标注文件(PXXXX.txt),这种一一对应的关系是转换脚本正常运行的前提。

3. 核心代码解析与实现

我们提供的转换工具包含两个关键脚本:dota_utils.py提供基础功能,YOLO_Transform.py执行实际转换。下面深入分析核心函数实现。

3.1 多边形解析函数

parse_dota_poly函数负责读取原始DOTA标注文件:

def parse_dota_poly(filename):
    objects = []
    with open(filename, 'r') as f:
        while True:
            line = f.readline()
            if not line:
                break
            if len(line.strip().split()) >= 9:
                obj = {
                    'name': splitlines[8],
                    'difficult': splitlines[9] if len(splitlines)>=10 else '0',
                    'poly': [(float(splitlines[0]), float(splitlines[1])),
                           (float(splitlines[2]), float(splitlines[3])),
                           (float(splitlines[4]), float(splitlines[5])),
                           (float(splitlines[6]), float(splitlines[7]))]
                }
                objects.append(obj)
    return objects

3.2 多边形到YOLO格式的转换

dots4ToRecC函数实现了核心的几何转换:

def dots4ToRecC(poly, img_w, img_h):
    xmin = min(p[0] for p in poly)
    xmax = max(p[0] for p in poly)
    ymin = min(p[1] for p in poly)
    ymax = max(p[1] for p in poly)
    
    center_x = (xmin + xmax) / 2 / img_w
    center_y = (ymin + ymax) / 2 / img_h
    width = (xmax - xmin) / img_w
    height = (ymax - ymin) / img_h
    
    return center_x, center_y, width, height

3.3 主转换流程

dota2darknet函数整合了整个转换流程:

def dota2darknet(imgpath, txtpath, dstpath, classnames):
    for txtfile in os.listdir(txtpath):
        # 获取对应图像尺寸
        img = Image.open(os.path.join(imgpath, os.path.splitext(txtfile)[0]+'.png'))
        w, h = img.size
        
        # 解析并转换每个标注对象
        with open(os.path.join(dstpath, txtfile), 'w') as f_out:
            for obj in parse_dota_poly(os.path.join(txtpath, txtfile)):
                if obj['difficult'] == '1':
                    continue
                if obj['name'] not in classnames:
                    continue
                    
                bbox = dots4ToRecC(obj['poly'], w, h)
                if any(x <= 0 or x >= 1 for x in bbox):
                    continue
                    
                f_out.write(f"{classnames.index(obj['name'])} {' '.join(map(str, bbox))}\n")

4. 实际应用与问题排查

4.1 自定义类别选择

DOTA包含18个类别,但实际项目中可能只需要其中部分类别。修改extractclassname参数即可:

# 只转换车辆和船舶类别
custom_classes = ['small-vehicle', 'large-vehicle', 'ship']
dota2darknet(img_path, txt_path, out_path, custom_classes)

4.2 常见错误处理

问题1:图像与标注文件不匹配

  • 检查文件名是否严格对应
  • 确认文件扩展名(.png/.txt)正确

问题2:坐标值超出[0,1]范围

  • 检查图像读取是否正确
  • 验证标注文件是否损坏

问题3:类别ID不连续

  • 确保extractclassname列表顺序固定
  • 检查是否有类别名称拼写错误

4.3 转换结果验证

建议使用以下代码可视化转换结果,验证标注是否正确:

import cv2
import random

def plot_yolo_boxes(img_path, txt_path):
    img = cv2.imread(img_path)
    h, w = img.shape[:2]
    
    with open(txt_path) as f:
        for line in f:
            cls_id, cx, cy, bw, bh = map(float, line.split())
            x1 = int((cx - bw/2) * w)
            y1 = int((cy - bh/2) * h)
            x2 = int((cx + bw/2) * w)
            y2 = int((cy + bh/2) * h)
            
            color = (random.randint(0,255), random.randint(0,255), random.randint(0,255))
            cv2.rectangle(img, (x1,y1), (x2,y2), color, 2)
    
    cv2.imshow('Verify', img)
    cv2.waitKey(0)

5. 高级技巧与性能优化

5.1 批量处理与并行加速

对于大规模数据集,可以使用多进程加速:

from multiprocessing import Pool

def process_single(args):
    imgpath, txtfile, dstpath, classes = args
    # 单文件处理逻辑...

if __name__ == '__main__':
    args_list = [(imgpath, f, dstpath, classes) for f in os.listdir(txtpath)]
    with Pool(processes=4) as pool:
        pool.map(process_single, args_list)

5.2 数据增强集成

在转换过程中可以直接集成常见的数据增强:

# 在写入YOLO标注前添加随机扰动
if random.random() < 0.5:  # 50%概率水平翻转
    bbox = [1 - bbox[0], bbox[1], bbox[2], bbox[3]]

5.3 格式转换的数学原理

深入理解多边形到外接矩形的转换数学:

  1. 计算所有x坐标的最小/最大值 → xmin, xmax
  2. 计算所有y坐标的最小/最大值 → ymin, ymax
  3. 中心点:((xmin+xmax)/2, (ymin+ymax)/2)
  4. 宽度:xmax - xmin
  5. 高度:ymax - ymin

这种转换虽然会丢失物体的旋转信息,但对于大多数检测任务已经足够。如果需要保留旋转信息,可以考虑扩展YOLO格式或使用其他支持旋转框的框架。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐