GCLing commited on
Commit
31f3668
·
verified ·
1 Parent(s): b6e2621

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +42 -91
src/streamlit_app.py CHANGED
@@ -2,44 +2,31 @@
2
  # src/streamlit_app.py
3
 
4
  import os
5
- # ========== 1. 环境准备(修复权限问题) ==========
6
- # Streamlit 会在 STREAMLIT_HOME 下写文件,强制指向 /app/.streamlit
7
  os.environ["STREAMLIT_HOME"] = os.path.join(os.getcwd(), ".streamlit")
8
- os.makedirs(os.environ["STREAMLIT_HOME"], exist_ok=True)
9
 
10
- # DeepFace 会在 DEEPFACE_HOME 下缓存模型权重,指向 /app/.deepface
11
- os.environ["DEEPFACE_HOME"] = os.path.join(os.getcwd(), ".deepface")
12
- os.makedirs(os.environ["DEEPFACE_HOME"], exist_ok=True)
13
-
14
- # ========== 2. 引入依赖 ==========
15
  import streamlit as st
16
- from streamlit_webrtc import VideoTransformerBase, webrtc_streamer
17
- import cv2
18
- import numpy as np
19
- import librosa
20
- import joblib
21
  from deepface import DeepFace
22
 
23
- # ========== 3. 模型加载 ==========
24
- st.set_page_config(page_title="多模態情緒分析", layout="wide")
25
-
26
  @st.cache_resource(show_spinner=False)
27
- def load_face_warmup():
28
- # 预热 DeepFace,避免首次卡顿
29
  DeepFace.analyze(
30
- img_path = np.zeros((224,224,3), dtype="uint8"),
31
- actions = ["emotion"],
32
- enforce_detection = False,
33
  )
34
- return True
 
 
35
 
36
- @st.cache_resource(show_spinner="Loading audio model…")
37
- def load_audio_model():
38
- # 必须保证 voice_model.joblib 在 /app 根目录
39
- return joblib.load("voice_model.joblib")
40
 
41
- # 文绪简单规则
42
- def analyze_text_fn(text:str) -> str:
43
  if any(w in text for w in ["開心","快樂","愉快","喜悅","歡喜","興奮","歡","高興"]):
44
  return "happy"
45
  if any(w in text for w in ["生氣","憤怒","不爽","發火","火大","氣憤"]):
@@ -50,79 +37,43 @@ def analyze_text_fn(text:str) -> str:
50
  return "surprise"
51
  if any(w in text for w in ["怕","恐懼","緊張","懼","膽怯","畏"]):
52
  return "fear"
53
- return "neutral"
54
 
55
- # 音情
56
- def analyze_audio_fn(path:str) -> str:
57
- y, sr = librosa.load(path, sr=None, duration=3, offset=0.5)
 
 
58
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
59
- feats = np.mean(mfccs.T, axis=0)
60
- model = load_audio_model()
61
- return model.predict([feats])[0]
62
-
63
- # 预加载
64
- _ = load_face_warmup()
65
- audio_model = load_audio_model()
66
 
67
- # ========== 4. 前端界==========
68
- st.title("📱 多模態時情緒分析(人臉/語音/文字)")
69
 
70
- tabs = st.tabs(["👤 人臉實時", "🎤 語音上傳", "✍️ 文本分析"])
 
 
 
 
71
 
72
- # ----- Tab1: 人脸实时 -----
73
  with tabs[0]:
74
- st.subheader("實時情緒")
75
- class FaceEmotionTransformer(VideoTransformerBase):
76
- def __init__(self):
77
- self.last_emo = "neutral"
78
- def transform(self, frame):
79
- img = frame.to_ndarray(format="bgr24")
80
- res = DeepFace.analyze(
81
- img, actions=["emotion"], enforce_detection=False
82
- )
83
- # DeepFace 可能返回 list 或 dict
84
- if isinstance(res, list):
85
- emo = res[0].get("dominant_emotion", "unknown")
86
- else:
87
- emo = res.get("dominant_emotion", "unknown")
88
- self.last_emo = emo
89
- # 在图上标文字
90
- cv2.putText(img, emo, (10,40),
91
- cv2.FONT_HERSHEY_SIMPLEX, 1.2,
92
- (0,255,0), 2, cv2.LINE_AA)
93
- return img
94
-
95
- ctx = webrtc_streamer(
96
- key="face-emotion",
97
- mode="SENDRECV",
98
- media_stream_constraints={"video": True, "audio": False},
99
- video_transformer_factory=FaceEmotionTransformer,
100
- async_transform=True,
101
- )
102
-
103
- if ctx.video_transformer:
104
- st.markdown(f"**目前檢測到情緒:** `{ctx.video_transformer.last_emo}`")
105
- else:
106
- st.write("請點擊下方「Start」按鈕開始攝像頭")
107
 
