☰
PaddleDetection人脸检测与情绪识别:从模型部署到推理优化实战
2026/10/9 17:06:18 网站建设 项目流程

简介:这份压缩包面向计算机视觉开发者和研究人员,提供基于PaddleDetection的人脸检测与情绪识别完整模型资源,适用于人机交互、智能安防、广告推荐等场景。包内包含检测模型配置、训练与推理脚本、文档说明等,可帮助读者快速上手人脸定位及快乐、悲伤、愤怒、惊讶、恐惧、厌恶、中立七类基本情绪分类任务。资源共1748个文件,涵盖Python脚本、YAML配置文件、预训练参数、Markdown文档及图片样例等,压缩包整体约603MB,目录结构清晰,适合在飞桨框架下开展二次开发或微调。检测部分覆盖YOLO、SSD、Faster R-CNN等常用模型,情绪识别部分提供基于VGG、ResNet等架构的预训练模型,并附有示例代码与部署相关文件,便于从数据准备到模型预测的完整流程实践。目前已有373人学习下载,适合需要快速落地视觉方案的开发者参考。

1. 人脸检测和情绪识别模型 PaddleDetection.zip:为什么一个压缩包装着两套模型

做视频分析类项目的开发者,很容易被人脸检测和情绪识别这两个词同时吸引,又同时在落地时卡住:PaddleDetection 能输出的只是「人脸框」,框里的人到底是开心、生气还是走神,它给不出答案。这个 zip 包把两件事装在了一起,解压后通常能看到检测模型目录、情绪分类模型目录和各自配套的推理脚本。适合谁?适合手里有摄像头数据、想在本地把「先检到脸、再判断表情」这条链路跑通的人。如果你只是想搜一个现成的 API,这个包不解决你的问题;如果你想自己掌控模型、数据和阈值,这套东西值得花时间解包。

2. 先跑通人脸检测:PaddleDetection 环境与最小推理命令

2.1 模型选型:人脸检测不是拿个通用检测模型就行

PaddleDetection 里能做人脸检测的模型不止一个,典型的有 PyramidBox 系列和 BlazeFace。很多人上来就挑 map 最高的模型,这个思路在小目标人脸上会翻车。PyramidBox 的 640 输入版在小脸和中脸场景上表现稳,但模型体积大、推理慢;BlazeFace 轻快,适合边缘盒子,但密集人群里漏检率会明显高。

我一般这样选型:单路摄像头、画面里人脸小于 40 像素,优先 PyramidBox 640;多路视频流或嵌入式部署,选轻量模型并把输入分辨率固定在 320 或 416。PaddleDetection 的配置文件把 backbone、neck、head 和 anchor 参数都写在 yml 里,换模型不等于换整个代码,改-c指向的配置文件就行。

解压 zip 后先别急着改参数,看目录里给了哪个检测模型的 inference 文件。.pdmodel结尾的是静态图模型,可以直接给 Paddle Inference 用;如果只有.pdparams训练权重,就得走一步模型导出。常见的包会两者都给,推理脚本默认读 inference 文件,训练权重放在weights/下备用。

2.2 最小推理:一张图的检测命令和输出解读

先不碰训练,把检测跑通。假设 zip 包解压到PaddleDetection/目录,里面带了一份训练好的检测权重。用自带预测脚本跑一张测试图,命令是这样的:

python tools/infer.py \ -c configs/pyramidbox/pyramidbox_640.yml \ -o weights=weights/face_det.pdparams \ --infer_img=test_face.jpg \ --output_dir=output/ \ --draw_threshold=0.6

这条命令里-c指定模型配置,-o里的weights覆盖配置文件里的权重路径,--draw_threshold控制画框时保留多少置信度以上的目标。跑完去output/目录看图,检测框画得过于密,就把阈值往上调到 0.7;一个人脸上出现好几个框,多半是 NMS 没把重复框压干净。

如果包里的推理脚本不是tools/infer.py,而是单独一个demo_detect.py,命令形式会不同,但套路一样:读图、前向推理、后处理画框。注意看入参里有没有--use_gpu,CPU 环境下不显式关掉,Paddle 会尝试初始化 CUDA 并报错。

