import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:typed_data'; import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:arc/arc.dart'; import 'package:camera/camera.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), debugShowCheckedModeBanner: false, ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { String _platformVersion = 'Unknown'; String _activeResult = '未激活'; String _initResult = '未初始化'; bool _isActivated = false; bool _isInitialized = false; final _arcPlugin = Arc(); /// 虹软 SDK 配置信息(请替换为您自己的 AppId、SdkKey 和 ActiveKey) final String _appId = '4nPFuS2TYAQh9werHL2qbKjtTH9nnoixk7G6yqSUyjVH'; final String _sdkKey = 'aWMTT3coxNQFETg9f3BGHCiBznAApXmcHFF3J5yQbsZ'; final String _activeKey = 'NEAT7AU4KLCEK682'; @override void initState() { super.initState(); initPlatformState(); } /// 获取平台版本 Future initPlatformState() async { String platformVersion; try { platformVersion = await _arcPlugin.getPlatformVersion() ?? 'Unknown platform version'; } on PlatformException { platformVersion = 'Failed to get platform version.'; } if (!mounted) return; setState(() { _platformVersion = platformVersion; }); } /// 激活 SDK Future _activeSDK() async { setState(() { _activeResult = '正在激活...'; }); try { final result = await _arcPlugin.activeOnline( appId: _appId, sdkKey: _sdkKey, activeKey: _activeKey, ); if (result != null) { final success = result['success'] as bool? ?? false; final errorCode = result['errorCode'] as int? ?? -1; final message = result['message'] as String? ?? '未知错误'; setState(() { _isActivated = success; _activeResult = success ? '激活成功!\n错误码: $errorCode\n$message' : '激活失败!\n错误码: $errorCode\n$message'; }); } } on PlatformException catch (e) { setState(() { _activeResult = '激活异常: ${e.message}'; }); } } /// 初始化引擎 Future _initEngine() async { if (!_isActivated) { setState(() { _initResult = '请先激活 SDK'; }); return; } setState(() { _initResult = '正在初始化...'; }); try { // 功能掩码组合: // ASF_FACE_DETECT = 0x00000001 (人脸检测) // ASF_FACE_RECOGNITION = 0x00000004 (人脸识别) // ASF_AGE = 0x00000008 (年龄检测) // ASF_GENDER = 0x00000010 (性别检测) // ASF_LIVENESS = 0x00000080 (RGB 活体检测) // 组合掩码 = 0x01 | 0x04 | 0x08 | 0x10 | 0x80 = 0x9D final result = await _arcPlugin.init( detectMode: 0, // 0=VIDEO 模式, 1=IMAGE 模式 orient: 0, // 检测角度优先级 maxFaceNum: 1, // 单人脸识别场景 combinedMask: 0x9D, // 功能组合掩码 ); if (result != null) { final success = result['success'] as bool? ?? false; final errorCode = result['errorCode'] as int? ?? -1; final message = result['message'] as String? ?? '未知错误'; setState(() { _isInitialized = success; _initResult = success ? '初始化成功!\n错误码: $errorCode\n$message' : '初始化失败!\n错误码: $errorCode\n$message'; }); } } on PlatformException catch (e) { setState(() { _initResult = '初始化异常: ${e.message}'; }); } } /// 跳转到人脸识别页面 void _navigateToFaceRecognition() async { try { final cameras = await availableCameras(); if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => FaceRecognitionScreen( arcPlugin: _arcPlugin, cameras: cameras, ), ), ); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('无法打开摄像头: $e')), ); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('虹软人脸识别 SDK 示例'), backgroundColor: Colors.blue, foregroundColor: Colors.white, ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 平台信息 Card( child: Padding( padding: const EdgeInsets.all(12.0), child: Text( '运行平台: $_platformVersion', style: const TextStyle(fontSize: 14), ), ), ), const SizedBox(height: 16), // 激活 SDK 按钮 ElevatedButton( onPressed: _isActivated ? null : _activeSDK, style: ElevatedButton.styleFrom( backgroundColor: _isActivated ? Colors.grey : Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Text( _isActivated ? '已激活' : '激活 SDK', style: const TextStyle(fontSize: 16), ), ), const SizedBox(height: 8), Card( color: _isActivated ? Colors.green[50] : Colors.red[50], child: Padding( padding: const EdgeInsets.all(12.0), child: Text( _activeResult, style: TextStyle( fontSize: 13, color: _isActivated ? Colors.green[800] : Colors.red[800], ), ), ), ), const SizedBox(height: 24), // 初始化引擎按钮 ElevatedButton( onPressed: _isInitialized ? null : _initEngine, style: ElevatedButton.styleFrom( backgroundColor: _isInitialized ? Colors.grey : Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), child: Text( _isInitialized ? '已初始化' : '初始化引擎', style: const TextStyle(fontSize: 16), ), ), const SizedBox(height: 8), Card( color: _isInitialized ? Colors.green[50] : Colors.red[50], child: Padding( padding: const EdgeInsets.all(12.0), child: Text( _initResult, style: TextStyle( fontSize: 13, color: _isInitialized ? Colors.green[800] : Colors.red[800], ), ), ), ), const SizedBox(height: 24), // 人脸识别按钮 ElevatedButton( onPressed: _isInitialized ? _navigateToFaceRecognition : null, style: ElevatedButton.styleFrom( backgroundColor: _isInitialized ? Colors.purple : Colors.grey, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text( '开始人脸识别', style: TextStyle(fontSize: 16), ), ), const SizedBox(height: 24), // 使用说明 const Card( child: Padding( padding: EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '使用说明:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8), Text( '1. 点击"激活 SDK"按钮进行在线激活\n' '2. 激活成功后点击"初始化引擎"按钮\n' '3. 点击"开始人脸识别"进行注册/验证\n\n' '注册流程: 人脸检测 → 活体检测 → 特征提取 → 存储\n' '验证流程: 人脸检测 → 活体检测 → 特征提取 → 比对', style: TextStyle(fontSize: 12), ), ], ), ), ), ], ), ), ); } } /// 人脸识别页面 - 实现完整的注册和验证流程 class FaceRecognitionScreen extends StatefulWidget { final Arc arcPlugin; final List cameras; const FaceRecognitionScreen({ super.key, required this.arcPlugin, required this.cameras, }); @override State createState() => _FaceRecognitionScreenState(); } class _FaceRecognitionScreenState extends State { CameraController? _cameraController; bool _isProcessing = false; String _statusMessage = '正在初始化摄像头...'; // 检测结果 int _faceCount = 0; int _rgbLivenessResult = -1; // -1=未检测, 0=非真人, 1=真人 // 注册的人脸特征数据存储 key static const String _storedFeatureKey = 'stored_face_feature'; // 当前帧的人脸信息 Map? _currentFaceInfo; Uint8List? _currentNv21Data; int _currentWidth = 0; int _currentHeight = 0; // 模式: 0=注册, 1=验证 int _mode = 0; String _modeLabel = '注册模式'; // 验证结果 double _similarity = 0.0; bool _verified = false; @override void initState() { super.initState(); _initCamera(); } /// 初始化摄像头 Future _initCamera() async { if (widget.cameras.isEmpty) { setState(() { _statusMessage = '没有可用的摄像头'; }); return; } // 选择前置摄像头 CameraDescription? selectedCamera; for (var camera in widget.cameras) { if (camera.lensDirection == CameraLensDirection.front) { selectedCamera = camera; break; } } selectedCamera ??= widget.cameras.first; _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, enableAudio: false, imageFormatGroup: ImageFormatGroup.nv21, ); try { await _cameraController!.initialize(); if (mounted) { setState(() { _statusMessage = '摄像头就绪'; }); _startImageStream(); } } catch (e) { if (mounted) { setState(() { _statusMessage = '摄像头初始化失败: $e'; }); } } } /// 启动图像流 void _startImageStream() { _cameraController?.startImageStream(_onImageAvailable); } /// 图像帧回调 - 人脸检测 void _onImageAvailable(CameraImage image) async { if (_isProcessing) return; _isProcessing = true; try { final int width = image.width; final int height = image.height; final nv21Data = _extractNV21(image, width, height); // 保存当前帧数据用于后续特征提取 _currentNv21Data = nv21Data; _currentWidth = width; _currentHeight = height; // 人脸检测 + RGB 活体检测 final detectResult = await widget.arcPlugin.detectFaces( data: nv21Data, width: width, height: height, format: 2050, ); if (detectResult != null) { final success = detectResult['success'] == true; final faceList = detectResult['faceList'] as List? ?? []; final rgbLiveness = detectResult['rgbLiveness'] as int? ?? -1; if (success && faceList.isNotEmpty) { // 保存第一张人脸信息 _currentFaceInfo = Map.from(faceList[0] as Map); if (mounted) { setState(() { _faceCount = faceList.length; _rgbLivenessResult = rgbLiveness; _statusMessage = _rgbLivenessResult == 1 ? '检测到真人' : '检测中...'; }); } } else { _currentFaceInfo = null; if (mounted) { setState(() { _faceCount = 0; _rgbLivenessResult = -1; _statusMessage = '未检测到人脸'; }); } } } } catch (e) { debugPrint('人脸检测异常: $e'); } finally { _isProcessing = false; } } /// 提取 NV21 数据 Uint8List _extractNV21(CameraImage image, int width, int height) { final ySize = width * height; final nv21Size = ySize * 3 ~/ 2; if (image.planes.length == 1) { final plane = image.planes[0]; final bytesPerRow = plane.bytesPerRow; if (bytesPerRow == width) { return plane.bytes; } final nv21 = Uint8List(nv21Size); for (int row = 0; row < height; row++) { final srcOffset = row * bytesPerRow; final dstOffset = row * width; nv21.setRange(dstOffset, dstOffset + width, plane.bytes, srcOffset); } final uvHeight = height ~/ 2; for (int row = 0; row < uvHeight; row++) { final srcOffset = (height + row) * bytesPerRow; final dstOffset = ySize + row * width; nv21.setRange(dstOffset, dstOffset + width, plane.bytes, srcOffset); } return nv21; } final yPlane = image.planes[0]; final yStride = yPlane.bytesPerRow; final nv21 = Uint8List(nv21Size); for (int row = 0; row < height; row++) { final srcOffset = row * yStride; final dstOffset = row * width; nv21.setRange(dstOffset, dstOffset + width, yPlane.bytes, srcOffset); } if (image.planes.length >= 2) { final uvPlane = image.planes[1]; final uvStride = uvPlane.bytesPerRow; final uvHeight = height ~/ 2; for (int row = 0; row < uvHeight; row++) { final srcOffset = row * uvStride; final dstOffset = ySize + row * width; nv21.setRange(dstOffset, dstOffset + width, uvPlane.bytes, srcOffset); } } return nv21; } /// 注册人脸 Future _registerFace() async { if (_currentFaceInfo == null || _currentNv21Data == null) { _showMessage('未检测到人脸'); return; } if (_rgbLivenessResult != 1) { _showMessage('请确保是真人'); return; } setState(() { _statusMessage = '正在提取特征...'; }); try { // 提取特征 (REGISTER 模式) final result = await widget.arcPlugin.extractFaceFeature( data: _currentNv21Data!, width: _currentWidth, height: _currentHeight, rectLeft: _currentFaceInfo!['rectLeft'] as int, rectTop: _currentFaceInfo!['rectTop'] as int, rectRight: _currentFaceInfo!['rectRight'] as int, rectBottom: _currentFaceInfo!['rectBottom'] as int, faceOrientation: _currentFaceInfo!['orient'] as int? ?? 0, faceId: _currentFaceInfo!['faceId'] as int? ?? -1, extractType: 0, // REGISTER 模式 mask: 0, ); if (result != null && result['success'] == true) { final featureData = result['featureData'] as Uint8List; // 存储特征数据到本地 final prefs = await SharedPreferences.getInstance(); await prefs.setString(_storedFeatureKey, _bytesToBase64(featureData)); setState(() { _statusMessage = '注册成功!特征已保存'; }); _showMessage('人脸注册成功!'); } else { final errorMsg = result?['message'] ?? '未知错误'; setState(() { _statusMessage = '特征提取失败: $errorMsg'; }); } } catch (e) { setState(() { _statusMessage = '注册异常: $e'; }); } } /// 验证人脸 Future _verifyFace() async { if (_currentFaceInfo == null || _currentNv21Data == null) { _showMessage('未检测到人脸'); return; } if (_rgbLivenessResult != 1) { _showMessage('请确保是真人'); return; } // 读取存储的特征 final prefs = await SharedPreferences.getInstance(); final storedFeatureBase64 = prefs.getString(_storedFeatureKey); if (storedFeatureBase64 == null) { _showMessage('请先注册人脸'); return; } setState(() { _statusMessage = '正在提取特征...'; }); try { // 提取当前帧特征 (RECOGNIZE 模式) final result = await widget.arcPlugin.extractFaceFeature( data: _currentNv21Data!, width: _currentWidth, height: _currentHeight, rectLeft: _currentFaceInfo!['rectLeft'] as int, rectTop: _currentFaceInfo!['rectTop'] as int, rectRight: _currentFaceInfo!['rectRight'] as int, rectBottom: _currentFaceInfo!['rectBottom'] as int, faceOrientation: _currentFaceInfo!['orient'] as int? ?? 0, faceId: _currentFaceInfo!['faceId'] as int? ?? -1, extractType: 1, // RECOGNIZE 模式 mask: 0, ); if (result != null && result['success'] == true) { final currentFeature = result['featureData'] as Uint8List; final storedFeature = _base64ToBytes(storedFeatureBase64); // 比对特征 final compareResult = await widget.arcPlugin.compareFaceFeature( featureData1: currentFeature, featureData2: storedFeature, compareModel: 0, // LIFE_PHOTO 模式 ); if (compareResult != null && compareResult['success'] == true) { final similarity = compareResult['similarity'] as double? ?? 0.0; final threshold = 0.8; // 推荐阈值 final isMatch = similarity >= threshold; setState(() { _similarity = similarity; _verified = isMatch; _statusMessage = isMatch ? '验证通过!相似度: ${(similarity * 100).toStringAsFixed(1)}%' : '验证失败!相似度: ${(similarity * 100).toStringAsFixed(1)}%'; }); } else { setState(() { _statusMessage = '比对失败'; }); } } else { setState(() { _statusMessage = '特征提取失败'; }); } } catch (e) { setState(() { _statusMessage = '验证异常: $e'; }); } } /// 切换模式 void _toggleMode() { setState(() { _mode = _mode == 0 ? 1 : 0; _modeLabel = _mode == 0 ? '注册模式' : '验证模式'; _similarity = 0.0; _verified = false; }); } /// 清除注册数据 Future _clearRegistration() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_storedFeatureKey); setState(() { _similarity = 0.0; _verified = false; _statusMessage = '已清除注册数据'; }); _showMessage('注册数据已清除'); } /// 字节数组转 Base64 String _bytesToBase64(Uint8List bytes) { return base64Encode(bytes); } /// Base64 转字节数组 Uint8List _base64ToBytes(String base64) { return base64Decode(base64); } /// 显示消息 void _showMessage(String message) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); } } @override void dispose() { _cameraController?.stopImageStream(); _cameraController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_modeLabel), backgroundColor: Colors.purple, foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.swap_horiz), onPressed: _toggleMode, tooltip: '切换模式', ), IconButton( icon: const Icon(Icons.delete_outline), onPressed: _clearRegistration, tooltip: '清除注册数据', ), ], ), body: Column( children: [ // 摄像头预览 Expanded( child: _cameraController != null && _cameraController!.value.isInitialized ? Center( child: CameraPreview(_cameraController!), ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), const SizedBox(height: 16), Text(_statusMessage), ], ), ), ), // 检测信息和操作面板 Container( width: double.infinity, padding: const EdgeInsets.all(16), color: Colors.black87, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 状态信息 Text( _statusMessage, style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), // 人脸和活体状态 Row( children: [ Icon( _faceCount > 0 ? Icons.face : Icons.face_retouching_off, color: _faceCount > 0 ? Colors.green : Colors.grey, size: 20, ), const SizedBox(width: 8), Text( '人脸: ${_faceCount > 0 ? "已检测" : "未检测"}', style: TextStyle( color: _faceCount > 0 ? Colors.green : Colors.grey, fontSize: 14, ), ), const SizedBox(width: 16), Icon( _rgbLivenessResult == 1 ? Icons.check_circle : _rgbLivenessResult == 0 ? Icons.cancel : Icons.help_outline, color: _rgbLivenessResult == 1 ? Colors.green : _rgbLivenessResult == 0 ? Colors.red : Colors.orange, size: 20, ), const SizedBox(width: 8), Text( '活体: ${_rgbLivenessResult == 1 ? "真人" : _rgbLivenessResult == 0 ? "非真人" : "未知"}', style: TextStyle( color: _rgbLivenessResult == 1 ? Colors.green : _rgbLivenessResult == 0 ? Colors.red : Colors.orange, fontSize: 14, ), ), ], ), // 验证结果显示 if (_mode == 1 && _similarity > 0) ...[ const SizedBox(height: 8), const Divider(color: Colors.white24), const SizedBox(height: 8), Row( children: [ Icon( _verified ? Icons.verified_user : Icons.warning, color: _verified ? Colors.green : Colors.red, size: 24, ), const SizedBox(width: 8), Text( '相似度: ${(_similarity * 100).toStringAsFixed(1)}%', style: TextStyle( color: _verified ? Colors.green : Colors.red, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), Text( _verified ? '验证通过' : '非本人', style: TextStyle( color: _verified ? Colors.green : Colors.red, fontSize: 14, ), ), ], ), ], const SizedBox(height: 12), // 操作按钮 Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: _mode == 0 ? _registerFace : _verifyFace, icon: Icon(_mode == 0 ? Icons.person_add : Icons.verified_user), label: Text(_mode == 0 ? '注册人脸' : '验证身份'), style: ElevatedButton.styleFrom( backgroundColor: _mode == 0 ? Colors.blue : Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ), // 提示 const SizedBox(height: 8), Text( _mode == 0 ? '提示: 注册时会提取人脸特征并保存到本地' : '提示: 验证时会与已注册的特征进行比对,阈值 0.8', style: const TextStyle( color: Colors.white54, fontSize: 11, ), ), ], ), ), ], ), ); } }