GCLing commited on
Commit
439571f
·
verified ·
1 Parent(s): 54a4a96

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -130
app.py CHANGED
@@ -1,126 +1,65 @@
1
- import gradio as gr
2
  import os
3
- import numpy as np
 
4
  import joblib
 
5
  import librosa
6
- import requests
7
  from huggingface_hub import hf_hub_download
 
8
 
9
- # --- DeepFace 条件导入 ---
10
- try:
11
- from deepface import DeepFace
12
- has_deepface = True
13
- except ImportError:
14
- print("本地未安装 deepface,将在本地跳过臉部情緒;Space 上会安装 deepface。")
15
- has_deepface = False
16
-
17
- # --- 1. 语音 SVM 加载 ---
18
- print("Downloading SVM model from Hugging Face Hub...")
19
- model_path = hf_hub_download(repo_id="GCLing/emotion-svm-model", filename="svm_emotion_model.joblib")
20
- svm_model = joblib.load(model_path)
21
- print("SVM model loaded.")
22
-
23
- # --- 2. 文本情绪分析:改用 Inference API ---
24
  HF_API_TOKEN = os.getenv("HF_API_TOKEN")
25
  if HF_API_TOKEN is None:
26
- print("警告:未检测到 HF_API_TOKEN,Inference API 可能失败。")
27
- # 选用公开存在的中文情感分类模型
28
  HF_TEXT_MODEL = "uer/roberta-base-finetuned-dianping-chinese"
29
  HF_API_URL = f"https://api-inference.huggingface.co/models/{HF_TEXT_MODEL}"
30
- headers = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {}
31
-
32
 
33
- def predict_text_via_api(text: str):
34
- if not text or text.strip()=="":
35
  return {}
36
  payload = {"inputs": text}
37
  try:
38
- resp = requests.post(HF_API_URL, headers=headers, json=payload, timeout=30)
39
- if resp.status_code != 200:
40
- print(f"Inference API 返回状态码 {resp.status_code}: {resp.text}")
41
- # 退回到简单规则或中性
42
- return {"中性": 1.0}
43
- data = resp.json()
44
- # 根据模型返回格式解析:假设返回 [{"label": "...", "score": ...}, ...]
45
- if isinstance(data, list) and len(data)>0 and isinstance(data[0], dict):
46
- # 选 top 3 展示
47
- result = {}
48
- for item in data[:3]:
49
- lbl = item.get("label", "")
50
  score = item.get("score", 0.0)
51
- # 若标签是英文,可映射到中文;若就是中文可直接用
52
- # 例如模型返回 "positive"/"negative"/"neutral",可映射:
53
- if lbl.lower() in ["positive","pos","正面"]:
54
- cn = "正面"
55
- elif lbl.lower() in ["negative","neg","负面","負面"]:
56
- cn = "負面"
57
- elif lbl.lower() in ["neutral","中性"]:
58
- cn = "中性"
59
- else:
60
- cn = lbl
61
- result[cn] = float(score)
62
- return result
63
  else:
64
- print("Inference API 返回格式异常:", data)
65
- return {"中性": 1.0}
 
 
66
  except Exception as e:
67
- print("调用 Inference API 出错:", e)
68
  return {"中性": 1.0}
69
 
70
- # 可保留简单规则优先,若规则命中则返回规则,否则调用 API
71
- emo_keywords = {
72
- "happy": ["開心","快樂","愉快","喜悦","喜悅","歡喜","興奮","高興"],
73
- "angry": ["生氣","憤怒","不爽","發火","火大","氣憤"],
74
- "sad": ["傷心","難過","哭","難受","心酸","憂","悲","哀","痛苦","慘","愁"],
75
- "surprise": ["驚訝","意外","嚇","驚詫","詫異","訝異","好奇"],
76
- "fear": ["","恐懼","緊張","懼","膽怯","畏"],
77
- "disgust": ["噁心","厭惡","反感"]
78
- }
79
- negations = ["不","沒","沒有","別","勿","非"]
80
- def keyword_emotion(text: str):
81
- text_proc = text.strip()
82
- counts = {emo:0 for emo in emo_keywords}
83
- for emo, kws in emo_keywords.items():
84
- for w in kws:
85
- idx = text_proc.find(w)
86
- if idx!=-1:
87
- neg=False
88
- for neg_word in negations:
89
- plen = len(neg_word)
90
- if idx-plen>=0 and text_proc[idx-plen:idx]==neg_word:
91
- neg=True; break
92
- if not neg:
93
- counts[emo]+=1
94
- total = sum(counts.values())
95
- if total>0:
96
- # 归一化并取最高
97
- top = max(counts, key=lambda k: counts[k])
98
- return {top: counts[top]/total}
99
- return None
100
-
101
- def predict_text_mixed(text: str):
102
- print("predict_text_mixed:", text)
103
- if not text or text.strip()=="":
104
- return {}
105
- res = keyword_emotion(text)
106
- if res:
107
- # 映射中文标签
108
- mapping = {
109
- "happy":"高興","angry":"憤怒","sad":"悲傷",
110
- "surprise":"驚訝","fear":"恐懼","disgust":"厭惡"
111
- }
112
- emo = list(res.keys())[0]; prob = float(res[emo])
113
- cn = mapping.get(emo, emo)
114
- return {cn: prob}
115
- # 规则未命中,调用 Inference API
116
- return predict_text_via_api(text)
117
 
