import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:math'; import 'package:flutter/services.dart'; import 'package:arc/arc.dart'; import 'package:camera/camera.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: 10, // 最大检测人脸数 combinedMask: 0x9D, // 功能组合掩码(人脸检测+识别+年龄+性别+RGB活体) ); 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 _navigateToCameraPreview() async { try { final cameras = await availableCameras(); if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => CameraPreviewScreen( 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 ? _navigateToCameraPreview : 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. 请先替换代码中的 _appId、_sdkKey 和 _activeKey 为您的真实密钥\n' '2. 点击"激活 SDK"按钮进行在线激活\n' '3. 激活成功后点击"初始化引擎"按钮\n' '4. 初始化成功后点击"打开摄像头预览"进行人脸检测', style: TextStyle(fontSize: 12), ), ], ), ), ), ], ), ), ); } } /// 摄像头预览页面,用于实时人脸检测和 RGB 活体检测 class CameraPreviewScreen extends StatefulWidget { final Arc arcPlugin; final List cameras; const CameraPreviewScreen({ super.key, required this.arcPlugin, required this.cameras, }); @override State createState() => _CameraPreviewScreenState(); } class _CameraPreviewScreenState extends State { CameraController? _cameraController; bool _isDetecting = false; int _faceCount = 0; String _lastDetectionTime = ''; String _statusMessage = '正在初始化摄像头...'; // RGB 活体检测相关状态 int _rgbLivenessResult = -1; // -1=未检测, 0=非真人, 1=真人 String _rgbLivenessStatus = '未检测'; // 人脸ID管理 /// 存储人脸特征到ID的映射(使用人脸矩形区域的hashCode作为特征标识) final Map _faceIdMap = {}; /// 当前帧检测到的人脸ID列表 List _currentFaceIds = []; /// 随机数生成器 final Random _random = Random(); @override void initState() { super.initState(); _initCamera(); } /// 初始化摄像头 Future _initCamera() async { if (widget.cameras.isEmpty) { debugPrint('没有可用的摄像头'); setState(() { _statusMessage = '没有可用的摄像头'; }); return; } // 打印所有摄像头信息 debugPrint('========== 摄像头列表 =========='); for (int i = 0; i < widget.cameras.length; i++) { final camera = widget.cameras[i]; debugPrint('摄像头 $i:'); debugPrint(' - name: ${camera.name}'); debugPrint(' - lensDirection: ${camera.lensDirection}'); debugPrint(' - sensorOrientation: ${camera.sensorOrientation}'); } debugPrint('================================'); // 选择后置摄像头作为默认摄像头 CameraDescription? selectedCamera; for (var camera in widget.cameras) { if (camera.lensDirection == CameraLensDirection.back) { selectedCamera = camera; break; } } selectedCamera ??= widget.cameras.first; debugPrint('选择摄像头: ${selectedCamera.name}'); _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, enableAudio: false, imageFormatGroup: ImageFormatGroup.nv21, ); try { await _cameraController!.initialize(); debugPrint('摄像头初始化成功'); final size = _cameraController!.value.previewSize; debugPrint('预览尺寸: ${size?.width}x${size?.height}'); if (mounted) { setState(() { _statusMessage = '摄像头: ${selectedCamera?.name ?? "未知"}'; }); _startImageStream(); } } catch (e) { debugPrint('摄像头初始化失败: $e'); if (mounted) { setState(() { _statusMessage = '摄像头初始化失败: $e'; }); } } } /// 启动图像流 void _startImageStream() { _cameraController?.startImageStream(_onImageAvailable); debugPrint('已启动图像流'); } /// 图像帧回调,进行人脸检测 void _onImageAvailable(CameraImage image) async { if (_isDetecting) return; _isDetecting = true; try { final int width = image.width; final int height = image.height; // 将图像数据转换为连续的 NV21 格式 final nv21Data = _extractNV21(image, width, height); // 人脸检测(同时进行 RGB 活体检测) final detectResult = await widget.arcPlugin.detectFaces( data: nv21Data, width: width, height: height, format: 2050, // NV21 格式 ); if (detectResult != null) { final success = detectResult['success'] == true; final faceList = detectResult['faceList'] as List? ?? []; if (success && faceList.isNotEmpty) { final now = DateTime.now(); final timeStr = '${now.hour.toString().padLeft(2, '0')}:' '${now.minute.toString().padLeft(2, '0')}:' '${now.second.toString().padLeft(2, '0')}.' '${now.millisecond.toString().padLeft(3, '0')}'; debugPrint('========== 人脸检测 [$timeStr] =========='); debugPrint('检测到 ${faceList.length} 张人脸'); for (int i = 0; i < faceList.length; i++) { final face = faceList[i] as Map; debugPrint('人脸 ${i + 1}: (${face['rectLeft']}, ${face['rectTop']}, ${face['rectRight']}, ${face['rectBottom']})'); } // 获取 RGB 活体检测结果 final rgbLiveness = detectResult['rgbLiveness'] as int? ?? -1; final rgbIsAlive = rgbLiveness == 1; if (mounted) { setState(() { _faceCount = faceList.length; _lastDetectionTime = timeStr; _rgbLivenessResult = rgbLiveness; _rgbLivenessStatus = rgbIsAlive ? '真人' : (rgbLiveness == 0 ? '非真人' : '未知'); // 更新当前人脸ID列表 _currentFaceIds = faceList.map((face) { final faceKey = _getFaceKey(face as Map); return _faceIdMap[faceKey] ?? '未注册'; }).toList(); }); } debugPrint('RGB 活体: liveness=$rgbLiveness, isAlive=$rgbIsAlive'); debugPrint('========================================'); } else { // 未检测到人脸时重置状态 if (mounted) { setState(() { _faceCount = 0; _rgbLivenessResult = -1; _rgbLivenessStatus = '未检测'; _currentFaceIds = []; }); } } } } catch (e, stackTrace) { debugPrint('人脸检测异常: $e'); debugPrint('堆栈: $stackTrace'); } finally { _isDetecting = false; } } /// 提取 NV21 数据(处理 stride 对齐) 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; } /// 生成随机人脸ID /// 生成格式: F + 8位随机数字 String _generateRandomFaceId() { final id = _random.nextInt(100000000).toString().padLeft(8, '0'); return 'F$id'; } /// 根据人脸矩形区域生成特征标识 String _getFaceKey(Map face) { // 使用人脸矩形区域的位置和大小作为特征标识 final rectLeft = face['rectLeft'] as int? ?? 0; final rectTop = face['rectTop'] as int? ?? 0; final rectRight = face['rectRight'] as int? ?? 0; final rectBottom = face['rectBottom'] as int? ?? 0; // 使用位置和大小计算一个简单的特征值(允许一定容差) final centerX = (rectLeft + rectRight) ~/ 2; final centerY = (rectTop + rectBottom) ~/ 2; // 按照区块划分(每50像素为一个区块) final blockX = centerX ~/ 50; final blockY = centerY ~/ 50; return '$blockX-$blockY'; } @override void dispose() { _cameraController?.stopImageStream(); _cameraController?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('人脸检测预览'), backgroundColor: Colors.purple, foregroundColor: Colors.white, ), body: Column( children: [ // 摄像头预览 Expanded( child: _cameraController != null && _cameraController!.value.isInitialized ? Center( child: RotatedBox( quarterTurns: -1, 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( '检测到人脸数: $_faceCount', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( '最后检测时间: $_lastDetectionTime', style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), const SizedBox(height: 8), // RGB 活体检测状态 Row( children: [ 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( 'RGB活体: $_rgbLivenessStatus', style: TextStyle( color: _rgbLivenessResult == 1 ? Colors.green : _rgbLivenessResult == 0 ? Colors.red : Colors.white70, fontSize: 14, ), ), ], ), // 人脸ID显示 if (_currentFaceIds.isNotEmpty) ...[ const SizedBox(height: 8), const Divider(color: Colors.white24), const SizedBox(height: 8), Text( '人脸ID:', style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Wrap( spacing: 8, runSpacing: 4, children: _currentFaceIds.asMap().entries.map((entry) { final index = entry.key; final id = entry.value; final isRegistered = id != '未注册'; return Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: isRegistered ? Colors.green.withValues(alpha: 0.3) : Colors.orange.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(4), border: Border.all( color: isRegistered ? Colors.green : Colors.orange, ), ), child: Text( '人脸${index + 1}: $id', style: TextStyle( color: isRegistered ? Colors.greenAccent : Colors.orangeAccent, fontSize: 12, ), ), ); }).toList(), ), ], // 注册按钮 if (_faceCount > 0) ...[ const SizedBox(height: 12), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () { // 为未注册的人脸生成随机ID setState(() { for (int i = 0; i < _faceCount; i++) { if (_currentFaceIds[i] == '未注册') { _currentFaceIds[i] = _generateRandomFaceId(); } } }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('人脸ID已生成!'), backgroundColor: Colors.green, ), ); }, icon: const Icon(Icons.person_add), label: const Text('注册人脸(生成随机ID)'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), ], ], ), ), ], ), ); } }