108
- # ----- Tab2: 语音上传 -----
109
  with tabs[1]:
110
- st.subheader("上傳 WAV文件 進行語音情緒分析")
111
- audio_file = st.file_uploader("选择 .wav 文件", type=["wav"])
112
- if audio_file is not None:
113
- tmp_path = "tmp_audio.wav"
114
- with open(tmp_path, "wb") as f:
115
- f.write(audio_file.getbuffer())
116
- emo = analyze_audio_fn(tmp_path)
117
- st.success(f"🎤 語音情緒預測:**{emo}**")
118
- os.remove(tmp_path)
119
 
120
- # ----- Tab3: 文本分析 -----
121
  with tabs[2]:
122
- st.subheader("輸入文字 進行情緒分析")
123
- txt = st.text_area("在此輸入句子", height=100)
124
- if st.button("分析文字情緒"):
125
  emo = analyze_text_fn(txt)
126
- st.success(f"✍️ 文本情緒預測:**{emo}**")
127
 
128
 
 
2
  # src/streamlit_app.py
3
 
4
  import os
5
+ # 告訴 Streamlit 將設定檔讀自專案的 .streamlit 資料夾
 
6
  os.environ["STREAMLIT_HOME"] = os.path.join(os.getcwd(), ".streamlit")
 
7
 
 
 
 
 
 
8
  import streamlit as st
9
+ import cv2, numpy as np, base64, io
10
+ import librosa, joblib
 
 
 
11
  from deepface import DeepFace
12
 
13
+ # —— 1. 預先載入模型 ——
 
 
14
  @st.cache_resource(show_spinner=False)
15
+ def load_models():
16
+ # a) 先讓 DeepFace 熱身(不實際偵測人臉)
17
  DeepFace.analyze(
18
+ img_path = np.zeros((224,224,3), dtype=np.uint8),
19
+ actions = ['emotion'],
20
+ enforce_detection=False
21
  )
22
+ # b) 載入你本機訓練好的語音模型
23
+ audio_model = joblib.load("voice_model.joblib")
24
+ return audio_model
25
 
26
+ audio_model = load_models()
 
 
 
27
 
28
+ # —— 2. 緒函式 ——
29
+ def analyze_text_fn(text):
30
  if any(w in text for w in ["開心","快樂","愉快","喜悅","歡喜","興奮","歡","高興"]):
31
  return "happy"
32
  if any(w in text for w in ["生氣","憤怒","不爽","發火","火大","氣憤"]):
 
37
  return "surprise"
38
  if any(w in text for w in ["怕","恐懼","緊張","懼","膽怯","畏"]):
39
  return "fear"
40
+ return "neutral"
41
 
42
+ # —— 3. 語音情式 ——
43
+ def analyze_audio_fn(wav_bytes):
44
+ # 將上傳的 bytes librosa 讀入
45
+ y, sr = librosa.load(io.BytesIO(wav_bytes), sr=None)
46
+ # 計算 MFCC 並取均值作為特徵
47
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
48
+ mf = np.mean(mfccs.T, axis=0)
49
+ # 回傳模型預測結果
50
+ return audio_model.predict([mf])[0]
 
 
 
 
51
 
52
+ # —— 4. 網頁介佈局 ——
53
+ st.title("📱 多模態時情緒分析")
54
 
55
+ tabs = st.tabs([
56
+ "🔴 臉部(僅限本地)",
57
+ "🎤 語音上傳",
58
+ "⌨️ 文字輸入"
59
+ ])
60
 
 
61
  with tabs[0]:
62
+ st.header("實時臉部(本地瀏覽器測試用)")
63
+ st.info("⚠️ HF Spaces 無法直接存取攝影機,僅本地測試有效。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
 
65
  with tabs[1]:
66
+ st.header("上傳 WAV 檔案進行分析")
67
+ wav_file = st.file_uploader("選擇一個 .wav 音訊檔", type="wav")
68
+ if wav_file:
69
+ emo = analyze_audio_fn(wav_file.read())
70
+ st.success(f"🎤 語音檢測到的情緒:**{emo}**")
 
 
 
 
71
 
 
72
  with tabs[2]:
73
+ st.header("輸入文字進行分析")
74
+ txt = st.text_area("在此輸入或貼上")
75
+ if st.button("開始分析"):
76
  emo = analyze_text_fn(txt)
77
+ st.success(f"📝 文本檢測到的情緒:**{emo}**")
78
 
79