最近开始学习语音信号处理,在CSDN上看到一个挺有意思的题目,因此依葫芦画瓢,花了两天时间,按照博客里的思路实现了这个练习,算作入门收获不小。
时间:2020.1.4-1.6
%% 原始波形
% m=[1 2 3 4 5 6 7 8 9 9 2];%电话号码
% creatdtmf(m);
filename = '1 2 3 4 5 6 7 8 9 9 2.wav';
[x,fs] = audioread(filename);
len = length(x);
T = (len-1)/fs;
t = linspace(0,T,len)';
f = -fs/2 : 1/T : fs/2;
X = fft(x);
X_shift = fftshift(X);
figure(1);
subplot(311);
plot(t,x./max(abs(x)));
grid on;
set(gca,'XMinorGrid','on');set(gca,'YMinorGrid','on');
xlabel('时间(s)');ylabel('归一化幅度(a.u.)');title('手机号录音');
subplot(312);
plot(f,abs(X_shift)./max(abs(X_shift)));
grid on;
set(gca,'XMinorGrid','on');set(gca,'YMinorGrid','on');
title('频谱密度');xlabel('频率(Hz)');ylabel('归一化幅度(a.u.)');
问:从原始音频中得到的数据效果很差,该怎么做?
答:进行预处理。分帧、降噪、滤波…
what: 把语音分割成一帧一帧的加过窗函数的短时信号,便于语音分析。
效果:原始-分帧-量化
how:
%% 方法1:
fram_time=0.1;%帧时长
fram_step_time=0.08;%帧步进
win='hamming';
[row,col]=size(x);
if row>col
x=x';
else
end
ts=1/fs;
l=length(x);%序列的总长度
% xn_time=l*ts;%序列的总时长
fram_length=ceil(fram_time/ts);%每一帧的长度
fram_step_length=ceil(fram_step_time/ts);%帧移的长度
if win=='hanning' win=hanning(fram_length);
elseif win=='hamming' win=hamming(fram_length);
end
%帧数的计算公式为:序列点数-帧长度+帧移)/帧移
numOfframs=(l-fram_length+fram_step_length)/fram_step_length;%计算得到帧数
%考虑到序列总长度和分帧结果不匹配,补0以满足分帧要求
numOfframs=ceil(numOfframs);
%反算为了满足分帧序列应该增加的长度
l_added=(numOfframs*fram_step_length-fram_step_length+fram_length);%得到序列应该有的长度
l=l_added-l;
x=[x,zeros(1,l)];%补0
xn_time=ceil(l*ts);
xn_frams=zeros(fram_length,numOfframs);%建立存放分帧结果的矩阵
%开始分帧
for k=1:numOfframs
dn=(k-1)*fram_step_length+(1:fram_length);
xn_frams(:,k)=x(dn).*win';
end
%% 频率量化
f_new=[0 700 800 900 1100 1250 2000];
for i=2:7
pos1=find(f_new(i-1)==f);%找到非零点
pos2=find(f==f_new(i));
fftframs_new(i-1,:)=sum(fftframs_end(pos1:pos2,:));
end
f_new=[1 2 3 4 5 6];
方法2:voicebox中:enframe
f=enframe(x,win,inc)
问:如何找到这11个短时信号呢?
答:通过短时功率(频域)或者短时能量(时域),找到最大值所在的帧数即可。
注意:阈值的设置。根据波形,手动设置。在第9个峰值附近有一个小峰值,起初的阈值为0.5时,也识别到了这个伪峰值,导致之后的识别不正确。(感觉手动更改阈值的方法,很麻烦。改进方法:如何自动设置阈值)
what:短时能量和过零率
how:
%计算短时功率
power_E = sum(fftframs_new);
power_E = power_E/max(power_E);%归一化
threshold=0.55;
计算过零率
function [f] = zcr(x)
f=zeros(size(x,1),1);
for i=1:size(x,1)
z=x(i,:);
for j=1:(length(z)-1)
if z(j)*z(j+1)<0
f(i)=f(i)+1;
end
end
end
end
显示阈值线
plot(1:numOfframs,power_E);
hold on;
plot(1:numOfframs,threshold*ones(numOfframs,1));
%% 基于短时功率找到最大11个峰值
power_prev = 0;
phone_number_frame_index = zeros(1,11); % 建立一个空矩阵,用来存放果电话号码按键音出现的帧数
N=1;
%找到最大值11个
for k1 = 1:numOfframs
idx_temp = 0;
power_temp = power_E(k1);
if power_prev <= power_temp
power_prev = power_temp;
elseif power_prev > power_temp && power_prev>threshold
power_prev = 0;
phone_number_frame_index(N) = k1-1;
N = N+1;
else
end
end
根据提取到的峰值帧数 phone_number_frame_index,把对应的帧信息提取出来。(6*11)
%% 基于短时功率的分割
phone_number_frame = fftframs_new(:,phone_number_frame_index);
% 分别对每一帧进行归一化,实现增强
feature = phone_number_frame./repmat(max(phone_number_frame),size(phone_number_frame,1),1);
char(1).feature=[1 0 0 1 0 0];
char(1).name='1';
问:如何识别出特征向量对应的数字呢?
答:用余弦相似度。计算待识别向量和已知向量的夹角余弦,值越大即越匹配。
%% 特征匹配 (计算余弦相似度)
for k=1:11
best=0;
for i=1:9
char_num=(feature(:,k)')*(char(i).feature)/norm(feature(:,k))/norm(char(i).feature);
if (char_num+0.1)>=best%说明有相同的特征而已,还需要比较是否有更多的相同特征
best=str2double(char(i).name);
else
end
end
phone_number(k)=best;
end
str=num2str(phone_number);
disp(str)
理解:
使用向量、使用只有1和0的向量进行模式识别的根本原因
计算余弦值一定会遇到两个向量做点乘的情况。点乘就意味着相应的位置相乘,自然,只有都是1的位置才不为0,都是1意味着什么?——意味着这两个向量具有相同的特征。
输入声音:12345678992
识别结果:13356619913
可能原因:预处理的分帧阶段存在问题
理由:观察到分帧后的2、3有肉眼可见的明显区别,但是经过频率量化后2、3基本相同,因此可能是频率量化的区间值设置存在问题。
结果:在修改量化区间之后,效果貌似更差了…(我也不知道该怎样去修正)
结论:量化区间值,与识别有很大很大很大关系,由此可见预处理的重要性。
感谢:
https://blog.csdn.net/shenziheng1/article/details/52891807
https://blog.csdn.net/hitzyh1505/article/details/79617498
https://blog.csdn.net/qcyfred/article/details/52998644