Files
biz_xfj_cloud/test/common/face_recognition_preview_layer_test.dart
T
2026-08-09 14:45:18 +08:00

187 lines
5.4 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:pj_supermarket_cloud/common/models/plugs/face_recognition_session_models.dart';
import 'package:pj_supermarket_cloud/common/style/xfj_design_tokens.dart';
import 'package:pj_supermarket_cloud/common/widgets/face/face_recognition_preview_layer.dart';
/// 验证仅识别最大脸时的面积排序、可见文案和语义颜色。
void main() {
const smallFace = FaceRecognitionFaceBox(
left: 0.05,
top: 0.10,
right: 0.25,
bottom: 0.30,
recognizeStatus: 'waiting',
livenessStatus: 'unknown',
);
const largestFace = FaceRecognitionFaceBox(
left: 0.30,
top: 0.20,
right: 0.70,
bottom: 0.70,
recognizeStatus: 'comparing',
livenessStatus: 'alive',
similarity: 0.86,
);
const mediumFace = FaceRecognitionFaceBox(
left: 0.72,
top: 0.15,
right: 0.97,
bottom: 0.55,
recognizeStatus: 'failed',
livenessStatus: 'notAlive',
similarity: 0.42,
);
test('会话事件解析原生最大脸配置并保留全部人脸框', () {
final event = FaceRecognitionSessionEvent.fromMap({
'sessionId': 'face-session-1',
'scene': 'cashier',
'state': 'detecting',
'revision': 3,
'keepMaxFace': true,
'faceBoxes': [
{
'left': 0.05,
'top': 0.10,
'right': 0.25,
'bottom': 0.30,
'recognizeStatus': 'waiting',
'livenessStatus': 'unknown',
},
{
'left': 0.30,
'top': 0.20,
'right': 0.70,
'bottom': 0.70,
'recognizeStatus': 'comparing',
'livenessStatus': 'alive',
},
],
});
expect(event.keepMaxFace, isTrue);
expect(event.eventSequence, 0);
expect(event.faceBoxes, hasLength(2));
});
test('原生事件可解析进程级单调序号', () {
final event = FaceRecognitionSessionEvent.fromMap({
'sessionId': 'face-session-2',
'scene': 'reservation',
'state': 'detecting',
'revision': 3,
'eventSequence': 17,
'faceBoxes': const [],
});
expect(event.eventSequence, 17);
});
test('归一化面积计算异常坐标时不会产生负占比', () {
const invalidFace = FaceRecognitionFaceBox(
left: 0.8,
top: 0.7,
right: 0.2,
bottom: 0.1,
recognizeStatus: 'waiting',
livenessStatus: 'unknown',
);
expect(smallFace.normalizedArea, closeTo(0.04, 0.000001));
expect(invalidFace.normalizedArea, 0);
});
test('最大脸模式按面积选中绿色目标,其余使用红色文字标识', () {
final painter = _painter(
boxes: const [smallFace, largestFace, mediumFace],
keepMaxFace: true,
);
expect(painter.primaryFaceIndex, 1);
expect(painter.colorFor(1), XfjDesignTokens.success);
expect(painter.colorFor(0), XfjDesignTokens.danger);
expect(painter.colorFor(2), XfjDesignTokens.danger);
expect(painter.labelFor(1), contains('识别目标 · 占画面 20.0%'));
expect(painter.labelFor(1), contains('相似度 86.0%'));
expect(painter.labelFor(0), contains('非识别目标 · 占画面 4.0%'));
expect(painter.labelFor(0), contains('不参与识别'));
});
test('旋转和镜像不改变最大脸面积判断', () {
final boxes = const [smallFace, largestFace, mediumFace];
final rotatedPainter = _painter(
boxes: boxes,
keepMaxFace: true,
rotation: 90,
mirror: true,
);
final normalPainter = _painter(
boxes: boxes,
keepMaxFace: true,
);
expect(rotatedPainter.primaryFaceIndex, normalPainter.primaryFaceIndex);
});
test('关闭最大脸模式后继续沿用识别结果颜色且不显示面积标签', () {
final painter = _painter(
boxes: const [smallFace, largestFace, mediumFace],
keepMaxFace: false,
);
expect(painter.colorFor(0), XfjDesignTokens.primary);
expect(painter.colorFor(1), XfjDesignTokens.success);
expect(painter.colorFor(2), XfjDesignTokens.danger);
expect(painter.labelFor(1), isNot(contains('占画面')));
expect(painter.labelFor(1), contains('相似度 86.0%'));
});
test('低相似度和尚未比对的人脸仍全部进入绘制器', () {
const lowSimilarityFace = FaceRecognitionFaceBox(
left: 0.08,
top: 0.12,
right: 0.34,
bottom: 0.48,
recognizeStatus: 'failed',
livenessStatus: 'unknown',
similarity: 0.12,
);
const waitingFace = FaceRecognitionFaceBox(
left: 0.55,
top: 0.10,
right: 0.88,
bottom: 0.52,
recognizeStatus: 'waiting',
livenessStatus: 'unknown',
);
final painter = _painter(
boxes: const [lowSimilarityFace, waitingFace],
keepMaxFace: true,
);
expect(painter.boxes, hasLength(2));
expect(painter.labelFor(0), contains('非识别目标'));
expect(painter.labelFor(0), contains('相似度 12.0%'));
expect(painter.labelFor(1), contains('识别目标'));
expect(painter.labelFor(1), contains('检测中'));
});
}
/// 创建只用于判断面积、颜色和标签的绘制器,不实际申请摄像头 Texture。
FaceRecognitionBoxPainter _painter({
required List<FaceRecognitionFaceBox> boxes,
required bool keepMaxFace,
int rotation = 0,
bool mirror = false,
}) {
return FaceRecognitionBoxPainter(
boxes: boxes,
rotation: rotation,
mirror: mirror,
livenessEnabled: true,
keepMaxFace: keepMaxFace,
similarityThreshold: 0.8,
);
}