118
- # --- 3. 语音情绪预测 ---
119
  def extract_feature(signal: np.ndarray, sr: int) -> np.ndarray:
120
  mfcc = librosa.feature.mfcc(y=signal, sr=sr, n_mfcc=13)
121
- return np.concatenate([np.mean(mfcc, axis=1), np.var(mfcc, axis=1)])
 
122
 
123
  def predict_voice(audio_path: str):
 
 
124
  if not audio_path:
125
  return {}
126
  try:
@@ -133,53 +72,60 @@ def predict_voice(audio_path: str):
133
  print("predict_voice error:", e)
134
  return {}
135
 
136
- # --- 4. 人脸情绪预测 ---
137
- import gradio as gr
138
-
139
  def predict_face(img: np.ndarray):
140
- # 你的 DeepFace 分析逻辑
141
  if img is None:
142
  return {}
143
- # ...
144
- return {"happy": 0.5, "sad": 0.5} # 举例
 
 
 
 
 
 
 
 
 
 
145
 
 
146
  def build_interface():
147
  with gr.Blocks() as demo:
148
- gr.Markdown("## 多模態情緒分析示例")
149
  with gr.Tabs():
150
- # 臉部情緒 Tab
151
  with gr.TabItem("臉部情緒"):
152
- gr.Markdown("### 臉部情緒 (即時 Webcam Streaming 分析)")
153
  with gr.Row():
154
- # 这里用 gr.Image(sources="webcam", streaming=True, type="numpy")
155
- webcam = gr.Image(sources="webcam", streaming=True, type="numpy", label="攝像頭畫面")
156
- face_out = gr.Label(label="情緒分佈")
157
- # 每帧送到 predict_face
158
- webcam.stream(fn=predict_face, inputs=webcam, outputs=face_out)
159
 
160
- # 語音情緒 Tab
161
  with gr.TabItem("語音情緒"):
162
- gr.Markdown("### 語音情緒 分析")
163
- with gr.Row():
164
- # 浏览器录音用 source="microphone"
165
- audio = gr.Audio(source="microphone", streaming=False, type="filepath", label="錄音")
166
- voice_out = gr.Label(label="語音情緒結果")
167
- audio.change(fn=predict_voice, inputs=audio, outputs=voice_out)
 
 
168
 
169
- # 文字情緒 Tab
170
  with gr.TabItem("文字情緒"):
171
- gr.Markdown("### 文字情緒 分析 (規則+Inference API)")
172
  with gr.Row():
173
- text = gr.Textbox(lines=3, placeholder="請輸入中文文字…")
174
  text_out = gr.Label(label="文字情緒結果")
175
- # 使用 submit 触发
176
- text.submit(fn=predict_text_mixed, inputs=text, outputs=text_out)
177
 
178
  return demo
179
 
180
-
181
-
182
  if __name__ == "__main__":
183
  demo = build_interface()
184
- # share=True 可在本地测试时生成临时公网链接
185
- demo.launch(share=True)
 
 
1
  import os
2
+ import gradio as gr
3
+ import requests
4
  import joblib
5
+ import numpy as np
6
  import librosa
 
7
  from huggingface_hub import hf_hub_download
8
+ from deepface import DeepFace
9
 
10
+ # --- 配置:Hugging Face Inference API 文本分析 ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  HF_API_TOKEN = os.getenv("HF_API_TOKEN")
12
  if HF_API_TOKEN is None:
13
+ print("警告:未检测到 HF_API_TOKEN,文字分析可能失败或限流。")
14
+ # 选用公开存在的中文情感分类模型 ID
15
  HF_TEXT_MODEL = "uer/roberta-base-finetuned-dianping-chinese"
16
  HF_API_URL = f"https://api-inference.huggingface.co/models/{HF_TEXT_MODEL}"
17
+ HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {}
 
18
 
19
+ def call_text_api(text: str):
20
+ if not text or text.strip() == "":
21
  return {}
22
  payload = {"inputs": text}
23
  try:
