batra43pvd commited on
Commit
0f41475
·
verified ·
1 Parent(s): 9022aca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +43 -47
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # Phiên bản hoàn chỉnh, đã thêm bước giảm nhiễu để cải thiện độ chính xác.
3
 
4
  import os
5
  import joblib
@@ -8,10 +8,12 @@ import librosa
8
  from flask import Flask, request, jsonify, render_template
9
  from werkzeug.utils import secure_filename
10
  import traceback
 
11
 
12
- # --- Thư viện mới để đọc audio giảm nhiễu ---
13
  from pydub import AudioSegment
14
  import noisereduce as nr
 
15
 
16
  # --- Cấu hình TensorFlow và các thư viện AI ---
17
  os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
@@ -21,8 +23,6 @@ import torch
21
 
22
  # --- KHỞI TẠO ỨNG DỤNG FLASK ---
23
  app = Flask(__name__)
24
-
25
- # Cấu hình thư mục tạm để lưu file audio
26
  UPLOAD_FOLDER = 'uploads/'
27
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
28
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
@@ -30,7 +30,6 @@ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
30
 
31
  # --- TẢI TẤT CẢ CÁC MÔ HÌNH KHI SERVER KHỞI ĐỘNG ---
32
  print(">>> Đang tải các mô hình AI, quá trình này có thể mất một lúc...")
33
-
34
  try:
35
  MODEL_PATH = 'models/'
36
  scaler = joblib.load(os.path.join(MODEL_PATH, 'scaler.pkl'))
@@ -40,17 +39,13 @@ try:
40
  model_cnn = tf.keras.models.load_model(os.path.join(MODEL_PATH, 'cnn.keras'))
41
  wav2vec_processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base")
42
  wav2vec_model = Wav2Vec2Model.from_pretrained("facebook/wav2vec2-base")
43
-
44
  print(">>> OK! Tất cả các mô hình đã được tải thành công!")
45
-
46
  except Exception as e:
47
  print(f"!!! LỖI NGHIÊM TRỌNG: Không thể tải một hoặc nhiều mô hình. Lỗi: {e}")
48
  traceback.print_exc()
49
  exit()
50
 
51
  # --- CÁC HÀM TRÍCH XUẤT ĐẶC TRƯNG (KHÔNG ĐỔI) ---
52
-
53
- # Các hằng số cấu hình
54
  SAMPLE_RATE = 22050
55
  MAX_LENGTH_SECONDS = 5.0
56
  MAX_SAMPLES = int(SAMPLE_RATE * MAX_LENGTH_SECONDS)
@@ -67,16 +62,13 @@ def _extract_traditional_features(y, sr):
67
  features = np.append(features, np.std(mel_spec_db, axis=1))
68
  features = np.append(features, np.max(mel_spec_db, axis=1))
69
  features = np.append(features, np.min(mel_spec_db, axis=1))
70
-
71
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
72
  features = np.append(features, np.mean(mfccs, axis=1))
73
  features = np.append(features, np.std(mfccs, axis=1))
74
-
75
  if len(features) > TRADITIONAL_FEATURE_SIZE:
76
  features = features[:TRADITIONAL_FEATURE_SIZE]
77
  elif len(features) < TRADITIONAL_FEATURE_SIZE:
78
  features = np.pad(features, (0, TRADITIONAL_FEATURE_SIZE - len(features)), mode='constant')
79
-
80
  return features
81
  except Exception as e:
82
  print(f"Lỗi trích xuất đặc trưng truyền thống: {e}")
@@ -106,42 +98,62 @@ def _create_spectrogram_image(y, sr):
106
  print(f"Lỗi tạo ảnh spectrogram: {e}")
107
  return np.zeros(SPECTROGRAM_SHAPE)
108
 
109
- # --- HÀM XỬ LÝ AUDIO ĐÃ ĐƯỢC CẬP NHẬT VỚI BƯỚC GIẢM NHIỄU ---
110
  def process_audio_file(file_path):
111
  """
112
- Hàm tổng hợp phiên bản mới: Dùng pydub, chuẩn hóa âm lượng, giảm nhiễu,
113
- sau đó chuyển đổi sang định dạng mà librosa có thể xử lý an toàn.
114
  """
115
  try:
116
  # 1. Dùng pydub để mở file audio
