498 lines
13 KiB
Markdown
498 lines
13 KiB
Markdown
# Arc Flutter 插件使用指南
|
||
|
||
## 简介
|
||
|
||
Arc 是一个封装虹软 (ArcSoft) Face SDK 的 Flutter 插件,提供 Android 平台的人脸检测、人脸识别、活体检测功能。本插件支持 1:1 人脸比对和 1:N 人脸搜索两种模式。
|
||
|
||
## 安装
|
||
|
||
### 1. 添加依赖
|
||
|
||
在您的 Flutter 项目 `pubspec.yaml` 中添加依赖:
|
||
|
||
```yaml
|
||
dependencies:
|
||
arc:
|
||
path: ../arc # 本地引用,或使用 git/path 方式
|
||
```
|
||
|
||
### 2. Android 配置
|
||
|
||
在 `android/app/build.gradle` 中确保最小 SDK 版本:
|
||
|
||
```gradle
|
||
android {
|
||
defaultConfig {
|
||
minSdkVersion 21 // 虹软 SDK 要求最低 Android 5.0
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 获取虹软 SDK 密钥
|
||
|
||
访问 [虹软开放平台](https://ai.arcsoft.com.cn/) 注册账号,创建应用获取:
|
||
- `appId`: 应用 ID
|
||
- `sdkKey`: SDK 密钥
|
||
- `activeKey`: 激活密钥
|
||
|
||
## 快速开始
|
||
|
||
### 基础流程
|
||
|
||
```
|
||
激活 SDK → 初始化引擎 → 人脸检测 → 特征提取 → 特征比对/注册
|
||
```
|
||
|
||
### 示例代码
|
||
|
||
```dart
|
||
import 'package:arc/arc.dart';
|
||
|
||
final _arc = Arc();
|
||
|
||
// 1. 激活 SDK(必须首先执行)
|
||
await _arc.activeOnline(
|
||
appId: '您的AppId',
|
||
sdkKey: '您的SdkKey',
|
||
activeKey: '您的ActiveKey',
|
||
);
|
||
|
||
// 2. 初始化引擎
|
||
await _arc.init(
|
||
detectMode: 0, // 0=视频流模式, 1=图像模式
|
||
orient: 0, // 检测角度
|
||
maxFaceNum: 1, // 最大检测人脸数
|
||
combinedMask: 0x9D, // 功能组合掩码
|
||
);
|
||
|
||
// 3. 人脸检测(需要摄像头 NV21 数据)
|
||
final result = await _arc.detectFaces(
|
||
data: nv21Data,
|
||
width: width,
|
||
height: height,
|
||
);
|
||
|
||
// 4. 提取特征
|
||
final featureResult = await _arc.extractFaceFeature(
|
||
data: nv21Data,
|
||
width: width,
|
||
height: height,
|
||
rectLeft: faceInfo['rectLeft'],
|
||
rectTop: faceInfo['rectTop'],
|
||
rectRight: faceInfo['rectRight'],
|
||
rectBottom: faceInfo['rectBottom'],
|
||
faceData: faceInfo['faceData'], // 重要!必须传递
|
||
);
|
||
|
||
// 5. 比对特征
|
||
final compareResult = await _arc.compareFaceFeature(
|
||
featureData1: feature1,
|
||
featureData2: feature2,
|
||
);
|
||
```
|
||
|
||
## API 详细说明
|
||
|
||
### activeOnline - SDK 激活
|
||
|
||
在线激活虹软 SDK,必须在使用其他功能前完成。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> activeOnline({
|
||
required String appId, // 虹软应用 ID
|
||
required String sdkKey, // SDK 密钥
|
||
required String activeKey, // 激活密钥
|
||
})
|
||
```
|
||
|
||
**返回值:**
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `success` | bool | 是否成功 |
|
||
| `errorCode` | int | 错误码(0=成功) |
|
||
| `message` | String | 结果描述 |
|
||
|
||
---
|
||
|
||
### init - 初始化引擎
|
||
|
||
初始化人脸识别引擎,配置检测模式和功能。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> init({
|
||
int? detectMode, // 检测模式
|
||
int? orient, // 检测角度
|
||
int? maxFaceNum, // 最大人脸数
|
||
int? combinedMask, // 功能掩码组合
|
||
})
|
||
```
|
||
|
||
**参数说明:**
|
||
|
||
| 参数 | 值 | 说明 |
|
||
|------|------|------|
|
||
| detectMode | 0 | VIDEO 模式(视频流,适合实时检测) |
|
||
| detectMode | 1 | IMAGE 模式(单张图片) |
|
||
| orient | 0/90/180/270/360 | 检测角度优先级 |
|
||
| maxFaceNum | 1-50 | 同时检测的最大人脸数量 |
|
||
|
||
**功能掩码组合:**
|
||
|
||
| 功能 | 值 | 说明 |
|
||
|------|------|------|
|
||
| ASF_FACE_DETECT | 0x00000001 | 人脸检测 |
|
||
| ASF_FACE_RECOGNITION | 0x00000004 | 人脸识别 |
|
||
| ASF_AGE | 0x00000008 | 年龄检测 |
|
||
| ASF_GENDER | 0x00000010 | 性别检测 |
|
||
| ASF_LIVENESS | 0x00000080 | RGB 活体检测 |
|
||
|
||
**推荐组合:**
|
||
```dart
|
||
combinedMask: 0x9D // 人脸检测 + 识别 + 年龄 + 性别 + 活体
|
||
// 计算方式: 0x01 | 0x04 | 0x08 | 0x10 | 0x80 = 0x9D
|
||
```
|
||
|
||
---
|
||
|
||
### detectFaces - 人脸检测
|
||
|
||
检测图像中的人脸,同时进行 RGB 活体检测。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> detectFaces({
|
||
required Uint8List data, // NV21 图像数据
|
||
required int width, // 图像宽度
|
||
required int height, // 图像高度
|
||
int format = 2050, // 图像格式(默认 NV21)
|
||
})
|
||
```
|
||
|
||
**返回值:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `success` | bool | 是否检测成功 |
|
||
| `errorCode` | int | 错误码 |
|
||
| `faceList` | List<Map> | 检测到的人脸列表 |
|
||
| `rgbLiveness` | int | RGB 活体结果(-1=未知, 0=非真人, 1=真人) |
|
||
| `isRgbAlive` | bool | 是否真人 |
|
||
|
||
**faceList 每项包含:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `rectLeft` | int | 人脸框左边界 |
|
||
| `rectTop` | int | 人脸框上边界 |
|
||
| `rectRight` | int | 人脸框右边界 |
|
||
| `rectBottom` | int | 人脸框下边界 |
|
||
| `faceOrientation` | int | 人脸角度 |
|
||
| `faceId` | int | 人脸 ID |
|
||
| `faceData` | Uint8List | **重要!特征提取必需的数据** |
|
||
|
||
---
|
||
|
||
### extractFaceFeature - 特征提取
|
||
|
||
从检测到的人脸中提取 512 字节的特征数据。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> extractFaceFeature({
|
||
required Uint8List data, // NV21 图像数据
|
||
required int width, // 图像宽度
|
||
required int height, // 图像高度
|
||
required int rectLeft, // 人脸框左边界(从 detectFaces 获取)
|
||
required int rectTop, // 人脸框上边界
|
||
required int rectRight, // 人脸框右边界
|
||
required int rectBottom, // 人脸框下边界
|
||
int format = 2050, // 图像格式
|
||
int faceOrientation = 0, // 人脸角度(从 detectFaces 获取)
|
||
int faceId = -1, // 人脸 ID
|
||
Uint8List? faceData, // **关键:人脸数据(从 detectFaces 获取)**
|
||
int extractType = 1, // 0=注册模式, 1=识别模式
|
||
int mask = 0, // 口罩状态
|
||
})
|
||
```
|
||
|
||
**参数说明:**
|
||
|
||
| 参数 | 值 | 说明 |
|
||
|------|------|------|
|
||
| extractType | 0 | REGISTER 模式(用于注册到人脸库) |
|
||
| extractType | 1 | RECOGNIZE 模式(用于比对验证) |
|
||
| mask | 0 | 未佩戴口罩 |
|
||
| mask | 1 | 已佩戴口罩 |
|
||
|
||
**返回值:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `success` | bool | 是否成功 |
|
||
| `errorCode` | int | 错误码 |
|
||
| `featureData` | Uint8List | 512 字节特征数据 |
|
||
|
||
---
|
||
|
||
### compareFaceFeature - 特征比对 (1:1)
|
||
|
||
比对两个人脸特征的相似度。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> compareFaceFeature({
|
||
required Uint8List featureData1, // 第一个特征
|
||
required Uint8List featureData2, // 第二个特征
|
||
int compareModel = 0, // 比对模型
|
||
})
|
||
```
|
||
|
||
**compareModel 参数:**
|
||
|
||
| 值 | 说明 | 推荐阈值 |
|
||
|------|------|------|
|
||
| 0 | 生活照模型 | 0.8 |
|
||
| 1 | 证件照模型 | 0.82 |
|
||
|
||
**返回值:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `success` | bool | 是否成功 |
|
||
| `similarity` | double | 相似度(0-1) |
|
||
|
||
---
|
||
|
||
### registerFaceFeature - 注册到人脸库 (1:N)
|
||
|
||
将人脸特征注册到本地人脸库,用于后续 1:N 搜索。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> registerFaceFeature({
|
||
required int searchId, // 唯一标识符(建议使用用户 ID)
|
||
required Uint8List featureData, // 特征数据
|
||
String? faceTag, // 附属信息(可选)
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
### registerFaceFeatureBatch - 批量注册
|
||
|
||
批量注册多张人脸特征。
|
||
|
||
```dart
|
||
Future<Map<String, dynamic>?> registerFaceFeatureBatch({
|
||
required List<Map<String, dynamic>> faceList,
|
||
})
|
||
|
||
// 示例:
|
||
await _arc.registerFaceFeatureBatch(
|
||
faceList: [
|
||
{'searchId': 1, 'featureData': feature1, 'faceTag': '张三'},
|
||
{'searchId': 2, 'featureData': feature2, 'faceTag': '李四'},
|
||
],
|
||
);
|
||
```
|
||
|
||
## 图像格式要求
|
||
|
||
### NV21 格式
|
||
|
||
- 虹软 SDK 使用 NV21 格式(Android 摄像头默认格式)
|
||
- 格式代码:`2050`
|
||
- 宽度必须是 **4 的倍数**
|
||
- 高度必须是 **2 的倍数**
|
||
|
||
### 从 Camera 提取 NV21
|
||
|
||
使用 `camera` 插件获取 NV21 数据:
|
||
|
||
```dart
|
||
// 配置摄像头使用 NV21 格式
|
||
_cameraController = CameraController(
|
||
camera,
|
||
ResolutionPreset.medium,
|
||
enableAudio: false,
|
||
imageFormatGroup: ImageFormatGroup.nv21, // 关键配置
|
||
);
|
||
|
||
// 处理图像流
|
||
void _onImageAvailable(CameraImage image) {
|
||
final nv21Data = _extractNV21(image, image.width, image.height);
|
||
|
||
// 人脸检测
|
||
final result = await _arc.detectFaces(
|
||
data: nv21Data,
|
||
width: image.width,
|
||
height: image.height,
|
||
);
|
||
}
|
||
|
||
// NV21 数据提取函数
|
||
Uint8List _extractNV21(CameraImage image, int width, int height) {
|
||
final ySize = width * height;
|
||
final nv21Size = ySize * 3 ~/ 2;
|
||
|
||
// ... 根据 planes 结构提取数据
|
||
// 参考 example/lib/main.dart 中的完整实现
|
||
}
|
||
```
|
||
|
||
## 推荐阈值
|
||
|
||
| 功能 | 阈值 | 说明 |
|
||
|------|------|------|
|
||
| 人脸比对(生活照) | 0.8 | 相似度 >= 0.8 认为匹配 |
|
||
| 人脸比对(证件照) | 0.82 | 相似度 >= 0.82 认为匹配 |
|
||
| RGB 活体检测 | 0.5 | 活体分数 >= 0.5 认为真人 |
|
||
| IR 活体检测 | 0.5 | 活体分数 >= 0.5 认为真人 |
|
||
|
||
## 完整使用流程示例
|
||
|
||
### 1:1 人脸验证
|
||
|
||
```dart
|
||
// 1. 激活 SDK
|
||
final activeResult = await _arc.activeOnline(
|
||
appId: appId,
|
||
sdkKey: sdkKey,
|
||
activeKey: activeKey,
|
||
);
|
||
if (activeResult?['success'] != true) {
|
||
print('激活失败');
|
||
return;
|
||
}
|
||
|
||
// 2. 初始化引擎
|
||
final initResult = await _arc.init(
|
||
detectMode: 0,
|
||
orient: 0,
|
||
maxFaceNum: 1,
|
||
combinedMask: 0x9D,
|
||
);
|
||
|
||
// 3. 摄像头获取图像
|
||
// 使用 camera 插件获取 NV21 数据...
|
||
|
||
// 4. 人脸检测
|
||
final detectResult = await _arc.detectFaces(
|
||
data: nv21Data,
|
||
width: width,
|
||
height: height,
|
||
);
|
||
|
||
if (detectResult?['success'] == true) {
|
||
final faceList = detectResult!['faceList'] as List;
|
||
|
||
if (faceList.isNotEmpty) {
|
||
final faceInfo = faceList[0] as Map;
|
||
final rgbLiveness = detectResult['rgbLiveness'] as int;
|
||
|
||
// 检查活体
|
||
if (rgbLiveness != 1) {
|
||
print('非真人');
|
||
return;
|
||
}
|
||
|
||
// 5. 提取特征
|
||
final featureResult = await _arc.extractFaceFeature(
|
||
data: nv21Data,
|
||
width: width,
|
||
height: height,
|
||
rectLeft: faceInfo['rectLeft'],
|
||
rectTop: faceInfo['rectTop'],
|
||
rectRight: faceInfo['rectRight'],
|
||
rectBottom: faceInfo['rectBottom'],
|
||
faceData: faceInfo['faceData'], // 重要!
|
||
extractType: 1, // 识别模式
|
||
);
|
||
|
||
if (featureResult?['success'] == true) {
|
||
final currentFeature = featureResult!['featureData'] as Uint8List;
|
||
|
||
// 6. 比对(与已存储的特征)
|
||
final compareResult = await _arc.compareFaceFeature(
|
||
featureData1: currentFeature,
|
||
featureData2: storedFeature,
|
||
compareModel: 0,
|
||
);
|
||
|
||
final similarity = compareResult?['similarity'] as double ?? 0.0;
|
||
if (similarity >= 0.8) {
|
||
print('验证通过,相似度: ${similarity}');
|
||
} else {
|
||
print('验证失败,相似度: ${similarity}');
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 1:N 人脸搜索
|
||
|
||
```dart
|
||
// 注册阶段
|
||
// 提取特征(注册模式)
|
||
final registerFeature = await _arc.extractFaceFeature(
|
||
data: nv21Data,
|
||
width: width,
|
||
height: height,
|
||
rectLeft: faceInfo['rectLeft'],
|
||
rectTop: faceInfo['rectTop'],
|
||
rectRight: faceInfo['rectRight'],
|
||
rectBottom: faceInfo['rectBottom'],
|
||
faceData: faceInfo['faceData'],
|
||
extractType: 0, // 注册模式
|
||
);
|
||
|
||
// 注册到人脸库
|
||
await _arc.registerFaceFeature(
|
||
searchId: userId, // 如: 10001
|
||
featureData: registerFeature!['featureData'],
|
||
faceTag: '张三',
|
||
);
|
||
|
||
// 搜索阶段(SDK 会自动搜索匹配)
|
||
// 使用 extractType=1 提取特征后,SDK 会自动进行 1:N 搜索
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
所有 API 返回统一的错误格式:
|
||
|
||
```dart
|
||
{
|
||
'success': false,
|
||
'errorCode': 81929, // 具体错误码
|
||
'message': '特征提取失败',
|
||
}
|
||
```
|
||
|
||
常见错误码:
|
||
- `0`: 成功
|
||
- `81929`: 特征提取失败(通常是因为未传递 faceData)
|
||
- `其他`: 参考 `android/src/.../FaceErrorCode.java` 中的 586 个错误码定义
|
||
|
||
## 注意事项
|
||
|
||
1. **必须先激活再初始化**:使用顺序为 `activeOnline` → `init` → 其他功能
|
||
2. **faceData 必须传递**:`extractFaceFeature` 的 `faceData` 参数是从 `detectFaces` 获取的关键数据,不传递会导致特征提取失败
|
||
3. **图像尺寸限制**:宽度必须为 4 的倍数,高度必须为 2 的倍数
|
||
4. **单设备激活**:虹软 SDK 激活与设备绑定,同一 AppId 在不同设备上需要重新激活
|
||
5. **特征存储**:特征数据为 512 字节,建议使用 Base64 编码后存储
|
||
|
||
## 依赖插件推荐
|
||
|
||
实现完整人脸识别功能通常需要配合:
|
||
|
||
```yaml
|
||
dependencies:
|
||
arc: ^0.0.1 # 人脸识别
|
||
camera: ^0.11.0 # 摄像头访问
|
||
shared_preferences: ^2.0.0 # 特征存储
|
||
```
|
||
|
||
## 参考资源
|
||
|
||
- [虹软开放平台](https://ai.arcsoft.com.cn/)
|
||
- [虹软人脸识别 SDK 文档](https://ai.arcsoft.com.cn/manual/)
|
||
- 本插件示例代码:`example/lib/main.dart`
|
||
- 虹软接口文档:`docs/虹软人脸识别接口文档.md` |