从标注文件看门道:手把手教你用Python解析UCAS-AOD、DOTA、FAIR1M的txt/xml标签
从标注文件看门道手把手教你用Python解析遥感目标检测数据集标签第一次打开UCAS-AOD数据集时我盯着那些密密麻麻的坐标数字完全摸不着头脑。x1,y1,x2,y2...这些数字到底代表什么为什么有的数据集用txt有的用xml更让人困惑的是同样的飞机目标在不同数据集里标注格式竟然完全不同。这就是大多数初学者面对遥感目标检测数据集时的真实写照——数据拿到了却不知道如何拆封使用。本文将用最直白的方式带你破解UCAS-AOD、DOTA和FAIR1M这三种典型数据集的标注密码。不同于单纯罗列数据集参数的介绍文章我们会用可运行的Python代码一步步教你如何解析不同格式的标注文件txt/xml提取关键坐标信息包括普通框和旋转框用OpenCV和matplotlib实现标注可视化理解不同标注格式的设计逻辑1. 解析UCAS-AOD的水平旋转框(HBB)UCAS-AOD采用了一种独特的水平旋转框标注方式。打开任意一个.txt标注文件你会看到类似这样的内容128.45 356.21 156.78 342.15 164.32 368.77 136.12 382.54 0.45 150.23 362.45 32.67 40.12这串数字到底怎么解读其实它们对应的是x1,y1, # 旋转框第一个顶点 x2,y2, # 旋转框第二个顶点 x3,y3, # 旋转框第三个顶点 x4,y4, # 旋转框第四个顶点 theta, # 旋转角度(弧度) x,y, # 水平外接矩形中心点 width,height # 水平外接矩形宽高关键点前8个数字定义了一个旋转矩形后5个数字则是该旋转矩形的水平外接矩形参数。这种双重标注方式既保留了目标的方向信息又提供了传统检测算法需要的水平框。下面这段代码展示了如何解析并绘制这种特殊标注import numpy as np import cv2 def parse_ucas_aod_line(line): data list(map(float, line.strip().split())) points np.array(data[:8]).reshape(4, 2) # 提取四个顶点 angle data[8] # 旋转角度 return points, angle def draw_rotated_box(img, points, color(0,255,0), thickness2): # 绘制旋转四边形 points points.reshape((-1,1,2)).astype(int) cv2.polylines(img, [points], isClosedTrue, colorcolor, thicknessthickness) # 标记顶点顺序 for i, (x,y) in enumerate(points.reshape(-1,2)): cv2.putText(img, str(i1), (x,y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0), 1) # 使用示例 img cv2.imread(P0001.png) with open(P0001.txt) as f: points, angle parse_ucas_aod_line(f.readline()) draw_rotated_box(img, points) cv2.imshow(UCAS-AOD, img) cv2.waitKey(0)注意UCAS-AOD的旋转角度theta定义为机尾指向机头的向量与x轴正向的夹角顺时针方向为正。这与OpenCV的坐标系定义一致。通过这段代码我们能直观看到标注框如何贴合飞机目标。下图展示了实际解析效果图示绿色轮廓为旋转框红色数字标记顶点顺序蓝色箭头表示机头方向2. 破解DOTA的任意四边形标注(OBB)DOTA数据集采用了更通用的OBBOriented Bounding Box标注格式。每个目标的标注信息如下x1 y1 x2 y2 x3 y3 x4 y4 category difficult与UCAS-AOD不同DOTA的四个顶点可以构成任意四边形不一定是矩形且顶点按顺时针顺序排列。这种标注方式对不规则目标如港口、立交桥的表征更加精确。解析DOTA标注的关键代码如下from matplotlib import pyplot as plt def parse_dota_annotation(ann_file): objects [] with open(ann_file) as f: for line in f: if line.startswith(imagesource): # 跳过文件头 continue parts line.strip().split() if len(parts) 9: # 确保有足够数据 continue points list(map(float, parts[:8])) category parts[8] difficult int(parts[9]) if len(parts) 9 else 0 objects.append({ points: np.array(points).reshape(4, 2), category: category, difficult: difficult }) return objects def plot_dota_objects(img_path, ann_path): img plt.imread(img_path) objects parse_dota_annotation(ann_path) fig, ax plt.subplots(figsize(10,10)) ax.imshow(img) for obj in objects: points obj[points] # 绘制四边形 poly plt.Polygon(points, fillFalse, edgecolorred, linewidth1) ax.add_patch(poly) # 标记类别 center points.mean(axis0) ax.text(center[0], center[1], obj[category], bboxdict(facecoloryellow, alpha0.5)) plt.show() # 使用示例 plot_dota_objects(P0006.png, P0006.txt)DOTA标注的几个特点值得注意顶点顺序一致性所有四边形的顶点必须按顺时针排列这对后续计算旋转角度很重要difficult标志标记难以识别的目标如遮挡严重或非常小的目标多尺度兼容同一张图片中可能同时存在极大和极小的目标下表对比了UCAS-AOD和DOTA的标注差异特征UCAS-AODDOTA标注形状旋转矩形任意四边形顶点顺序无严格要求必须顺时针角度信息显式提供需通过顶点计算类别粒度2类(飞机/汽车)15类适用场景方向敏感目标任意形状目标3. 解析FAIR1M的XML结构标注FAIR1M作为目前最精细的遥感数据集采用了XML格式存储标注信息。一个典型的标注片段如下object coordinatepixel/coordinate typerectangle/type possibleresult nameLiquid Cargo Ship/name /possibleresult points point1275.0,458.0/point point1494.0,88.0/point point1417.0,43.0/point point1199.0,414.0/point point1275.0,458.0/point /points /object与之前两种数据集相比FAIR1M的标注特点包括层次化结构XML格式天然支持嵌套数据细粒度分类如船舶细分为Liquid Cargo Ship等子类闭合多边形第五个点与第一个点相同形成闭合路径使用Python的xml.etree.ElementTree解析FAIR1M标注import xml.etree.ElementTree as ET def parse_fair1m_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() objects [] for obj in root.findall(objects/object): category obj.find(possibleresult/name).text points [] for point in obj.findall(points/point): x, y map(float, point.text.split(,)) points.append([x, y]) # 移除重复的闭合点 if len(points) 1 and points[-1] points[0]: points points[:-1] objects.append({ category: category, points: np.array(points) }) return objects def visualize_fair1m(img_path, xml_path): img cv2.imread(img_path) objects parse_fair1m_xml(xml_path) for obj in objects: points obj[points] # 绘制多边形 cv2.polylines(img, [points.astype(int)], isClosedTrue, color(0,255,255), thickness2) # 标记类别 center points.mean(axis0).astype(int) cv2.putText(img, obj[category], tuple(center), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,0), 1) cv2.imshow(FAIR1M, img) cv2.waitKey(0)FAIR1M的XML解析需要注意几个细节坐标类型检查coordinate标签可能指定像素坐标或地理坐标头部标记某些类别如飞机的第一个点表示头部位置多目标支持一个XML文件可能包含数百个对象实例4. 三种数据集标注转换实战实际项目中我们经常需要统一不同数据集的标注格式。下面提供三种常见转换方法4.1 旋转框转水平框def rotated_to_horizontal(points): 将旋转框转换为水平外接矩形 x_min points[:,0].min() x_max points[:,0].max() y_min points[:,1].min() y_max points[:,1].max() return np.array([ [x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max] ])4.2 四边形转旋转矩形def quad_to_rotated_rect(points): 将任意四边形转换为旋转矩形 rect cv2.minAreaRect(points) box cv2.boxPoints(rect) return box4.3 XML转YOLO格式def fair1m_to_yolo(xml_path, img_size(1024,1024)): 将FAIR1M XML转换为YOLO格式 objects parse_fair1m_xml(xml_path) yolo_lines [] for obj in objects: points obj[points] # 计算水平外接矩形 x_min, y_min points.min(axis0) x_max, y_max points.max(axis0) # 转换为YOLO格式(中心点宽高归一化) x_center ((x_min x_max) / 2) / img_size[0] y_center ((y_min y_max) / 2) / img_size[1] width (x_max - x_min) / img_size[0] height (y_max - y_min) / img_size[1] yolo_lines.append(f0 {x_center} {y_center} {width} {height}) return \n.join(yolo_lines)提示实际转换时需要考虑类别ID映射上述代码假设所有对象都属于类别0下表总结了三种数据集标注的互换性转换方向可行性信息损失OBB → HBB高丢失旋转信息HBB → OBB低无法恢复原始形状XML → TXT高可能丢失层级信息TXT → XML中需要补充元数据5. 标注可视化进阶技巧基础的框绘制可能无法满足分析需求下面介绍几种增强可视化效果的方法5.1 带透明度的填充def draw_transparent_polygon(img, points, color, alpha0.3): overlay img.copy() cv2.fillPoly(overlay, [points.astype(int)], color) cv2.addWeighted(overlay, alpha, img, 1-alpha, 0, img)5.2 方向箭头标记def draw_orientation(img, points, color(255,0,0)): 根据顶点顺序绘制方向箭头 head points[0] # 假设第一个点是头部 tail points[2] # 对角点作为尾部 cv2.arrowedLine(img, tuple(tail.astype(int)), tuple(head.astype(int)), color, 2)5.3 多类别颜色编码COLOR_MAP { plane: (0,255,0), ship: (0,0,255), vehicle: (255,0,0), # 其他类别... } def draw_by_category(img, objects): for obj in objects: color COLOR_MAP.get(obj[category], (255,255,255)) draw_rotated_box(img, obj[points], color)5.4 复杂场景下的可视化优化当图像中存在大量密集目标时常规绘制会导致视觉混乱。解决方法包括分层显示按目标尺寸或类别分层显示交互式查看使用matplotlib的缩放功能热力图叠加用不同颜色表示目标密度def interactive_plot(img_path, ann_path): img plt.imread(img_path) objects parse_annotation(ann_path) # 通用解析函数 fig, ax plt.subplots(figsize(15,15)) ax.imshow(img) # 按类别分组绘制 categories set(obj[category] for obj in objects) colors plt.cm.get_cmap(tab20, len(categories)) for i, cat in enumerate(categories): cat_objs [obj for obj in objects if obj[category]cat] for obj in cat_objs: poly plt.Polygon(obj[points], fillFalse, edgecolorcolors(i), linewidth1, labelcat if i0 else ) ax.add_patch(poly) plt.legend() plt.show()6. 常见问题与解决方案在实际解析过程中可能会遇到各种意外情况。以下是几个典型问题及解决方法6.1 坐标越界处理def clip_coordinates(points, img_width, img_height): 确保坐标不超出图像边界 points[:,0] np.clip(points[:,0], 0, img_width-1) points[:,1] np.clip(points[:,1], 0, img_height-1) return points6.2 无效标注过滤def validate_annotation(points): 检查标注是否有效 # 检查面积是否过小 area cv2.contourArea(points) if area 4: # 小于2x2像素 return False # 检查是否为凸多边形 hull cv2.convexHull(points) if not np.array_equal(points, hull.squeeze()): return False return True6.3 顶点顺序校正def ensure_clockwise(points): 确保顶点按顺时针排列 if cv2.contourArea(points) 0: return points[::-1] return points6.4 多线程解析加速对于大型数据集如FAIR1M可以使用多线程加速解析from concurrent.futures import ThreadPoolExecutor def batch_parse_xml(xml_files, workers8): with ThreadPoolExecutor(max_workersworkers) as executor: results list(executor.map(parse_fair1m_xml, xml_files)) return results7. 从解析到训练构建数据管道掌握了标注解析方法后下一步是构建完整的数据加载管道。以PyTorch为例from torch.utils.data import Dataset class RemoteSenseDataset(Dataset): def __init__(self, img_dir, ann_dir, transformNone): self.img_dir img_dir self.ann_dir ann_dir self.transform transform self.img_files [f for f in os.listdir(img_dir) if f.endswith((.png,.jpg))] def __len__(self): return len(self.img_files) def __getitem__(self, idx): img_path os.path.join(self.img_dir, self.img_files[idx]) img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 根据扩展名选择解析方式 ann_path os.path.join(self.ann_dir, os.path.splitext(self.img_files[idx])[0] (.txt if DOTA in self.ann_dir else .xml)) if ann_path.endswith(.txt): objects parse_dota_annotation(ann_path) else: objects parse_fair1m_xml(ann_path) # 转换为模型需要的格式 boxes [] labels [] for obj in objects: boxes.append(obj[points].flatten().tolist()) labels.append(CLASS_DICT[obj[category]]) target { boxes: torch.as_tensor(boxes, dtypetorch.float32), labels: torch.as_tensor(labels, dtypetorch.int64) } if self.transform: img, target self.transform(img, target) return img, target关键设计考虑统一接口处理不同数据集的差异延迟加载只在需要时读取图像和标注转换支持集成数据增强管道内存效率避免一次性加载所有数据8. 标注质量检查与修复低质量标注会严重影响模型性能。以下是几种自动化检查方法8.1 重叠检测def find_overlaps(objects, iou_thresh0.3): overlaps [] for i, obj1 in enumerate(objects): for j, obj2 in enumerate(objects[i1:], i1): iou calculate_polygon_iou(obj1[points], obj2[points]) if iou iou_thresh: overlaps.append((i,j,iou)) return overlaps8.2 小目标检测def find_small_objects(objects, min_area16): return [i for i, obj in enumerate(objects) if cv2.contourArea(obj[points]) min_area]8.3 类别平衡分析def analyze_class_balance(objects_list): counter Counter() for objects in objects_list: counter.update(obj[category] for obj in objects) plt.bar(counter.keys(), counter.values()) plt.xticks(rotation45) plt.title(Class Distribution) plt.show()8.4 自动修复工具对于常见问题可以编写自动修复脚本def auto_fix_annotation(objects, img_size): fixed [] for obj in objects: # 修复坐标越界 obj[points] clip_coordinates(obj[points], *img_size) # 过滤无效标注 if not validate_annotation(obj[points]): continue # 校正顶点顺序 obj[points] ensure_clockwise(obj[points]) fixed.append(obj) return fixed9. 自定义标注工具开发理解标注格式后我们可以开发专用标注工具。核心功能包括格式转换器不同标注格式互转可视化检查器带缩放和筛选功能的查看器质量分析仪统计标注分布和质量指标半自动标注基于模型预测辅助标注import tkinter as tk from PIL import Image, ImageTk class AnnotationViewer: def __init__(self, master, img_path, ann_path): self.master master self.img Image.open(img_path) self.objects self.parse_annotation(ann_path) self.canvas tk.Canvas(master, widthself.img.width, heightself.img.height) self.canvas.pack() self.tk_img ImageTk.PhotoImage(self.img) self.canvas.create_image(0,0, anchortk.NW, imageself.tk_img) self.draw_annotations() def draw_annotations(self): for obj in self.objects: points obj[points].flatten().tolist() self.canvas.create_polygon(points, outlinered, fill, width2) center obj[points].mean(axis0) self.canvas.create_text(center[0], center[1], textobj[category], fillyellow)这个简单的Tkinter应用展示了如何构建一个基础标注查看器。实际项目中可以考虑使用专业的图像处理库如OpenCV或PyQt构建功能更完善的工具。10. 性能优化与最佳实践处理大规模遥感数据集时性能优化至关重要内存映射文件对于超大型图像def read_big_image(img_path): return np.memmap(img_path, dtypeuint8, moder, shape(height, width, channels))标注缓存机制避免重复解析from functools import lru_cache lru_cache(maxsize1000) def cached_parse(ann_path): return parse_annotation(ann_path)批量图像处理利用GPU加速def batch_visualize(img_paths, ann_paths): imgs [cv2.imread(p) for p in img_paths] anns [parse_annotation(p) for p in ann_paths] # 使用GPU加速绘制 with torch.no_grad(): batch torch.stack([torch.from_numpy(img) for img in imgs]) batch batch.cuda().permute(0,3,1,2).float() # ... GPU上的批量绘制逻辑 ...分布式处理适用于超大规模数据集from multiprocessing import Pool def parallel_process(args): img_path, ann_path args img cv2.imread(img_path) objects parse_annotation(ann_path) # 处理逻辑... return result with Pool(processes8) as pool: results pool.map(parallel_process, zip(img_paths, ann_paths))11. 实际项目经验分享在多个遥感目标检测项目中我总结了以下几点实用建议混合数据集训练当单个数据集样本不足时可以合并多个数据集。这时需要统一标注格式建议转换为DOTA格式处理类别名称不一致问题如car vs vehicle平衡不同数据集的样本分布处理标注不一致不同数据集的标注标准可能不同例如UCAS-AOD标注整个飞机而FAIR1M可能只标注可见部分对于被遮挡目标有的数据集标注完整轮廓有的只标注可见部分数据增强策略遥感图像的特殊性需要考虑旋转增强要同步更新角度标注裁剪时要注意不要切掉重要目标色彩增强要适度保持光谱特性处理极端长宽比目标如桥梁、跑道等目标需要特殊的数据增强方法定制化的锚框设计考虑旋转不变性的网络结构多尺度训练技巧遥感图像中的目标尺度差异巨大使用FPN等多尺度网络结构实施分尺度训练策略对极小目标使用特殊处理如超分辨率12. 扩展应用基于标注的分析标注数据不仅可以用于训练还能支持多种分析数据集统计分析def analyze_dataset(ann_dir): sizes [] ratios [] for ann_file in glob.glob(os.path.join(ann_dir, *.txt)): objects parse_annotation(ann_file) for obj in objects: w obj[points][:,0].max() - obj[points][:,0].min() h obj[points][:,1].max() - obj[points][:,1].min() sizes.append(w * h) ratios.append(max(w,h)/min(w,h)) plt.figure(figsize(12,4)) plt.subplot(121) plt.hist(sizes, bins50) plt.title(Object Size Distribution) plt.subplot(122) plt.hist(ratios, bins50) plt.title(Aspect Ratio Distribution) plt.show()目标分布热力图def plot_density_heatmap(img_size, objects): heatmap np.zeros(img_size) for obj in objects: points obj[points].astype(int) cv2.fillPoly(heatmap, [points], 1) plt.imshow(heatmap, cmaphot) plt.colorbar() plt.title(Object Density Heatmap) plt.show()方向分布分析对旋转目标尤为重要def analyze_orientation(objects): angles [] for obj in objects: rect cv2.minAreaRect(obj[points]) angles.append(rect[2]) plt.hist(angles, bins36, range(0,180)) plt.title(Object Orientation Distribution) plt.xlabel(Angle (degrees)) plt.show()类别共现分析def analyze_cooccurrence(objects_list): cooccur defaultdict(int) for objects in objects_list: categories [obj[category] for obj in objects] for cat1, cat2 in combinations(set(categories), 2): cooccur[(min(cat1,cat2), max(cat1,cat2))] 1 # 转换为矩阵显示 cats sorted(set(cat for pair in cooccur for cat in pair)) matrix np.zeros((len(cats), len(cats))) for i, cat1 in enumerate(cats): for j, cat2 in enumerate(cats[i:], i): matrix[i,j] cooccur.get((cat1,cat2), 0) plt.imshow(matrix, cmapBlues) plt.xticks(range(len(cats)), cats, rotation90) plt.yticks(range(len(cats)), cats) plt.title(Category Co-occurrence Matrix) plt.colorbar() plt.show()13. 前沿标注格式探索随着技术进步新的标注格式不断涌现COCO格式已成为通用目标检测基准{ info: {...}, licenses: [...], images: [...], annotations: [ { id: 1, image_id: 1, category_id: 1, segmentation: [[x1,y1,x2,y2,...]], area: 123.45, bbox: [x,y,width,height], iscrowd: 0 } ], categories: [...] }YOLOv8格式支持旋转目标class x_center y_center width height angleLIDAR融合标注结合点云数据object camera_bboxx1,y1,x2,y2,x3,y3,x4,y4/camera_bbox lidar_bboxcenter_x,center_y,center_z,length,width,height,yaw/lidar_bbox /object时序标注用于视频分析{ video_id: 001, frames: [ { frame_id: 0, objects: [ {track_id: 1, points: [...]}, ... ] }, ... ] }14. 标注规范建议根据实际项目经验推荐以下标注规范文件结构dataset/ ├── images/ │ ├── train/ │ └── val/ └── annotations/ ├── train/ │ ├── P0001.txt │ └── ... └── val/ ├── P1001.txt └── ...命名规则图像[集合][序号].[扩展名]如P0001.png标注与图像同名扩展名根据格式变化标注内容每个目标一行包含足够的元数据如difficult标志明确顶点顺序约定版本控制使用Git管理标注文件每次修改保留变更记录对大规模数据集使用DVC质量检查清单[ ] 所有目标都被标注[ ] 无遗漏的小目标[ ] 边界框紧贴目标边缘[ ] 无重叠框特殊要求除外[ ] 类别标签正确15. 总结与进阶方向通过本文的代码示例和分析你应该已经掌握三种主流遥感数据集的标注解析方法标注可视化和质量检查技巧不同标注格式间的转换策略高效处理大规模标注数据的优化方法进一步学习方向包括自动化标注工具研究SAM等模型辅助标注标注质量提升开发自动检查和修复工具多模态标注融合光学、SAR、LiDAR等多源数据动态标注处理视频时序数据众包标注系统设计高效的分布式标注平台在实际项目中我建议先从DOTA数据集入手因为它的标注格式兼顾了灵活性和简洁性。当遇到性能瓶颈时可以考虑使用多进程解析和内存映射技术。对于需要长期维护的项目建立严格的标注规范和版本控制流程至关重要。