Files
Shaun ZhouandClaude Opus 5 ddd77653b5 fix(detect): 推理尺寸与训练尺寸不一致时报警
用 640 整图训练的权重跑 160 切片推理,结果看起来完全正常——照常出框、照常有置信度,
但类别系统性地错:实测 mosquito 的 TP 归零,760 张里 599 张被判成 blackdot,
而同一权重整图推理时 mosquito 有 92% 判对。这种错误不会自己暴露。

从 checkpoint 里读训练时的 imgsz,与当前切片尺寸不符就警告。三个调用点都接上:
可视化、批量推送预测、ML backend 加载权重时。visualize 的 summary.json 也补上
tile_size / tile_overlap,免得事后分不清某次产出是怎么跑的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 15:33:41 +08:00

62 lines
1.9 KiB
Python

"""推理尺寸与训练尺寸的一致性校验。
回归:用 640 整图训练的权重跑 160 切片推理,结果看起来完全正常——照常出框、
照常有置信度,但类别系统性地错(实测 mosquito 的 TP 归零、599/760 被判成 blackdot)。
这种错误不会自己暴露,必须主动核对。
"""
from engine.tiled_inference import check_size_matches, training_imgsz
class _Model:
def __init__(self, ckpt=None, overrides=None):
if ckpt is not None:
self.ckpt = ckpt
if overrides is not None:
self.overrides = overrides
def test_reads_imgsz_from_checkpoint():
assert training_imgsz(_Model(ckpt={"train_args": {"imgsz": 640}})) == 640
def test_reads_imgsz_from_overrides():
assert training_imgsz(_Model(overrides={"imgsz": 160})) == 160
def test_missing_imgsz_returns_none():
assert training_imgsz(_Model()) is None
assert training_imgsz(_Model(ckpt={})) is None
def test_mismatch_is_reported():
message = check_size_matches(_Model(ckpt={"train_args": {"imgsz": 640}}), 160)
assert message is not None
assert "640" in message and "160" in message
def test_matching_sizes_are_silent():
assert check_size_matches(_Model(ckpt={"train_args": {"imgsz": 160}}), 160) is None
def test_whole_image_mode_is_not_checked():
"""--tile-size 0 是显式退回整图推理,交给 ultralytics letterbox,不该报警。"""
assert check_size_matches(_Model(ckpt={"train_args": {"imgsz": 640}}), 0) is None
def test_unknown_training_size_is_not_reported():
"""读不到就不报——宁可不说,也不误导。"""
assert check_size_matches(_Model(), 160) is None
def test_logger_receives_the_warning():
seen = []
class _Logger:
def warning(self, fmt, *args):
seen.append(fmt % args)
check_size_matches(_Model(overrides={"imgsz": 640}), 160, _Logger())
assert seen and "不一致" in seen[0]