117
  audio = AudioSegment.from_file(file_path)
118
 
119
- # 2. Chuẩn hóa âm lượng về một mức tiêu chuẩn
120
  target_dbfs = -20.0
121
  change_in_dbfs = target_dbfs - audio.dBFS
122
  audio = audio.apply_gain(change_in_dbfs)
123
 
124
- # 3. Đảm bảo audio là mono (1 kênh) và có sample rate đúng
 
 
125
  audio = audio.set_channels(1)
126
- audio = audio.set_frame_rate(SAMPLE_RATE)
127
 
128
- # 4. Chuyển đổi audio của pydub thành mảng NumPy cho librosa
129
- samples = np.array(audio.get_array_of_samples()).astype(np.float32)
130
- y = samples / (2**(audio.sample_width * 8 - 1))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- # 5. **BƯỚC MỚI: GIẢM NHIỄU (NOISE REDUCTION)**
133
- # Thực hiện giảm nhiễu trên tín hiệu âm thanh
134
  print("DEBUG | Bắt đầu giảm nhiễu...")
135
  y_reduced_noise = nr.reduce_noise(y=y, sr=SAMPLE_RATE, prop_decrease=0.8)
136
  print("DEBUG | Giảm nhiễu hoàn tất.")
137
 
138
- # 6. Chuẩn hóa độ dài audio về MAX_SAMPLES (sử dụng tín hiệu đã giảm nhiễu)
139
  if len(y_reduced_noise) > MAX_SAMPLES:
140
  y_final = y_reduced_noise[:MAX_SAMPLES]
141
  else:
142
  y_final = np.pad(y_reduced_noise, (0, MAX_SAMPLES - len(y_reduced_noise)), mode='constant')
143
 
144
- # 7. Trích xuất đồng thời các bộ đặc trưng (dùng tín hiệu cuối cùng)
145
  traditional_features = _extract_traditional_features(y_final, SAMPLE_RATE)
146
  wav2vec_features = _extract_wav2vec_features(y_final, SAMPLE_RATE)
147
  spectrogram = _create_spectrogram_image(y_final, SAMPLE_RATE)
@@ -156,12 +168,10 @@ def process_audio_file(file_path):
156
  # --- ĐỊNH NGHĨA CÁC ROUTE CỦA ỨNG DỤNG ---
157
  @app.route('/', methods=['GET'])
158
  def home():
159
- """Render trang chủ của ứng dụng."""
160
  return render_template('index.html')
161
 
162
  @app.route('/predict', methods=['POST'])
163
  def predict():
164
- """API endpoint để nhận file audio, xử lý và trả về kết quả dự đoán."""
165
  if 'audio_file' not in request.files:
166
  return jsonify({'error': 'Không có file audio nào trong yêu cầu.'}), 400
167
 
@@ -173,50 +183,37 @@ def predict():
173
  filename = secure_filename(file.filename)
174
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
175
  file.save(filepath)
176
-
177
- # Xử lý file audio để trích xuất tất cả các đặc trưng cần thiết
178
  trad_feats, w2v_feats, spec_img = process_audio_file(filepath)
179
-
180
  if trad_feats is None:
181
  return jsonify({'error': 'Không thể xử lý file audio.'}), 500
182
 
183
- # ========== BẮT ĐẦU PHẦN GHI LOG CHẨN ĐOÁN ==========
184
- print("\n--- BẮT ĐẦU CHẨN ĐOÁN DỮ LIỆU ĐẦU VÀO (SAU KHI GIẢM NHIỄU) ---")
185
  print(f"DEBUG | trad_feats stats: mean={np.mean(trad_feats):.2f}, std={np.std(trad_feats):.2f}, min={np.min(trad_feats):.2f}, max={np.max(trad_feats):.2f}")
186
  print(f"DEBUG | w2v_feats stats: mean={np.mean(w2v_feats):.2f}, std={np.std(w2v_feats):.2f}, min={np.min(w2v_feats):.2f}, max={np.max(w2v_feats):.2f}")
187
  print(f"DEBUG | spec_img stats: mean={np.mean(spec_img):.2f}, std={np.std(spec_img):.2f}, min={np.min(spec_img):.2f}, max={np.max(spec_img):.2f}")