提示:--infer_img只认图片路径,想测摄像头得用--infer_video。包里的脚本不一定实现了视频输入,检查一下再动手。

3. 解包落地:从训练权重到可部署 inference 模型的转换

3.1 模型导出:从 pdparams 到 pdmodel 的转换

zip 包里如果只给了.pdparams训练权重,推理环境就加载不了。PaddleDetection 的训练产物是动态图参数,部署时要先导出成静态图模型。常见做法是用套件自带的导出脚本:

python tools/export_model.py \ -c configs/pyramidbox/pyramidbox_640.yml \ -o weights=weights/face_det.pdparams \ --output_dir=inference_model

导出成功后,inference_model/下会生成model.pdmodel、model.pdiparams和infer_cfg.yml三个文件。infer_cfg.yml里的draw_threshold是默认画框阈值,use_dynamic_shape控制输入尺寸是否可变。导出时如果报 shape 相关错误,多半是配置里用了固定 scale 的 anchor 生成逻辑,把TestReader里的image_shape改成固定值就好了。

导出后最好验证一下模型能不能正常推理。用 Paddle Inference 加载的方式和训练完全不同,写一个极简验证脚本:

import paddle.inference as paddle_infer config = paddle_infer.Config( "inference_model/model.pdmodel", "inference_model/model.pdiparams") config.enable_memory_optim() predictor = paddle_infer.create_predictor(config) print("inference model loaded")

这段代码先构造推理配置,再创建 predictor。enable_memory_optim()打开显存优化,部署时建议保留;如果是在内存紧张的老机器上,加了反而可能抖动,到时候去掉再看。

3.2 阈值设置:人脸置信度、NMS、类别过滤

跑通导出只是第一步,真正影响检测质量的参数有三个:置信度阈值、NMS 阈值、输入尺寸。PaddleDetection 的推理脚本通常支持从命令行覆盖这些配置项。

置信度阈值低了,会把背景当成人脸,画面上到处是框;高了,人脸稍微侧一点就漏检。做实时情绪识别时,我的经验是把检测置信度放到 0.65~0.75,理由是后续情绪分类模型吃的是人脸图块,检测框稍有偏移,切出来的图就不准,宁缺毋滥。

python tools/infer.py \ -c configs/pyramidbox/pyramidbox_640.yml \ -o weights=weights/face_det.pdparams \ --infer_img=group_face.jpg \ --draw_threshold=0.7 \ --nms_threshold=0.45

nms_threshold大一点,重复框保留得多;小一点,重叠的人脸可能被误删。密集人群场景建议 0.4~0.5,单人场景 0.5 即可。这两个参数属于典型的「改一个不够,要配着改」:阈值调高了漏检,调低了误检,NMS 再跟着收紧,整体效果才会收敛。

类别过滤这个坑最容易忽略。PyramidBox 这类人脸模型输出只有一个类别,没有过滤问题;但如果你拿通用检测模型来跑人脸,输出里会混进 person、cat 之类的东西。包里的推理脚本一般靠infer_cfg.yml里的labels列表判断类别名,跑出来的框不对,先看这个文件里的类别表,再决定是改配置还是加一行类别白名单过滤。

4. 情绪识别怎么接:从检测框到分类结果的完整推理链路

4.1 情绪识别为什么要单独做:检测头与分类头的分工

有人问能不能直接在 PaddleDetection 的人脸检测模型后面加一个情绪分类头?技术上可行,但工程上不建议。检测和分类的监督信号完全不同:检测头要学「框住脸」,情绪分类要学「脸里面的表情是什么」。硬合成一个多任务模型,需要同时标注框和情绪标签,数据成本翻倍,而且检测数据里的表情分布未必均匀,训练容易失衡。

我见过某组件化方案就是把两个模型分开部署:检测模型只出框,情绪模型只吃框。两边可以各自换模型、单独调阈值,排查问题时边界也清晰。检测模型还可以换成任意轻量模型,情绪模型那边完全不受影响。这个「两个模型串一条链路」的架构,比单模型自适应性强得多。

