检信ALLEMOTION VibrationAI 2.4.0 12维度情绪识别开源源代码

""

检信ALLEMOTION VibrationAI 技术的核心,是通过摄像头非接触式 捕捉面部肌肉微振动(频率、振幅、能量),结合多模态AI模型在60秒内客观输出12维心理情绪指标(如压力、攻击性)。

它的核心应用场景是大规模早期心理筛查 ,可无缝集成到教育、医疗、安保、军工等领域的现有系统中,为岗前测评、学生建档、社区预警等提供量化数据支撑,有效补传统量表主观性强、效率低的短板 项目核心12维度技术开源如下:如果需要帮助 请连携我们QQ 515164561@QQ.COM

12-Dimensional Emotion Quantification Fusion Engine.

Implements PRD 4.6-4.9 | V2.4.0: Full log1p scaling on all 12 dimensions.

All formulas match PRD. No hard-coded weights or thresholds in functions.

"""

import math

from typing import Dict, List, Optional, Tuple

DIMENSION_NAMES: Liststr = [

"aggression", "suspicion", "stress", "tension",

"inhibition", "neuroticism", "depression", "self_regulation",

"energy", "balance", "confidence", "happiness",

]

DEFAULT_WEIGHTS: Dictstr, float = {

"aggression": 0.12, "suspicion": 0.11, "stress": 0.10,

"tension": 0.10, "inhibition": 0.09, "neuroticism": 0.09,

"depression": 0.08, "self_regulation": 0.07, "energy": 0.06,

"balance": 0.06, "confidence": 0.07, "happiness": 0.05,

}

V1.2.2: 人口学参数 --- 年龄分组生理基线

AGE_GROUP_PROFILES = {

"child": {"tag": "少年(8-17)", "hr_baseline": 85, "hrv_baseline": 45},

"young": {"tag": "青年(18-35)", "hr_baseline": 70, "hrv_baseline": 35},

"middle": {"tag": "中年(36-55)", "hr_baseline": 72, "hrv_baseline": 30},

"senior": {"tag": "老年(56+)", "hr_baseline": 75, "hrv_baseline": 25},

}

V1.2.2: 人口学参数 --- 性别生理基线

GENDER_PROFILES = {

"male": {"hr_baseline": 70, "hrv_baseline": 32, "depression_bias": -5},

"female": {"hr_baseline": 76, "hrv_baseline": 38, "depression_bias": +5},

}

def _clamp(value: float, low: float = 0.0, high: float = 100.0) -> float:

"""Clamp *value* to inclusive \*low\*, \*high\*."""

return max(low, min(high, value))

def _round2(value: float) -> float:

"""Round to two decimal places."""

return round(value, 2)

---------------------------------------------------------------------------

PRD 4.6 -- Weighted Base Score

---------------------------------------------------------------------------

def calculate_base_score(

emotion_vector: Dictstr, float,

weights: OptionalDict\[str, float] = None,

age_group: Optionalstr = None,

gender: Optionalstr = None,

) -> float:

"""S_base = sum(w_i * x_i) over 12 dimensions, clamped 0, 100.

V1.2.2: age_group and gender params accepted for future population-based

baseline correction; currently forward-compatible placeholders.

"""

w = weights if weights is not None else DEFAULT_WEIGHTS

total = sum(w.get(d, 0.0) * emotion_vector.get(d, 0.0) for d in DIMENSION_NAMES)

return _round2(_clamp(total))

---------------------------------------------------------------------------

PRD 4.7 -- Global Variance Penalty

---------------------------------------------------------------------------

def calculate_global_variance(emotion_vector: Dictstr, float) -> float:

"""Population variance sigma^2 = (1/N) * sum((x_i - mu)^2) across 12 dims."""

vals = emotion_vector.get(d, 0.0) for d in DIMENSION_NAMES

n = len(vals)

if n == 0:

return 0.0

mu = sum(vals) / n

return _round2(sum((v - mu) ** 2 for v in vals) / n)

def calculate_penalty_coefficient(

global_variance: float,

lambda_param: float = 0.8,

) -> float:

"""K_std = 1 - lambda * var_norm, clamped 0, 1.

Normalises *global_variance* by dividing by 10000.0 (the maximum

theoretical variance for 12 dimensions bounded 0, 100) so that the

penalty operates in a well-conditioned 0, 1 range.

V1.2.1.1 fix: previously raw variance (0-2500) caused penalty to

zero out at sigma^2 >= 1.25, collapsing nearly all real data.

