实战指南:如何用Python脚本将YOLO检测结果转换为Anti-UAV竞赛标准格式(附完整代码)

张开发
2026/6/13 23:58:54 15 分钟阅读
实战指南:如何用Python脚本将YOLO检测结果转换为Anti-UAV竞赛标准格式(附完整代码)
实战指南如何用Python脚本将YOLO检测结果转换为Anti-UAV竞赛标准格式附完整代码参加Anti-UAV竞赛的开发者们常常面临一个棘手问题训练好的YOLO模型输出的检测结果格式与竞赛要求的提交格式不匹配。这种格式差异可能导致评分系统无法正确读取你的检测结果直接影响比赛成绩。本文将深入解析YOLO格式与Anti-UAV标准格式的转换原理并提供可直接复用的Python脚本帮助你在竞赛中避免因格式问题失分。1. 理解YOLO与Anti-UAV格式差异在开始编写转换脚本前我们需要清楚两种格式的具体差异。YOLO模型输出的检测结果通常采用相对坐标表示法而Anti-UAV竞赛则要求绝对坐标的特定格式。1.1 YOLO格式解析YOLO格式的边界框表示为(cx, cy, w, h)其中cx和cy是边界框中心点的x、y坐标w和h是边界框的宽度和高度所有值都是相对于图像宽度和高度的比例值0到1之间例如一个YOLO格式的检测结果可能是0.5 0.3 0.2 0.15表示中心点位于图像宽度50%、高度30%的位置宽度为图像宽度的20%高度为图像高度的15%。1.2 Anti-UAV竞赛格式要求Anti-UAV竞赛通常要求提交的检测结果为(x, y, w, h)格式x和y是边界框左上角的绝对坐标像素值w和h是边界框的绝对宽度和高度像素值所有值都是浮点数精确到小数点后多位此外竞赛可能还要求每个序列的结果保存在单独的文本文件中结果文件采用JSON或其他特定格式封装对于未检测到目标的帧需要用特定值如全零填充2. 核心转换算法实现格式转换的核心是将YOLO的相对坐标转换为Anti-UAV要求的绝对坐标。下面我们详细解析转换函数的关键实现。2.1 坐标转换函数def convert_yolo_to_xywh(yolo_box, img_w, img_h): 将YOLO格式转换为左上角坐标宽高格式保留浮点精度 cx, cy, w, h map(float, yolo_box) # 计算绝对坐标保持浮点运算 cx_abs cx * img_w cy_abs cy * img_h w_abs w * img_w h_abs h * img_h # 计算左上角坐标 x cx_abs - w_abs / 2 y cy_abs - h_abs / 2 # 边界检查使用浮点比较 x max(0.0, min(x, img_w - 1.0)) y max(0.0, min(y, img_h - 1.0)) w max(0.0, min(w_abs, img_w - x)) h max(0.0, min(h_abs, img_h - y)) return [x, y, w, h]这个函数的关键点包括保持浮点运算精度避免多次转换导致精度损失进行边界检查确保转换后的坐标不会超出图像范围正确处理中心坐标到左上角坐标的转换2.2 边界检查的重要性在目标检测任务中模型可能会预测出部分超出图像边界的边界框。如果不进行边界检查可能会导致负坐标或超过图像尺寸的坐标计算出的宽度或高度为负值后续处理步骤出现异常我们的边界检查逻辑确保坐标不会小于0或大于图像尺寸宽度和高度不会导致边界框超出图像范围所有值都保持合理的浮点数范围3. 完整处理流程实现现在我们将转换函数整合到完整的处理流程中处理整个图像序列的检测结果。3.1 序列处理函数def process_sequence(image_dir, label_dir): 处理单个序列精确浮点版本 results [] images sorted( [f for f in os.listdir(image_dir) if f.lower().endswith((.jpg, .png, .jpeg))], keylambda x: int(os.path.splitext(x)[0]) ) for img_file in images: img_path os.path.join(image_dir, img_file) base_name os.path.splitext(img_file)[0] label_path os.path.join(label_dir, f{base_name}.txt) try: with Image.open(img_path) as img: img_w, img_h img.size if not os.path.exists(label_path): results.append([0.0, 0.0, 0.0, 0.0]) # 保持浮点格式 continue with open(label_path, r) as f: lines f.readlines() if not lines: results.append([0.0, 0.0, 0.0, 0.0]) continue parts lines[0].strip().split() if len(parts) 5: results.append([0.0, 0.0, 0.0, 0.0]) continue try: converted convert_yolo_to_xywh(parts[1:5], img_w, img_h) # 保留原始浮点精度 results.append([ float(f{converted[0]:.15f}), float(f{converted[1]:.15f}), float(f{converted[2]:.15f}), float(f{converted[3]:.15f}) ]) except: results.append([0, 0, 0, 0]) except Exception as e: print(fError processing {img_file}: {str(e)}) results.append([0, 0, 0, 0]) return results这个函数处理一个完整序列的关键步骤图像排序确保按照帧顺序处理图像图像尺寸获取使用Pillow库读取图像尺寸标签文件处理检查标签文件是否存在及格式是否正确异常处理对可能出现的各种错误情况进行妥善处理结果收集保持一致的浮点精度格式3.2 主处理函数def main(root_dir, image_root, output_dir): 主处理函数 # 创建输出目录 os.makedirs(output_dir, exist_okTrue) # 遍历所有子文件夹 for seq_name in os.listdir(root_dir): seq_path os.path.join(root_dir, seq_name) if not os.path.isdir(seq_path): continue # 对应图片目录 img_seq_dir os.path.join(image_root, seq_name) if not os.path.exists(img_seq_dir): print(fWarning: Missing image folder for {seq_name}) continue # 标签目录 label_dir os.path.join(seq_path, labels) if not os.path.exists(label_dir): print(fWarning: Missing labels folder for {seq_name}) continue # 处理序列 print(fProcessing {seq_name}...) res process_sequence(img_seq_dir, label_dir) # 写入结果文件 output_path os.path.join(output_dir, f{seq_name}.txt) with open(output_path, w) as f: json.dump({res: res}, f)主函数负责创建输出目录遍历所有序列文件夹检查必要的目录结构调用序列处理函数将结果保存为JSON格式4. 性能优化与错误处理在实际应用中我们还需要考虑脚本的性能和健壮性。以下是几个关键的优化点4.1 性能优化技巧批量处理使用多进程处理多个序列内存优化避免同时加载所有图像缓存机制对重复读取的图像尺寸进行缓存from multiprocessing import Pool def process_single_sequence(args): seq_name, root_dir, image_root, output_dir args seq_path os.path.join(root_dir, seq_name) img_seq_dir os.path.join(image_root, seq_name) label_dir os.path.join(seq_path, labels) if not (os.path.isdir(seq_path) and os.path.exists(img_seq_dir) and os.path.exists(label_dir)): return res process_sequence(img_seq_dir, label_dir) output_path os.path.join(output_dir, f{seq_name}.txt) with open(output_path, w) as f: json.dump({res: res}, f) def main_parallel(root_dir, image_root, output_dir, workers4): 并行版本的主处理函数 os.makedirs(output_dir, exist_okTrue) sequences [(name, root_dir, image_root, output_dir) for name in os.listdir(root_dir)] with Pool(workers) as p: p.map(process_single_sequence, sequences)4.2 错误处理最佳实践详细的错误日志记录处理失败的具体原因结果验证检查转换后的坐标是否合理进度反馈显示处理进度便于监控长时间运行的任务def validate_conversion(original, converted, img_w, img_h): 验证转换结果是否合理 x, y, w, h converted if x 0 or y 0 or w 0 or h 0: return False if x w img_w or y h img_h: return False return True def process_sequence_with_validation(image_dir, label_dir, log_fileconversion_errors.log): 带验证的序列处理 results [] error_count 0 with open(log_file, a) as log: for img_file in sorted(os.listdir(image_dir)): # ...原有处理逻辑... try: converted convert_yolo_to_xywh(parts[1:5], img_w, img_h) if not validate_conversion(parts[1:5], converted, img_w, img_h): log.write(fInvalid conversion for {img_file}: {parts[1:5]} - {converted}\n) error_count 1 results.append(converted) except Exception as e: log.write(fError in {img_file}: {str(e)}\n) error_count 1 results.append([0, 0, 0, 0]) print(fProcessing completed with {error_count} errors logged to {log_file}) return results5. 实际应用中的注意事项在Anti-UAV竞赛中使用这个转换脚本时有几个关键点需要注意路径配置确保输入输出路径正确特别是当处理大量数据时精度要求不同竞赛对浮点数精度的要求可能不同空结果处理明确竞赛对未检测到目标的帧的处理要求文件命名结果文件的命名规则必须严格遵循竞赛要求提示在实际提交前建议先用少量数据测试转换脚本的输出是否符合竞赛系统的要求格式。可以创建一个简单的验证脚本来检查输出文件的结构和内容。对于大规模数据集处理可以考虑以下优化使用更高效的文件操作库如pathlib实现断点续处理功能添加进度条显示如tqdm库from tqdm import tqdm from pathlib import Path def process_large_dataset(root_dir, image_root, output_dir): 处理大规模数据集的优化版本 output_path Path(output_dir) output_path.mkdir(exist_okTrue) for seq_dir in tqdm(list(Path(root_dir).iterdir()), descProcessing sequences): if not seq_dir.is_dir(): continue seq_name seq_dir.name img_seq_dir Path(image_root) / seq_name label_dir seq_dir / labels if not (img_seq_dir.exists() and label_dir.exists()): continue results [] for img_file in tqdm(sorted(img_seq_dir.glob(*.[jJ][pP][gG])), descfSequence {seq_name}, leaveFalse): try: with Image.open(img_file) as img: img_w, img_h img.size label_file label_dir / f{img_file.stem}.txt if not label_file.exists(): results.append([0.0]*4) continue with open(label_file, r) as f: line f.readline() if not line.strip(): results.append([0.0]*4) continue parts line.strip().split() if len(parts) 5: results.append([0.0]*4) continue converted convert_yolo_to_xywh(parts[1:5], img_w, img_h) results.append([float(f{v:.15f}) for v in converted]) except Exception as e: print(fError in {img_file}: {e}) results.append([0.0]*4) with open(output_path / f{seq_name}.txt, w) as f: json.dump({res: results}, f)

更多文章