情绪分类模型普遍做法是轻量分类网络,输入人脸图块,输出 7 类概率——生气、厌恶、恐惧、开心、伤心、惊讶、中性。zip 包里的情绪模型目录通常就包含这 7 类的标签映射文件。拿到的第一件事不是跑图,而是打开标签文件确认分类顺序,否则后面画标签时很容易张冠李戴。

4.2 检测结果切块与情绪分类推理脚本

把两个模型串起来的核心代码是:检测模型输出框坐标,按框从原图裁剪人脸图块,再送进情绪模型。以下是用 Paddle Inference 同时加载两个模型的推理脚本骨架:

import cv2 import numpy as np import paddle.inference as paddle_infer def load_predictor(model_path, params_path): config = paddle_infer.Config(model_path, params_path) config.enable_memory_optim() return paddle_infer.create_predictor(config) def run_face_detect(predictor, image): input_names = predictor.get_input_names() input_handle = predictor.get_input_handle(input_names[0]) input_handle.copy_from_cpu(image[np.newaxis, :, :, :]) predictor.run() output_names = predictor.get_output_names() output_handle = predictor.get_output_handle(output_names[0]) return output_handle.copy_to_cpu() det_predictor = load_predictor("face_det/model.pdmodel", "face_det/model.pdiparams") emo_predictor = load_predictor("emotion/model.pdmodel", "emotion/model.pdiparams") img = cv2.imread("test_face.jpg") rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) boxes = run_face_detect(det_predictor, rgb_img) for box in boxes: x1, y1, x2, y2, score = int(box[0]), int(box[1]), int(box[2]), int(box[3]), box[4] if score < 0.65: continue face_crop = rgb_img[y1:y2, x1:x2] face_resized = cv2.resize(face_crop, (112, 112)) emotion_probs = run_face_detect(emo_predictor, face_resized) emotion_id = np.argmax(emotion_probs[0]) print(f"face at ({x1},{y1}) score={score:.2f} emotion={emotion_id}")

这段代码有四个关键点。第一,检测模型输出格式是[N, 6],每行分别是类别 id、置信度、左上角 x/y、右下角 x/y;实际顺序可能因导出配置不同而变,先打印一行的内容确认。第二,裁剪坐标直接用检测框的原始像素坐标,但输入给检测模型时图片可能被 resize 过,如果推理脚本内部改了尺寸,框坐标要按缩放比例换算回原图。第三,情绪模型输入尺寸要跟训练时一致,训练用的 112 就 resize 到 112,换尺寸等于换数据分布,准确率直接掉。第四,run_face_detect函数名是我临时起的,情绪模型的前向调用和检测模型完全一致,都是 copy 数据、run、取输出,所以复用同一个函数没问题。

帧率问题是串行推理最大的隐患。检测模型跑一帧可能要 30ms,情绪模型又要 20ms,加起来 50ms,勉强能到 20FPS。多路摄像头同时跑就直接掉到个位数。常见做法是检测不每帧都跑,每隔 5 帧检测一次,中间帧沿用最近一次检测框。这样检测耗时摊薄,情绪模型每帧照跑,CPU 占用能降一半以上。

注意:检测框偶尔会跳到背景上,情绪模型照样会输出一个结果。接线上游最好加一个「检测框面积变化超过 30% 不采信」的规则,比单纯调阈值省心。

5. 落地避坑:5 个 PaddleDetection 实战中反复踩到的问题

5.1 模型加载报错:把 inference model 当训练模型取用

现象:直接拿.pdparams文件传给paddle.jit.load,报错说不认识这个参数结构,或者加载成功但推理结果全零。

原因:训练权重和推理模型是两种格式。.pdparams是动态图参数,只存权重;推理模型是静态图结构加权重,必须成对使用.pdmodel和.pdiparams。

解决:先跑一遍第 3 章的导出命令,把.pdparams转成inference_model/目录,再加载。这个坑基本每个新手都会踩一次,看到 zip 里只有训练权重时,别急着改脚本,先补导出这一步。

5.2 小脸漏检:anchor 与输入分辨率不匹配

现象:一张多人合影,正中间的人脸检测出来了,角落里两三张侧脸根本没框;把图放大后同样位置又能检到。

