Files
biz_xfj_cloud/lib/common/models/plugs/face_discern_models.dart
T
2026-08-31 15:09:38 +08:00

769 lines
25 KiB
Dart

import 'package:json_annotation/json_annotation.dart';
part 'face_discern_models.g.dart';
@JsonSerializable()
class FaceStream {
/// 本次比对相似度,范围为 0 到 1。
double similar;
/// 虹软完成本次比对耗时,单位为毫秒。
int cost;
/// 本地虹软人脸库记录编号,旧协议可能为空。
int? faceId;
/// 服务端同步到终端的人脸样本编号。
int? sampleId;
/// 识别成功人员的服务端用户编号。
String? userId;
/// 识别成功人员的展示姓名。
String? userName;
/// 原生会话编号,用于把一次成功识别绑定到唯一订单;旧原生插件为空。
String? sessionId;
/// 实际命中比对的 NV21 帧采集时间,单位为 Unix 毫秒。
int? capturedAt;
/// 原生裁剪后的现场 JPEG 私有路径;该路径只在终端本地队列使用。
String? evidenceFilePath;
/// 现场 JPEG 的 SHA-256 摘要,文件缺失或裁剪失败时为空。
String? evidenceHash;
/// 现场 JPEG 大小,单位为字节。
int? evidenceSize;
/// 现场 JPEG 宽度,单位为像素。
int? evidenceWidth;
/// 现场 JPEG 高度,单位为像素。
int? evidenceHeight;
/// 本次人脸比对采用的阈值快照,范围为 0 到 1。
double? similarityThreshold;
/// 现场证据生成失败的安全摘要,不得包含完整路径、原始帧或图片内容。
String? evidenceError;
FaceStream({
required this.similar,
required this.cost,
this.faceId,
this.sampleId,
this.userId,
this.userName,
this.sessionId,
this.capturedAt,
this.evidenceFilePath,
this.evidenceHash,
this.evidenceSize,
this.evidenceWidth,
this.evidenceHeight,
this.similarityThreshold,
this.evidenceError,
});
factory FaceStream.fromJson(Map<String, dynamic> json) =>
_$FaceStreamFromJson(json);
Map<String, dynamic> toJson() => _$FaceStreamToJson(this);
}
@JsonSerializable()
class FaceConfig {
String? appId;
String? sdkKey;
String? activeKey;
// 是否激活
bool isActive = false;
FaceConfig({
this.appId,
this.sdkKey,
this.activeKey,
this.isActive = false,
});
factory FaceConfig.fromJson(Map<String, dynamic> json) =>
_$FaceConfigFromJson(json);
Map<String, dynamic> toJson() => _$FaceConfigToJson(this);
}
/// 终端本机的人脸识别策略,对应 Android `RecognizeConfiguration` 的可调白名单。
class FaceRecognitionConfiguration {
const FaceRecognitionConfiguration({
required this.enableLiveness,
required this.similarThreshold,
required this.rgbLivenessThreshold,
required this.enableImageQuality,
required this.imageQualityNoMaskRecognizeThreshold,
required this.imageQualityMaskRecognizeThreshold,
required this.libraryFaceQualityThreshold,
required this.keepMaxFace,
required this.maxDetectFaces,
required this.extractRetryCount,
required this.livenessRetryCount,
required this.recognizeFailedRetryInterval,
required this.livenessFailedRetryInterval,
required this.livenessType,
});
const FaceRecognitionConfiguration.defaults()
: enableLiveness = false,
similarThreshold = 0.8,
rgbLivenessThreshold = 0.5,
enableImageQuality = true,
imageQualityNoMaskRecognizeThreshold = 0.49,
imageQualityMaskRecognizeThreshold = 0.29,
libraryFaceQualityThreshold = 0.60,
keepMaxFace = true,
maxDetectFaces = 1,
extractRetryCount = 3,
livenessRetryCount = 3,
recognizeFailedRetryInterval = 0,
livenessFailedRetryInterval = 0,
livenessType = 'RGB';
final bool enableLiveness;
final double similarThreshold;
final double rgbLivenessThreshold;
final bool enableImageQuality;
final double imageQualityNoMaskRecognizeThreshold;
final double imageQualityMaskRecognizeThreshold;
/// 在库照片诊断阈值;只决定低质量列表分类,不阻止人脸同步或参与识别。
final double libraryFaceQualityThreshold;
final bool keepMaxFace;
final int maxDetectFaces;
final int extractRetryCount;
final int livenessRetryCount;
final int recognizeFailedRetryInterval;
final int livenessFailedRetryInterval;
final String livenessType;
factory FaceRecognitionConfiguration.fromMap(Map<dynamic, dynamic> map) {
const defaults = FaceRecognitionConfiguration.defaults();
return FaceRecognitionConfiguration(
enableLiveness: _asBoolOr(map['enableLiveness'], defaults.enableLiveness),
similarThreshold:
_asDouble(map['similarThreshold'], defaults.similarThreshold),
rgbLivenessThreshold: _asDouble(
map['rgbLivenessThreshold'],
defaults.rgbLivenessThreshold,
),
enableImageQuality:
_asBoolOr(map['enableImageQuality'], defaults.enableImageQuality),
imageQualityNoMaskRecognizeThreshold: _asDouble(
map['imageQualityNoMaskRecognizeThreshold'],
defaults.imageQualityNoMaskRecognizeThreshold,
),
imageQualityMaskRecognizeThreshold: _asDouble(
map['imageQualityMaskRecognizeThreshold'],
defaults.imageQualityMaskRecognizeThreshold,
),
libraryFaceQualityThreshold: _asDouble(
map['libraryFaceQualityThreshold'],
defaults.libraryFaceQualityThreshold,
).clamp(0.0, 1.0).toDouble(),
keepMaxFace: _asBoolOr(map['keepMaxFace'], defaults.keepMaxFace),
maxDetectFaces: _asIntOr(map['maxDetectFaces'], defaults.maxDetectFaces),
extractRetryCount:
_asIntOr(map['extractRetryCount'], defaults.extractRetryCount),
livenessRetryCount:
_asIntOr(map['livenessRetryCount'], defaults.livenessRetryCount),
recognizeFailedRetryInterval: _asIntOr(
map['recognizeFailedRetryInterval'],
defaults.recognizeFailedRetryInterval,
),
livenessFailedRetryInterval: _asIntOr(
map['livenessFailedRetryInterval'],
defaults.livenessFailedRetryInterval,
),
livenessType: map['livenessType']?.toString() ?? defaults.livenessType,
);
}
Map<String, dynamic> toMap() => {
'enableLiveness': enableLiveness,
'similarThreshold': similarThreshold,
'rgbLivenessThreshold': rgbLivenessThreshold,
'enableImageQuality': enableImageQuality,
'imageQualityNoMaskRecognizeThreshold':
imageQualityNoMaskRecognizeThreshold,
'imageQualityMaskRecognizeThreshold':
imageQualityMaskRecognizeThreshold,
'libraryFaceQualityThreshold':
libraryFaceQualityThreshold.clamp(0.0, 1.0).toDouble(),
'keepMaxFace': keepMaxFace,
'maxDetectFaces': maxDetectFaces,
'extractRetryCount': extractRetryCount,
'livenessRetryCount': livenessRetryCount,
'recognizeFailedRetryInterval': recognizeFailedRetryInterval,
'livenessFailedRetryInterval': livenessFailedRetryInterval,
'livenessType': livenessType,
};
bool get usesDefaults =>
this == const FaceRecognitionConfiguration.defaults();
FaceRecognitionConfiguration copyWith({
bool? enableLiveness,
double? similarThreshold,
double? rgbLivenessThreshold,
bool? enableImageQuality,
double? imageQualityNoMaskRecognizeThreshold,
double? imageQualityMaskRecognizeThreshold,
double? libraryFaceQualityThreshold,
bool? keepMaxFace,
int? maxDetectFaces,
int? extractRetryCount,
int? livenessRetryCount,
int? recognizeFailedRetryInterval,
int? livenessFailedRetryInterval,
}) {
return FaceRecognitionConfiguration(
enableLiveness: enableLiveness ?? this.enableLiveness,
similarThreshold: similarThreshold ?? this.similarThreshold,
rgbLivenessThreshold: rgbLivenessThreshold ?? this.rgbLivenessThreshold,
enableImageQuality: enableImageQuality ?? this.enableImageQuality,
imageQualityNoMaskRecognizeThreshold:
imageQualityNoMaskRecognizeThreshold ??
this.imageQualityNoMaskRecognizeThreshold,
imageQualityMaskRecognizeThreshold: imageQualityMaskRecognizeThreshold ??
this.imageQualityMaskRecognizeThreshold,
libraryFaceQualityThreshold:
(libraryFaceQualityThreshold ?? this.libraryFaceQualityThreshold)
.clamp(0.0, 1.0)
.toDouble(),
keepMaxFace: keepMaxFace ?? this.keepMaxFace,
maxDetectFaces: maxDetectFaces ?? this.maxDetectFaces,
extractRetryCount: extractRetryCount ?? this.extractRetryCount,
livenessRetryCount: livenessRetryCount ?? this.livenessRetryCount,
recognizeFailedRetryInterval:
recognizeFailedRetryInterval ?? this.recognizeFailedRetryInterval,
livenessFailedRetryInterval:
livenessFailedRetryInterval ?? this.livenessFailedRetryInterval,
livenessType: livenessType,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FaceRecognitionConfiguration &&
enableLiveness == other.enableLiveness &&
similarThreshold == other.similarThreshold &&
rgbLivenessThreshold == other.rgbLivenessThreshold &&
enableImageQuality == other.enableImageQuality &&
imageQualityNoMaskRecognizeThreshold ==
other.imageQualityNoMaskRecognizeThreshold &&
imageQualityMaskRecognizeThreshold ==
other.imageQualityMaskRecognizeThreshold &&
libraryFaceQualityThreshold == other.libraryFaceQualityThreshold &&
keepMaxFace == other.keepMaxFace &&
maxDetectFaces == other.maxDetectFaces &&
extractRetryCount == other.extractRetryCount &&
livenessRetryCount == other.livenessRetryCount &&
recognizeFailedRetryInterval == other.recognizeFailedRetryInterval &&
livenessFailedRetryInterval == other.livenessFailedRetryInterval &&
livenessType == other.livenessType;
@override
int get hashCode => Object.hash(
enableLiveness,
similarThreshold,
rgbLivenessThreshold,
enableImageQuality,
imageQualityNoMaskRecognizeThreshold,
imageQualityMaskRecognizeThreshold,
libraryFaceQualityThreshold,
keepMaxFace,
maxDetectFaces,
extractRetryCount,
livenessRetryCount,
recognizeFailedRetryInterval,
livenessFailedRetryInterval,
livenessType,
);
}
@JsonSerializable()
class RegisterAllStorageFaceRes {
// 注册成功数量
int successCount;
// 注册失败数量
int failedCount;
// 注册数量
int registerCount;
// 本地文件数量
int fileCount;
RegisterAllStorageFaceRes({
required this.successCount,
required this.failedCount,
required this.registerCount,
required this.fileCount,
});
factory RegisterAllStorageFaceRes.fromJson(Map<String, dynamic> json) =>
_$RegisterAllStorageFaceResFromJson(json);
Map<String, dynamic> toJson() => _$RegisterAllStorageFaceResToJson(this);
}
@JsonSerializable()
class FaceRes {
int searchId;
List<int> featureData;
String faceTag;
FaceRes({
required this.searchId,
required this.featureData,
required this.faceTag,
});
factory FaceRes.fromJson(Map<String, dynamic> json) =>
_$FaceResFromJson(json);
Map<String, dynamic> toJson() => _$FaceResToJson(this);
}
/// 原生图片特征提取结果;提取阶段不修改 SQLite 或虹软内存库。
class FaceFeatureExtractionResult {
/// 创建一次原生特征提取结果。
const FaceFeatureExtractionResult({
required this.success,
required this.message,
required this.featureData,
this.qualityStatus = FaceQualityStatus.pending,
this.qualityScore,
this.qualityErrorCode,
});
/// 从 MethodChannel 返回映射创建特征提取结果。
factory FaceFeatureExtractionResult.fromMap(Map<dynamic, dynamic> map) {
final rawFeature = map['featureData'];
return FaceFeatureExtractionResult(
success: map['success'] == true,
message: map['message']?.toString() ?? '人脸特征提取失败',
featureData: rawFeature is List
? rawFeature.map((item) => (item as num).toInt()).toList()
: const [],
qualityStatus: FaceQualityStatus.fromCode(
map['qualityStatus']?.toString(),
),
qualityScore: (map['qualityScore'] as num?)?.toDouble(),
qualityErrorCode: (map['qualityErrorCode'] as num?)?.toInt(),
);
}
/// 是否成功提取且返回非空特征。
final bool success;
/// 不包含图片路径和特征内容的安全结果摘要。
final String message;
/// 虹软注册特征字节;仅在进程内传递给增量应用通道。
final List<int> featureData;
/// 静态图片质量检测状态;失败不改变 [success] 的特征提取语义。
final FaceQualityStatus qualityStatus;
/// 虹软图片质量分,范围为 0 到 1;未检测或检测失败时为空。
final double? qualityScore;
/// 虹软质量检测错误码;插件内部错误使用负值,成功时为空。
final int? qualityErrorCode;
}
/// 在库人脸质量状态的跨端持久化协议。
enum FaceQualityStatus {
/// 历史数据尚未执行质量检测。
pending('pending'),
/// 虹软已返回有效的 0 到 1 质量分。
detected('detected'),
/// 图片、授权或 SDK 异常导致本次未取得有效质量分。
failed('failed');
/// 创建一个数据库和 MethodChannel 共用的稳定状态值。
const FaceQualityStatus(this.code);
/// 写入 SQLite 和原生通道的稳定编码,不依赖 Dart 枚举名称。
final String code;
/// 将未知或空状态兼容为待检测,避免旧版本数据被误判为低质量。
static FaceQualityStatus fromCode(String? code) {
return FaceQualityStatus.values.firstWhere(
(value) => value.code == code,
orElse: () => FaceQualityStatus.pending,
);
}
}
/// 提交给 Android 原生批量检测的一条本地图片引用。
class FaceQualityAuditItem {
/// 创建一条仅在进程内传递的样本与本地文件映射。
const FaceQualityAuditItem({
required this.sampleId,
required this.filePath,
});
/// 服务端同步到终端的虹软样本编号。
final String sampleId;
/// 终端私有存储中的图片绝对路径;只传给本机原生层,不写日志或上报。
final String filePath;
/// 转换为 StandardMessageCodec 支持的原生参数。
Map<String, dynamic> toMap() => {
'sampleId': sampleId,
'filePath': filePath,
};
}
/// 原生批量质量检测中的单条结果。
class FaceImageQualityResult {
/// 创建一条只包含样本编号和诊断信息的质量结果。
const FaceImageQualityResult({
required this.sampleId,
required this.status,
required this.message,
this.score,
this.errorCode,
});
/// 从 Android StandardMessageCodec 映射解析单条结果。
factory FaceImageQualityResult.fromMap(Map<dynamic, dynamic> map) =>
FaceImageQualityResult(
sampleId: map['sampleId']?.toString() ?? '',
status: FaceQualityStatus.fromCode(map['status']?.toString()),
message: map['message']?.toString() ?? '图片质量检测失败',
score: (map['score'] as num?)?.toDouble(),
errorCode: (map['errorCode'] as num?)?.toInt(),
);
/// 服务端同步到终端的虹软样本编号。
final String sampleId;
/// 本次图片质量检测状态。
final FaceQualityStatus status;
/// 不包含文件路径和人员信息的安全结果摘要。
final String message;
/// 虹软质量分,检测成功时范围为 0 到 1。
final double? score;
/// 虹软或插件内部错误码,检测成功时为空。
final int? errorCode;
}
/// 原生批量质量检测的实时进度。
class FaceQualityAuditProgress {
/// 创建一份可供设置页展示的批量检测进度快照。
const FaceQualityAuditProgress({
required this.processed,
required this.total,
required this.successCount,
required this.failedCount,
required this.inProgress,
required this.message,
this.currentSampleId,
});
/// 从质量检测事件通道解析进度快照。
factory FaceQualityAuditProgress.fromMap(Map<dynamic, dynamic> map) =>
FaceQualityAuditProgress(
processed: (map['processed'] as num?)?.toInt() ?? 0,
total: (map['total'] as num?)?.toInt() ?? 0,
successCount: (map['successCount'] as num?)?.toInt() ?? 0,
failedCount: (map['failedCount'] as num?)?.toInt() ?? 0,
inProgress: map['inProgress'] == true,
message: map['message']?.toString() ?? '等待检测',
currentSampleId: map['currentSampleId']?.toString(),
);
/// 已完成检测的样本数量。
final int processed;
/// 本轮提交给原生层的样本总数。
final int total;
/// 已取得有效质量分的样本数量。
final int successCount;
/// 未取得有效质量分的样本数量。
final int failedCount;
/// 原生批量任务是否仍持有人脸操作互斥锁。
final bool inProgress;
/// 当前阶段的安全展示文案。
final String message;
/// 当前处理样本编号;空值表示尚未开始或已经结束。
final String? currentSampleId;
/// 供进度条使用的 0 到 1 比例,空任务视为 0。
double get fraction =>
total <= 0 ? 0.0 : (processed / total).clamp(0.0, 1.0).toDouble();
}
/// 原生批量质量检测完成结果。
class FaceQualityAuditResult {
/// 创建一轮不可变的质量检测结果集合。
const FaceQualityAuditResult({
required this.results,
required this.message,
});
/// 从 MethodChannel 返回映射解析全部单条结果。
factory FaceQualityAuditResult.fromMap(Map<dynamic, dynamic> map) {
final rawResults = map['results'];
return FaceQualityAuditResult(
results: rawResults is List
? rawResults
.whereType<Map>()
.map(FaceImageQualityResult.fromMap)
.toList(growable: false)
: const [],
message: map['message']?.toString() ?? '在库人脸质量检测完成',
);
}
/// 与请求样本一一对应的检测结果。
final List<FaceImageQualityResult> results;
/// 本轮检测完成后的安全摘要。
final String message;
}
/// 原生单样本人脸增删结果。
class FaceDeltaApplyResult {
/// 创建一次原生增量应用结果。
const FaceDeltaApplyResult({
required this.success,
required this.message,
required this.code,
});
/// 从 MethodChannel 返回映射创建增量结果。
factory FaceDeltaApplyResult.fromMap(Map<dynamic, dynamic> map) =>
FaceDeltaApplyResult(
success: map['success'] == true,
message: map['message']?.toString() ?? '人脸库增量更新失败',
code: (map['code'] as num?)?.toInt() ?? -99,
);
/// 虹软内存库是否已完成目标状态。
final bool success;
/// 不包含用户姓名、图片地址或特征的安全结果摘要。
final String message;
/// 虹软 SDK 返回码;插件异常使用内部负值。
final int code;
}
/// 原生人脸插件、授权文件和内存人脸库的实时状态。
class FacePluginStatus {
const FacePluginStatus({
required this.activated,
required this.activationCode,
required this.parametersComplete,
required this.engineReady,
required this.engineCode,
required this.databaseCount,
required this.memoryCount,
required this.registrationInProgress,
required this.libraryConsistent,
this.activationStartTime,
this.activationEndTime,
this.registrationProgress,
this.lastRegistrationResult,
});
final bool activated;
final int activationCode;
final bool parametersComplete;
final bool engineReady;
final int engineCode;
final int databaseCount;
final int memoryCount;
final bool registrationInProgress;
final bool libraryConsistent;
final String? activationStartTime;
final String? activationEndTime;
final FaceRegistrationProgress? registrationProgress;
final FaceRegistrationResult? lastRegistrationResult;
bool get ready =>
activated && engineReady && !registrationInProgress && libraryConsistent;
factory FacePluginStatus.fromMap(Map<dynamic, dynamic> map) {
return FacePluginStatus(
activated: _asBool(map['activated']),
activationCode: _asInt(map['activationCode']),
parametersComplete: _asBool(map['parametersComplete']),
engineReady: _asBool(map['engineReady']),
engineCode: _asInt(map['engineCode']),
databaseCount: _asInt(map['databaseCount']),
memoryCount: _asInt(map['memoryCount']),
registrationInProgress: _asBool(map['registrationInProgress']),
libraryConsistent: _asBool(map['libraryConsistent']),
activationStartTime: _asNullableString(map['activationStartTime']),
activationEndTime: _asNullableString(map['activationEndTime']),
registrationProgress: map['registrationProgress'] is Map
? FaceRegistrationProgress.fromMap(
map['registrationProgress'] as Map,
)
: null,
lastRegistrationResult: map['lastRegistrationResult'] is Map
? FaceRegistrationResult.fromMap(
map['lastRegistrationResult'] as Map,
)
: null,
);
}
}
class FaceActivationResult {
const FaceActivationResult({
required this.success,
required this.code,
required this.message,
required this.requiresPermission,
required this.canOpenSettings,
});
final bool success;
final int code;
final String message;
final bool requiresPermission;
final bool canOpenSettings;
factory FaceActivationResult.fromMap(Map<dynamic, dynamic> map) {
return FaceActivationResult(
success: _asBool(map['success']),
code: _asInt(map['code']),
message: _asString(map['message'], '人脸插件激活失败'),
requiresPermission: _asBool(map['requiresPermission']),
canOpenSettings: _asBool(map['canOpenSettings']),
);
}
}
class FaceRegistrationProgress {
const FaceRegistrationProgress({
required this.phase,
required this.message,
required this.processed,
required this.total,
required this.successCount,
required this.failedCount,
required this.inProgress,
this.currentSampleId,
});
final String phase;
final String message;
final int processed;
final int total;
final int successCount;
final int failedCount;
final bool inProgress;
final int? currentSampleId;
double get fraction =>
total <= 0 ? 0.0 : (processed / total).clamp(0.0, 1.0).toDouble();
factory FaceRegistrationProgress.fromMap(Map<dynamic, dynamic> map) {
return FaceRegistrationProgress(
phase: _asString(map['phase'], 'idle'),
message: _asString(map['message'], '尚未执行人脸库注册'),
processed: _asInt(map['processed']),
total: _asInt(map['total']),
successCount: _asInt(map['successCount']),
failedCount: _asInt(map['failedCount']),
inProgress: _asBool(map['inProgress']),
currentSampleId: map['currentSampleId'] == null
? null
: _asInt(map['currentSampleId']),
);
}
}
class FaceRegistrationResult {
const FaceRegistrationResult({
required this.success,
required this.message,
required this.total,
required this.successCount,
required this.failedCount,
required this.failedSampleIds,
required this.durationMs,
});
final bool success;
final String message;
final int total;
final int successCount;
final int failedCount;
final List<int> failedSampleIds;
final int durationMs;
factory FaceRegistrationResult.fromMap(Map<dynamic, dynamic> map) {
final failedSampleIds = map['failedSampleIds'];
return FaceRegistrationResult(
success: _asBool(map['success']),
message: _asString(map['message'], '人脸库注册失败'),
total: _asInt(map['total']),
successCount: _asInt(map['successCount']),
failedCount: _asInt(map['failedCount']),
failedSampleIds: failedSampleIds is List
? failedSampleIds.map(_asInt).toList(growable: false)
: const [],
durationMs: _asInt(map['durationMs']),
);
}
}
bool _asBool(dynamic value) => value == true;
int _asInt(dynamic value) => value is num ? value.toInt() : 0;
int _asIntOr(dynamic value, int fallback) =>
value is num ? value.toInt() : fallback;
double _asDouble(dynamic value, double fallback) =>
value is num ? value.toDouble() : fallback;
bool _asBoolOr(dynamic value, bool fallback) =>
value is bool ? value : fallback;
String _asString(dynamic value, String fallback) {
final result = value?.toString();
return result == null || result.isEmpty ? fallback : result;
}
String? _asNullableString(dynamic value) {
final result = value?.toString();
return result == null || result.isEmpty ? null : result;
}