"""

var_norm = global_variance / 10000.0

return _round2(_clamp(1.0 - lambda_param * var_norm, 0.0, 1.0))

---------------------------------------------------------------------------

PRD 4.8 -- Extreme-Value Correction

---------------------------------------------------------------------------

def check_extreme_dimensions(

emotion_vector: Dictstr, float,

high_threshold: float = 80.0,

low_threshold: float = 15.0,

) -> dict:

"""Return dict of dimensions exceeding high_thr or below low_thr with metadata."""

high_dims = {d: v for d, v in emotion_vector.items() if v > high_threshold}

low_dims = {d: v for d, v in emotion_vector.items() if v < low_threshold}

all_vals = list(emotion_vector.values()) if emotion_vector else 0.0

return {

"high_dims": high_dims,

"low_dims": low_dims,

"has_high_risk": len(high_dims) > 0,

"has_low_anomaly": len(low_dims) > 0,

"high_count": len(high_dims),

"low_count": len(low_dims),

"max_value": _round2(max(all_vals)),

"min_value": _round2(min(all_vals)),

"extreme_count": len(high_dims) + len(low_dims),

}

def calculate_extreme_correction(extreme_info: dict) -> float:

"""eta correction factor: high-risk=0.85, mild anomaly=0.92, normal=1.00."""

if extreme_info.get("has_high_risk", False):

return 0.85

if extreme_info.get("has_low_anomaly", False):

return 0.92

return 1.0

---------------------------------------------------------------------------

PRD 4.9 -- Final Comprehensive Score & Risk Level

---------------------------------------------------------------------------

def calculate_final_score(

base_score: float,

penalty_coeff: float,

extreme_correction: float,

age_group: Optionalstr = None,

gender: Optionalstr = None,

) -> float:

"""S_all = clamp(S_base * K_std * eta, 0, 100), rounded to 2 decimals.

V1.2.2: age_group and gender params accepted for future population-based

correction; currently forward-compatible placeholders.

"""

return _round2(_clamp(base_score * penalty_coeff * extreme_correction))

def determine_risk_level(final_score: float) -> Tuplestr, str:

"""(risk_level, description). 85+:良好 | 65+:一般 | 40+:欠佳 | <40:提醒."""

if final_score >= 85.0:

return ("良好", "情绪稳定、身心平和、状态良好")

if final_score >= 65.0:

return ("一般", "情绪基本稳定、轻微波动、无风险")

if final_score >= 40.0:

return ("欠佳", "情绪不稳定、负面情绪偏高、需关注")

return ("提醒", "情绪波动剧烈、存在高危心理风险")

===========================================================================

Single-Dimension Quantification Functions (12 dimensions)

Each: <=3 params, <=30 lines, clamp(expr, 0, 100)

Normal ranges specified per dimension as defined in PRD.

===========================================================================

def quantify_aggression(

high_freq_energy: float,

jaw_motion: float,

pulse_spike: float,

) -> float:

"""Aggression via 8-15 Hz HF peak * jaw micro-motion * pulse spike. Normal:20-50.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = high_freq_energy * jaw_motion * pulse_spike

if raw <= 0.0:

return 10.0

scaled = math.log1p(raw) * 13.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_stress(

fullband_baseline: float,

temporal_stability: float,

) -> float:

"""Stress via full-band baseline * 1/stability. Normal:20-40.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

if temporal_stability == 0.0:

temporal_stability = 1e-6

raw = fullband_baseline * (1.0 / temporal_stability)

if raw <= 0.0:

return 10.0

scaled = math.log1p(raw) * 8.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_tension(

eye_high_freq_density: float,

short_term_fluctuation: float,

) -> float:

"""Tension via periocular HF density * short-term fluctuation. Normal:20-40.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = eye_high_freq_density * short_term_fluctuation

if raw <= 0.0:

return 10.0

scaled = math.log1p(raw) * 9.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_confidence(

low_freq_ordered_ratio: float,

stability_coeff: float,

) -> float:

"""Confidence via LF ordered ratio * stability. Normal:40-100.

V1.2.2: Applied log1p scaling for robust range mapping.

M7: multiplier 22->28 to increase dynamic range, floor 25->20

to restore frame-to-frame variance lost when S2 stability fixes

narrowed input range.

"""

raw = low_freq_ordered_ratio * stability_coeff

if raw <= 0.0:

return 20.0 # M7: floor 25->20

scaled = math.log1p(raw) * 28.0 # M7: 22->28, amplify small differences

return _round2(_clamp(scaled, 20.0, 100.0)) # M7: floor 25->20

def quantify_balance(

phase_consistency: float,

temporal_dispersion_inverse: float,

) -> float:

"""Balance via phase consistency * temporal dispersion inverse. Normal:50-100.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = phase_consistency * temporal_dispersion_inverse

if raw <= 0.0:

return 20.0

scaled = math.log1p(raw) * 20.0

return _round2(_clamp(scaled, 20.0, 100.0))

def quantify_suspicion(

muscle_stiffness: float,

intermittent_spike: float,

) -> float:

"""Suspicion via muscle stiffness * intermittent spike. Normal:20-50.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = muscle_stiffness * intermittent_spike

if raw <= 0.0:

return 10.0

scaled = math.log1p(raw) * 12.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_energy(fullband_total_energy: float) -> float:

"""Energy via total vibration energy integral. Normal:10-50.

Applies log1p-based scaling to map raw fullband energy into the expected

10-50 normal range. Low inputs (<5) remain near floor; mid-range inputs

(10-60) map into 15-40; very high inputs asymptote toward ~50-75.

"""

if fullband_total_energy <= 0.0:

return 10.0 # floor at normal minimum

log1p scaling: gentle compression to keep typical values in 10-50

scaled = math.log1p(fullband_total_energy) * 13.0

return _round2(_clamp(scaled, 5.0, 75.0))

def quantify_self_regulation(

peak_decay_rate: float,

recovery_speed: float,

) -> float:

"""Self-regulation via peak decay rate * recovery speed. Normal:50-100.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = peak_decay_rate * recovery_speed

if raw <= 0.0:

return 25.0

scaled = math.log1p(raw) * 23.0

return _round2(_clamp(scaled, 25.0, 100.0))

def quantify_depression(low_freq_lethargy_ratio: float) -> float:

"""Depression via 0.1-3 Hz LF lethargic energy ratio. Normal:15-50.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

if low_freq_lethargy_ratio <= 0.0:

return 10.0

scaled = math.log1p(low_freq_lethargy_ratio) * 10.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_neuroticism(

high_freq_fluctuation: float,

instability_coeff: float,

) -> float:

"""Neuroticism via HF fluctuation * instability. Normal:10-60.

V1.2.2: Applied log1p-based scaling to map the raw product into

the expected normal range, consistent with quantify_energy.

Previously the theoretical maximum was 9.5 (9.5*1.0), far below

the documented normal range bottom of 15. This was an omission bug

-- only energy had log1p scaling applied in V1.2.1.

"""

raw = high_freq_fluctuation * instability_coeff

if raw <= 0.0:

return 10.0

scaled = math.log1p(raw) * 20.0

return _round2(_clamp(scaled, 10.0, 100.0))

def quantify_inhibition(

external_low_amp: float,

internal_high_energy: float,

) -> float:

"""Inhibition via external low-amp * internal high-energy. Normal:10-40.

V1.2.2: Applied log1p scaling to constrain the product into the

narrow normal range. Previously the product range was 0.04-90.25

with no scaling, causing both underflow (<15) and overflow (>25).

The log1p*5.5 compression maps 0.04, 90.25 -> 10.0, 24.8,

covering the normal range 10-40 centrally.

"""

raw = external_low_amp * internal_high_energy

if raw <= 0.0:

return 5.0 # M8: floor 10->5, match widened clamp

scaled = math.log1p(raw) * 12.0 # M8: 5.5->12.0, widen inhibition dynamic range

return _round2(_clamp(scaled, 5.0, 60.0)) # M8: clamp 10,100->5,60

def quantify_happiness(

low_freq_relax_ratio: float,

pulse_stability: float,

) -> float:

"""Happiness via LF relaxed ratio * pulse stability. Normal:30-80.

V1.2.2: Applied log1p scaling for robust range mapping.

"""

raw = low_freq_relax_ratio * pulse_stability

if raw <= 0.0:

return 15.0

scaled = math.log1p(raw) * 16.0

return _round2(_clamp(scaled, 15.0, 100.0))

===========================================================================

Full 12-Dimension Vector Computation

===========================================================================

_FEATURE_MAP: Dictstr, Tuple\[callable, List\[str]] = {

"aggression": (quantify_aggression, "high_freq_energy", "jaw_motion", "pulse_spike"),

"suspicion": (quantify_suspicion, "muscle_stiffness", "intermittent_spike"),

"stress": (quantify_stress, "fullband_baseline", "temporal_stability"),

"tension": (quantify_tension, "eye_high_freq_density", "short_term_fluctuation"),

"inhibition": (quantify_inhibition, "external_low_amp", "internal_high_energy"),

"neuroticism": (quantify_neuroticism, "high_freq_fluctuation", "instability_coeff"),

"depression": (quantify_depression, "low_freq_lethargy_ratio"),

"self_regulation": (quantify_self_regulation, "peak_decay_rate", "recovery_speed"),

"energy": (quantify_energy, "fullband_total_energy"),

"balance": (quantify_balance, "phase_consistency", "temporal_dispersion_inverse"),

"confidence": (quantify_confidence, "low_freq_ordered_ratio", "stability_coeff"),

"happiness": (quantify_happiness, "low_freq_relax_ratio", "pulse_stability"),

}