原因:输入分辨率太低时,小脸对应的像素区域太小,anchor 覆盖不到。PaddleDetection 的 anchor 是按配置文件里输入尺寸设计的,输入 320 时最小 anchor 只能框住约 40 像素的目标。

解决:把输入尺寸提到 640 或者 768,检测置信度相应放宽 0.05。代价是推理耗时上涨,实测单帧耗时可能从 20ms 涨到 60ms。如果场景固定是会议室摄像头,可以试试裁掉无用区域再放大,比整体放大更划算。

5.3 情绪标签闪烁:光照变化和单帧噪声

现象:同一个人坐在同一个位置,情绪标签在开心和中性之间来回跳,一分钟能跳十几次。

原因:情绪分类模型是单帧输入,光照变化、头部轻微转动都会造成分类概率明显变化。argmax 取最大概率时,两个类别的概率只要差 0.01 就会换标签。

解决:给情绪输出加滑动窗口。维护一个长度为 5 的队列,取最近 5 帧概率平均后再取 argmax。这个方法对摄像头场景的提升很明显,实现成本只有一段 20 行的代码。要再稳一点,就把窗口拉长到 10,但表情变化时反应会慢半拍,看业务取舍。

5.4 CPU 推理掉帧:MKLDNN 与输入尺寸的配合

现象:代码在 GPU 机器上跑得好好的,换到纯 CPU 的服务器上,推理耗时翻了 10 倍,画面卡成幻灯片。

原因:模型没有针对 CPU 做优化。Paddle Inference 在 CPU 上默认不开 MKLDNN,卷积算子走的通用实现,速度远不如优化后的核。

解决:给推理配置加一行config.enable_mkldnn(),再搭配config.set_cpu_math_library_num_threads(4)。实测常见检测模型能提速 2 到 4 倍。如果还卡,就把输入分辨率从 640 降到 416,并在情绪模型侧把输入从 112 降到 96。注意 MKLDNN 对部分算子支持不完整,开启后如果出现精度异常,先关掉再排查。

5.5 检测框边界溢出:人脸图块超出图像范围

现象:情绪识别前裁剪人脸时,程序报index out of bounds,或者切出来的图片边缘是黑的。

原因:检测模型输出的框坐标可能超出原图边界,比如人脸在画面边缘时,框的右下角坐标比图片尺寸大。

解决:裁剪之前做一次 clip。用x1 = max(0, x1)、y2 = min(image_height, y2)这样的方式把框限制在图像范围内。这个代码写起来很简单,但很多人会忘,而且只在部分图上触发,属于典型的「测试时没事、上线就崩」的问题。

6. 用一个视频脚本验证整套链路:检测框、情绪标签和耗时一起看

6.1 端到端验证脚本

单张图片验证不了稳定性,我习惯把检测加情绪识别直接接到视频上跑一段。写一个简单脚本,输出每帧的检测框数量、情绪标签和平均耗时,用这个指标判断模型在真实场景里能不能用。

import cv2 import time cap = cv2.VideoCapture("test_video.mp4") face_count_total = 0 frame_count = 0 time_start = time.time() while True: ret, frame = cap.read() if not ret: break boxes = run_face_detect(det_predictor, frame) face_crops = [crop_face(frame, b) for b in boxes if b[4] > 0.65] emotions = [np.argmax(run_face_detect(emo_predictor, c)[0]) for c in face_crops] for emotion_id in emotions: print(f"frame {frame_count}: emotion {emotion_id}") frame_count += 1 face_count_total += len(boxes) if frame_count == 100: break avg_time = (time.time() - time_start) / frame_count print(f"avg inference time per frame: {avg_time * 1000:.1f} ms")

看结果时重点看两个数字:平均耗时和情绪标签跳变次数。耗时超过 100ms 说明部署环境扛不住,考虑降分辨率或改轻量模型。标签跳变率超过 20%,别急着调模型,先检查检测框稳不稳——框一抖,情绪跟着乱。

我后来在项目里养成的习惯是:每换一个场景,先跑一个 30 秒的 demo 视频,把情绪标签的变化率打印出来,而不是只盯着单张准确率。单张准确率高不代表场景里可用,标签跳变才是上线前最该解决的事。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询