02 MTCNN 人脸检测应用
本章节介绍一个基于 GK7206 NPU 的完整人脸检测应用示例 —— face_recognize。该应用利用 MTCNN(Multi-task Cascaded Convolutional Networks)三阶段级联网络,在板端实现实时人脸检测与关键点定位,并通过内嵌 Web 服务器提供浏览器可视化界面。
应用源码位于 SDK 目录 app_sample/face_recognize/,是一个独立的、自包含的示例工程,涵盖了视频采集、NPU 推理、Web 展示的完整链路,适合作为开发自定义 AI 视觉应用的参考模板。
1 应用概述
1.1 功能特性
- MTCNN 三阶段级联检测:P-Net(粗筛)→ R-Net(精筛)→ O-Net(关键点),逐级过滤人脸候选框
- 5 点面部关键点定位:左眼、右眼、鼻尖、左嘴角、右嘴角
- 实时 Web 可视化:内嵌 HTTP 服务器,浏览器直接查看 MJPEG 视频流和检测结果叠加画面
- 帧间跟踪稳定:基于 IoU 匹配的帧间人脸跟踪 + EMA 平滑,消除检测框抖动
- 图像金字塔多尺度检测:支持不同尺度的人脸检测
1.2 技术参数
| 参数 | 值 |
|---|---|
| 传感器分辨率 | 2560 × 1440(SC465SL) |
| 检测输入分辨率 | 320 × 180(16:9,匹配传感器宽高比) |
| NPU 推理帧率 | ~8 FPS(受限于 P-Net 滑窗数量) |
| Web 服务端口 | 80 |
| 视频编码格式 | MJPEG |
| 模型格式 | .xmm(GK7206 NPU 专用格式) |
| 最小人脸尺寸 | 48 × 48 像素(检测图中) |
| 级联阈值 | P-Net: 0.50 / R-Net: 0.70 / O-Net: 0.30 |
1.3 目录结构
app_sample/face_recognize/
├── Makefile # 构建脚本
├── src/
│ └── main.c # 主程序(~1800 行,含 NPU 推理 + Web 服务)
├── models/
│ ├── pnet.xmm # P-Net 模型(粗筛,输入 12×12)
│ ├── rnet.xmm # R-Net 模型(精筛,输入 24×24)
│ └── onet.xmm # O-Net 模型(关键点,输入 48×48)
└── web/
├── index.html # Web 前端页面
├── app.js # 前端逻辑(轮询 API、绘制检测框)
└── style.css # 页面样式2 编译与部署
2.1 前置条件
在编译本应用之前,请确保已完成以下准备工作:
- SDK 环境已搭建:参照 SDK 编译 完成交叉编译工具链和 SDK 配置
- SDK 已完整编译一次:应用依赖 SDK 的公共库和头文件,需先执行完整编译生成
out/目录 - 板端驱动已加载:确保 NPU 内核模块
xm_npu.ko已加载
2.2 编译应用
进入应用目录,直接使用 SDK 构建系统编译:
# 进入应用目录
cd <SDK_PATH>/app_sample/face_recognize
# 编译(SDK Makefile 会自动使用交叉编译工具链)
make clean && make编译原理
Makefile 通过 include $(SDK_DIR)/build/base.mk 和 include $(SAMPLE_DIR)/sample_base.mk 引入 SDK 的编译规则,自动配置交叉编译器、头文件路径和链接库。无需手动设置编译工具链。
编译成功后,在当前目录下生成可执行文件 face_recognize。
2.3 部署到板端
相关信息
由于应用程序体积较大,需挂载 SD 卡后运行。
mkdir -p /sd_card
mount /dev/mmcblk1p1 /sd_card使用 SCP 将以下文件传输到开发板:
目录结构要求
- Web 前端文件必须放置在板端
/www/目录下(这是web_server模块默认的静态文件根目录WEB_WWW_ROOT) - models 模型文件必须放在应用的工作目录下
# 在开发主机上执行
# 1. 创建板端应用目录
ssh root@<板端IP> "mkdir -p /sd_card/models /www"
# 2. 传输可执行文件
scp face_recognize root@<板端IP>:/sd_card/
# 3. 传输模型文件
scp -Or models root@<板端IP>:/sd_card/
# 4. 传输 Web 前端文件
scp web/* root@<板端IP>:/www/2.4 运行应用
# 进入应用目录
cd /sd_card
# 添加执行权限(首次)
chmod +x face_recognize
# 运行
./face_recognize启动后,终端会输出以下日志信息:
=== MTCNN Face Detection for GK7206 ===
[init] sensor: 2560x1440 @ 30fps
[npu] Initializing...
[npu] Found 1 device(s)
[npu] Loading P-Net...
[npu] ./models/pnet.xmm loaded
[npu] Loading R-Net...
[npu] ./models/rnet.xmm loaded
[npu] Loading O-Net...
[npu] ./models/onet.xmm loaded
[npu] All models loaded!
[init] Pipeline: VI→VPSS→VENC(MJPEG) + VPSS→NPU(320x180 MTCNN)
[main] System ready. Open http://<board-ip>/ in browser2.5 浏览器访问
在电脑浏览器中打开 http://<板端IP>/,即可看到实时的人脸检测画面:
- 页面中央显示 MJPEG 视频流
- 检测到的人脸会以绿色矩形框标注
- 每个人脸框上方显示置信度百分比
- 人脸关键点(5 点)以蓝色圆点标注,并用虚线连接
- 底部状态栏显示检测 FPS 和当前人脸数量
2.6 停止应用
在终端按 Ctrl+C 发送 SIGINT 信号即可正常退出。应用会依次停止 NPU 推理线程、卸载模型、释放视频管线资源。
提示
若使用 Ctrl+C 无法正常退出或退出耗时较长,可通过以下命令查看 ./face_recognize 进程的 PID,并强制终止该进程:
ps | grep face_recognize
kill -9 <PID>3 系统架构
3.1 整体数据流
应用的整体架构遵循「视频采集 → 前处理 → NPU 推理 → Web 展示」的经典嵌入式 AI 视觉管线:

3.2 线程模型
应用采用多线程架构,各线程职责如下:
| 线程 | 职责 | 关键操作 |
|---|---|---|
| 主线程 | 运行 Web 服务器(阻塞循环) | 接收 HTTP 请求、分发路由、MJPEG 流推送 |
| NPU 推理线程 | 循环获取帧并执行 MTCNN 检测 | VPSS 获取帧 → YUV→RGB → MTCNN → 更新检测结果 |
| ISP 线程(SDK 内部) | 图像信号处理 | 3A(AE/AWB/AF)、降噪、色彩校正 |
线程间通过 pthread_mutex_t g_face_mutex 互斥锁保护检测结果(g_faces 数组和 g_face_count),确保 NPU 线程写入与 HTTP 线程读取的数据一致性。
4 内部执行逻辑详解
4.1 启动流程
main() 函数的启动流程分为 5 个阶段:
// 阶段 1:初始化视频管线(VI → VPSS → VENC)
init_system();
// 阶段 2:初始化 NPU(加载 3 个 MTCNN 模型)
init_npu();
// 阶段 3:启动 NPU 推理线程
g_npu_running = XMEDIA_TRUE;
pthread_create(&g_npu_thread, NULL, npu_inference_thread, NULL);
// 阶段 4:注册 MJPEG 处理器并启动 Web 服务器(阻塞)
web_server_set_mjpeg_handler(mjpeg_send_stream, mjpeg_request_stop);
web_server_run(project_route_get);
// 阶段 5:停止时清理资源
g_npu_running = XMEDIA_FALSE;
pthread_join(g_npu_thread, NULL);
deinit_npu();
deinit_system();4.2 视频管线初始化
init_system() 按照以下顺序初始化 MPP 视频管线:
系统初始化:配置 VB(Video Buffer)内存池
- Pool 0:VI 采集缓冲区(全分辨率)
- Pool 1:VPSS 全分辨率 / VENC 编码缓冲区
- Pool 2:VPSS NPU 输入缓冲区(320 × 180)
模块初始化:依次初始化 VI、VPSS、VENC 模块
ISP 初始化:配置图像信号处理参数(帧率、像素格式、分辨率等)
VI 启动:启动视频输入,从 Sensor 获取图像
VPSS 配置:配置两个输出通道
ochn0:全分辨率输出 → 送 VENC 编码 MJPEGochn1:缩放到 320 × 180 → 送 NPU 推理
绑定关系:
VI → VPSS → VENC
VI(pipe=0, chn=0) ──bind──→ VPSS(pipe=0, ochn=0) ──bind──→ VENC(chn=0, MJPEG)
└→ VPSS(pipe=0, ochn=1) ──手动获取──→ NPU 推理线程NPU 帧获取方式
VENC 通过 bind 模式自动获取 VPSS 输出帧,而 NPU 推理线程使用 xmedia_vpss_acquire_ochn_frame() 手动获取 VPSS ochn1 的帧。这是因为 NPU 推理速度慢于视频帧率,不适合 bind 模式。
4.3 NPU 模型加载
init_npu() 函数加载 3 个 MTCNN 模型,每个模型的加载流程如下:
// 1. 查询模型所需内存大小
xmedia_cl_graph_querysize_from_file(path, &worksize, &weightsize);
// 2. 分配 MMZ 内存(物理连续内存,NPU DMA 访问需要)
mmz_alloc_map("npu_work", &work_phy, &work_buf, worksize);
mmz_alloc_map("npu_weight", &weight_phy, &weight_buf, weightsize);
// 3. 加载模型到 NPU
xmedia_cl_graph_loadmodel_from_file_withmem(&ctx, path,
work_buf, worksize, weight_buf, weightsize, &graph);
// 4. 获取输入/输出 Tensor 信息(两趟查询:第 1 次获取数量,第 2 次获取详情)
xmedia_cl_graph_get_input(graph, 0, &input); // 第 1 次:获取数量
input.tensor = malloc(sizeof(...) * input.num);
xmedia_cl_graph_get_input(graph, input.num, &input); // 第 2 次:获取详情
// 5. 分配输入/输出缓冲区
mmz_alloc_map("npu_in", &input_phy, &input_buf, inputsize);
mmz_alloc_map("npu_out", &output_phy, &output_buf, outputsize);
// 6. 设置 Tensor 地址并绑定
xmedia_cl_graph_set_inout(graph, &input, &output);内存分配注意
NPU 需要访问的缓冲区(workspace、weight、input、output)必须使用 xmedia_mmz_alloc() 分配物理连续内存(MMZ),不能使用普通的 malloc()。这是因为 NPU 通过 DMA 直接访问物理内存。
三个 MTCNN 模型的规格如下:
| 模型 | 输入尺寸 | 输出 Tensor | 说明 |
|---|---|---|---|
| P-Net | [1, 3, 12, 12] | score(2ch) + bbox(4ch) | 粗筛网络,通过图像金字塔+滑窗提取候选 |
| R-Net | [1, 3, 24, 24] | score(2ch) + bbox(4ch) | 精筛网络,逐候选裁剪推理 |
| O-Net | [1, 3, 48, 48] | score(2ch) + bbox(4ch) + landmark(10ch) | 输出网络,精修 + 5 点关键点 |
4.4 NPU 推理线程
npu_inference_thread() 是核心推理循环,每帧执行以下步骤:

4.5 MTCNN 检测算法详解
4.5.1 P-Net:图像金字塔 + 滑窗检测
P-Net 是一个全卷积网络,接收 12 × 12 的图像块,判断是否包含人脸。由于人脸大小不一,需要构建图像金字塔实现多尺度检测:
// 构建金字塔:从 12/min_face_size 开始,逐层缩小
scale = (float)PNET_PATCH_SIZE / PNET_MIN_FACE_SIZE; // 初始: 12/48 = 0.25
while (scaled_image >= 12×12) {
resize_rgb(src, DET_WIDTH, DET_HEIGHT, scaled, sw, sh);
// 滑窗扫描
for (y = 0; y <= sh - 12; y += PNET_STRIDE) {
for (x = 0; x <= sw - 12; x += PNET_STRIDE) {
// 提取 12×12 块 → NPU 推理 → 置信度过滤
// 坐标映射回原始图像 + bbox 回归
}
}
scale *= PNET_SCALE_FACTOR; // 0.707 (= 1/√2,标准 MTCNN)
}关键参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
PNET_PATCH_SIZE | 12 | P-Net 输入尺寸 |
PNET_STRIDE | 6 | 滑窗步长;6=快速,4=平衡,2=最佳召回率 |
PNET_MIN_FACE_SIZE | 48 | 最小可检测人脸尺寸(检测图中的像素数) |
PNET_SCALE_FACTOR | 0.707 | 金字塔缩放因子(1/√2) |
FACE_CONF_THRESHOLD | 0.50 | P-Net 置信度阈值 |
4.5.2 R-Net:候选精筛
R-Net 接收 P-Net 输出的候选框,从原始图像中裁剪对应区域并缩放到 24 × 24 进行二次筛选:
for (i = 0; i < pnet_count; i++) {
// 1. 裁剪候选区域并缩放到 24×24
crop_resize(rgb_img, DET_WIDTH, DET_HEIGHT, &cands[i], crop, 24);
// 2. 复制到模型输入(处理 NCHW 布局)
copy_rgb_to_model_input(&g_rnet, crop, 24, 24);
// 3. NPU 推理
xmedia_cl_graph_process(g_rnet.graph);
// 4. 解量化 + 置信度过滤 + bbox 回归
dequantize_output(&g_rnet, score_idx, s_vals, 2);
dequantize_output(&g_rnet, box_idx, b_vals, 4);
if (prob >= 0.70f) { // R-Net 阈值更高,严格过滤
bbox_reg(&cands[out_count], b_vals);
out_count++;
}
}
// NMS 去重
out_count = nms(cands, out_count, 0.50f);4.5.3 O-Net:最终精修 + 关键点
O-Net 在 R-Net 基础上进一步精修框位置,并输出 5 个面部关键点:
for (i = 0; i < rnet_count; i++) {
// 裁剪 → 48×48 → NPU 推理
crop_resize(rgb_img, DET_WIDTH, DET_HEIGHT, &cands[i], crop, 48);
copy_rgb_to_model_input(&g_onet, crop, 48, 48);
xmedia_cl_graph_process(g_onet.graph);
// 解量化:score(2) + bbox(4) + landmark(10)
dequantize_output(&g_onet, score_idx, s_vals, 2);
dequantize_output(&g_onet, box_idx, b_vals, 4);
dequantize_output(&g_onet, lm_idx, l_vals, 10);
// 关键点回归(模型输出 planar 格式: [x0..x4, y0..y4])
for (k = 0; k < 5; k++) {
landmarks[k*2] = x1 + l_vals[k] * bw; // X 坐标
landmarks[k*2 + 1] = y1 + l_vals[5 + k] * bh; // Y 坐标
}
}关键点顺序:左眼(LE) → 右眼(RE) → 鼻尖(N) → 左嘴角(ML) → 右嘴角(MR)。
4.6 后处理与帧间跟踪
形状过滤
NMS 后,filter_implausible() 会根据以下启发式规则过滤不合理的检测框:
// 拒绝非人脸形状的框
- 宽高比 < 0.20 或 > 3.0(注意:量化 MTCNN 的 bbox 回归会系统性收窄框)
- 宽或高 < 6 像素(太小的框)
- 面积 > 检测图像 90%(太大的框)帧间跟踪
update_tracks() 实现基于 IoU 匹配的简单帧间跟踪,用于:
- 稳定显示:通过 EMA(指数移动平均)平滑检测框位置,消除帧间抖动
- 延迟消失:人脸暂时被遮挡或检测丢失时,保持显示若干帧(
MAX_MISS = 3) - 唯一 ID:每条跟踪记录分配唯一 ID
// 每帧处理流程:
// 1. 用 IoU 匹配新检测与已有跟踪
// 2. 匹配成功 → EMA 平滑更新位置
// 匹配失败 → 新建跟踪 或 miss_count++
// 3. miss_count > MAX_MISS → 移除跟踪4.7 输出量化与反量化
NPU 输出的是 INT8 量化数据,需要反量化为浮点值才能使用:
// 反量化公式
float_value = (int8_value - zero_point) × scale
// 代码实现
static void dequantize_output(const npu_model_t *m, int tensor_idx,
float *out, int count)
{
unsigned char *data = get_output_data(m, tensor_idx);
float scale = m->output.tensor[tensor_idx].quant.scale;
int zp = m->output.tensor[tensor_idx].quant.zp;
for (i = 0; i < count; i++)
out[i] = ((float)data[i] - zp) * scale;
}4.8 Web 服务与 API
应用内嵌了一个轻量级 HTTP 服务器(web_server.c),提供以下路由:
| 路由 | 方法 | 功能 |
|---|---|---|
/ | GET | 返回 index.html 页面 |
/mjpeg | GET | MJPEG 视频流(multipart/x-mixed-replace) |
/api/faces | GET | 返回 JSON 格式的检测结果 |
/api/status | GET | 返回简要状态信息 |
/style.css、/app.js | GET | 静态资源文件 |
/api/faces 返回的 JSON 格式:
{
"count": 2,
"fps": 18,
"faces": [
{
"x": 0.3125,
"y": 0.2056,
"w": 0.1875,
"h": 0.3333,
"score": 0.998,
"landmarks": [
{ "x": 0.35, "y": 0.29 },
{ "x": 0.44, "y": 0.285 },
{ "x": 0.395, "y": 0.34 },
{ "x": 0.36, "y": 0.41 },
{ "x": 0.43, "y": 0.405 }
]
}
]
}其中坐标值均已归一化到 0.0 ~ 1.0 范围(相对于检测图像尺寸)。
5 如何编写类似应用
本节以 face_recognize 为参考模板,讲解如何开发一个基于 GK7206 NPU 的自定义 AI 视觉应用。
5.1 开发步骤总览
步骤 1: 准备模型 → 训练/下载模型 → XMTVM 工具转换为 .xmm 格式
步骤 2: 创建工程 → 复制 Makefile 模板 → 编写 main.c
步骤 3: 初始化管线 → VI + VPSS + VENC 配置
步骤 4: 加载模型 → xmedia_cl_* API 加载 .xmm
步骤 5: 推理循环 → 获取帧 → 前处理 → NPU 推理 → 后处理
步骤 6: 结果输出 → Web 展示 / RTSP 推流 / 其他方式5.2 步骤 1:准备模型
使用 XMTVM 模型转换工具将训练好的模型(ONNX/PyTorch)转换为 GK7206 NPU 专用的 .xmm 格式:
# 示例:使用 XMTVM 工具转换模型
python3 convert.py --model yolov5s.onnx \
--input_shape 1,3,320,180 \
--input_format RGB \
--quantize int8 \
--output model.xmm模型转换注意事项
- 模型的
input_format设置决定了输入数据格式(RGB/YUV 等) - 量化方式(INT8/UINT8)会影响推理精度和后处理的反量化参数
- 建议在 YAML 配置中指定
(pixel - 127.5) / 128归一化,由 NPU 内部完成,减轻 CPU 负担
5.3 步骤 2:创建工程
参照 face_recognize 的目录结构创建新工程:
mkdir -p my_app/src my_app/models my_app/web编写 Makefile(可直接复制 face_recognize/Makefile 并修改):
ifeq ($(CFG_SDK_EXPORT_FLAG),)
SDK_DIR := $(shell cd $(CURDIR)/../.. && /bin/pwd)
endif
include $(SDK_DIR)/build/base.mk
include $(SAMPLE_DIR)/sample_base.mk
TARGET := my_app # 修改为你的应用名
LIBS := -lxmedia_svp -lxmedia_npu $(SAMPLE_LIBS) $(SAMPLE_COMMON_LIB) -lpthread
INCLUDES := $(SAMPLE_INCLUDES)
INCLUDES += -I$(SDK_DIR)/project/common # 如果使用 web_server.c
CFLAGS := $(SAMPLE_CFLAGS) $(LIBS) $(INCLUDES)
SRCS := $(wildcard src/*.c) $(SDK_DIR)/project/common/web_server.c
OBJS := $(patsubst %.c, %.o, $(SRCS))
.PHONY: all clean
all: $(OBJS)
$(AT)$(CC) -o $(TARGET) $^ $(CFLAGS)
%.o : %.c
$(AT)$(CC) -c -o $@ $< $(CFLAGS)
clean:
$(AT)rm -rf $(OBJS) $(TARGET)5.4 步骤 3:初始化视频管线
视频管线的初始化代码可以直接复用 face_recognize 的 init_system() 函数框架,需要根据实际需求调整以下参数:
// 1. 根据你的模型输入尺寸调整 NPU 检测分辨率
#define DET_WIDTH 320 // 匹配模型输入宽度
#define DET_HEIGHT 180 // 匹配模型输入高度
// 2. VB 内存池配置(3 个 Pool)
// - Pool 0: VI 采集缓冲
// - Pool 1: VPSS 全分辨率 / VENC
// - Pool 2: VPSS NPU 输入(DET_WIDTH × DET_HEIGHT)
// 3. VPSS 输出通道配置
// - ochn0: 全分辨率 → VENC (用于 Web 展示)
// - ochn1: 缩放到 DET_WIDTH×DET_HEIGHT → NPU 推理5.5 步骤 4:加载 NPU 模型
加载模型的代码可以直接复用 load_one_model() 函数。该函数封装了完整的模型加载流程:
// 定义模型结构体(封装 graph + tensor + buffer)
typedef struct {
xmedia_cl_graph graph;
xmedia_cl_tensor_info_inout input;
xmedia_cl_tensor_info_inout output;
void *work_buf, *weight_buf, *input_buf, *output_buf;
xmedia_u64 work_phy, weight_phy, input_phy, output_phy;
} npu_model_t;
// 加载模型
npu_model_t my_model;
load_one_model(&my_model, g_cl_ctx, "./models/my_model.xmm");5.6 步骤 5:编写推理循环
推理循环的核心模式是「获取帧 → 前处理 → NPU 推理 → 后处理」:
void *inference_thread(void *arg)
{
while (running) {
// 1. 从 VPSS 获取帧
xmedia_vpss_acquire_ochn_frame(pipe, ochn, &frame, timeout);
// 2. 前处理(根据模型需求)
// - mmap 帧数据到用户空间
// - YUV → RGB 转换(如果模型需要 RGB)
// - resize / crop 到模型输入尺寸
// - 处理数据布局(HWC → NCHW 等)
void *frame_y = xmedia_mmz_map(frame.addr.y_phy_addr, ...);
void *frame_uv = xmedia_mmz_map(frame.addr.c_phy_addr, ...);
yuv420sp_to_rgb888(frame_y, frame_uv, rgb_buf, ...);
copy_rgb_to_model_input(&model, rgb_buf, width, height);
// 3. 执行 NPU 推理
xmedia_cl_graph_process(model.graph);
// 4. 后处理
// - 从输出 buffer 读取并反量化
dequantize_output(&model, tensor_idx, output, count);
// - 解析检测结果(NMS、阈值过滤等)
// - 更新共享状态(加锁保护)
// 5. 释放帧
xmedia_vpss_release_ochn_frame(pipe, ochn, &frame);
xmedia_mmz_unmap(frame_y);
xmedia_mmz_unmap(frame_uv);
}
}5.7 关键编程要点
MMZ 内存管理
NPU 相关的缓冲区必须使用 MMZ(Media Memory Zone)分配物理连续内存:
// 分配 + 映射
xmedia_u64 phy = xmedia_mmz_alloc(mmz_name, buf_name, size);
void *virt = xmedia_mmz_map(phy, size, cached);
// 使用完毕后
xmedia_mmz_unmap(virt);
xmedia_mmz_free(phy);数据布局转换
NPU 模型通常使用 NCHW 布局 [1, C, H, W],而相机输出的 RGB 数据是 HWC 布局。copy_rgb_to_model_input() 自动检测模型布局并进行转换:
if (model_input_is_nchw(m)) {
// HWC → NCHW: 分离 R/G/B 三个通道平面
for (c = 0; c < 3; c++)
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
dst[c*plane + y*w + x] = src[(y*w + x)*3 + c];
} else {
memcpy(dst, src, w * h * 3); // 直接拷贝
}量化输出处理
NPU 输出的是量化后的整数数据,需要根据输出 Tensor 的量化参数(scale 和 zero_point)进行反量化:
float scale = m->output.tensor[idx].quant.scale;
int zp = m->output.tensor[idx].quant.zp;
float value = (float)(int8_data[i] - zp) * scale;输出 Tensor 索引
当模型有多个输出 Tensor 时,需要通过通道数来识别各输出的含义:
// 通过输出通道数查找对应的 Tensor 索引
int score_idx = find_output_by_ch(&model, 2); // 2 通道 = 分类得分
int box_idx = find_output_by_ch(&model, 4); // 4 通道 = bbox 回归
int lm_idx = find_output_by_ch(&model, 10); // 10 通道 = 5 点关键点5.8 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| NPU 加载模型失败 | .xmm 文件路径错误或格式不匹配 | 检查路径是否正确,确认模型输入尺寸与代码匹配 |
| 检测不到人脸 | 置信度阈值过高 / YUV→RGB 转换错误 | 降低阈值调试;dump 首帧 RGB 验证颜色转换 |
| 检测框位置偏移 | bbox 回归坐标未正确映射 | 检查坐标从缩放图像到原图的映射计算 |
| 帧率过低 | P-Net 滑窗步长太小 / 金字塔层数过多 | 增大 PNET_STRIDE 或 PNET_MIN_FACE_SIZE |
| Web 页面无法访问 | 前端文件未放置在正确目录 | 确保 HTML/JS/CSS 在 Web 服务器的静态文件目录中 |
| MMZ 分配失败 | 系统内存不足 | 减少 VB Pool 数量或块大小 |
