1
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:observer/camera/camera_view_model.dart';
|
||||
import 'package:observer/detection/detection_result.dart';
|
||||
import 'package:observer/reminder/reminder.dart';
|
||||
|
||||
DetectionResult box(
|
||||
String label,
|
||||
double score,
|
||||
double l,
|
||||
double t,
|
||||
double r,
|
||||
double b, {
|
||||
int modelId = -1,
|
||||
int classId = 0,
|
||||
}) =>
|
||||
DetectionResult(
|
||||
label: label,
|
||||
score: score,
|
||||
left: l,
|
||||
top: t,
|
||||
right: r,
|
||||
bottom: b,
|
||||
modelId: modelId,
|
||||
classId: classId,
|
||||
);
|
||||
|
||||
/// 真实 Reminder 会触碰音频/震动插件通道,测试用记录桩(onDetected 同步记录)。
|
||||
/// AudioPlayer 构造里的 _create 平台调用在测试环境抛 MissingPluginException,
|
||||
/// 已被插件内部 catch,不影响用例。
|
||||
class _RecordingReminder extends Reminder {
|
||||
final List<String> alerts = [];
|
||||
@override
|
||||
void onDetected(String label) => alerts.add(label);
|
||||
}
|
||||
|
||||
void feed(CameraViewModel vm, List<DetectionResult> results) {
|
||||
vm.onFramesAnalyzed(results, 90, 1280, 720, const [], const [],
|
||||
lastProcessMs: 8);
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('dedupeVisibleOverlaps', () {
|
||||
test('同类别同位置重复框:只保留最高分', () {
|
||||
final visible = [
|
||||
box('雉鸡', 0.6, 0.3, 0.3, 0.5, 0.5, modelId: 1),
|
||||
box('斑鸠', 0.5, 0.3, 0.3, 0.5, 0.5, modelId: 2),
|
||||
];
|
||||
final out = dedupeVisibleOverlaps(visible);
|
||||
expect(out.length, 1);
|
||||
expect(out.single.label, '雉鸡');
|
||||
expect(out.single.score, 0.6);
|
||||
});
|
||||
|
||||
test('不同类别同位置(目标×疑似):不同语义各自保留', () {
|
||||
final visible = [
|
||||
box('雉鸡', 0.6, 0.3, 0.3, 0.5, 0.5, classId: 0),
|
||||
box('生境', 0.8, 0.2, 0.2, 0.6, 0.6, classId: 1),
|
||||
];
|
||||
final out = dedupeVisibleOverlaps(visible);
|
||||
expect(out.length, 2);
|
||||
});
|
||||
|
||||
test('中心不在对方框内的偏移重叠:两个独立目标不误并', () {
|
||||
// 覆盖 ~40% 但中心互不在对方框内(并肩两个目标)
|
||||
final visible = [
|
||||
box('雉鸡', 0.7, 0.10, 0.10, 0.40, 0.40),
|
||||
box('雉鸡', 0.5, 0.28, 0.10, 0.58, 0.40),
|
||||
];
|
||||
expect(dedupeVisibleOverlaps(visible).length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('跨标签同目标补挂:交替检出同一目标只一条轨迹/一次提醒', () async {
|
||||
final reminder = _RecordingReminder();
|
||||
final vm = CameraViewModel(reminder: reminder);
|
||||
feed(vm, [box('雉鸡', 0.6, 0.2, 0.2, 0.5, 0.5, modelId: 1)]);
|
||||
// 越过 500ms 显示窗(确认轨迹开始显示、提醒窗口开启)
|
||||
await Future<void>.delayed(const Duration(milliseconds: 600));
|
||||
// 模型 2 检出同一目标:细长小框贴大框边角(中心距 ~0.2 > 跨标签收紧半径
|
||||
// 0.072、覆盖 ~0.31 也不到 0.45),但小框中心在大框内 → 补挂进雉鸡
|
||||
// 轨迹而不是新建第二条
|
||||
feed(vm, [box('斑鸠', 0.55, 0.38, 0.445, 0.62, 0.535, modelId: 2)]);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
|
||||
expect(reminder.alerts, ['雉鸡'],
|
||||
reason: '补挂进原轨迹:提醒按轨迹原标签只触发一次;若新建第二条轨迹'
|
||||
'(斑鸠未达 500ms 显示窗)本帧不会触发提醒');
|
||||
expect(vm.state.results.length, 1, reason: '同位置只有一条轨迹可见');
|
||||
expect(vm.state.results.single.label, '斑鸠', reason: '框内容跟随后到的检测');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user