YOLO生成预测json标签迁移问题

使用YOLO val.py中 saved_json=True,生成的prediction.json 出现映射偏移

比如原始是从0开始,而YOLO生成的 prediction.json 是从1开始,

需要诊断,校准

1️⃣先诊断

python 复制代码
import json
import os
from pycocotools.coco import COCO
import numpy as np

def diagnose_coco_mismatch(anno_json, pred_json):
    """全面诊断COCO标注和预测文件的不匹配问题"""
    
    print("="*60)
    print("COCO评估诊断工具")
    print("="*60)
    
    # 1. 加载数据
    print("\n[1] 加载文件...")
    anno = COCO(anno_json)
    with open(pred_json, 'r') as f:
        preds = json.load(f)
    
    print(f"    ✓ 标注文件: {len(anno.getImgIds())} 张图片, {len(anno.getAnnIds())} 个标注框")
    print(f"    ✓ 预测文件: {len(preds)} 个预测框")
    
    if len(preds) == 0:
        print("    ✗ 错误: 预测文件为空!")
        return False
    
    # 2. 检查图像ID匹配
    print("\n[2] 检查图像ID匹配...")
    gt_img_ids = set(anno.getImgIds())
    pred_img_ids = set([p['image_id'] for p in preds])
    common_imgs = gt_img_ids & pred_img_ids
    
    print(f"    标注图片数: {len(gt_img_ids)}")
    print(f"    预测图片数: {len(pred_img_ids)}")
    print(f"    匹配图片数: {len(common_imgs)}")
    
    if len(common_imgs) == 0:
        print("    ✗ 严重错误: 没有任何图片ID匹配!")
        print(f"    标注图片ID示例: {list(gt_img_ids)[:5]}")
        print(f"    预测图片ID示例: {list(pred_img_ids)[:5]}")
        return False
    else:
        print(f"    ✓ 匹配成功: {len(common_imgs)} 张图片")
    
    # 3. 检查类别ID匹配(最关键)
    print("\n[3] 检查类别ID匹配...")
    gt_cats = anno.loadCats(anno.getCatIds())
    gt_cat_ids = set([cat['id'] for cat in gt_cats])
    pred_cat_ids = set([p['category_id'] for p in preds])
    common_cats = gt_cat_ids & pred_cat_ids
    
    print(f"    标注类别: {[(cat['id'], cat['name']) for cat in gt_cats]}")
    print(f"    预测类别ID范围: min={min(pred_cat_ids)}, max={max(pred_cat_ids)}")
    print(f"    匹配的类别ID: {sorted(common_cats)}")
    
    if len(common_cats) == 0:
        print("    ✗ 严重错误: 没有任何类别ID匹配!")
        print("    可能原因: YOLO类别ID从0开始,COCO标注从1开始")
        print(f"    建议映射: 将预测的category_id + 1")
        return False
    elif len(common_cats) < len(gt_cat_ids):
        print(f"    ⚠ 警告: 只有部分类别匹配 {len(common_cats)}/{len(gt_cat_ids)}")
    
    # 4. 检查bbox格式
    print("\n[4] 检查bbox格式...")
    sample_pred = preds[0]
    sample_bbox = sample_pred['bbox']
    
    print(f"    示例bbox: {sample_bbox}")
    print(f"    COCO要求: [x, y, width, height] (绝对像素值)")
    
    # 检查是否可能是绝对坐标
    if max(sample_bbox) <= 1.0:
        print("    ✗ 错误: bbox坐标 <= 1,可能是归一化坐标!")
        print("    需要转换: 将归一化坐标乘以图像尺寸")
        return False
    elif max(sample_bbox) > 10000:
        print("    ⚠ 警告: bbox坐标异常大,可能单位错误")
    
    # 检查width/height是否为正
    if sample_bbox[2] <= 0 or sample_bbox[3] <= 0:
        print("    ✗ 错误: bbox的width或height <= 0")
        return False
    
    print("    ✓ bbox格式基本正确")
    
    # 5. 检查置信度分数
    print("\n[5] 检查置信度分数...")
    if 'score' in sample_pred:
        scores = [p.get('score', 0) for p in preds]
        print(f"    分数范围: [{min(scores):.4f}, {max(scores):.4f}]")
        print(f"    平均分数: {np.mean(scores):.4f}")
        
        # 检查是否有过高阈值
        high_score = [s for s in scores if s > 0.5]
        print(f"    分数>0.5的框: {len(high_score)}/{len(scores)} ({len(high_score)/len(scores)*100:.1f}%)")
        
        if max(scores) < 0.1:
            print("    ⚠ 警告: 所有分数都很低,考虑降低验证时的conf阈值")
    else:
        print("    ⚠ 警告: 预测文件中没有score字段")
    
    # 6. 统计信息
    print("\n[6] 统计信息:")
    # 计算每张图的平均预测框数
    preds_per_img = {}
    for p in preds:
        preds_per_img[p['image_id']] = preds_per_img.get(p['image_id'], 0) + 1
    
    if preds_per_img:
        avg_preds = np.mean(list(preds_per_img.values()))
        print(f"    平均每张图预测框数: {avg_preds:.2f}")
        print(f"    预测框最多的图片: {max(preds_per_img.values())} 个框")
    
    # 获取标注统计
    anns_per_img = {}
    for img_id in common_imgs:
        ann_ids = anno.getAnnIds(imgIds=[img_id])
        anns_per_img[img_id] = len(ann_ids)
    
    if anns_per_img:
        avg_gts = np.mean(list(anns_per_img.values()))
        print(f"    平均每张图标框数: {avg_gts:.2f}")
    
    print("\n" + "="*60)
    print("诊断完成!")
    print("="*60)
    return True

