人在发音时,根据声带振动情况将语音信号分为浊音和清音。浊音又称有声语言,包含声音中大部分的能量,浊音在时域上会呈现出明显的周期性;而清音类似于白噪声,没有明显的周期性。发浊音时,气流通过声门使声带产生张弛震荡式振动,产生准周期的激励脉冲。这种声带振动的频率称为基音频率,相应的周期就成为基音周期。
通常,基音频率与声带的长短、薄厚、韧性、劲度和发音习惯等有关系,在很大程度上反应了个人特征。此外,基音频率还跟随着人的性别、年龄不同而有所不同。一般来说,男性说话者的基音频率较低,而女性说话者和小孩的基音频率相对较高。
基音周期的估计称谓基音检测,基音检测的最终目的是为了找出和声带振动频率完全一致或尽可能相吻合的轨迹曲线。
基音周期作为语音信号处理中描述激励源的重要参数之一,在语音合成、语音压缩编码、语音识别和说话人确认等领域都有着广泛而重要的问题,尤其对汉语更是如此。汉语是一种有调语言,而基因周期的变化称为声调,声调对于汉语语音的理解极为重要。因为在汉语的相互交谈中,不但要凭借不同的元音、辅音来辨别这些字词的意义,还需要从不同的声调来区别它,也就是说声调具有辨义作用;另外,汉语中存在着多音字现象,同一个字的不同的语气或不同的词义下具有不同的声调。因此准确可靠地进行基音检测对汉语语音信号的处理显得尤为重要。
信号x(n)的短时自相关函数定义为:
R ( m ) = ∑ n = − ∞ n = + ∞ x ( n ) x ( n + m ) R(m)=\sum_{n=-\infty}^{n=+\infty} x(n) x(n+m) R(m)=n=−∞∑n=+∞x(n)x(n+m)
此公式表示一个信号和延迟m点后的该信号本身的相似性。如果信号x(n)具有周期性,那么它的自相关函数也具有周期性,而且周期与信号x(n)的周期性相同。自相关函数提供了一种获取周期信号周期的方法。在周期信号周期的整数倍上,它的自相关函数可以达到最大值,因此可以不考虑起始时间,而从自相关函数的第一个最大值的位置估计出信号的基音周期,这使自相关函数成为信号基音周期估计的一种工具。
语音信号是非稳态信号它的特征是随时间变化的,但在一个很短的时间段内可以认为具有相对稳定的特征即短时平稳性。因此语音具有短时自相关性。这个时间段约5ms-50ms。为其统计特性和频谱特性都是对短时段而言的。这使得要对语音信号作数字处理必须先按短时段对语音信号分帧。这样每一帧信号都具有短时平稳性从而进行短时相关分析。
能量有限的语音信号s(n)的短时自相关函数定义为:
R n ( τ ) = ∑ m = 0 N − 1 − τ [ s ( n + m ) w ( m ) ] [ s ( n + m + τ ) w ( m + τ ) ] R_{n}(\tau)=\sum_{m=0}^{N-1-\tau}[s(n+m) w(m)][s(n+m+\tau) w(m+\tau)] Rn(τ)=m=0∑N−1−τ[s(n+m)w(m)][s(n+m+τ)w(m+τ)]
因为基音周期可以低至40Hz(低男音)或高达600Hz(高女音或儿童音),一般要求一帧至少包含2个以上的周期。基音周期可以是第二个极值点或者50Hz~500Hz(fs/500-fs/50,即fs=16K,2ms-20ms)之间的极值点(推荐)。
%% 分帧基音求取
function [F0, T, R] = spPitchTrackCorr(x, fs, frame_length, frame_overlap, maxlag, show)
% Initialization
N = length(x);
if ~exist('frame_length', 'var') || isempty(frame_length)
frame_length = 30;
end
if ~exist('frame_overlap', 'var') || isempty(frame_overlap)
frame_overlap = 20;
end
if ~exist('maxlag', 'var')
maxlag = [];
end
if ~exist('show', 'var') || isempty(show)
show = 0;
end
nsample = round(frame_length * fs / 1000); % convert ms to points
noverlap = round(frame_overlap * fs / 1000); % convert ms to points
% Pitch detection for each frame
pos = 1; i = 1;
while (pos+nsample < N)
frame = x(pos:pos+nsample-1);
frame = frame - mean(frame); % mean subtraction
R(:,i) = spCorr(frame, fs);
F0(i) = spPitchCorr(R(:,i), fs);
pos = pos + (nsample - noverlap);
i = i + 1;
end
T = (round(nsample/2):(nsample-noverlap):N-1-round(nsample/2))/fs;
if show
% plot waveform
subplot(2,1,1);
t = (0:N-1)/fs;
plot(t, x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
xlim([t(1) t(end)]);
% plot F0 track
subplot(2,1,2);
plot(T,F0);
legend('pitch track');
xlabel('Time (s)');
ylabel('Frequency (Hz)');
xlim([t(1) t(end)]);
end
end
%% 基音搜索,第二个极值点或者50Hz~500Hz之间的极值点
function [f0] = spPitchCorr(r, fs)
% search for maximum between 2ms (=500Hz) and 20ms (=50Hz)
ms2=floor(fs/500); % 2ms
ms20=floor(fs/50); % 20ms
% half is just mirror for real signal
r = r(floor(length(r)/2):end);
[maxi,idx]=max(r(ms2:ms20));
f0 = fs/(ms2+idx-1);
end
% 自相关
function [r] = spCorr(x, fs, maxlag)
%% Initialization
if ~exist('maxlag', 'var') || isempty(maxlag)
maxlag = fs/50; % F0 is greater than 50Hz => 20ms maxlag
end
if ~exist('show', 'var') || isempty(show)
show = 0;
end
% Auto-correlation
r = xcorr(x, maxlag, 'coeff');
end
声音信号是将声带激励序列 e [ n ] e [n] e[n]与声道离散脉冲响应 θ [ n ] θ[n] θ[n]卷积的结果。通过频域变换可以把卷积变为乘法。然后,利用对数函数 l o g A B = l o g A + l o g B log AB = log A + log B logAB=logA+logB的性质,可以将乘法关系转换为加法关系。最后,将信号 s [ n ] = e [ n ] ∗ θ [ n ] s [n] = e [n] *θ[n] s[n]=e[n]∗θ[n]的真实倒谱定义为:
c [ n ] = 1 2 π ∫ − π π log ∣ S ( w ) ∣ e j n w d w c[n] = \frac{1}{{2\pi }}\int_{ - \pi }^\pi {\log } |S(w)|{e^{jnw}}dw c[n]=2π1∫−ππlog∣S(w)∣ejnwdw
故:
S ( w ) = ∑ n = − inf i n f s [ n ] e − j w n S(w) = \sum\limits_{n = - \inf }^{{\rm{inf}}} s [n]{e^{ - jwn}} S(w)=n=−inf∑infs[n]e−jwn
即,倒谱是信号的对数幅度频谱的傅立叶分析。如果对数振幅频谱包含许多规则间隔的谐波,则频谱的傅立叶分析将显示一个与谐波之间的间隔相对应的峰值:即基频。实际上,将信号频谱视为目标,然后在频谱中寻找周期性。
倒频谱是将频谱从内到外翻转,倒谱的X轴具有单位的频率,倒频谱中的峰值(与频谱的周期性有关)被称为倒谐波。为了从倒谱中获得基本频率的估计值,在频率区域中寻找一个与典型语音基音频率相对应的峰值(1 /频率)。
%% 分帧基音求取
function [F0, T, C] = spPitchTrackCepstrum(x, fs, frame_length, frame_overlap, window, show)
% Initialization
N = length(x);
if ~exist('frame_length', 'var') || isempty(frame_length)
frame_length = 30;
end
if ~exist('frame_overlap', 'var') || isempty(frame_overlap)
frame_overlap = 20;
end
if ~exist('window', 'var') || isempty(window)
window = 'hamming';
end
if ~exist('show', 'var') || isempty(show)
show = 0;
end
nsample = round(frame_length * fs / 1000); % convert ms to points
noverlap = round(frame_overlap * fs / 1000); % convert ms to points
if ischar(window)
window = eval(sprintf('%s(nsample)', window)); % e.g., hamming(nfft)
end
% Pitch detection for each frame
pos = 1; i = 1;
while (pos+nsample < N)
frame = x(pos:pos+nsample-1);
C(:,i) = spCepstrum(frame, fs, window);
F0(i) = spPitchCepstrum(C(:,i), fs);
pos = pos + (nsample - noverlap);
i = i + 1;
end
T = (round(nsample/2):(nsample-noverlap):N-1-round(nsample/2))/fs;
if show
% plot waveform
subplot(2,1,1);
t = (0:N-1)/fs;
plot(t, x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
xlim([t(1) t(end)]);
% plot F0 track
subplot(2,1,2);
plot(T,F0);
legend('pitch track');
xlabel('Time (s)');
ylabel('Frequency (Hz)');
xlim([t(1) t(end)]);
end
end
%% 基于倒谱法的基音检测
function [f0] = spPitchCepstrum(c, fs)
% search for maximum between 2ms (=500Hz) and 20ms (=50Hz)
ms2=floor(fs*0.002); % 2ms
ms20=floor(fs*0.02); % 20ms
[maxi,idx]=max(abs(c(ms2:ms20)));
f0 = fs/(ms2+idx-1);
end
%% 倒谱法
function [c, y] = spCepstrum(x, fs, window, show)
% Initialization
N = length(x);
x = x(:); % assure column vector
if ~exist('show', 'var') || isempty(show)
show = 0;
end
if ~exist('window', 'var') || isempty(window)
window = 'rectwin';
end
if ischar(window);
window = eval(sprintf('%s(N)', window)); % hamming(N)
end
% do fourier transform of a windowed signal
x = x(:) .* window(:);
y = fft(x, N);
% Cepstrum is IDFT (or DFT) of log spectrum
c = ifft(log(abs(y)+eps));
end
语音分析的一种非常强大的方法是基于线性预测编码(LPC),也称为自回归(AR)建模。由于该方法快速,简单,但却是估计语音信号主要参数的有效方法,因此被广泛使用。
具有足够数量的极点的全极点滤波器是语音信号的良好近似。因此,我们可以将滤波器H(z)建模为
H ( z ) = X ( z ) E ( z ) = 1 1 − ∑ k = 1 p a k z − k = 1 A ( z ) H(z) = \frac{{X(z)}}{{E(z)}} = \frac{1}{{1 - \sum\limits_{k = 1}^p {{a_k}} {z^{ - k}}}} = \frac{1}{{A(z)}} H(z)=E(z)X(z)=1−k=1∑pakz−k1=A(z)1
其中p是LPC分析的顺序。在式中取反z变换。 结果:
x [ n ] = ∑ k = 1 p a k x [ n − k ] + e [ n ] x[n] = \sum\limits_{k = 1}^p {{a_k}} x[n - k] + e[n] x[n]=k=1∑pakx[n−k]+e[n]
线性预测编码之所以得名,是因为它预测当前样本是其过去p个样本的线性组合。如果绘制 H ( e j w ) H(ej^w) H(ejw),我们期望在分母的根部看到峰值。基于这个事实,我们将能够检测共振峰频率。
%% 分帧共振峰检测
function [F, T] = spFormantsTrackLpc(x, fs, ncoef, frame_length, frame_overlap, window, show)
% Initialization
N = length(x);
if ~exist('frame_length', 'var') || isempty(frame_length)
frame_length = 30;
end
if ~exist('frame_overlap', 'var') || isempty(frame_overlap)
frame_overlap = 20;
end
if ~exist('window', 'var') || isempty(window)
window = 'hamming';
end
if ~exist('show', 'var') || isempty(show)
show = 0;
end
if ~exist('ncoef', 'var')
ncoef = [];
end
nsample = round(frame_length * fs / 1000); % convert ms to points
noverlap = round(frame_overlap * fs / 1000); % convert ms to points
window = eval(sprintf('%s(nsample)', window)); % e.g., hamming(nfft)
pos = 1; t = 1;
F = []; % formants
T = []; % time (s) at the frame
mid = round(nsample/2);
while (pos+nsample <= N)
frame = x(pos:pos+nsample-1);
frame = frame - mean(frame);
a = spLpc(frame, fs, ncoef);
fm = spFormantsLpc(a, fs);
for i=1:length(fm)
F = [F fm(i)]; % number of formants are not same for each frame
T = [T (pos+mid)/fs];
end
pos = pos + (nsample - noverlap);
t = t + 1;
end
if show
% plot waveform
t=(0:N-1)/fs;
subplot(2,1,1);
plot(t,x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
xlim([t(1) t(end)]);
% plot formants trace
subplot(2,1,2);
plot(T, F, '.');
hold off;
legend('Formants');
xlabel('Time (s)');
ylabel('Frequency (Hz)');
xlim([t(1) t(end)]);
end
end
%% 共振峰检测
function [F] = spFormantsLpc(a, fs)
r = roots(a);
r = r(imag(r)>0.01);
F = sort(atan2(imag(r),real(r))*fs/(2*pi));
end
%% 基于LPC方法检测共振峰
function [a P e] = spLpc(x, fs, ncoef)
if ~exist('ncoef', 'var') || isempty(ncoef)
ncoef = 2 + round(fs / 1000); % rule of thumb for human speech
end
[a P] = lpc(x, ncoef);
if nargout > 2,
est_x = filter([0 -a(2:end)],1,x); % Estimated signal
e = x - est_x; % Residual signal
end
end
https://blog.csdn.net/S20091103372/article/details/39225615
https://blog.csdn.net/zouxy09/article/details/9141875/
http://note.sonots.com/SciSoftware/Pitch.html