Files
observer/flutter_app/test/camera_view_model_test.dart
T
2026-09-03 17:50:23 +08:00

94 lines
3.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: '框内容跟随后到的检测');
});
}