188
- # ========== KẾT THÚC PHẦN GHI LOG CHẨN ĐOÁN ===========
189
-
190
- # Chuẩn bị dữ liệu đầu vào cho các mô hình
191
  combined_feats = np.concatenate([trad_feats, w2v_feats]).reshape(1, -1)
192
  scaled_feats = scaler.transform(combined_feats)
193
  spec_img = spec_img / 255.0
194
  spec_img = np.expand_dims(spec_img, axis=0)
195
-
196
- # Lấy dự đoán từ tất cả các mô hình
197
  pred_xgb = model_xgb.predict_proba(scaled_feats)[0][1]
198
  pred_lgb = model_lgb.predict_proba(scaled_feats)[0][1]
199
  pred_cnn = model_cnn.predict(spec_img, verbose=0)[0][0]
200
 
201
- # ========== BẮT ĐẦU PHẦN GHI LOG DỰ ĐOÁN ==========
202
  print(f"DEBUG | Individual probabilities (for Male): XGB={pred_xgb:.4f}, LGBM={pred_lgb:.4f}, CNN={pred_cnn:.4f}")
203
- # ========== KẾT THÚC PHẦN GHI LOG DỰ ĐOÁN ===========
204
-
205
-
206
- # Ensemble: Kết hợp kết quả bằng cách lấy trung bình xác suất
207
  final_prediction_prob = (pred_xgb + pred_lgb + pred_cnn) / 3
208
  final_prediction_label_index = 1 if final_prediction_prob > 0.5 else 0
209
  result_label_text = label_encoder.inverse_transform([final_prediction_label_index])[0]
210
-
211
- os.remove(filepath)
212
 
 
213
  print(f"Phân tích hoàn tất. Kết quả: {result_label_text.upper()} (Xác suất: {final_prediction_prob:.2f})")
214
-
215
  return jsonify({
216
  'prediction': result_label_text.capitalize(),
217
  'probability': f"{final_prediction_prob:.2f}"
218
  })
219
-
220
  except Exception as e:
221
  print(f"Đã xảy ra lỗi trong quá trình dự đoán: {e}")
222
  traceback.print_exc()
@@ -226,4 +223,3 @@ def predict():
226
  if __name__ == '__main__':
227
  port = int(os.environ.get("PORT", 7860))
228
  app.run(host='0.0.0.0', port=port)
229
-
 
1
  # app.py
2
+ # Phiên bản cuối cùng: Thêm Voice Activity Detection (VAD) để lọc bỏ khoảng lặng.
3
 
4
  import os
5
  import joblib
 
8
  from flask import Flask, request, jsonify, render_template
9
  from werkzeug.utils import secure_filename
10
  import traceback
11
+ import collections
12
 
13
+ # --- Thư viện mới để đọc audio, giảm nhiễu và VAD ---
14
  from pydub import AudioSegment
15
  import noisereduce as nr
16
+ import webrtcvad
17
 
18
  # --- Cấu hình TensorFlow và các thư viện AI ---
19
  os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
 
23
 
24
  # --- KHỞI TẠO ỨNG DỤNG FLASK ---
25
  app = Flask(__name__)
 
 
26
  UPLOAD_FOLDER = 'uploads/'
27
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
28
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
 
30
 
31
  # --- TẢI TẤT CẢ CÁC MÔ HÌNH KHI SERVER KHỞI ĐỘNG ---
32
  print(">>> Đang tải các mô hình AI, quá trình này có thể mất một lúc...")
 
33
  try:
34
  MODEL_PATH = 'models/'
35
  scaler = joblib.load(os.path.join(MODEL_PATH, 'scaler.pkl'))
 
39
  model_cnn = tf.keras.models.load_model(os.path.join(MODEL_PATH, 'cnn.keras'))
40
  wav2vec_processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base")
41
  wav2vec_model = Wav2Vec2Model.from_pretrained("facebook/wav2vec2-base")
 
42
  print(">>> OK! Tất cả các mô hình đã được tải thành công!")
 
43
  except Exception as e:
44
  print(f"!!! LỖI NGHIÊM TRỌNG: Không thể tải một hoặc nhiều mô hình. Lỗi: {e}")
45
  traceback.print_exc()
46
  exit()
47
 
48
  # --- CÁC HÀM TRÍCH XUẤT ĐẶC TRƯNG (KHÔNG ĐỔI) ---
 
 
