""
检信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,
}