使用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()