IMX6U_SYS/docs/system_flow_and_interface_s...

1316 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 课前拍照打卡系统总流程与接口规范
## 1. 文档目的
本文档用于说明课前拍照打卡系统的整体实现流程、模块拆分、模块内部职责、模块之间的接口规范、数据结构、错误码、数据库读写约定和后续扩展方向。
当前阶段系统按单机板端方案设计,优先实现两个基础能力:
1. 大图人头数统计:输入课堂大图,检测人头或人脸位置,输出人数统计结果。
2. 单张人脸匹配:输入单张清晰人脸,和本地已注册人脸库匹配,输出识别结果。
完整课堂大图多人脸识别打卡属于后续扩展功能。当前文档会预留接口,但不会把第一阶段目标复杂化。
## 2. 设计原则
1. 单机优先:程序、模型、配置、数据库都部署在 IMX6ULL_ALPHA 板端。
2. 先命令行,后界面:第一阶段使用命令行运行,方便调试、答辩和移植。
3. 模块解耦:摄像头、模型推理、后处理、数据库、业务流程分离。
4. 模型可替换接口层不绑定具体模型ncnn 和 TFLite 通过适配层切换。
5. 数据可追溯:每次检测、匹配都保存输入图片路径、结果、阈值、模型版本和时间。
6. 板端友好:避免重复加载模型,避免保存过多大文件,避免不必要的内存拷贝。
## 3. 默认技术决策
当前先采用以下默认方案,后续如果实测性能不满足,再做调整。
| 项目 | 默认方案 | 原因 |
| --- | --- | --- |
| 开发语言 | C++17 | 适合嵌入式 Linux便于调用 OpenCV、SQLite、ncnn |
| 构建系统 | CMake | 方便 PC 编译和交叉编译 |
| 图像处理 | OpenCV | 摄像头采集、图片读写、resize、画框方便 |
| 推理框架 | ncnn 优先TFLite 预留 | ncnn 对 ARM CPU 友好,部署文件简单 |
| 本地数据库 | SQLite | 单机部署简单,不需要数据库服务 |
| 配置格式 | JSON | 结构清晰,便于维护 |
| 第一阶段运行方式 | 命令行工具 | 降低 UI 干扰,先跑通核心功能 |
| 时间格式 | 本地时间字符串 `YYYY-MM-DD HH:MM:SS` | 便于查看数据库记录 |
| 图片路径 | 相对项目运行目录保存 | 方便板端迁移 |
## 4. 系统总体架构
```text
+-----------------------------+
| attendance_app |
| command line entry point |
+--------------+--------------+
|
v
+-----------------------------+
| AppContext |
| config / db / model paths |
+--+-----------+-----------+--+
| | |
+-----------+ | +----------------+
v v v
+---------------+ +---------------+ +----------------+
| CameraCapture | | HeadCounter | | FaceMatchFlow |
| V4L2/OpenCV | | large image | | register/match |
+-------+-------+ +-------+-------+ +--------+-------+
| | |
v v v
+---------------+ +---------------+ +----------------+
| ImageUtils | | HeadDetector | | FaceDetector |
| read/save | | model infer | | single face |
| draw/resize | +-------+-------+ +--------+-------+
+---------------+ | |
v v
+---------------+ +----------------+
| Postprocess | | FaceAligner |
| decode / NMS | | crop / align |
+-------+-------+ +--------+-------+
| |
v v
+---------------+ +----------------+
| AttendanceDB |<-----------| FeatureExtract |
| SQLite write | | embedding |
+---------------+ +--------+-------+
|
v
+----------------+
| FaceMatcher |
| cosine search |
+----------------+
```
## 5. 程序运行模式
主程序 `attendance_app` 负责解析命令行参数,并根据 `--mode` 调用不同业务流程。
| 模式 | 功能 | 当前阶段 |
| --- | --- | --- |
| `capture` | 摄像头拍照并保存图片 | 可选实现 |
| `head_count` | 大图人头数统计 | 必须实现 |
| `register_face` | 注册学生人脸特征 | 必须实现 |
| `face_match` | 单张人脸匹配 | 必须实现 |
| `init_db` | 初始化数据库 | 必须实现 |
| `full_attendance` | 大图多人脸识别打卡 | 后续扩展 |
### 5.1 命令行规范
```bash
./attendance_app --mode init_db
./attendance_app --mode capture --output data/images/capture_001.jpg
./attendance_app --mode head_count --image data/images/classroom.jpg
./attendance_app --mode register_face --student-no 20240001 --name zhangsan --class-name class01 --image data/faces/zhangsan.jpg
./attendance_app --mode face_match --image data/images/test_face.jpg
```
### 5.2 通用命令行参数
| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `--mode` | 是 | 运行模式 |
| `--config` | 否 | 主配置文件路径,默认 `config/app_config.json` |
| `--image` | 部分模式必填 | 输入图片路径 |
| `--output` | 否 | 输出图片路径 |
| `--save-result-image` | 否 | 是否保存带框结果图 |
| `--verbose` | 否 | 输出详细日志 |
## 6. 总流程设计
## 6.1 程序启动流程
所有模式共用启动流程。
```text
main()
-> parse command line
-> load app_config.json
-> load camera_config.json
-> load model_config.json
-> create AppContext
-> init log module
-> ensure data directories exist
-> open SQLite database
-> dispatch by mode
```
### 6.1.1 启动阶段检查项
| 检查项 | 失败处理 |
| --- | --- |
| 配置文件存在且可解析 | 返回 `CONFIG_ERROR` |
| 数据目录可写 | 返回 `FILE_IO_ERROR` |
| 数据库可打开 | 返回 `DATABASE_ERROR` |
| 模型文件存在 | 进入对应模式时返回 `MODEL_LOAD_ERROR` |
| 输入图片存在 | 返回 `INVALID_ARGUMENT``FILE_IO_ERROR` |
## 6.2 大图人头数统计流程
### 6.2.1 流程说明
```text
输入图片路径或摄像头采集
-> 读取原图 cv::Mat
-> 检查图片是否为空
-> resize / letterbox 到模型输入尺寸
-> 模型推理
-> 解码模型输出
-> 置信度过滤
-> NMS 去重
-> 坐标映射回原图
-> 统计有效目标数量
-> 绘制检测框
-> 保存结果图片
-> 写入 head_count_records
-> 输出 JSON 或终端结果
```
### 6.2.2 顺序调用关系
```text
HeadCountFlow
-> ImageUtils::ReadImage()
-> HeadCounter::Count()
-> HeadDetector::Detect()
-> HeadPreprocessor::Preprocess()
-> ModelEngine::Forward()
-> DetectPostprocess::Decode()
-> DetectPostprocess::Nms()
-> ImageUtils::DrawDetections()
-> AttendanceDB::InsertHeadCountRecord()
```
### 6.2.3 输入
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `image_path` | string | 待检测课堂大图 |
| `save_result_image` | bool | 是否保存带框图片 |
| `result_image_path` | string | 结果图片路径,可为空 |
| `confidence_threshold` | float | 检测置信度阈值 |
| `nms_threshold` | float | NMS 阈值 |
### 6.2.4 输出
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `head_count` | int | 最终统计人数 |
| `detections` | array | 检测框列表 |
| `result_image_path` | string | 带框图片路径 |
| `elapsed_ms` | int | 处理耗时 |
| `record_id` | int64 | 数据库记录 ID |
## 6.3 单张人脸注册流程
### 6.3.1 流程说明
```text
输入学生信息和人脸图片
-> 检查 student_no 是否已存在
-> 读取图片
-> 人脸检测
-> 如果多张人脸,选择最大人脸
-> 人脸关键点定位
-> 人脸对齐为固定尺寸
-> 特征提取
-> 特征向量 L2 归一化
-> 保存学生信息
-> 保存人脸图片和特征向量
-> 输出注册结果
```
### 6.3.2 顺序调用关系
```text
FaceRegisterFlow
-> FaceRepository::FindStudentByNo()
-> ImageUtils::ReadImage()
-> FaceDetector::Detect()
-> FaceAligner::Align()
-> FaceFeatureExtractor::Extract()
-> FaceMatcher::Normalize()
-> FaceRepository::InsertStudent()
-> FaceRepository::InsertFaceTemplate()
```
### 6.3.3 输入
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `student_no` | string | 学号,唯一 |
| `name` | string | 姓名 |
| `class_name` | string | 班级 |
| `image_path` | string | 注册人脸图片 |
| `overwrite` | bool | 学号已存在时是否覆盖 |
### 6.3.4 输出
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `student_id` | int64 | 数据库学生 ID |
| `template_id` | int64 | 人脸模板 ID |
| `face_image_path` | string | 保存的人脸图片路径 |
| `feature_dim` | int | 特征维度 |
| `message` | string | 注册说明 |
## 6.4 单张人脸匹配流程
### 6.4.1 流程说明
```text
输入测试人脸图片
-> 读取图片
-> 人脸检测
-> 如果多张人脸,选择最大人脸
-> 人脸对齐
-> 特征提取
-> 特征向量归一化
-> 从数据库读取所有注册特征
-> 逐个计算余弦相似度
-> 选择最高分
-> 根据阈值判断 matched / unknown
-> 写入 face_match_records
-> 输出匹配结果
```
### 6.4.2 顺序调用关系
```text
FaceMatchFlow
-> ImageUtils::ReadImage()
-> FaceDetector::Detect()
-> FaceAligner::Align()
-> FaceFeatureExtractor::Extract()
-> FaceRepository::LoadAllTemplates()
-> FaceMatcher::Match()
-> FaceRepository::InsertFaceMatchRecord()
```
### 6.4.3 输入
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `image_path` | string | 待匹配人脸图片 |
| `threshold` | float | 匹配阈值 |
| `save_aligned_face` | bool | 是否保存对齐后人脸 |
### 6.4.4 输出
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `matched` | bool | 是否匹配成功 |
| `student_id` | int64 | 匹配到的学生 ID |
| `student_no` | string | 学号 |
| `name` | string | 姓名 |
| `score` | float | 最高相似度 |
| `threshold` | float | 使用阈值 |
| `status` | string | `matched`、`unknown`、`failed` |
| `record_id` | int64 | 识别记录 ID |
## 6.5 后续完整打卡流程
完整流程在第一阶段之后实现。
```text
课堂大图拍照
-> 大图人脸检测
-> 裁剪每个人脸
-> 对每个人脸做对齐和特征提取
-> 对每个人脸做数据库匹配
-> 同一学生结果去重
-> 生成一次课堂 session
-> 保存每个学生的到课状态
-> 保存未知人脸和低置信度人脸
```
为避免第一阶段过度复杂,当前仅预留 `full_attendance` 模式和数据库扩展方向。
## 7. 模块详细设计
## 7.1 App 模块
### 7.1.1 职责
| 文件 | 职责 |
| --- | --- |
| `include/app/app_config.h` | 配置结构定义 |
| `src/app/app_config.cpp` | JSON 配置读取、默认值填充 |
| `include/app/app_context.h` | 全局运行上下文 |
| `src/app/app_context.cpp` | 初始化目录、数据库、公共资源 |
| `src/main.cpp` | 命令行入口和模式分发 |
### 7.1.2 AppContext 内容
```cpp
struct AppContext {
AppConfig app_config;
CameraConfig camera_config;
ModelConfig model_config;
std::shared_ptr<AttendanceDB> db;
std::shared_ptr<Logger> logger;
};
```
### 7.1.3 约束
1. `AppContext` 只保存公共依赖,不直接实现业务逻辑。
2. 模型对象由具体业务模块懒加载或在流程开始时加载。
3. 数据库连接在程序启动时打开,程序退出时关闭。
## 7.2 Config 模块
### 7.2.1 app_config.json
```json
{
"data_root": "data",
"database_path": "data/database/attendance.db",
"log_path": "data/logs/attendance.log",
"save_result_image": true,
"timezone": "Asia/Shanghai"
}
```
### 7.2.2 camera_config.json
```json
{
"device": "/dev/video0",
"width": 1280,
"height": 720,
"fps": 15,
"format": "MJPEG",
"warmup_frames": 5
}
```
### 7.2.3 model_config.json
```json
{
"engine": "ncnn",
"head_detect": {
"param": "models/head_detect/head_detect.param",
"bin": "models/head_detect/head_detect.bin",
"input_width": 416,
"input_height": 416,
"confidence_threshold": 0.4,
"nms_threshold": 0.45,
"model_version": "head_detect_v1"
},
"face_detect": {
"param": "models/face_recognition/face_detect.param",
"bin": "models/face_recognition/face_detect.bin",
"input_width": 320,
"input_height": 320,
"confidence_threshold": 0.6,
"nms_threshold": 0.4,
"model_version": "face_detect_v1"
},
"face_feature": {
"param": "models/face_recognition/face_feature.param",
"bin": "models/face_recognition/face_feature.bin",
"input_width": 112,
"input_height": 112,
"feature_dim": 128,
"match_threshold": 0.75,
"model_version": "face_feature_v1"
}
}
```
### 7.2.4 配置读取规则
1. 配置缺失时使用默认值,但模型路径、数据库路径不能缺失。
2. 命令行参数优先级高于配置文件。
3. 阈值必须在 `[0, 1]` 范围内,否则返回 `CONFIG_ERROR`
4. 文件路径统一使用 `/`,在 Windows 调试时由 C++ 标准库兼容处理。
## 7.3 Camera 模块
### 7.3.1 职责
1. 打开摄像头设备。
2. 设置分辨率、帧率、格式。
3. 采集一帧图片。
4. 保存拍照图片。
5. 出错时返回明确错误码。
### 7.3.2 接口定义
```cpp
struct CameraConfig {
std::string device;
int width;
int height;
int fps;
std::string format;
int warmup_frames;
};
struct CaptureResult {
cv::Mat image;
std::string saved_path;
int width;
int height;
std::string captured_at;
};
class CameraCapture {
public:
Status Open(const CameraConfig& config);
Result<CaptureResult> CaptureOne(const std::string& save_path);
void Close();
};
```
### 7.3.3 实现约定
1. PC 调试阶段可使用 OpenCV `VideoCapture`
2. 板端如果 OpenCV 摄像头采集不稳定,再切换为 V4L2 实现。
3. 摄像头打开后先丢弃 `warmup_frames` 帧,降低曝光不稳定影响。
4. 保存图片默认使用 JPEG质量参数建议 `85`
## 7.4 ImageUtils 模块
### 7.4.1 职责
1. 图片读取和保存。
2. 图片尺寸检查。
3. resize 和 letterbox。
4. 检测框绘制。
5. 图片路径生成。
### 7.4.2 接口定义
```cpp
struct LetterboxInfo {
float scale;
int pad_x;
int pad_y;
int resized_width;
int resized_height;
};
Result<cv::Mat> ReadImage(const std::string& path);
Status SaveImage(const std::string& path, const cv::Mat& image);
Result<cv::Mat> ResizeWithLetterbox(
const cv::Mat& image,
int target_width,
int target_height,
LetterboxInfo* info
);
cv::Rect MapBoxToOriginal(
const cv::Rect2f& model_box,
const LetterboxInfo& info,
int original_width,
int original_height
);
void DrawDetections(cv::Mat* image, const std::vector<Detection>& detections);
```
### 7.4.3 图片坐标约定
1. 原点在左上角。
2. `x` 向右增大,`y` 向下增大。
3. 检测框格式默认使用 `x, y, width, height`
4. 数据库存储整数坐标,模型内部可使用浮点坐标。
## 7.5 ModelEngine 模块
### 7.5.1 职责
为 ncnn 或 TFLite 提供统一推理接口,让业务模块不直接依赖具体推理框架。
### 7.5.2 接口定义
```cpp
struct Tensor {
std::vector<int> shape;
std::vector<float> data;
};
struct ModelInput {
std::string name;
Tensor tensor;
};
struct ModelOutput {
std::string name;
Tensor tensor;
};
class ModelEngine {
public:
virtual ~ModelEngine() = default;
virtual Status Load(const ModelLoadOptions& options) = 0;
virtual Result<std::vector<ModelOutput>> Forward(
const std::vector<ModelInput>& inputs
) = 0;
};
```
### 7.5.3 第一阶段简化方案
如果时间紧,可以先不抽象 `ModelEngine`,直接在 `HeadDetector`、`FaceDetector`、`FaceFeatureExtractor` 中调用 ncnn。
但头文件接口仍然建议保持稳定,避免后续切换模型时影响业务流程。
## 7.6 HeadCount 模块
### 7.6.1 文件职责
| 文件 | 职责 |
| --- | --- |
| `include/head_count/head_detector.h` | 人头检测模型接口 |
| `src/head_count/head_detector.cpp` | 模型加载、预处理、推理 |
| `include/head_count/detect_postprocess.h` | 检测后处理接口 |
| `src/head_count/detect_postprocess.cpp` | 输出解码、阈值过滤、NMS |
| `include/head_count/head_counter.h` | 人头统计业务接口 |
| `src/head_count/head_counter.cpp` | 串联检测、统计、保存结果 |
### 7.6.2 核心数据结构
```cpp
struct Detection {
int class_id;
std::string class_name;
float score;
float x;
float y;
float width;
float height;
};
struct HeadCountRequest {
std::string image_path;
bool save_result_image;
std::string result_image_path;
float confidence_threshold;
float nms_threshold;
};
struct HeadCountResult {
int head_count;
std::vector<Detection> detections;
std::string image_path;
std::string result_image_path;
int elapsed_ms;
int64_t record_id;
};
```
### 7.6.3 HeadDetector 接口
```cpp
class HeadDetector {
public:
Status Load(const HeadDetectConfig& config);
Result<std::vector<Detection>> Detect(const cv::Mat& image);
};
```
### 7.6.4 HeadCounter 接口
```cpp
class HeadCounter {
public:
HeadCounter(HeadDetector* detector, AttendanceDB* db);
Result<HeadCountResult> Count(const HeadCountRequest& request);
};
```
### 7.6.5 后处理规则
1. 先按置信度过滤。
2. 再按类别过滤,只保留 `head``face` 类别。
3. 执行 NMS。
4. 将检测框映射回原图。
5. 裁剪超出图片边界的坐标。
6. 最终 `head_count = detections.size()`
### 7.6.6 NMS 接口
```cpp
std::vector<Detection> Nms(
const std::vector<Detection>& input,
float iou_threshold
);
```
## 7.7 FaceMatch 模块
### 7.7.1 文件职责
| 文件 | 职责 |
| --- | --- |
| `include/face_match/face_detector.h` | 单张图片人脸检测接口 |
| `src/face_match/face_detector.cpp` | 人脸检测模型推理 |
| `include/face_match/face_aligner.h` | 人脸对齐接口 |
| `src/face_match/face_aligner.cpp` | 关键点对齐和裁剪 |
| `include/face_match/face_feature_extractor.h` | 特征提取接口 |
| `src/face_match/face_feature_extractor.cpp` | 人脸特征模型推理 |
| `include/face_match/face_matcher.h` | 特征匹配接口 |
| `src/face_match/face_matcher.cpp` | 余弦相似度匹配 |
### 7.7.2 核心数据结构
```cpp
struct FaceLandmarks {
cv::Point2f left_eye;
cv::Point2f right_eye;
cv::Point2f nose;
cv::Point2f left_mouth;
cv::Point2f right_mouth;
};
struct FaceDetection {
float score;
cv::Rect2f box;
FaceLandmarks landmarks;
};
struct FaceFeature {
std::vector<float> values;
int dim;
std::string model_version;
};
struct Student {
int64_t id;
std::string student_no;
std::string name;
std::string class_name;
};
struct FaceTemplate {
int64_t id;
Student student;
FaceFeature feature;
std::string face_image_path;
};
```
### 7.7.3 FaceDetector 接口
```cpp
class FaceDetector {
public:
Status Load(const FaceDetectConfig& config);
Result<std::vector<FaceDetection>> Detect(const cv::Mat& image);
};
```
### 7.7.4 FaceAligner 接口
```cpp
class FaceAligner {
public:
Result<cv::Mat> Align(
const cv::Mat& image,
const FaceDetection& face,
int output_width,
int output_height
);
};
```
### 7.7.5 FaceFeatureExtractor 接口
```cpp
class FaceFeatureExtractor {
public:
Status Load(const FaceFeatureConfig& config);
Result<FaceFeature> Extract(const cv::Mat& aligned_face);
};
```
### 7.7.6 FaceMatcher 接口
```cpp
struct FaceMatchCandidate {
int64_t student_id;
std::string student_no;
std::string name;
float score;
};
struct FaceMatchRequest {
std::string image_path;
float threshold;
bool save_aligned_face;
};
struct FaceMatchResult {
bool matched;
std::string status;
FaceMatchCandidate best;
float threshold;
std::string input_image_path;
std::string aligned_face_path;
int elapsed_ms;
int64_t record_id;
};
class FaceMatcher {
public:
static void Normalize(FaceFeature* feature);
static float CosineSimilarity(
const std::vector<float>& a,
const std::vector<float>& b
);
Result<FaceMatchResult> Match(
const FaceFeature& query,
const std::vector<FaceTemplate>& templates,
float threshold
);
};
```
### 7.7.7 多人脸处理规则
单张人脸匹配功能原则上要求输入图片只有一个人脸。若检测到多张人脸,第一阶段默认选择面积最大的人脸。
后续可以增加策略:
1. 选择置信度最高的人脸。
2. 选择离图片中心最近的人脸。
3. 返回错误,要求重新拍摄。
当前推荐:注册和单张匹配都选择面积最大人脸,并在日志中记录检测到的人脸数量。
## 7.8 Database 模块
### 7.8.1 文件职责
| 文件 | 职责 |
| --- | --- |
| `include/database/attendance_db.h` | 数据库连接和表初始化 |
| `src/database/attendance_db.cpp` | SQLite 执行 SQL、事务封装 |
| `include/database/face_repository.h` | 学生、人脸模板、识别记录的数据访问 |
| `src/database/face_repository.cpp` | 业务数据读写实现 |
### 7.8.2 AttendanceDB 接口
```cpp
class AttendanceDB {
public:
Status Open(const std::string& db_path);
Status InitSchema();
Status Execute(const std::string& sql);
sqlite3* RawHandle();
void Close();
};
```
### 7.8.3 FaceRepository 接口
```cpp
class FaceRepository {
public:
explicit FaceRepository(AttendanceDB* db);
Result<int64_t> InsertStudent(const Student& student);
Result<Student> FindStudentByNo(const std::string& student_no);
Result<int64_t> InsertFaceTemplate(
int64_t student_id,
const std::string& image_path,
const FaceFeature& feature
);
Result<std::vector<FaceTemplate>> LoadAllTemplates();
Result<int64_t> InsertFaceMatchRecord(const FaceMatchResult& result);
Result<int64_t> InsertHeadCountRecord(const HeadCountResult& result);
};
```
### 7.8.4 事务约定
1. 注册学生时,`students` 和 `face_templates` 必须在同一事务内写入。
2. 写入检测记录时,如果结果图片保存失败,不写数据库记录。
3. 数据库写失败时,业务流程返回失败,但不删除原始输入图片。
4. 特征向量以 float32 二进制 BLOB 保存。
## 8. 通用接口规范
## 8.1 命名空间
所有业务代码建议放入统一命名空间:
```cpp
namespace attendance {
// ...
}
```
## 8.2 返回值规范
推荐使用 `Status``Result<T>` 统一表达成功、失败和错误信息。
```cpp
enum class StatusCode {
OK = 0,
INVALID_ARGUMENT,
CONFIG_ERROR,
FILE_IO_ERROR,
CAMERA_ERROR,
MODEL_LOAD_ERROR,
MODEL_INFER_ERROR,
IMAGE_PROCESS_ERROR,
FACE_NOT_FOUND,
MULTIPLE_FACES_FOUND,
FEATURE_ERROR,
DATABASE_ERROR,
NOT_FOUND,
UNKNOWN_ERROR
};
struct Status {
StatusCode code;
std::string message;
bool ok() const {
return code == StatusCode::OK;
}
};
template <typename T>
struct Result {
Status status;
T value;
bool ok() const {
return status.ok();
}
};
```
### 8.2.1 错误码说明
| 错误码 | 说明 |
| --- | --- |
| `OK` | 成功 |
| `INVALID_ARGUMENT` | 命令行参数或函数入参不合法 |
| `CONFIG_ERROR` | 配置文件缺失、格式错误、字段非法 |
| `FILE_IO_ERROR` | 图片、模型、目录读写失败 |
| `CAMERA_ERROR` | 摄像头打开或采集失败 |
| `MODEL_LOAD_ERROR` | 模型文件加载失败 |
| `MODEL_INFER_ERROR` | 模型推理失败 |
| `IMAGE_PROCESS_ERROR` | 图片 resize、转换、裁剪失败 |
| `FACE_NOT_FOUND` | 未检测到人脸 |
| `MULTIPLE_FACES_FOUND` | 检测到多张人脸且当前策略不允许 |
| `FEATURE_ERROR` | 特征维度错误或特征提取失败 |
| `DATABASE_ERROR` | SQLite 执行失败 |
| `NOT_FOUND` | 数据不存在 |
| `UNKNOWN_ERROR` | 未分类错误 |
## 8.3 日志规范
日志格式:
```text
[2026-06-04 09:00:00] [INFO] [HeadCounter] image=data/images/classroom.jpg head_count=36 elapsed_ms=2530
```
日志级别:
| 级别 | 使用场景 |
| --- | --- |
| `DEBUG` | 模型输入尺寸、检测框数量、阈值等调试信息 |
| `INFO` | 主要流程开始和结束 |
| `WARN` | 非致命问题,例如检测到多张人脸但选择最大人脸 |
| `ERROR` | 流程失败,例如模型加载失败、数据库写失败 |
## 8.4 JSON 输出规范
命令行工具建议支持 `--json` 参数,便于后续 UI 或脚本调用。
### 8.4.1 head_count 输出
```json
{
"status": "ok",
"mode": "head_count",
"image_path": "data/images/classroom.jpg",
"result_image_path": "data/results/classroom_detected.jpg",
"head_count": 36,
"detections": [
{
"class_id": 0,
"class_name": "head",
"score": 0.91,
"x": 120,
"y": 85,
"width": 32,
"height": 36
}
],
"elapsed_ms": 2530,
"record_id": 1
}
```
### 8.4.2 face_match 输出
```json
{
"status": "matched",
"mode": "face_match",
"input_image_path": "data/images/test_face.jpg",
"matched": true,
"student_id": 1,
"student_no": "20240001",
"name": "zhangsan",
"score": 0.82,
"threshold": 0.75,
"elapsed_ms": 840,
"record_id": 12
}
```
### 8.4.3 失败输出
```json
{
"status": "error",
"code": "FACE_NOT_FOUND",
"message": "no face detected in image",
"mode": "face_match"
}
```
## 9. 数据库设计
## 9.1 students
```sql
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_no TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
class_name TEXT,
created_at TEXT NOT NULL,
updated_at TEXT
);
```
## 9.2 face_templates
```sql
CREATE TABLE IF NOT EXISTS face_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id INTEGER NOT NULL,
face_image_path TEXT NOT NULL,
feature BLOB NOT NULL,
feature_dim INTEGER NOT NULL,
model_version TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(student_id) REFERENCES students(id)
);
```
## 9.3 head_count_records
```sql
CREATE TABLE IF NOT EXISTS head_count_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
image_path TEXT NOT NULL,
result_image_path TEXT,
head_count INTEGER NOT NULL,
detections_json TEXT NOT NULL,
confidence_threshold REAL NOT NULL,
nms_threshold REAL NOT NULL,
model_version TEXT NOT NULL,
elapsed_ms INTEGER,
created_at TEXT NOT NULL
);
```
## 9.4 face_match_records
```sql
CREATE TABLE IF NOT EXISTS face_match_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
input_image_path TEXT NOT NULL,
aligned_face_path TEXT,
matched_student_id INTEGER,
score REAL,
threshold REAL NOT NULL,
status TEXT NOT NULL,
model_version TEXT NOT NULL,
elapsed_ms INTEGER,
created_at TEXT NOT NULL,
FOREIGN KEY(matched_student_id) REFERENCES students(id)
);
```
## 9.5 后续扩展表
完整课堂打卡时建议增加:
```sql
CREATE TABLE IF NOT EXISTS attendance_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
class_name TEXT,
course_name TEXT,
image_path TEXT NOT NULL,
result_image_path TEXT,
expected_count INTEGER,
detected_count INTEGER,
recognized_count INTEGER,
unknown_count INTEGER,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS attendance_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER NOT NULL,
student_id INTEGER,
face_image_path TEXT,
bbox_json TEXT,
score REAL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(session_id) REFERENCES attendance_sessions(id),
FOREIGN KEY(student_id) REFERENCES students(id)
);
```
## 10. 文件和目录规范
## 10.1 目录用途
| 目录 | 用途 |
| --- | --- |
| `config/` | 配置文件 |
| `models/head_detect/` | 大图人头检测模型 |
| `models/face_recognition/` | 人脸检测和特征模型 |
| `data/images/` | 原始图片 |
| `data/faces/` | 注册人脸、对齐人脸、裁剪人脸 |
| `data/results/` | 带框结果图 |
| `data/database/` | SQLite 数据库 |
| `data/logs/` | 日志文件 |
| `include/` | 头文件 |
| `src/` | 源码 |
| `tools/` | 独立工具程序 |
| `tests/` | 单元测试 |
| `docs/` | 设计文档 |
## 10.2 文件命名
拍照图片:
```text
data/images/capture_YYYYMMDD_HHMMSS.jpg
```
人头统计结果图:
```text
data/results/head_count_YYYYMMDD_HHMMSS.jpg
```
注册人脸图:
```text
data/faces/register_{student_no}_YYYYMMDD_HHMMSS.jpg
```
单张匹配对齐图:
```text
data/faces/match_aligned_YYYYMMDD_HHMMSS.jpg
```
## 11. 模块间数据流
## 11.1 head_count 数据流
| 阶段 | 输入 | 输出 |
| --- | --- | --- |
| 图片读取 | `image_path` | `cv::Mat original` |
| 预处理 | `original` | `model_input`, `LetterboxInfo` |
| 推理 | `model_input` | raw tensors |
| 后处理 | raw tensors, `LetterboxInfo` | `vector<Detection>` |
| 统计 | `vector<Detection>` | `head_count` |
| 画图 | `original`, detections | result image |
| 入库 | result object | record ID |
## 11.2 face_match 数据流
| 阶段 | 输入 | 输出 |
| --- | --- | --- |
| 图片读取 | `image_path` | `cv::Mat image` |
| 人脸检测 | `image` | `vector<FaceDetection>` |
| 人脸选择 | detections | `FaceDetection selected` |
| 对齐 | image, selected | `cv::Mat aligned_face` |
| 特征提取 | aligned_face | `FaceFeature query` |
| 模板读取 | database | `vector<FaceTemplate>` |
| 匹配 | query, templates | `FaceMatchResult` |
| 入库 | result | record ID |
## 12. 性能和资源约束
IMX6ULL_ALPHA 板端性能有限,代码实现时需要遵守以下约束:
1. 模型只加载一次,不能每张图片重新加载。
2. 尽量使用较小输入尺寸,例如 `320x320`、`416x416`。
3. 大图检测优先使用 letterbox不直接拉伸变形。
4. 数据库特征模板加载后可以缓存,注册新人后再刷新缓存。
5. 图片保存默认 JPEG避免保存 BMP/PNG 大文件。
6. 日志不要频繁写大量检测框细节,调试时再打开 `DEBUG`
7. 避免在循环中频繁创建大 `cv::Mat`,可复用缓冲区。
8. 第一阶段采用单线程顺序流程,确保稳定后再考虑多线程。
## 13. 测试计划
## 13.1 单元测试
| 测试文件 | 测试内容 |
| --- | --- |
| `tests/test_head_postprocess.cpp` | NMS、坐标映射、置信度过滤 |
| `tests/test_face_similarity.cpp` | 余弦相似度、特征归一化 |
| `tests/test_config.cpp` | 配置读取和默认值 |
| `tests/test_database.cpp` | 数据库初始化、插入、查询 |
## 13.2 PC 端集成测试
1. 使用本地图片测试 `head_count`
2. 注册 5 名学生。
3. 用同一学生不同图片测试 `face_match`
4. 用未注册人员图片测试 `unknown`
5. 删除模型文件,确认返回 `MODEL_LOAD_ERROR`
6. 输入不存在图片,确认返回 `FILE_IO_ERROR`
## 13.3 板端测试
1. 摄像头能正常拍照并保存。
2. `head_count` 能在板端完成一次推理。
3. `face_match` 能在板端完成一次推理。
4. 连续运行 20 次不崩溃。
5. 断电重启后数据库仍可读取。
6. 存储空间不足时能返回明确错误。
## 14. 第一阶段验收标准
## 14.1 大图人头数统计
1. 能读取本地课堂大图。
2. 能输出检测到的人头数量。
3. 能保存带框结果图。
4. 能将检测记录写入 SQLite。
5. 命令行能输出文本结果和 JSON 结果。
## 14.2 单张人脸匹配
1. 能注册至少 5 名学生。
2. 能保存学生信息和特征向量。
3. 能识别已注册学生。
4. 能将未注册人员识别为 `unknown`
5. 能保存识别记录。
## 15. 待确认决策项
以下问题建议在开始编码核心模型模块前确认。
| 编号 | 决策项 | 推荐默认值 | 影响 |
| --- | --- | --- | --- |
| D1 | 推理框架选 ncnn 还是 TFLite | ncnn | 影响模型转换和 C++ 调用方式 |
| D2 | 大图统计检测对象是人头还是人脸 | 人头优先,脸部可备选 | 人头对低头、侧脸更稳;人脸方便后续识别 |
| D3 | 摄像头类型 | 先 USB 摄像头 | OpenCV 采集更容易调试 |
| D4 | 人脸特征维度 | 按模型确定,优先 128 或 512 | 影响数据库 BLOB 大小和匹配速度 |
| D5 | 注册时一人保存几个模板 | 第一阶段 1 个 | 多模板准确率更高,但管理更复杂 |
| D6 | 多人脸输入单张匹配时如何处理 | 选择最大人脸 | 简单可演示,但可能误选背景人脸 |
| D7 | 匹配阈值 | 初始 0.75 | 需要通过测试集调参 |
| D8 | 是否加入 LCD/Web 界面 | 第一阶段不加 | 先保证算法和数据链路稳定 |
| D9 | 是否做完整大图多人脸识别 | 第二阶段做 | 第一阶段先验证两个基础能力 |
## 16. 建议实施顺序
1. 建立基础 CMake 工程。
2. 实现 `Status`、`Result<T>`、日志、时间工具。
3. 实现配置读取。
4. 实现 SQLite 初始化和基础表结构。
5. 实现图片读写、画框、letterbox、坐标映射。
6. 实现 `head_count` 的后处理和假数据测试。
7. 接入真实人头检测模型。
8. 实现人脸特征相似度和数据库模板读写。
9. 接入人脸检测、人脸对齐、特征提取模型。
10. 打通 `register_face`
11. 打通 `face_match`
12. 在 PC 端完整测试。
13. 交叉编译部署到 IMX6ULL_ALPHA。
14. 根据板端耗时和准确率调整模型和阈值。
## 17. 后续扩展接口预留
完整课堂打卡可以新增以下业务接口。
```cpp
struct AttendanceSessionRequest {
std::string image_path;
std::string class_name;
std::string course_name;
bool save_result_image;
};
struct AttendanceRecord {
int64_t student_id;
std::string student_no;
std::string name;
Detection face_box;
float score;
std::string status;
std::string face_image_path;
};
struct AttendanceSessionResult {
int64_t session_id;
int detected_count;
int recognized_count;
int unknown_count;
std::vector<AttendanceRecord> records;
std::string image_path;
std::string result_image_path;
};
class AttendanceFlow {
public:
Result<AttendanceSessionResult> Run(
const AttendanceSessionRequest& request
);
};
```
后续完整打卡流程可以复用第一阶段已经实现的:
1. 图片读取和保存。
2. 人脸或人头检测。
3. 人脸对齐。
4. 特征提取。
5. 特征匹配。
6. SQLite 入库。
## 18. 风险点和规避方案
| 风险 | 表现 | 规避方案 |
| --- | --- | --- |
| 板端推理慢 | 一张图处理十几秒以上 | 降低输入尺寸、换更小模型、只做演示级检测 |
| 后排人脸太小 | 漏检严重 | 使用更高分辨率拍照、调整摄像头位置、使用人头检测模型 |
| 光照差 | 检测和识别不稳定 | 固定摄像头曝光,增加补光,加入模糊/亮度检查 |
| 阈值不合适 | 误识别或大量 unknown | 用真实班级图片调参 |
| 数据库特征模型版本混乱 | 新旧特征不可比 | `face_templates` 必须保存 `model_version` |
| 中文路径或姓名乱码 | 数据显示异常 | 源码和数据库统一 UTF-8 |
| 摄像头驱动不稳定 | 拍照失败 | 先用 USB 摄像头,必要时改 V4L2 |
## 19. 当前推荐结论
第一阶段按以下路线推进最稳:
1. 使用 C++17 + OpenCV + SQLite + ncnn。
2. 先实现命令行程序,不做界面。
3. 大图人头数统计和单张人脸匹配分开实现。
4. 人头统计先只输出人数,不做人脸身份识别。
5. 单张人脸匹配先要求输入清晰单人脸。
6. 所有结果都保存到 SQLite便于答辩展示和后续扩展。