def compute_full_emotion_vector(features: dict,

age_group: Optionalstr = None,

gender: Optionalstr = None) -> Dictstr, float:

"""Compute all 12 emotion dimensions from a signal-feature dictionary.

Args:

features: Dict of raw signal features keyed by feature name.

Missing features default to 0.0.

age_group: Optional age group key ("child","young","middle","senior")

for population-based baseline correction. Default None.

gender: Optional gender key ("male","female") for gender-based

physiological bias correction. Default None.

Returns:

12-dim dict {dim_name: float} with values clamped 0, 100.

"""

result: Dictstr, float = {}

for dim_name, (quantifier_fn, param_keys) in _FEATURE_MAP.items():

args = features.get(k, 0.0) for k in param_keys

resultdim_name = quantifier_fn(*args)

V1.2.2: 性别偏差校正 - 抑郁评分受生理基线差异影响

if gender is not None and gender in GENDER_PROFILES:

profile = GENDER_PROFILESgender

bias = profile.get("depression_bias", 0)

if "depression" in result:

result"depression" = _round2(_clamp(

result"depression" + bias, 10.0, 100.0))

return result

===========================================================================

Temporal Statistics

===========================================================================

def _compute_per_dim_stats(

sequence: ListDict\[str, float],

dim_names: Liststr,

) -> Tupledict, List\[float]:

"""Return per-dimension {mean, variance, max, min} and list of variances."""

n = len(sequence)

per_dim = {}

variances = \[\]

for dim in dim_names:

vals = frame.get(dim, 0.0) for frame in sequence

mu = sum(vals) / n

var = sum((v - mu) ** 2 for v in vals) / n

per_dimdim = {

"mean": _round2(mu),

"variance": _round2(var),

"max": _round2(max(vals)),

"min": _round2(min(vals)),

}

variances.append(var)

return per_dim, variances

def _derive_global_metrics(dim_variances: Listfloat) -> Tuplefloat, float, float:

"""From per-dim variances: (global_variance, stability_coeff, combined_fluctuation)."""

if not dim_variances:

return 0.0, 0.0, 0.0

mean_var = sum(dim_variances) / len(dim_variances)

sqrt_mv = math.sqrt(mean_var)

stability = 1.0 / (1.0 + sqrt_mv)

return _round2(mean_var), _round2(stability), _round2(sqrt_mv)

def compute_temporal_statistics(

emotion_sequence: ListDict\[str, float],

) -> dict:

"""Compute per-dimension mean/variance/max/min, global variance, stability,

and combined fluctuation over a time-series of 12-dim emotion vectors.

Returns dict with keys: frame_count, per_dimension, global_variance,

stability_coefficient, combined_fluctuation.

"""

if not emotion_sequence:

return {

"frame_count": 0,

"per_dimension": {},

"global_variance": 0.0,

"stability_coefficient": 0.0,

"combined_fluctuation": 0.0,

}

per_dim, variances = _compute_per_dim_stats(emotion_sequence, DIMENSION_NAMES)

global_var, stability, fluctuation = _derive_global_metrics(variances)

return {

"frame_count": len(emotion_sequence),

"per_dimension": per_dim,

"global_variance": global_var,

"stability_coefficient": stability,

"combined_fluctuation": fluctuation,

}

相关推荐
Dovis(誓平步青云)1 小时前
DevEco Studio 6.1.1 Windows 安装实录:从下载校验到首次启动
android·开发语言·数据库·人工智能·windows·harmonyos
qeen871 小时前
【数据结构】红黑树的算法原理解析与实现
开发语言·数据结构·c++·算法·红黑树
AC赳赳老秦1 小时前
语义采集进阶实战:利用 OpenClaw AI 语义识别自动提取网页核心信息,无需手动编写选择器
java·运维·服务器·python·信息可视化·deepseek·openclaw
Logintern092 小时前
什么时候应该用多进程什么时候用多线程呢?
开发语言·python
long3162 小时前
枚举(Enums)
java·开发语言·数据库
无凭2 小时前
字节跳动 DeerFlow:Agent Harness 怎么让大模型主动向用户提问?
人工智能·python
蜀道山老天师2 小时前
Python + Playwright 实现问卷星自动化填写
python
ShiXZ2132 小时前
网络调试四剑客:ping / telnet / nc / netstat 速查指令集
运维·开发语言·网络·php
码农大叔的博客2 小时前
golang示例:switch
开发语言·后端·golang