24
+ res = requests.post(HF_API_URL, headers=HEADERS, json=payload, timeout=15)
25
+ res.raise_for_status()
26
+ data = res.json()
27
+ result = {}
28
+ if isinstance(data, list):
29
+ for item in data:
30
+ label = item.get("label", "")
 
 
 
 
 
31
  score = item.get("score", 0.0)
32
+ result[label] = float(score)
 
 
 
 
 
 
 
 
 
 
 
33
  else:
34
+ # 如果返回不同结构,可根据实际调整
35
+ print("call_text_api 返回格式未预期:", data)
36
+ return {}
37
+ return result
38
  except Exception as e:
39
+ print("call_text_api error:", e)
40
  return {"中性": 1.0}
41
 
42
+ # --- 语音情绪分析 SVM 模型加载 ---
43
+ USE_VOICE = True
44
+ svm_model = None
45
+ if USE_VOICE:
46
+ try:
47
+ print("下载并加载语音 SVM 模型...")
48
+ model_path = hf_hub_download(repo_id="GCLing/emotion-svm-model", filename="svm_emotion_model.joblib")
49
+ svm_model = joblib.load(model_path)
50
+ print("SVM 模型加载完成")
51
+ except Exception as e:
52
+ print("语音 SVM 模型加载失败,禁用语音模块:", e)
53
+ USE_VOICE = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
 
55
  def extract_feature(signal: np.ndarray, sr: int) -> np.ndarray:
56
  mfcc = librosa.feature.mfcc(y=signal, sr=sr, n_mfcc=13)
57
+ feat = np.concatenate([np.mean(mfcc, axis=1), np.var(mfcc, axis=1)])
58
+ return feat
59
 
60
  def predict_voice(audio_path: str):
61
+ if not USE_VOICE or svm_model is None:
62
+ return {"error": 1.0}
63
  if not audio_path:
64
  return {}
65
  try:
 
72
  print("predict_voice error:", e)
73
  return {}
74
 
75
+ # --- 臉部情緒分析,使用 DeepFace 分析上傳或拍照圖片 ---
 
 
76
  def predict_face(img: np.ndarray):
77
+ # img numpy array,或 None
78
  if img is None:
79
  return {}
80
+ try:
81
+ res = DeepFace.analyze(img, actions=["emotion"], detector_backend="opencv")
82
+ if isinstance(res, list):
83
+ first = res[0] if res else {}
84
+ emo = first.get("emotion", {}) if isinstance(first, dict) else {}
85
+ else:
86
+ emo = res.get("emotion", {}) if isinstance(res, dict) else {}
87
+ emo_fixed = {k: float(v) for k, v in emo.items()}
88
+ return emo_fixed
89
+ except Exception as e:
90
+ print("DeepFace.analyze error:", e)
91
+ return {}
92
 
93
+ # --- Gradio 界面 ---
94
  def build_interface():
95
  with gr.Blocks() as demo:
96
+ gr.Markdown("## 多模態情緒分析(簡化版:上傳/拍照人臉 + 語音 + 文字)")
97
  with gr.Tabs():
98
+ # 臉部 Tab:上傳或拍照
99
  with gr.TabItem("臉部情緒"):
100
+ gr.Markdown("### 臉部情緒 分析 (上傳或拍照圖片)")
101
  with gr.Row():
102
+ # sources=["upload"] 在手機上點上傳可調出相機拍照
103
+ face_input = gr.Image(sources=["upload"], type="numpy", label="上傳或拍照圖片")
104
+ face_out = gr.Label(label="情緒分布")
105
+ face_input.change(fn=predict_face, inputs=face_input, outputs=face_out)
 
106
 
107
+ # 語音 Tab
108
  with gr.TabItem("語音情緒"):
109
+ gr.Markdown("### 語音情緒 分析 (錄音並上傳)")
110
+ if USE_VOICE:
111
+ with gr.Row():
112
+ audio_input = gr.Audio(source="microphone", streaming=False, type="filepath", label="錄音")
113
+ voice_out = gr.Label(label="語音情緒結果")
114
+ audio_input.change(fn=predict_voice, inputs=audio_input, outputs=voice_out)
115
+ else:
116
+ gr.Markdown("語音模塊不可用。")
117
 
118
+ # 文字 Tab
119
  with gr.TabItem("文字情緒"):
120
+ gr.Markdown("### 文字情緒 分析 (Hugging Face Inference API)")
121
  with gr.Row():
122
+ text_input = gr.Textbox(lines=3, placeholder="請輸入中文文字…")
123
  text_out = gr.Label(label="文字情緒結果")
124
+ text_input.submit(fn=call_text_api, inputs=text_input, outputs=text_out)
 
125
 
126
  return demo
127
 
 
 
128
  if __name__ == "__main__":
129
  demo = build_interface()
130
+ # share=True 可生成临时公开链接;部署到 Spaces 时无需此参数
131
+ demo.launch()