Revert "约束预测框互不重叠"(eba533a、9439cbb)

去掉强制非重叠:detect.py 不再给 DetectionValidator.postprocess 打补丁,
配置里移除 train.max_overlap 与 agnostic_nms,可视化脚本恢复为直接使用模型原始输出,
engine/detection 里的 suppress_overlaps / filter_overlapping_outputs 一并移除。

约束会把 mAP50 从 0.555 压到 0.483(precision 0.536->0.612),代价是按置信度抑制时
高分错框会压掉本可命中的低分框。需要时用 git revert 这次提交即可取回。

按类别分目录的可视化产出结构(36fff98)保留。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-18 15:34:13 +08:00
co-authored by Claude Opus 5
parent 36fff984b7
commit 154677032f
5 changed files with 5 additions and 273 deletions
+2 -31
View File
@@ -6,11 +6,8 @@
--checkpoint runs/detect/film/weights/best.pt
数据是在 Label Studio 里修正过的标签,导出并划分 train/val 之后放在 data/ 下的数据集。
框来自 Grad-CAM / SHAP 的弱监督标注再经人工修正。
**预测框约束为互不重叠**(`train.max_overlap`,默认 0)。真值里一处缺陷只有一个框、
零重叠,模型却会输出重叠框,所以验证阶段补一层抑制——早停与 best.pt 的选择都据此进行,
指标才和最终推理一致。设为负数可关掉。
框来自 Grad-CAM / SHAP 的弱监督标注,不是人工标注——指标该按"与热力区域的一致性"
来读,而不是当成真值上的检测精度。
"""
from __future__ import annotations
@@ -22,7 +19,6 @@ from pathlib import Path
import yaml
from engine.detection import filter_overlapping_outputs
from utils import load_config, seed_everything, setup_logging
from utils.device import DeviceNotAvailableError, describe_device, resolve_device
@@ -39,23 +35,6 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args()
def install_overlap_suppression(max_overlap: float) -> None:
"""给验证器的后处理包一层"框不重叠"约束。
只能用打补丁的方式:ultralytics 没有对应的开关——端到端模型(yolo26)的 NMS 早退,
传入的 iou / agnostic_nms 都不参与 IoU 抑制(见 engine/detection.filter_overlapping_outputs
的说明)。补在 postprocess 之后,训练期验证、早停、best.pt 的选择就都遵守这条约束。
"""
from ultralytics.models.yolo.detect import DetectionValidator
original = DetectionValidator.postprocess
def postprocess(self, preds):
return filter_overlapping_outputs(original(self, preds), max_iou=max_overlap)
DetectionValidator.postprocess = postprocess
def to_ultralytics_device(device) -> str | int:
"""torch.device -> ultralytics 的 device 记法(cuda 用序号,其余用字符串)。"""
if device.type == "cuda":
@@ -100,18 +79,10 @@ def main() -> int:
train_cfg = dict(cfg.get("train", {}))
swanlab_cfg = dict(train_cfg.pop("swanlab", {}))
# 不是 ultralytics 的参数,得先摘出来,否则它会拒绝未知键
max_overlap = float(train_cfg.pop("max_overlap", 0.0))
from ultralytics import YOLO
from ultralytics import settings as ul_settings
if max_overlap >= 0:
install_overlap_suppression(max_overlap)
logger.info("验证时约束预测框互不重叠(最大 IoU %.2f)", max_overlap)
else:
logger.warning("未约束预测框重叠:指标里会计入真值中不可能出现的重叠框")
# ultralytics 默认把数据集根指到 ~/datasets 之类的全局目录;我们的 data.yaml 里写的是
# 绝对路径,但它仍会拿 datasets_dir 去拼相对路径,统一指到项目根省得踩坑。
ul_settings.update({"datasets_dir": str(Path.cwd())})