def fix_common_issues(anno_json, pred_json, output_json):
    """自动修复常见问题"""
    
    print("\n[修复] 尝试自动修复...")
    
    # 加载数据
    anno = COCO(anno_json)
    with open(pred_json, 'r') as f:
        preds = json.load(f)
    
    fixed_preds = []
    for pred in preds:
        # 修复1: 类别ID偏移(YOLO从0开始,COCO从1开始)
        # 根据标注的类别ID范围自动调整
        gt_cat_ids = anno.getCatIds()
        if min(gt_cat_ids) == 1 and min([p['category_id'] for p in preds]) == 0:
            pred['category_id'] += 1
            print("    ✓ 修复类别ID偏移 (+1)")
        
        # 修复2: 确保bbox格式正确
        bbox = pred['bbox']
        if len(bbox) == 4:
            # 如果是归一化坐标,需要转换(但我们不知道图像尺寸,只能警告)
            if max(bbox) <= 1.0:
                print("    ✗ 无法自动修复归一化坐标,需要知道图像尺寸")
                continue
        
        fixed_preds.append(pred)
    
    # 保存修复后的文件
    with open(output_json, 'w') as f:
        json.dump(fixed_preds, f)
    
    print(f"    修复后保存到: {output_json}")
    return output_json

if __name__ == '__main__':
    # 您的文件路径
    anno_json = 'dataset/dronevehicle/val.json'
    pred_json = 'uav-base/yolov26/runs/detect/runs/val/exp4/predictions.json'
    
    # 运行诊断
    success = diagnose_coco_mismatch(anno_json, pred_json)
    
    if not success:
        print("\n尝试自动修复...")
        fixed_json = fix_common_issues(anno_json, pred_json, 'fixed_predictions.json')
        
        # 重新评估
        print("\n使用修复后的文件重新评估...")
        from pycocotools.coco import COCO
        from pycocotools.cocoeval import COCOeval
        
        anno = COCO(anno_json)
        pred = anno.loadRes(fixed_json)
        eval = COCOeval(anno, pred, 'bbox')
        eval.evaluate()
        eval.accumulate()
        print("\n修复后的评估结果:")
        eval.summarize()

2️⃣校准

python 复制代码
import json
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval

def fix_predictions():
    anno_json = 'dataset/dronevehicle/val.json'
    pred_json = 'yolov26/runs/detect/runs/val/exp3/predictions.json'
    
    # 加载预测文件
    with open(pred_json, 'r') as f:
        preds = json.load(f)
    
    print(f"原始预测类别ID范围: {min([p['category_id'] for p in preds])} - {max([p['category_id'] for p in preds])}")
    
    # 将所有预测类别ID减1(从1-5转为0-4)
    for pred in preds:
        pred['category_id'] -= 1
    
    print(f"修改后类别ID范围: {min([p['category_id'] for p in preds])} - {max([p['category_id'] for p in preds])}")
    
    # 保存修复后的文件
    fixed_json = '/home/emily/liyi/uav-base/yolov26/runs/detect/runs/val/exp3/predictions_fixed_ids.json'
    with open(fixed_json, 'w') as f:
        json.dump(preds, f)
    
    # 重新评估
    print("\n重新评估修复后的结果...")
    anno = COCO(anno_json)
    pred = anno.loadRes(fixed_json)
    eval = COCOeval(anno, pred, 'bbox')
    eval.evaluate()
    eval.accumulate()
    eval.summarize()
    
    return fixed_json

if __name__ == '__main__':
    fix_predictions()
相关推荐
骑士雄师1 小时前
18.4 长期记忆可修改版
python
~小先生~2 小时前
Python从入门到放弃(一)
开发语言·python
天佑木枫2 小时前
第2天:变量与数据类型 —— 让程序记住信息
python
Dust-Chasing3 小时前
Claude Code源码剖析 - Claude Code 上下文压缩机制
人工智能·python·ai
Cloud_Shy6184 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第五章 Item 33 - 35)
开发语言·人工智能·笔记·python·学习方法
abcy0712135 小时前
python pandas csv异步后台清洗前端优先返回成功信息
前端·python·pandas
颜酱5 小时前
LangChain使用RAG 入门:让大模型读懂你的私有文档
python·langchain
天天进步20155 小时前
Python全栈项目--校园智能宿舍管理系统
开发语言·python
测试员周周6 小时前
【AI测试智能体-面试】AI测试面试60题(附回答思路)
人工智能·python·功能测试·测试工具·单元测试·自动化·测试用例