基于Librosa的卡拉OK评分系统异常低分问题排查求助
问题排查与修正方案
1. 音高MSE计算的核心错误
你的MSE计算逻辑完全颠倒,且未正确对齐音高轮廓:
- 当两个音高数组长度不一致时,你对短数组补零后,却用补零后的数组和原短数组做差,结果全为0,完全没意义——应该用补零/插值后的数组和另一个完整数组做比较。
- MSE的正确公式是差值平方的均值,你写成了均值的平方,逻辑顺序错误。
- 直接用Hz值计算偏差不符合人耳对数感知特性,应转成半音刻度再计算偏差,避免低频小偏差被放大、高频大偏差被低估。
修正后的音高计算代码:
import librosa import numpy as np # 统一采样率加载,确保时间轴对齐 sr_target = 22050 y, sr = librosa.load('karaoke_performance.wav', sr=sr_target) ref_y, ref_sr = librosa.load('song_reference.wav', sr=sr_target) # 提取半音刻度的音高轮廓 def get_pitch_contour(audio, sr): # YIN算法提取基频(Hz) pitch_hz = librosa.core.pitch.yin(audio, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7'), sr=sr) # 转半音,将静音段的0值替换为NaN pitch_midi = librosa.hz_to_midi(pitch_hz) pitch_midi[pitch_midi == -np.inf] = np.nan return pitch_midi arr = get_pitch_contour(y, sr) arr2 = get_pitch_contour(ref_y, ref_sr) # 用插值对齐两个音高轮廓的长度(比补零更贴合时间轴) max_len = max(len(arr), len(arr2)) arr_aligned = np.interp(np.linspace(0, len(arr)-1, max_len), np.arange(len(arr)), arr) arr2_aligned = np.interp(np.linspace(0, len(arr2)-1, max_len), np.arange(len(arr2)), arr2) # 忽略静音段,计算有效音高的MSE valid_mask = ~np.isnan(arr_aligned) & ~np.isnan(arr2_aligned) mse = np.mean((arr_aligned[valid_mask] - arr2_aligned[valid_mask]) ** 2) if np.any(valid_mask) else 0
2. 节奏偏差计算的逻辑错误
beat_frames是音频帧的索引,两个音频长度不同时直接相减完全无意义——需先将节拍帧转成时间戳,再用动态时间规整(DTW)对齐两个节拍序列后计算偏差:
修正后的节奏计算代码:
# 提取节拍时间戳(秒) def get_beat_timestamps(audio, sr): tempo, beat_frames = librosa.beat.beat_track(audio, sr=sr) return librosa.frames_to_time(beat_frames, sr=sr), tempo beat_times, tempo = get_beat_timestamps(y, sr) ref_beat_times, ref_tempo = get_beat_timestamps(ref_y, ref_sr) # 用DTW对齐节拍序列,匹配对应节拍点 from librosa.sequence import dtw D, wp = dtw(beat_times.reshape(-1,1), ref_beat_times.reshape(-1,1)) # 计算对齐后的节拍时间偏差 aligned_beat = beat_times[wp[:,0]] aligned_ref = ref_beat_times[wp[:,1]] rhythm_deviation = np.mean(np.abs(aligned_beat - aligned_ref))
3. 得分映射的合理性问题
直接用100 - (mse * 100)会因MSE数值过大导致负分,需将偏差归一化到0-100的得分区间:
- 半音MSE的合理上限为25(对应5半音偏差),用
100 - min(mse * 4, 100)限制得分在0-100之间。 - 节奏偏差以秒为单位,假设最大允许0.5秒偏差,用
100 - min(rhythm_deviation * 200, 100)限制得分。
修正后的得分计算:
pitch_score = max(0, 100 - min(mse * 4, 100)) rhythm_score = max(0, 100 - min(rhythm_deviation * 200, 100)) normalized_final_score = (pitch_score + rhythm_score) / 2 print("Pitch Score:", pitch_score) print("Rhythm Score:", rhythm_score) print("Final Score:", normalized_final_score)
额外注意事项
- 确保演唱音频和参考音频时间段落匹配,裁剪掉开头/结尾的空白片段,避免对齐误差。
- YIN算法在静音段输出的0值需标记为NaN并忽略,否则会干扰偏差计算的准确性。
内容的提问来源于stack exchange,提问作者Martin Benjámin Muzsik
相关产品推荐
相关产品推荐