49
  SAMPLE_RATE = 22050
50
  MAX_LENGTH_SECONDS = 5.0
51
  MAX_SAMPLES = int(SAMPLE_RATE * MAX_LENGTH_SECONDS)
 
62
  features = np.append(features, np.std(mel_spec_db, axis=1))
63
  features = np.append(features, np.max(mel_spec_db, axis=1))
64
  features = np.append(features, np.min(mel_spec_db, axis=1))
 
65
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
66
  features = np.append(features, np.mean(mfccs, axis=1))
67
  features = np.append(features, np.std(mfccs, axis=1))
 
68
  if len(features) > TRADITIONAL_FEATURE_SIZE:
69
  features = features[:TRADITIONAL_FEATURE_SIZE]
70
  elif len(features) < TRADITIONAL_FEATURE_SIZE:
71
  features = np.pad(features, (0, TRADITIONAL_FEATURE_SIZE - len(features)), mode='constant')
 
72
  return features
73
  except Exception as e:
74
  print(f"Lỗi trích xuất đặc trưng truyền thống: {e}")
 
98
  print(f"Lỗi tạo ảnh spectrogram: {e}")
99
  return np.zeros(SPECTROGRAM_SHAPE)
100
 
101
+ # --- HÀM XỬ LÝ AUDIO ĐÃ ĐƯỢC CẬP NHẬT VỚI VAD ---
102
  def process_audio_file(file_path):
103
  """
104
+ Hàm tổng hợp phiên bản cuối cùng: Thêm Voice Activity Detection (VAD) để
105
+ lọc bỏ các khoảng lặng trước khi xử lý.
106
  """
107
  try:
108
  # 1. Dùng pydub để mở file audio
109
  audio = AudioSegment.from_file(file_path)
110
 
111
+ # 2. Chuẩn hóa âm lượng
112
  target_dbfs = -20.0
113
  change_in_dbfs = target_dbfs - audio.dBFS
114
  audio = audio.apply_gain(change_in_dbfs)
115
 
116
+ # 3. Đảm bảo audio là mono và có sample rate đúng cho VAD
117
+ # VAD hoạt động tốt nhất ở các sample rate 8000, 16000, 32000, 48000
118
+ vad_sample_rate = 32000
119
  audio = audio.set_channels(1)
120
+ audio = audio.set_frame_rate(vad_sample_rate)
121
 
122
+ # 4. **BƯỚC MỚI: VOICE ACTIVITY DETECTION**
123
+ print("DEBUG | Bắt đầu Voice Activity Detection (VAD)...")
124
+ vad = webrtcvad.Vad(3) # Mức độ mạnh nhất (0-3)
125
+ frame_duration_ms = 30 # 30ms mỗi frame
126
+ frame_bytes = int(vad_sample_rate * frame_duration_ms / 1000) * audio.sample_width
127
+
128
+ frames = [audio[i:i + frame_duration_ms] for i in range(0, len(audio), frame_duration_ms)]
129
+ voiced_frames = [f for f in frames if vad.is_speech(f.raw_data, vad_sample_rate)]
130
+
131
+ if voiced_frames:
132
+ # Nối các frame có tiếng nói lại với nhau
133
+ audio_voiced = sum(voiced_frames, AudioSegment.empty())
134
+ print(f"DEBUG | VAD hoàn tất. Giữ lại {len(audio_voiced)}ms âm thanh.")
135
+ else:
136
+ # Nếu không tìm thấy tiếng nói, dùng lại audio gốc
137
+ audio_voiced = audio
138
+ print("DEBUG | VAD không tìm thấy âm thanh, sử dụng audio gốc.")
139
+
140
+ # 5. Chuyển đổi audio đã lọc về sample rate mục tiêu
141
+ audio_final = audio_voiced.set_frame_rate(SAMPLE_RATE)
142
+ samples = np.array(audio_final.get_array_of_samples()).astype(np.float32)
143
+ y = samples / (2**(audio_final.sample_width * 8 - 1))
144
 
145
+ # 6. Giảm nhiễu trên tín hiệu đã được lọc
 
146
  print("DEBUG | Bắt đầu giảm nhiễu...")
