手把手教你将DOTA遥感数据集标注转成COCO格式(附完整Python代码)

发布时间:2026/5/31 14:31:37

手把手教你将DOTA遥感数据集标注转成COCO格式(附完整Python代码) 从DOTA到COCO遥感图像标注格式转换实战指南在计算机视觉领域数据标注格式的统一性直接影响着模型训练的效率与效果。DOTA数据集作为遥感图像分析的重要基准其独特的任意四边形标注方式能够精确捕捉物体的实际轮廓而COCO格式作为通用目标检测框架的标准输入则采用简洁的矩形框标注。本文将深入探讨两种格式的核心差异并提供一套完整的Python解决方案帮助开发者实现高效转换。1. 理解DOTA与COCO标注的本质差异DOTA数据集专为航空图像分析设计其标注文件采用TXT格式存储每个物体由四个顶点坐标(x1,y1,x2,y2,x3,y3,x4,y4)定义这种表示方法能够准确描述任意朝向的物体轮廓。例如一架斜向停放的飞机或一艘转向中的轮船都能通过四边形顶点精确标注。相比之下COCO格式使用JSON文件组织数据其标注核心是矩形框的左上角坐标和宽高(x,y,width,height)。这种Axis-Aligned Bounding Box(AABB)表示法虽然丢失了物体朝向信息但简化了检测任务的计算复杂度与大多数检测框架如MMDetection、Detectron2等天然兼容。关键差异对比特征DOTA格式COCO格式几何表示任意四边形正矩形坐标维度8个值(4个点)4个值(x,y,w,h)文件结构每图对应一个TXT文件整个数据集一个JSON文件类别定义包含在每行标注末尾集中定义在categories中适用场景高精度方向敏感任务通用目标检测2. 转换流程的核心逻辑与实现格式转换的核心是将任意四边形转换为最小外接正矩形。这个过程需要计算所有x坐标的最小/最大值和y坐标的最小/最大值形成能够完全包含原始四边形的矩形区域。def convert_dota_to_coco(dota_coords): 将DOTA四边形坐标转换为COCO矩形框 参数: dota_coords - 包含8个数字的列表[x1,y1,x2,y2,x3,y3,x4,y4] 返回: (x_min, y_min, x_max, y_max)形式的矩形坐标 x_coords dota_coords[0::2] # 提取所有x坐标 y_coords dota_coords[1::2] # 提取所有y坐标 x_min min(x_coords) x_max max(x_coords) y_min min(y_coords) y_max max(y_coords) return (x_min, y_min, x_max, y_max)实际工程中还需要考虑以下关键点坐标边界处理确保转换后的矩形不超出图像边界类别映射建立DOTA类别到COCO category_id的对应关系标注ID生成为每个标注对象分配唯一ID图像信息整合收集图像的宽高等元数据注意DOTA标注中的最后两个值分别是类别名称和difficult标志需要正确解析。difficult标志通常映射为COCO中的iscrowd字段。3. 完整工程实现方案下面提供一个完整的Python脚本实现从DOTA格式到COCO格式的批量转换import os import json from PIL import Image from typing import List, Dict, Tuple class DOTA2COCOConverter: def __init__(self, class_names: List[str]): 初始化转换器 :param class_names: 有序的类别名称列表 self.categories [] self.class_name2id {} # 构建COCO格式的categories和名称到ID的映射 for idx, name in enumerate(class_names, start1): self.categories.append({ id: idx, name: name, supercategory: name }) self.class_name2id[name] idx def parse_dota_line(self, line: str) - Tuple[List[float], str, int]: 解析单行DOTA标注 :param line: 标注行如 x1 y1 x2 y2 x3 y3 x4 y4 class_name difficult :return: (坐标列表, 类别名称, iscrowd) parts line.strip().split() coords list(map(float, parts[:8])) class_name parts[8] iscrowd int(parts[9]) if len(parts) 9 else 0 return coords, class_name, iscrowd def convert_annotation(self, coords: List[float], class_name: str, iscrowd: int, annotation_id: int, image_id: int) - Dict: 转换单个标注为COCO格式 x_min, y_min, x_max, y_max self.convert_dota_to_coco(coords) return { segmentation: [[x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max]], bbox: [x_min, y_min, x_max - x_min, y_max - y_min], area: (x_max - x_min) * (y_max - y_min), category_id: self.class_name2id[class_name], iscrowd: iscrowd, image_id: image_id, id: annotation_id } def process_image(self, image_path: str, label_path: str, image_id: int, annotation_id: int) - Tuple[List[Dict], int]: 处理单张图片及其标注 annotations [] # 获取图像尺寸 with Image.open(image_path) as img: width, height img.size # 解析DOTA标注文件 with open(label_path, r) as f: for line in f: if line.strip() : continue coords, class_name, iscrowd self.parse_dota_line(line) annotation self.convert_annotation( coords, class_name, iscrowd, annotation_id, image_id ) annotations.append(annotation) annotation_id 1 image_info { file_name: os.path.basename(image_path), height: height, width: width, id: image_id } return image_info, annotations, annotation_id def convert_dataset(self, image_dir: str, label_dir: str, output_path: str): 转换整个数据集 coco_data { images: [], annotations: [], categories: self.categories } annotation_id 1 image_id 1 # 遍历标注目录 for label_file in os.listdir(label_dir): if not label_file.endswith(.txt): continue base_name os.path.splitext(label_file)[0] image_file base_name .png # 假设图像为PNG格式 image_path os.path.join(image_dir, image_file) label_path os.path.join(label_dir, label_file) if not os.path.exists(image_path): continue image_info, annotations, annotation_id self.process_image( image_path, label_path, image_id, annotation_id ) coco_data[images].append(image_info) coco_data[annotations].extend(annotations) image_id 1 # 保存为JSON文件 with open(output_path, w) as f: json.dump(coco_data, f, indent2) # 使用示例 if __name__ __main__: # DOTA数据集的15个默认类别 CLASSES [ plane, baseball-diamond, bridge, ground-track-field, small-vehicle, large-vehicle, ship, tennis-court, basketball-court, storage-tank, soccer-ball-field, roundabout, harbor, swimming-pool, helicopter ] converter DOTA2COCOConverter(CLASSES) converter.convert_dataset( image_dirpath/to/images, label_dirpath/to/labels, output_pathoutput/annotations.json )4. 验证转换结果的正确性完成格式转换后必须验证结果的准确性。推荐采用以下验证方法可视化检查将转换后的COCO标注绘制到原图上确认边界框是否合理包含目标统计验证检查转换前后各类别的实例数量是否一致面积比对比较原始四边形与转换后矩形的面积比率确保没有过度膨胀import cv2 import numpy as np def visualize_coco_annotation(image_path, coco_annotations): 可视化COCO标注 image cv2.imread(image_path) for ann in coco_annotations: x, y, w, h ann[bbox] cv2.rectangle(image, (int(x), int(y)), (int(x w), int(y h)), (0, 255, 0), 2) # 添加类别标签 class_name next( (cat[name] for cat in categories if cat[id] ann[category_id]), str(ann[category_id]) ) cv2.putText(image, class_name, (int(x), int(y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imshow(Visualization, image) cv2.waitKey(0) cv2.destroyAllWindows()常见问题排查清单坐标越界检查转换后的矩形坐标是否超出图像尺寸类别丢失确认所有DOTA类别都有对应的COCO category_idID冲突确保每个annotation和image都有唯一ID文件路径验证图像路径在JSON中是否正确相对路径5. 高级技巧与性能优化对于大规模数据集处理可以考虑以下优化策略并行处理使用多进程加速图像处理和标注转换增量更新支持向现有COCO标注文件追加新数据内存优化对于极大图像采用分块处理策略from multiprocessing import Pool def parallel_convert(args): 并行处理的worker函数 image_path, label_path, image_id, annotation_id args converter DOTA2COCOConverter(CLASSES) return converter.process_image(image_path, label_path, image_id, annotation_id) # 在convert_dataset方法中使用 with Pool(processes4) as pool: # 使用4个进程 results pool.map(parallel_convert, task_args)对于特殊场景的额外考虑旋转目标处理如果需要保留方向信息可以扩展COCO格式存储旋转角度多任务学习同时保留四边形和矩形标注支持多种任务训练数据增强集成在转换过程中直接应用随机裁剪等增强技术实际项目中我们发现在处理极端长宽比目标如桥梁、船舶时简单的AABB转换会导致大量背景区域被包含。针对这种情况可以考虑以下改进方案长宽比约束对转换后的矩形施加最大长宽比限制分割融合结合语义分割结果裁剪背景区域方向感知使用旋转矩形而非正矩形格式转换只是数据处理流程的第一步一个完整的计算机视觉项目还需要考虑数据集划分合理分配训练、验证、测试集标注质量检查识别并修正错误标注数据平衡处理类别不均衡问题版本控制管理不同版本的数据集和标注

相关新闻