112 lines
3.8 KiB
Dart
112 lines
3.8 KiB
Dart
import 'package:observer/detection/detection_result.dart';
|
||
import 'package:observer/detection/detector_worker.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
|
||
DetectionResult box(String label, double score, double x, double y,
|
||
{int modelId = -1, String modelName = ''}) =>
|
||
DetectionResult(
|
||
label: label,
|
||
score: score,
|
||
left: x,
|
||
top: y,
|
||
right: x + 0.1,
|
||
bottom: y + 0.1,
|
||
modelId: modelId,
|
||
modelName: modelName,
|
||
classId: 0,
|
||
);
|
||
|
||
DetectionResult rect(String label, double score, double l, double t, double r,
|
||
double b, {int modelId = -1}) =>
|
||
DetectionResult(
|
||
label: label,
|
||
score: score,
|
||
left: l,
|
||
top: t,
|
||
right: r,
|
||
bottom: b,
|
||
modelId: modelId,
|
||
classId: 0,
|
||
);
|
||
|
||
void main() {
|
||
test('不同模型同标签重复框:NMS 去重取高分', () {
|
||
// 两个模型都对同一目标检出同标签框(重复框需去重)
|
||
final all = [
|
||
box('target', 0.18, 0.3, 0.3, modelId: 1, modelName: '模型A'),
|
||
box('target', 0.55, 0.31, 0.3, modelId: 2, modelName: '模型B'),
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 1);
|
||
expect(merged.first.score, 0.55);
|
||
expect(merged.first.modelName, '模型B');
|
||
});
|
||
|
||
test('不同类别重叠:去重取高分(实测多模型对同一目标检异类别)', () {
|
||
final all = [
|
||
box('target', 0.3, 0.5, 0.5, modelId: 1),
|
||
box('second', 0.7, 0.5, 0.5, modelId: 2), // 同位置但不同类别
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 1);
|
||
expect(merged.first.label, 'second');
|
||
expect(merged.first.score, 0.7);
|
||
});
|
||
|
||
test('不同类别不重叠:都保留', () {
|
||
final all = [
|
||
box('target', 0.3, 0.1, 0.1, modelId: 1),
|
||
box('second', 0.7, 0.8, 0.8, modelId: 2), // 远处互不重叠
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 2);
|
||
});
|
||
|
||
test('同模型内部与跨模型合并一致:远处不重叠保留', () {
|
||
final all = [
|
||
box('target', 0.2, 0.1, 0.1, modelId: 1, modelName: '模型A'),
|
||
box('target', 0.3, 0.8, 0.8, modelId: 1, modelName: '模型A'),
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 2);
|
||
expect(merged.first.score, 0.3); // 按分排序
|
||
});
|
||
|
||
test('单条结果原样返回', () {
|
||
final single = [box('suspect', 0.11, 0.2, 0.2)];
|
||
final merged = mergeAcrossModels(single, 0.45);
|
||
expect(identical(merged, single), isTrue);
|
||
});
|
||
|
||
test('不同模型偏移框(小框中心在大框内):0.45 阈值外的窗口也去重', () {
|
||
// 覆盖 ≈ 0.31 < 0.45:纯阈值会漏;小框中心在大框内 → 同一目标取高分
|
||
// (2026-09-03:不同输入分辨率模型对同一目标的框几何系统性偏移)
|
||
final all = [
|
||
rect('target', 0.5, 0.38, 0.445, 0.62, 0.535, modelId: 1),
|
||
rect('second', 0.6, 0.2, 0.2, 0.5, 0.5, modelId: 2),
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 1);
|
||
expect(merged.first.label, 'second');
|
||
expect(merged.first.score, 0.6);
|
||
});
|
||
|
||
test('同模型偏移框:不套用跨模型窗口,正常保留', () {
|
||
final all = [
|
||
rect('target', 0.5, 0.38, 0.445, 0.62, 0.535, modelId: 1),
|
||
rect('second', 0.6, 0.2, 0.2, 0.5, 0.5, modelId: 1),
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 2, reason: '同模型框对由模型内类内 NMS 负责');
|
||
});
|
||
|
||
test('无来源模型(modelId -1):不套用跨模型窗口(存量语义不变)', () {
|
||
final all = [
|
||
rect('target', 0.5, 0.38, 0.445, 0.62, 0.535),
|
||
rect('second', 0.6, 0.2, 0.2, 0.5, 0.5),
|
||
];
|
||
final merged = mergeAcrossModels(all, 0.45);
|
||
expect(merged.length, 2);
|
||
});
|
||
}
|