
当下青少年心理健康问题愈发受到社会、学校与家庭的重点关注,师生沟通、亲子疏导、心理辅导过程中,传统人工情绪判断存在主观性强、滞后性明显、数据无法量化、实时性不足等痛点。心理老师仅凭主观经验判断学生情绪状态,难以精准捕捉细微的情绪波动,也无法留存标准化的情绪数据,不利于长期心理状态追踪与干预。
人工智能人脸情绪识别技术的成熟,为青少年心理沟通场景提供了全新的解决方案。本文所解析的项目,是基于实际应用的纯本地轻量化人脸情绪监测系统,移除了冗余的人脸注册、账号登录功能,聚焦核心的8类人脸情绪识别任务,适配青少年心理沟通、线上线下心理辅导、课堂情绪状态监测等场景。
系统依托insightface人脸检测、EmotiEffLib大模型情绪推理、FERPlus兜底模型构建双引擎识别架构,结合FastAPI轻量化后端与可视化前端页面,实现摄像头实时抓拍、本地图片上传双模式情绪识别,同时搭配数据统计、图表可视化、历史记录分析功能。相较于传统云端识别方案,本项目全程本地推理,无数据上传泄露风险,完美适配青少年隐私保护需求;同时通过模型锐化、人脸精准裁剪、双模型容错机制,解决了轻量化模型识别准确率低、情绪概率区分度弱、画面干扰容错差等难题,为AI心理监测场景应用经过实践调优的可行技术方案。