147
  y_reduced_noise = nr.reduce_noise(y=y, sr=SAMPLE_RATE, prop_decrease=0.8)
148
  print("DEBUG | Giảm nhiễu hoàn tất.")
149
 
150
+ # 7. Chuẩn hóa độ dài
151
  if len(y_reduced_noise) > MAX_SAMPLES:
152
  y_final = y_reduced_noise[:MAX_SAMPLES]
153
  else:
154
  y_final = np.pad(y_reduced_noise, (0, MAX_SAMPLES - len(y_reduced_noise)), mode='constant')
155
 
156
+ # 8. Trích xuất đặc trưng
157
  traditional_features = _extract_traditional_features(y_final, SAMPLE_RATE)
158
  wav2vec_features = _extract_wav2vec_features(y_final, SAMPLE_RATE)
159
  spectrogram = _create_spectrogram_image(y_final, SAMPLE_RATE)
 
168
  # --- ĐỊNH NGHĨA CÁC ROUTE CỦA ỨNG DỤNG ---
169
  @app.route('/', methods=['GET'])
170
  def home():
 
171
  return render_template('index.html')
172
 
173
  @app.route('/predict', methods=['POST'])
174
  def predict():
 
175
  if 'audio_file' not in request.files:
176
  return jsonify({'error': 'Không có file audio nào trong yêu cầu.'}), 400
177
 
 
183
  filename = secure_filename(file.filename)
184
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
185
  file.save(filepath)
 
 
186
  trad_feats, w2v_feats, spec_img = process_audio_file(filepath)
 
187
  if trad_feats is None:
188
  return jsonify({'error': 'Không thể xử lý file audio.'}), 500
189
 
190
+ print("\n--- BẮT ĐẦU CHẨN ĐOÁN DỮ LIỆU ĐẦU VÀO (SAU KHI VAD & GIẢM NHIỄU) ---")
 
191
  print(f"DEBUG | trad_feats stats: mean={np.mean(trad_feats):.2f}, std={np.std(trad_feats):.2f}, min={np.min(trad_feats):.2f}, max={np.max(trad_feats):.2f}")
192
  print(f"DEBUG | w2v_feats stats: mean={np.mean(w2v_feats):.2f}, std={np.std(w2v_feats):.2f}, min={np.min(w2v_feats):.2f}, max={np.max(w2v_feats):.2f}")
193
  print(f"DEBUG | spec_img stats: mean={np.mean(spec_img):.2f}, std={np.std(spec_img):.2f}, min={np.min(spec_img):.2f}, max={np.max(spec_img):.2f}")
194
+
 
 
195
  combined_feats = np.concatenate([trad_feats, w2v_feats]).reshape(1, -1)
196
  scaled_feats = scaler.transform(combined_feats)
197
  spec_img = spec_img / 255.0
198
  spec_img = np.expand_dims(spec_img, axis=0)
199
+
 
200
  pred_xgb = model_xgb.predict_proba(scaled_feats)[0][1]
201
  pred_lgb = model_lgb.predict_proba(scaled_feats)[0][1]
202
  pred_cnn = model_cnn.predict(spec_img, verbose=0)[0][0]
203
 
 
204
  print(f"DEBUG | Individual probabilities (for Male): XGB={pred_xgb:.4f}, LGBM={pred_lgb:.4f}, CNN={pred_cnn:.4f}")
205
+
 
 
 
206
  final_prediction_prob = (pred_xgb + pred_lgb + pred_cnn) / 3
207
  final_prediction_label_index = 1 if final_prediction_prob > 0.5 else 0
208
  result_label_text = label_encoder.inverse_transform([final_prediction_label_index])[0]
 
 
209
 
210
+ os.remove(filepath)
211
  print(f"Phân tích hoàn tất. Kết quả: {result_label_text.upper()} (Xác suất: {final_prediction_prob:.2f})")
212
+
213
  return jsonify({
214
  'prediction': result_label_text.capitalize(),
215
  'probability': f"{final_prediction_prob:.2f}"
216
  })
 
217
  except Exception as e:
218
  print(f"Đã xảy ra lỗi trong quá trình dự đoán: {e}")
219
  traceback.print_exc()
 
223
  if __name__ == '__main__':
224
  port = int(os.environ.get("PORT", 7860))
225
  app.run(host='0.0.0.0', port=port)