电子科技大学数字信号处理DSP课程设计钢琴音符识别Word格式文档下载.docx
《电子科技大学数字信号处理DSP课程设计钢琴音符识别Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《电子科技大学数字信号处理DSP课程设计钢琴音符识别Word格式文档下载.docx(14页珍藏版)》请在冰点文库上搜索。
![电子科技大学数字信号处理DSP课程设计钢琴音符识别Word格式文档下载.docx](https://file1.bingdoc.com/fileroot1/2023-5/5/6b9aeb36-fe3d-4a46-bed7-a317fbb5f511/6b9aeb36-fe3d-4a46-bed7-a317fbb5f5111.gif)
4.以短时平均过零率为准,从低门限点往左右搜索,找到短时平均过零率低于某阈值的两点,为语音的起止点;
图1:
双门限法示意图
算法中的阀值是根据实验过程调节的。
该算法在实际应用的过程中发现:
在语音信号频率分布较为集中的时候,端点检测出来的结果比较准确,但当语音信号频率分布比较分散的时候,很难通过控制固定的阀值来检测到每个音符;
2>
.自相关法:
由于两种信号的自相关函数存在极大的差异,可以利用这种差别来提取语音端点。
根据噪声的情况,设置两个阈值和,当相关函数最大值大于时,便判定是语音;
当相关函数最大值大于或小于时,则判定为语音信号的端点。
该算法同样存在当语音信号频率分布较广的时候,阀值比较难控制的问题。
3>
.基于谱熵的端点检测:
基于谱熵语音端点检测方法是通过检测谱的平坦程度,来进行语音端点检测的,为了更好进行语音端点检测,采用语音信号的短时功率谱构造语音信息谱熵,从而对语音段和噪声段进行区分。
检测思路:
对语音信号进行分帧加窗;
计算每一帧的谱能量;
计算出每一帧中每个样本点的概率密度函数;
计算出每一帧的谱熵值(由信息论知识知道,熵值在自变量服从均匀分布的时候,熵值达到最大值,所以噪声的熵值是比较大的,而钢琴音符的熵值是比较小的,由此区别了噪声和音符);
设置判决门限;
根据各帧的谱熵值进行端点检测。
在实验过程中发现:
依然存在当语音信号频率分布较广时,阀值不太好控制的问题。
因此对该方法进行改进,引入,能熵比的概念:
谱熵值类似于过零率,能熵比的表示为。
由于噪声和信号的能熵比差别很大。
因此在能熵比的图像中,每一个“尖刺”就代表了一个特定频率的语言信号。
图2:
能熵比图中的“尖刺”
在检测过程中,依然不能通过简单的设置阀值的办法来进行端点检测,原因是语音频率分布较广时,每个音符的能熵比变化范围差别较大,如下图所示,有的“尖刺”完全在门限之上,而有的则完全在门限之下。
图3:
88阶全音的能熵比图
因此,采用检测能熵比中的“低谷点”(该点比左右两边的一定数目的点的能熵比都小)的方法。
语音信号一定位于两个低谷点之间的部分,再对低谷点进行适当的左右移动作为语音信号的起止点。
如下图所示:
图4:
标记起止点的能熵比图
(绿色为起始点,红色为截止点)
设计框架和流程:
用audioread函数读入钢琴音乐,并用sound函数播放;
2.为了方便处理,对信号以11.025kHz的频率进行重新采样,并统一转换成单声道的信号;
3.因为语言信号可以在短时间内认为是平稳的,因此对语音信号进行分帧的处理,设置帧长320,为了减小误差,两帧之间设置重叠部分,因此帧移取80;
4.计算每一帧的能熵比;
5.找到能熵比中的“低谷点”(该点比左右两边的一定数目的点的能熵比都小);
6.如果两个低谷点之间的距离大于miniL(认为持续长度超过一定长度的为音符,最小长度miniL可自行设置)。
则低谷点右移sr(即shiftright,数值可自行调节)帧作为一段信号的起始点,将低谷点左移sl(即shiftleft,数值可自行调节)帧作为截止点[注:
采用该方法的优点是通过调节相关参数能适应多种情况,缺点是检测环境发生较大变化时,需要重新设置参数];
7.将找到的语音段转换成未分帧时对应坐标的语音段,并对每段做快速傅里叶变换;
8.找到每段快速傅里叶变换中的最大值以及最大值所对应的横坐标(fft点),将横坐标转换成相应的频率,得到的频率即为该段音符的频率;
9.利用比值法进行频率的校正,窗函数选择矩形窗;
10.根据检测到的频率确定音符,计算公式为:
,为第几个按键,再通过查表得到对应音符;
11.分析结果。
三.具体设计过程:
部分代码(测试部分缺省):
主函数部分:
[x,fs]=audioread('
钢琴音频.WAV'
);
formatshort;
wlen=320;
inc=80;
%分帧的帧长和帧移
overlap=wlen-inc;
%帧之间的重叠部分
sound(x,fs);
%播放音乐
x=calsample(x,fs);
%为了方便处理,重新以11025Hz的频率采样,并转换成单声道
x=x-mean(x);
%消去直流分量
x=x/max(abs(x));
%幅值归一化
y=Enframe(wlen,inc,x)'
;
%分帧
fn=size(y,2);
%取得帧数
time=(0:
length(x)-1)/11025;
%计算时间坐标
frameTime=frame2time(fn,wlen,inc,11025);
%计算各帧对应的时间坐标
sr=2;
sl=13;
miniL=33;
%配置左右移动的帧数和要求的最短帧数
[voicesegment,vos,Ef]=get_segment(y,fn,sr,sl,miniL);
%获得语音段
[real_f,ft,ax]=get_f(x,voicesegment,vos,wlen,inc);
%检测频率的结果
fori=1:
length(real_f)
real_f(i)=roundn(real_f(i),-4);
end
real_node(i)=get_node(real_f(i))
%**********************************绘图部分********************************
subplot211;
stem(real_f);
title('
频率检测结果'
xlabel('
音符/个'
ylabel('
频率/Hz'
subplot212;
stem(real_node,'
r'
音符检测结果'
对应按键'
figure
(2);
plot(Ef);
能熵比图及语音起止点'
帧数/个'
能熵比'
length(voicesegment)
text(voicesegment(i).begin,Ef(voicesegment(i).begin),'
o'
'
color'
g'
)
text(voicesegment(i).end,Ef(voicesegment(i).end),'
subplot212,plot(time,x,'
k'
title('
语音信号端点检测结果'
axis([0max(time)-11]);
ylabel('
幅值'
fork=1:
vos%标出有话段
nx1=voicesegment(k).begin;
nx2=voicesegment(k).end;
nxl=voicesegment(k).duration;
fprintf('
%4d%4d%4d%4d\n'
k,nx1,nx2,nxl);
subplot212
line([frameTime(nx1)frameTime(nx1)],[-11],'
linestyle'
-'
line([frameTime(nx2)frameTime(nx2)],[-11],'
--'
其中的用到的子函数:
1.calsample.m(调整采样率和声道)
functionsample=calsample(sampledata,FS)
temp_sample=resample(sampledata,1,FS/11025);
%调整采样频率
[~,n]=size(temp_sample);
if(n==2)%转换成单声道
sample=temp_sample(:
1);
else
sample=temp_sample;
2.Enframe.m(分帧函数)
functionf=Enframe(len,inc,x)%对读入的语音进行分帧,len为帧长,
%inc为帧重叠样点数,x为输入语音数据
fh=fix(((size(x,1)-len)/inc)+1);
%计算帧数
f=zeros(fh,len);
%设置一个零矩阵,行为帧数,列为帧长
i=1;
n=1;
whilei<
=fh%帧间循环
j=1;
whilej<
=len%帧内循环
f(i,j)=x(n);
j=j+1;
n=n+1;
n=n-len+inc;
%下一帧开始位置
i=i+1;
3.frame2time.m(坐标刻度转换)
functionframeTime=frame2time(frameNum,framelen,inc,fs)
frameTime=(((1:
frameNum)-1)*inc+framelen/2)/fs;
%求对应的时间坐标
4.get_segment.m(端点检测,确定音符段)
function[voicesegment,vos,Ef]=get_segment(y,fn,sr,sl,miniL)
ifsize(y,2)~=fn,y=y'
end%把y转换为每列数据表示一帧语音信号
wlen=size(y,1);
%取得帧长
fn
Sp=abs(fft(y(:
i)));
%FFT取幅值
Sp=Sp(1:
wlen/2+1);
%只取正频率部分
Esum(i)=sum(Sp.*Sp);
%计算能量值
prob=Sp/(sum(Sp));
%计算概率
H(i)=-sum(prob.*log(prob+eps));
%求谱熵值
hindex=find(H<
0.1);
H(hindex)=max(H);
Ef=sqrt(1+abs(Esum./H));
%计算能熵比
Ef=Ef/max(Ef);
%归一化
[x1,y1]=get_max(Ef);
%找到能熵比中的“高峰点”
[x2,y2]=get_min(Ef);
%找到能熵比中的“低估点”
voicesegment
(1).begin=x1
(1)-8;
%由于仅仅靠低谷点无法检测出第一个音符起始位置,因此将高峰点的第一个值左移8帧作为第一个音符的起始点
voicesegment
(1).end=x2
(1)-sl;
%将第一个低谷点作为第一个音符的截止点
voicesegment
(1).duration=voicesegment
(1).end-voicesegment
(1).begin+1;
%一个音符的持续帧数
fork=2:
length(x2)%将找到的低谷点作为音符的起止点
ifx2(k)-x2(k-1)>
=miniL%剔除持续帧长度小于miniL的音符段
temp1=x2(k-1)+sr;
%将低谷点右移sr个帧,作为一个音符的起始点
voicesegment(j).begin=temp1;
temp2=x2(k)-sl;
%将低谷点左移sl个帧,作为一个音符的截止点
voicesegment(j).end=temp2;
voicesegment(j).duration=voicesegment(j).end-voicesegment(j).begin+1;
%音符持续帧数
vos=length(voicesegment);
%返回音符个数
5.get_max.m(找到高峰点)
function[max_x,max]=get_max(x)
l=length(x);
%获得数组的长度
max=[];
max_x=[];
fori=20:
l-15%找到“峰值点”
if(x(i)>
x(i-1))&
&
(x(i)>
x(i-2))&
x(i-3))&
x(i-4))&
x(i-5))...
x(i-6))&
x(i-7))&
x(i-8))&
x(i-9))&
x(i-10))...
x(i-11))&
x(i-12))&
x(i-13))&
x(i-14))...
x(i-15))&
x(i-16))&
x(i-17))...
x(i+1))&
x(i+2))&
x(i+3))&
x(i+4))&
x(i+5))...
x(i+6))&
x(i+7))&
x(i+8))&
x(i+9))&
x(i+10))...
x(i+11))&
x(i+12))&
x(i+13))&
x(i+14))...
x(i+15))&
x(i+16))...
0.1)
max_x(j)=i;
%找到后赋值给返回参数
max(j)=x(i);
6.get_min.m(找到低谷点)
function[min_x,min]=get_min(x)
min=[];
min_x=[];
fori=100:
l-10%寻找低谷点
if(x(i)<
(x(i)<
x(i-17))&
x(i-18))&
x(i-19))...
x(i-20))&
x(i-21))&
x(i-22))&
x(i-23))&
x(i-24))...
x(i-25))&
x(i-26))&
x(i-27))&
x(i-28))&
x(i-29))...
x(i-30))&
x(i-31))&
x(i-32))&
x(i-33))&
x(i-34))......
min_x(j)=i;
%找到后赋值给返回值
min(j)=x(i);
7.get_f.m(确定频率)
function[real_f,ft,ax]=get_f(x,voicesegment,vos,wlen,inc)
vos%横坐标转化,将起止点坐标的单位由帧转换成点
ax(i).begin=(voicesegment(i).begin-1)*inc+1;
ax(i).end=(voicesegment(i).end-1)*inc+wlen;
ax(i).duration=ax(i).end-ax(i).begin+1;
temp=x(ax(i).begin:
ax(i).end);
%获得语音段
ft{i}=fft(temp);
%做快速傅里叶变换,并将结果保存在元胞矩阵中
ft{i}=abs(ft{i});
%将得到的fft去模值
[fm(i),fm_x(i)]=max(ft{i});
%找到每个语音段对应的fft中的最大值及最大值对应的横坐标
real_f1(i)=fm_x(i)*11025./ax(i).duration;
%进行频率转换,将横坐标乘以分辨率得到真实频率
real_f(i)=Specorrm(x(ax(i).begin:
ax(i).end),11025,ax(i).duration,real_f1(i)-10,real_f1(i)+10);
%利用比值法进行频率修正,窗函数选择矩形窗
8.Specorrm.m(频率校正)
functionZ=Specorrm(x,fs,N,nx1,nx2)
%x是被测信号,fs是采样频率N为FFT的长度,nx1和nx2被测信号频率的区间,nx2>
nx1
[nx,mx]=size(x);
ifmx==1,x=x'
end%转换成行矩阵
M=fix(N/2)+mod(N,2);
xf=fft(x);
%xf=xf(1:
M)*2/N;
ddf=fs/N;
%频率分辨率
n1=fix(nx1/ddf);
%将频率转换成fft对应的点
n2=round(nx2/ddf);
A=abs(xf);
%取fft的模值
[Amax,index]=max(A(n1:
n2));
%找到fft点n1到n2之间幅值的最大值
index=index+n1-1;
%移动到n1和n2中间
%比值法
%加矩形窗
indsecL=A(index-1)>
A(index+1);
%满足条件则为1,不满足则为0
df=indsecL.*A(index-1)./(Amax+A(index-1))-(1-indsecL).*A(index+1)./(Amax+A(index+1));
Z=(index-1-df)*ddf;
%修正后的频率
9.get_node.m(确定音符)
functionnode=get_node(f)
node=12*log2(f/440)+49;
node=round(node);
%四舍五入确定对应按键
生成的图与数据等LINKExcel.Sheet.12"
C:
\\Users\\pc\\Desktop\\端点检测结果.xlsx"
"
Sheet1!
R2C1:
R38C4"
\a\f5\h\*MERGEFORMAT
对数据的分析:
红色标记出检测错误的音符。
上表为检测结果的统计及误差统计。
大部分误差都小于1%,平均误差为0.82%,误差较小.音符检测发生一个错误,正确了为97.14%,很好地完成了任务。
四.总结:
收获
小组分工情况
小组就邱晨曦一个人,邱晨曦负责全部工作。
LINKExcel.Sheet.12"
工作簿1"
R1C1:
R36C8"
参考文献:
谢明,丁康:
离散频谱的一种新校正方法,重庆,载重庆大学学报:
1995年3月第8卷第2期47~54