青少年处于心理发育关键期,情绪波动频繁且隐蔽性强,叛逆、自卑、焦虑、低落等负面情绪往往不会主动对外表露。在心理沟通场景中,沟通对象的面部情绪是反映真实心理状态的核心依据,能够直观体现当下的心理波动、信任程度与情绪倾向。
传统心理沟通依赖人工观察记录,存在三大核心短板:
同时,青少年人脸数据、情绪数据属于敏感隐私数据,云端AI识别方案存在数据上传、存储泄露的风险,无法满足校园、心理机构的隐私合规要求。基于以上行业痛点,本项目针对性开发端侧本地人脸情绪监测系统,聚焦心理沟通专属场景,轻量化、高安全、可落地,适配中小学校、社区心理辅导站、家庭教育心理疏导等多场景使用。
本项目摒弃通用型人脸系统的冗余功能,专注青少年心理沟通场景,确立四大核心开发目标,兼顾技术实用性、场景适配性与数据安全性:
本项目采用前后端一体化轻量化架构,无分离部署复杂度,单脚本启动即可完成服务托管,整体分为三层结构,层级清晰、耦合度低、易于部署维护:
后端主服务完整代码片段(main.py):
整体架构实现“输入-预处理-检测-推理-输出-可视化”全链路闭环,适配心理沟通实时监测需求。项目核心包含insightface人脸检测模块、EmotiEffLib大模型情绪推理主模块、FERPlus-8 ONNX轻量化兜底模块。搭配图像预处理、概率锐化、人脸裁剪、数据归一化工具函数,完成全链路推理。
import cv2
import numpy as np
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from insightface.app import FaceAnalysis
import onnxruntime as ort
app = FastAPI(title="青少年情绪监测后端服务")
# 全局跨域放行
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 托管前端静态页面
app.mount("/static", StaticFiles(directory="static"), name="static")
# 模型全局懒加载变量
face_analyzer = None
emotion_main_model = None
emotion_backup_model = None
# 人脸裁剪函数
def crop_face_square(img, bbox, expand_ratio=1.4):
x1, y1, x2, y2 = bbox
cx, cy = (x1+x2)/2, (y1+y2)/2
w, h = x2-x1, y2-y1
side = max(w, h) * expand_ratio
half = side / 2
# 正方形边界
nx1, ny1 = int(cx - half), int(cy - half)
nx2, ny2 = int(cx + half), int(cy + half)
# 填充灰色
h_img, w_img = img.shape[:2]
pad_x1, pad_y1 = max(0, -nx1), max(0, -ny1)
pad_x2, pad_y2 = max(0, nx2-w_img), max(0, ny2-h_img)
img_pad = cv2.copyMakeBorder(img, pad_y1, pad_y2, pad_x1, pad_x2, cv2.BORDER_CONSTANT, value=(114,114,114))
crop = img_pad[ny1+pad_y1:ny2+pad_y1, nx1+pad_x1:nx2+pad_x1]
return crop
# 概率锐化算法
def sharpen_probs(probs, sharp=3.0):
probs = np.clip(probs, 1e-8, 1.0)
p_pow = np.power(probs, sharp)
return p_pow / np.sum(p_pow)
@app.get("/")
async def index():
from fastapi.responses import FileResponse
return FileResponse("static/index.html")
@app.post("/api/emotion/recognize")
async def recognize_emotion(file: UploadFile = File(...)):
global face_analyzer
# 人脸检测懒加载
if face_analyzer is None:
face_analyzer = FaceAnalysis(name="buffalo_l", providers=['CPUExecutionProvider'])
face_analyzer.prepare(ctx_id=0, det_size=(640,640))
# 读取图片
content = await file.read()
arr = np.frombuffer(content, np.uint8)
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
faces = face_analyzer.get(img)
if len(faces) == 0:
return {"code":-1, "msg":"未检测到人脸"}
# 选取画面最大人脸
max_face = max(faces, key=lambda f:(f['bbox'][2]-f['bbox'][0])*(f['bbox'][3]-f['bbox'][1]))
bbox = max_face["bbox"]
w_face = bbox[2] - bbox[0]
if w_face < 16:
return {"code":-1, "msg":"人脸区域过小,无法识别"}
face_crop = crop_face_square(img, bbox)
# 此处调用双模型情绪推理
# ...(推理逻辑)
return {
"code":0,
"msg":"识别成功",
"face_count": len(faces),
"emotion_result": {
"emotion_name": "calm",
"confidence": 0.82,
"probs": [0.82,0.05,0.03,0.04,0.02,0.02,0.01,0.01]
}
}
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000)核心模块:
项目采用轻量化、高适配、低门槛的技术栈,兼顾开发效率、运行稳定性与场景落地性,核心技术分为三大类:
后端技术:
AI模型技术:
图像与数据处理技术:
前端技术:
前端核心脚本实现:
以下脚本实现了前端摄像头实时情绪监测的完整流程:
// 向后端上传图片请求识别
async function uploadImageToBackend(imgBlob){
const formData = new FormData();
formData.append("file", imgBlob);
const resp = await fetch("/api/emotion/recognize",{
method:"POST",
body:formData
})
const resJson = await resp.json();
return resJson;
}
// 多帧投票算法,提升实时监测稳定性
function voteResult(frameEmotionList){
const emotionCount = {};
let totalProb = Array(8).fill(0);
frameEmotionList.forEach(item=>{
const emo = item.emotion_name;
emotionCount[emo] = (emotionCount[emo]||0)+1;
totalProb = totalProb.map((v,i)=>v+item.probs[i]);
})
// 统计最高频次情绪
let maxCnt = 0;
let finalEmo = "";
Object.keys(emotionCount).forEach(k=>{
if(emotionCount[k]>maxCnt){
maxCnt = emotionCount[k];
finalEmo = k;
}
})
const avgProbs = totalProb.map(v=>v / frameEmotionList.length);
const consistentRate = maxCnt / frameEmotionList.length;
return {
finalEmotion: finalEmo,
avgProbs: avgProbs,
frameConsistency: consistentRate
}
}
// 摄像头轮询采集识别
let timer = null;
function startCameraDetect(){
timer = setInterval(async ()=>{
const canvas = document.getElementById("cameraCanvas");
const blob = await new Promise(res=>canvas.toBlob(res,"image/jpeg"));
const ret = await uploadImageToBackend(blob);
if(ret.code===0){
pushFrameData(ret.emotion_result);
renderChart();
}
},2000);
}
function stopCameraDetect(){
clearInterval(timer);
}人脸检测是情绪识别的前置核心步骤,检测精度直接决定情绪识别的准确性。本项目选用insightface buffalo_l人脸检测模型,该模型是工业级轻量化人脸检测模型,针对普通CPU设备优化,无需GPU加速即可实现高精度检测。
相较于传统检测算法,该模型具备三大核心优势,适配青少年心理监测场景:
项目针对心理场景做了专属逻辑优化:
情绪识别是项目的核心功能,本项目创新采用大模型主推理+轻量化模型兜底的双引擎架构,解决单一模型精度不足、稳定性差、场景适配弱的痛点。
主模型:EmotiEffLib enet_b2_8大模型
兜底模型:FERPlus-8 ONNX轻量化模型
模型统一适配优化
双模型切换推理示例:
以下示例实现了情绪识别的双模型统一推理逻辑。优先调用EmotiEffLib主模型(enet_b2_8)进行情绪预测,若主模型加载或推理异常,则自动降级切换至FERPlus ONNX兜底模型,将人脸裁剪图转灰度、缩放至64×64后送入推理。最终对输出概率统一执行概率锐化处理,增强高置信度情绪的区分度,返回对齐后的情绪概率分布。整体设计体现了"主模型优先 + 兜底降级 + 结果统一"的容错思路。
def emotion_probs_unified(face_crop):
global emotion_main_model, emotion_backup_model
try:
# 优先使用EmotiEffLib主模型推理
if emotion_main_model is None:
from emotiefflib.facial_analysis import EmotiEffLibRecognizer
emotion_main_model = EmotiEffLibRecognizer(engine="onnx", model_name="enet_b2_8")
probs, _ = emotion_main_model.predict_emotions(face_crop)
# 索引映射,对齐FERPlus输出顺序
map_index = [0,1,2,3,4,5,6,7]
aligned_probs = [probs[i] for i in map_index]
except Exception as e:
print(f"主模型异常,切换兜底模型:{e}")
# 加载FERPlus ONNX兜底模型
if emotion_backup_model is None:
emotion_backup_model = ort.InferenceSession("ferplus8.onnx", providers=["CPUExecutionProvider"])
gray = cv2.cvtColor(face_crop, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray,(64,64))
inp = gray[np.newaxis,np.newaxis,:,:].astype(np.float32)
out = emotion_backup_model.run(None, {"input":inp})
aligned_probs = out[0][0].tolist()
# 概率锐化
aligned_probs = sharpen_probs(np.array(aligned_probs), sharp=3.0)
return aligned_probs.tolist()传统情绪识别模型存在普遍痛点:Softmax输出概率过于平滑,最优情绪与次要情绪差距极小,置信度仅0.2-0.3,结果可信度低,无法支撑心理评估。
项目中结合实际优化创新加入概率锐化算法(SHARP=3.0),详细流程:

流程说明:
阶段 | 核心操作 | 效果 |
|---|---|---|
锐化 | 对原始概率进行3次幂运算 | 放大主打与次要情绪差距 |
重归一化 | 确保概率总和为1 | 保持概率分布有效性 |
智能自适应 | 判断概率分布特征 | 主导明显→强化区分;均等→保留不确定 |
核心原则 | 不改变排序 | 不篡改识别结果,不伪造情绪 |
核心逻辑:
模型推理精度高度依赖预处理质量,错误预处理会导致输入分布偏离训练域,出现识别恒平静、结果错乱等问题。本项目严格遵循官方训练标准,定制专属预处理流程:

该套预处理方案彻底对齐模型训练数据分布,大幅提升细微情绪识别准确率。
图像预处理完整函数:
def emotion_preprocess(face_bgr_img):
# BGR转灰度
gray_img = cv2.cvtColor(face_bgr_img, cv2.COLOR_BGR2GRAY)
# 缩放到64*64
resized = cv2.resize(gray_img, (64,64))
# 构建模型输入shape (1,1,64,64)
input_tensor = np.expand_dims(np.expand_dims(resized, axis=0), axis=0).astype(np.float32)
# 保留0~255像素值域,不额外归一化
return input_tensor后端基于FastAPI实现轻量化一体化服务,精简冗余功能,聚焦情绪识别核心业务,核心功能如下:

前端采用原生JavaScript开发,轻量化无依赖,专为心理监测实操场景设计,核心功能:
摄像头实时抓拍识别:

历史评测结果曲线:

本地图片上传识别:

可视化模块专为心理评估场景定制,将抽象情绪转化为量化图表,核心功能:
系统内置自动化报告体系,分为单次评估与历史统计两类,为心理干预提供标准化依据:
人脸裁剪是保障识别精度的关键前置操作,项目自定义crop_face_square函数,实现自适应外扩裁剪与越界填充:

相较于传统裁剪方式,该算法最大化保留有效人脸特征,完美适配模型正方形输入规范。
def crop_face_square(img, bbox, scale=1.4):
"""以人脸 bbox 中心为基准外扩 scale 倍裁剪正方形区域,越界部分用灰色 (114) 填充"""
h, w = img.shape[:2]
bx1, by1, bx2, by2 = [float(v) for v in bbox]
cx = (bx1 + bx2) / 2
cy = (by1 + by2) / 2
side = max(int(max(bx2 - bx1, by2 - by1) * scale), 16)
half = side // 2
x1 = int(cx - half)
y1 = int(cy - half)
x2 = x1 + side
y2 = y1 + side
# 原图越界量(copyMakeBorder 时用灰色补上)
left = -min(x1, 0)
top = -min(y1, 0)
right = max(x2 - w, 0)
bottom = max(y2 - h, 0)
# 先夹取到图内
x1 = max(x1, 0)
y1 = max(y1, 0)
x2 = min(x2, w)
y2 = min(y2, h)
crop = img[y1:y2, x1:x2]
if left or top or right or bottom:
crop = cv2.copyMakeBorder(crop, top, bottom, left, right,
cv2.BORDER_CONSTANT, value=(114, 114, 114))
return cropemotion_preprocess函数遵循模型训练标准,是高精度识别的核心保障:

def emotion_preprocess(crop_bgr):
"""人脸区域 → 模型输入 (1,1,64,64) float32。
注意:ONNX Model Zoo emotion-ferplus-8 官方 README 明确——输入为灰度图,
直接 resize 到 64x64 即可,像素保持 0~255,无需任何归一化。
(此前误用 (x-128)/3.5 压到 [-36.6,36.3],输入分布严重偏离训练域,
导致模型几乎恒输出 neutral)
"""
gray = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, (64, 64), interpolation=cv2.INTER_LINEAR)
x = gray.astype(np.float32)
return x.reshape(1, 1, 64, 64)sharpen_probs函数用于提升情绪区分度,优化模糊情绪识别场景:

def sharpen_probs(p):
"""p: (8,) 概率数组 -> 锐化后仍归一化的概率数组"""
p = np.maximum(np.asarray(p, dtype=float), 0.0)
if p.sum() <= 0:
return np.full(len(p), 1.0 / len(p))
s = np.power(p + 1e-9, SHARP)
return s / s.sum()emotion_probs_unified函数实现智能双模型切换,保障系统高稳定性:

def emotion_probs_unified(crop_bgr):
"""人脸区域 → 统一 8 类顺序概率数组。主引擎 AffectNet,异常自动回退 FERPlus"""
rec = get_affect_recognizer()
if rec is not None:
try:
rgb = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB)
_labels, scores = rec.predict_emotions(rgb, logits=False)
src = np.asarray(scores[0], dtype=float)
p = np.empty(len(_AFFECT_SRC), dtype=float)
for i, j in enumerate(_AFFECT_SRC):
p[i] = src[j]
return sharpen_probs(p)
except Exception:
pass
x = emotion_preprocess(crop_bgr)
sess = get_emotion_session()
out = sess.run(None, {sess.get_inputs()[0].name: x})[0][0]
e = np.exp(out - np.max(out))
return sharpen_probs(e / e.sum())前端voteResult函数解决单帧识别偶然性误差,提升实时监测稳定性:

本项目是一套面向青少年心理沟通场景的轻量化、高安全、高精度本地人脸情绪监测系统,基于FastAPI+原生前端+双AI模型架构开发,彻底摒弃云端识别的隐私风险与通用模型的精度短板。通过insightface高精度人脸检测、EmotiEffLib大模型核心推理、FERPlus模型兜底、概率锐化优化、多帧投票校准、可视化数据分析等核心技术,实现了8类情绪的精准、稳定、实时识别。
涵盖图像输入、智能识别、数据可视化、报告生成、历史统计全流程功能,完美适配校园心理辅导、家庭亲子沟通、社会心理服务等多场景落地需求。相较于传统人工评估与通用AI识别方案,本项目在隐私安全、识别精度、场景适配、数据量化四大维度具备显著优势,能够有效解决青少年心理情绪监测的行业痛点。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。