【微实验】基频提取的MATLAB实现(优化版)
前情提要:
【超详细】科普:别再只会用自相关!YIN 和 PYIN 如何破解音频隐藏密码?-CSDN博客
【微实验】妈妈我的MATLAB会识别声音的基频了!-CSDN博客
今天用MATLAB把算法封装成函数,然后调用对比结果。
一、算法简介
- YIN 算法:一种经典的基频(F0)提取算法,通过计算信号的自相关函数及其累积均值归一化,来定位基频周期,适用于语音、乐器等信号的基频检测,具有较好的抗噪声性能。
- PYIN 算法:基于 YIN 算法的改进版本,引入了概率模型,通过对基频候选值进行置信度评估,进一步提升了复杂环境下基频提取的准确性,尤其在处理清音 / 浊音过渡、低信噪比信号时表现更优。
二、MATLAB 实现步骤
1. 读取音频文件(voice.mp3)
MATLAB 中可使用audioread
函数读取音频,注意 mp3 格式需确保 MATLAB 支持(可先转换为 wav 格式避免兼容问题):
% 读取音频文件
[audio, fs] = audioread('voice.mp3'); % audio为音频数据,fs为采样率
audio = audio(:, 1); % 取单声道
t = (0:length(audio)-1)/fs; % 时间轴
2. YIN 算法实现(基频提取核心)
核心思路:计算归一化累积均值差函数(CMND),寻找最小值对应的周期即为基频周期。
function f0_yin = yin_algorithm(audio, fs, frame_len, hop_len)% 初始化参数n_frames = floor((length(audio) - frame_len) / hop_len) + 1;f0_yin = zeros(n_frames, 1);min_freq = 50; % 最低基频(Hz)max_freq = 500; % 最高基频(Hz)min_period = fs / max_freq;max_period = fs / min_freq;for i = 1:n_frames% 取一帧数据frame = audio((i-1)*hop_len + 1 : (i-1)*hop_len + frame_len);frame = frame - mean(frame); % 去直流% 计算自相关函数r = xcorr(frame, frame);r = r(frame_len:end); % 取后半部分(正延迟)% 计算累积均值差函数(CMND)cmnd = zeros(length(r), 1);cmnd(1) = 1; % 避免除以0for tau = 2:length(r)sum_r = sum(r(1:tau-1));if sum_r == 0cmnd(tau) = 1;elsecmnd(tau) = r(tau) / (sum_r / (tau-1));endend% 寻找最小CMND对应的周期(在有效范围内)tau_range = round(min_period) : round(max_period);[~, min_idx] = min(cmnd(tau_range));tau = tau_range(min_idx);f0_yin(i) = fs / tau;end
end
3. PYIN 算法实现(引入概率模型)
在 YIN 算法基础上,增加基频候选值的置信度计算,筛选最可能的基频:
function [f0_pyin, conf] = pyin_algorithm(audio, fs, frame_len, hop_len)% 调用YIN算法获取初始基频候选f0_initial = yin_algorithm(audio, fs, frame_len, hop_len);n_frames = length(f0_initial);f0_pyin = zeros(n_frames, 1);conf = zeros(n_frames, 1); % 置信度(0-1)% 简化的概率评估(示例):基于基频连续性和CMND最小值for i = 2:n_frames-1% 计算当前帧与前后帧的基频偏差delta = abs(f0_initial(i) - (f0_initial(i-1) + f0_initial(i+1))/2);% 置信度与偏差成反比conf(i) = max(0, 1 - delta / 100); % 假设偏差<100Hz时置信度较高% 根据置信度筛选基频if conf(i) > 0.5f0_pyin(i) = f0_initial(i);elsef0_pyin(i) = NaN; % 低置信度时标记为无效endend
end
4. 可视化结果
将音频波形、YIN 与 PYIN 提取的基频曲线绘制在同一图中,直观对比效果:
% 参数设置
frame_len = 2048; % 帧长
hop_len = 512; % 帧移% 提取基频
f0_yin = yin_algorithm(audio, fs, frame_len, hop_len);
[f0_pyin, ~] = pyin_algorithm(audio, fs, frame_len, hop_len);% 生成基频时间轴
f0_t = (0:length(f0_yin)-1)*hop_len / fs;% 绘图
figure;
subplot(2,1,1);
plot(t, audio);
title('语音波形');
xlabel('时间(s)');
ylabel('振幅');subplot(2,1,2);
plot(f0_t, f0_yin, 'b', 'LineWidth', 1.2);
hold on;
plot(f0_t, f0_pyin, 'r--', 'LineWidth', 1.2);
title('基频提取结果');
xlabel('时间(s)');
ylabel('基频(Hz)');
legend('YIN算法', 'PYIN算法');
grid on;
hold off;
三、效果说明
emm这个音频太长了,密密麻麻看不太出来。
???
把帧移改小一点呢
- 运行代码后,会显示语音波形图和基频曲线对比图,其中 PYIN 算法的结果(红色虚线)相比 YIN 算法(蓝色实线)更平滑,无效值(NaN)更少,尤其在语音停顿或噪声段表现更稳定。
- 可通过调整
frame_len
、hop_len
、min_freq
、max_freq
等参数优化提取效果,适配不同类型的voice.mp3
文件。
对比:

这个效果只能说是一言难尽,两言也难,千言万语不想说。毕竟它也不是专门服务于语言学、人声音频的软件,所以就这样吧。
Praat的效果应该非常好才对——

看着不对劲……原来是参数没调好:
把窗长度改大一点——
这样看起来就基本上吻合了。
四、注意事项
- 复杂场景下(如多声部、强噪声),建议增加预处理步骤(如滤波、端点检测)。
- PYIN 算法的概率模型可进一步细化,优化置信度计算方法提升精度。
五、总结
这是一次失败的实验,下次继续。